Commit Graph
13 Commits
Author SHA1 Message Date
Sam SungandClaude Opus 5.5 5d25ac9286 entities: a torn-down net unit gets no new visual work (attach, rebuild, re-dress, re-seat, held items), so no command lands on a despawned unit (#623)
A unit torn down (out of range or destroyed) before its visual was built still got one built, and the build's plain entity commands could apply after `apply_despawn_fade` had despawned it. That chain is unordered against the attach chain, and Bevy's default error handler panics.

The reference frees the object at once on destroy (`0x4674a0`, `0x465ec0` → `0x464920`), and only an already-built model fades (`0x672df0`). So a torn-down unit now gets no new visual work: `Without<DespawnFade>` on the queries of `attach_entity_visuals`, `refresh_live_display`, `heal_rig_starved`, `redress_player_looks`, `reseat_mounts` and `attach_held_items`.

`a_torn_down_unit_is_not_built` drives the real `attach_entity_visuals`, despawns the torn-down unit between the system's run and its command flush, and panics with "Entity despawned" on `insert<AppliedMount>` without the filter. `a_torn_down_starved_unit_is_not_rebuilt` covers the heal path. Built and gated on macOS.

Fixes #612

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 18:20:17 +07:00
Sam SungandClaude Opus 5.5 34b7c30af2 The core loads the chain's Bindings.xml and DefaultBindings.wtf, and every command runs its stock body (#587)
The core now loads the chain's `Interface\FrameXML\Bindings.xml` after the `FrameXML.toc` rows, as the reference's UI load does. The load checks the file at `0x48fff9` and loads it through the addons' loader `0x4b6f70` at `0x490018`. The default keys come from the chain's `WTF\DefaultBindings.wtf` (`0x4b62b0`). Every command now runs the stock file's own body, and the registry compiled into benilla, with its 91 retyped bodies, is deleted.

- **The loader's rules (`0x4b6f70`).** It reads the root's children only and skips empty, `debug="true"` and non-Windows rows. A second definition of a name is skipped, and duplicate headers are refused.
- **Keys and commands are separate tables, as in 1.12.** `SetBinding` never reads the command (`0x4b7490`). `RunBinding` runs the body in the VM. `RunCommand` sets `keystate` around the body and clears it after (`0x4b7c42`).
- **Engine behaviour sits behind the Lua globals the bodies call.** These are the movement, turn and mouse-steer bits. Focus loss clears the direction bits and keeps the mouse pair (`0x514490`). One key's stop clears its bit, as `0x514b70` does, so releasing W while UP is held stops the character.
- **Behaviour changes that follow the stock bodies.**
  - The nameplate keys (V, Shift-V, Ctrl-V) now run the stock bodies.
  - ALT-Z runs `CloseAllWindows(); UIParent:Hide()`.
  - The minimap, sound, stance and bonus-bar keys call their stock functions.
  - The wheel zooms one `CameraZoomIn(1.0)` per notch.
- **Platform.** benilla answers as the PC build, so a `platform="mac"` row does not register on any OS.

Verified:
- New tests: a fixture `Bindings.xml` (reordered, edited, and with debug, empty, nested and Mac rows) drives the command list, defaults and bodies. The V keys follow the stock bodies. No stock body's text remains in the tree. Every stock body's calls resolve after a production load. Each test fails with its change undone.
- `scripts/gates.sh` and `scripts/smoke.sh` are green.
- A live run with `WOW_GM=off`:
  - W and Space moved and jumped.
  - B toggled the backpack.
  - 1 reached `UseAction`.
  - V, Shift-V and Ctrl-V behaved as the stock bodies do.

Built on macOS; no platform seam touched.

Fixes #570
Fixes #563

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 10:51:21 +07:00
Sam SungandClaude Opus 5.5 459e0e90c1 The stock video window's boxes are registered and ffx, ffxGlow, ffxDeath and gxMaximize work; benilla's options gain Spell Detail; load failures go to FrameXML.log (#585)
**#549: the stock video window's CVars.** Its check boxes, `ffx` and `gxRefresh` are registered at the reference's defaults. `pixelShaders` and `specular` ship as `overridden` "1" (`hwDetect`, `0x639a60`), and `gxFixLag` ships "1".
- `ffx`, `ffxGlow` and `ffxDeath` gate the full-screen effect passes every frame, as the reference's gate does (`0x6cd890`; `CFFXGlow` reads `ffxGlow` at `0x6cc5a8`, `CFFXDeath` reads `ffxDeath` at `0x6cdf10`).
- `gxMaximize` is latched. At the `RestartGx` commit it makes a windowed client borderless over the monitor, as the reference's window rebuild does (`0x58cf10`).
- The rest are registered with no effect, and each row says why: `useWeatherShaders` (snow's fallback leg is a separate CPU draw), `lod`, `M2UseShaders`, `M2UsePixelShaders`, `movieSubtitle`, `gxTripleBuffer`, `gxCursor`, `gxRefresh`, `DesktopGamma`.
- `cvars.rs` is split into the runtime and the table.

**#329: Spell Detail.** benilla's options window gains 1.12's Spell Detail Level slider on the Graphics page, over `spellEffectLevel` on the stock 0-2 grid.

**#583: `FrameXML.log`.** Load failures now go to `benilla-config/Logs/FrameXML.log`, as the reference's go to `Logs\FrameXML.log`, and no longer to chat.
- The log uses the reference's lines ("Couldn't open", "Error loading", "Couldn't parse XML in") and its banners.
- A UI load that reports something rewrites the file, and a runtime `LoadAddOn` appends to it (`0x490187`, `0x46aac0`).

Verified:
- New tests for each switch, the maximize latch, every box's stored value with no unknown-CVar warning, the Spell Detail row and the log file's rewrite and append, each failing on main.
- A region dump of the options window shows the new row, and the rows below it moved 35 px, and nothing else.
- `scripts/gates.sh` and `scripts/smoke.sh` are green.
- A live run unticked Full Screen Glow in the stock window, which turned the glow pass off and saved `ffxGlow = "0"`, and a missing addon file produced its four log lines.

Built on macOS; no platform seam touched.

Fixes #549
Fixes #329
Fixes #583

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 10:09:02 +07:00
Sam SungandClaude Opus 5.5 33c2c5aa70 The fourteen missing 1.12.1 functions are defined, the keyring hover runs the stock path, and the stock CloseAllBags keeps a backpack that was open (#581)
The fourteen 1.12.1 functions #336 lists are defined, each derived from its reference address, and benilla's layer drops the two places it stood in for the stock bag code.

- **Keyring:** `KeyRingButtonIDToInvSlotID` (`0x4c8150`) exists, and the keyring slots answer as inventory slots 82-113. The stock `ContainerFrame.lua:616-619` hover path now runs unchanged, and the layer's keyring `OnEnter` wrapper is deleted.
- **Bags:** the layer no longer replaces `CloseAllBags`. The stock one (`ContainerFrame.lua:702-707`) closes the backpack only when it was not open before the vendor or mailbox opened it (#541). The deviation that opens every bag stays, and its comment now names its only callers.
- **Inventory:** `ShowInventorySellCursor`, `SetInventoryPortaitTexture` and `UpdateInventoryAlertStatus` exist.
- **Character and pet:** `GetDamageBonusStat` (ChrClasses field 2), `GetPetTimeRemaining` (armed from `SMSG_PET_SPELLS`) and `GetActionAutocast` exist.
- **Mail:** `ClearSendMail`, `GetNumPackages`, `GetPackageInfo` and `SelectPackage` exist, over a new `Package.dbc` loader. A selected package rides in the mail send. The compose reset now also clears the stationery, the package and the attachment, as `0x4acdc0` does.
- **Tabard:** `GetTabardInfo` opens the tabard designer through the registrar (`0x5e00e0`).
- **World map:** `GetWorldLocMapPosition` projects a map position onto the displayed map (`0x4a7360`).
- **Loot:** `SetLootPortrait` binds a unit loot source's portrait (`0x4c2b40`).

Verified:
- A test per function for its shapes, raises and effects, failing on main.
- A keyring hover test through the stock path, failing on main.
- A backpack-over-vendor test, failing on main.
- `scripts/gates.sh` and `scripts/smoke.sh` are green.
- A live A/B run: hovering a key asked for `player:82` and showed the key's tooltip. After a vendor, opening the Help panel left the backpack open and closed bag 1; on main the backpack closed.

Built on macOS; no platform seam touched.

Fixes #336
Fixes #541

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 03:19:56 +07:00
Sam SungandClaude Opus 5.5 22634279bd benilla's own interface loads as one layer after the stock FrameXML and before the addons; the core loads StatsFrame.xml and runs LocaleProperties.lua once, as FrameXML.toc does (#562)
benilla's own interface now loads as one layer on top of the stock UI, as `docs/METHOD.md` now states: the core loads the stock FrameXML rows, the layer (`assets/ui/layer.toc`) loads after the last of them and before the third-party addons, and like FrameXML it has no AddOn API row and fires no `ADDON_LOADED` (the reference loads `FrameXML.toc` outside `AddOn_Load`, `UI_Init` `0x48fbf0`).

- Moved into the layer unchanged: `ScrollTemplates.xml`, `KeyBindingsPage.xml`, `OptionsFrame.xml`, `ContainerFrameAdapters.xml`, `SpellBookAdapters.xml`, `ScriptLogFrame.xml`. `GameMenuFrame.xml` stays the core's last row until #553.
- The host's Lua fixes to stock code (`FCF_ChatTabFadeFinished`, the `DurabilityFrame` re-seat) move into the layer's `FrameXMLFixes.xml`; the resize re-seat stays host-side as an engine adaptation.
- The core now loads `StatsFrame.xml` and runs `LocaleProperties.lua` once, from `UIParent.xml`, so its chain rows are exactly the stock `FrameXML.toc` rows but the menu; the order is #555.
- A dev build boots without the layer under `WOW_STOCK_UI=1`; the host's calls into the layer are guarded.

Verified: new tests for the layer's file list, the core list against the stock toc, the load position (a `_G` definition log), absence from the AddOn API and the stock-only boot, each failing with the change undone; a before/after dump of every global, frame, draw order and saved variable differs only by `StatsFrame` and the moved fixes' source; `scripts/gates.sh` and `scripts/smoke.sh` green, and a live logout/re-login under `WOW_STOCK_UI=1` clean. Built on macOS; no platform seam touched.

Part of #552
Fixes #542
Fixes #543

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 21:01:28 +07:00
Sam SungandClaude Opus 5.5 6633a11d60 A dev build of a crate on top of benilla finds its install, benilla-config/ and .probe-identity in its own folder, which its build stamp hands in, not in cargo's checkout of benilla (#504)
A dev build finds its project folder, where its `WoW` link, `benilla-config/` and `.probe-identity` live, from its launcher, not from a benilla crate's own `CARGO_MANIFEST_DIR`. For a crate built on top of benilla, where benilla is a git dependency, that manifest dir is cargo's checkout of benilla.

- `benilla_buildstamp::emit()` stamps `BENILLA_PROJECT_DIR`, the launcher's workspace root (the nearest folder up holding `Cargo.lock`), only when the launcher's `dev` feature is on. A player build stamps it empty and carries no source path. The shims pass it in as `BuildId::project_dir`.
- `benilla_formats::project_folder` is the one home for the folder. Each entry point records the stamped folder once, before anything resolves from it. The install's dev rung, the state folder (which keeps its step from a linked worktree to the primary checkout) and the probe identity all read it. With nothing recorded (a test, an example, a tool) it is benilla's checkout, as before, and in a player build it is `None`.
- Live `assets/ui` and the addon corpus stay on benilla's own source.
- The example launcher's header now says a dev build of a crate on top keeps these in its own folder.
- The comment above the stamp's resource names the build banner, the stamp's reader beside the panel footer.

Fixes #494
Fixes #493

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 14:55:24 +07:00
Sam SungandClaude Opus 5.5 03b1072220 A script's selection, cast and targeting-cursor calls apply in the order it made them, so a macro's /target then /cast casts at the new target (#473)
A script's calls that touch the selection, the player's cast or the targeting cursor now take effect in the order the script made them, as in 1.12.1, where each such call is done when it returns (the macro runner `0x4f14e0` fires every line in one pass; `TargetUnit` `0x4899d0` and `TargetByName` `0x489d60` commit through `SetSelection` `0x493540`; `CastSpellByName` `0x4b4ab0` casts at the selection as it stands then).

- The VM keeps one ordered queue of these calls (`ScriptCall`) in place of fifteen per-kind queues, and one system applies it front to back in the target chain, each call through its owner's applier.
- A macro's lines run inside the `UseAction` that pressed it (`0x4e6098 call 0x4f1460`), so the calls they make are applied before the next queued call.
- `ClearTarget` deselects whatever is selected when it applies, so `TargetUnit("player") ClearTarget()` ends with no target.
- `SpellStopCasting`, `SpellStopTargeting`, `TargetNearestFriend`, pet bar presses and orders and pet-book casts join the queue, since they touch the same state: `/script SpellStopCasting()` then `/cast`, and `/assist` then `/petattack`, now apply in order too.

Tests run the real dispatcher on the headless client: target then cast casts at the new target, cast then target at the old one, a clear after a target ends with nothing selected, and a macro's calls land inside the `UseAction` that ran it. Built on macOS; no platform seam.

Fixes #451

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 19:48:00 +07:00
Sam SungandClaude Opus 5.5 db3be64158 With realms in more than one category, the realm list shows the stock category tabs, so every realm can be reached (#467)
With realms in more than one category, the realm list shows the stock category tabs, so every realm can be reached. Before, it listed only the first category's realms and had no way to the rest.

- **The Region** is `Wow.ini`'s `[WoW Config] Region`, read through the archive chain as `0x40249d` loads it (the stock install ships it in `interface.MPQ`), with the section and key compared case-insensitively (`0x445356`, `0x4453cb`) and the value parsed as `0x445470` does. It is 0 when the file or the key is missing.
- **The categories** are the `Cfg_Categories.dbc` rows whose Region column equals it, in file order (`0x46e300`): id from column 0, name from the row.
- **Placement** (`0x46e510`): a realm's category byte 0 takes the first category's id (`0x46e5b8`), then joins every category with that id (`0x46e60b`). A byte matching no category puts the realm on no tab, as in 1.12.1. Tab ordinals count the non-empty categories only (`0x46edf0`), and the load-band statistics still run over every realm.
- **The tabs** are built from `RealmList.xml`: hidden for one category, `RealmListTab1`..`N` with the category names for two to eight, the front one raised and disabled. A click fronts the tab and re-lists (`RealmListTab_OnClick`), the highlight falls back to the current realm or none, and the scroll clamps to the new tab. Change Realm and `OnShow` keep the stored category (`0x46f188`, `0x46f400`).
- **No Region rows**: every realm lists on one untabbed page. The reference reads a NULL category there (`0x46f26a`, `0x46e5b8`), and benilla does not copy the fault.
- **Logging in** by name, the remembered or current realm, still searches every realm, as the reference resolves by name.
- A dev capture, `WOW_CAPTURE=glue-realmlist`, shows two tabs over five invented realms.

Checked: tests for the Region read, the category rows for a US and an EU Region from the install's DBC, placement (byte 0 folded, an unmatched byte on no tab), the strip hidden for one category and named for two, a tab click through the real click path, the highlight and scroll on a switch, the stored category across Change Realm, the statistics over every realm, and the no-rows fallback. Each fails with its piece undone. Gates and the smoke run green on macOS; no platform seam touched.

Fixes #362

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 14:27:13 +07:00
Sam SungandClaude Opus 5.5 3be64967ec Logging in to an account with no characters lands on the empty character list, as in 1.12.1, instead of creating a Human Warrior (#457)
Logging in to an account or realm with no characters lands on the empty character list. Until now the login cycle created a male Human Warrior on the server, named `WOW_CHAR` or "One", on every login that found an empty roster, a typed one included and again after Change Realm.

- **The reference.** The client builds `CMSG_CHAR_CREATE` in one place, `0x5aac50`, reached only from the glue binding `CreateCharacter` (`0x4719e0` → `0x4711d0` → `0x46b290` → `0x5aac50`), which stock `CharacterCreate.lua` calls only from `CharacterCreate_Okay`. With an empty roster, stock `CharacterSelect.lua`'s `UpdateCharacterList` disables Enter World and Delete and shows Create New Character, which benilla's select screen already does.
- **The change.** The create-if-empty step is gone from the per-realm cycle, and `NetConfig`, which existed only for it, goes with it. The create screen's own path is untouched.
- **Scripted runs.** `WOW_CHAR` must now be on the account: a driverless run whose `WOW_CHAR` is missing fails at select, as it already did on a non-empty roster, and a player's run shows the list. `docs/CONTRIBUTING.md` says so, and names `WOW_PROBE_CHARCREATE` as the explicit way to make one.

Checked: `an_empty_account_parks_at_select_without_creating_a_character` drives the real login cycle against a fake realmd (the server half of SRP6) and a fake world server answering an empty roster. It fails with the old step restored (the client sends `CMSG_CHAR_CREATE` on its own) and passes without it. Live against a local vmangos on a fresh account with no characters: before, the server gained a level-1 "One"; after, the client parks on the empty list and the account still holds no character. With a `WOW_CHAR` not on the account, a driverless run exits at select, and nothing is created. Gates and the smoke run green on macOS; no platform seam touched.

Fixes #343

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 10:43:33 +07:00
Sam SungandClaude Opus 5.5 4dc33b4afd Comments in benilla-protocol: the second read, citations on the pinned revision and restored facts (#375)
The second read of `benilla-protocol`'s comments, the last step #41 asked for: that crate's comments were rewritten to the rule before second readers existed. Four readers compared every file's comments against their state before the rewrite and against the code as it is now.

Fixes #41.

- **Citations.** vmangos line citations are re-pointed to the pinned revision: off-by-a-few lines (`Pet.cpp`, `Guild.cpp`, `Petition.cpp`, `LootHandler.cpp`, `ChatHandler.cpp`), and paths the rewrite dropped where a bare file name is ambiguous (`Server/Packets/Spell.cpp` against `Spells/Spell.cpp`, the two `Item.cpp`, `Group.cpp` and `Chat.cpp`). The gossip-hello citation is `NPCHandler.cpp:347`.
- **Facts restored.** The move-time-skipped chain (`0x603b40` → `0x601560` → `0x61ab90`), the addon-message TAB surviving vmangos's chat sanitizer, the zone-under-attack sender, `SMSG_ADDON_INFO` answering the auth session's addon block, the officer-note permission gate, and `SMSG_ITEM_COOLDOWN` on equipping an item with an on-use spell (the old text said a proc).
- **Deviation labels.** A label now carries its reason, or goes when the behaviour is only unbuilt (the survey window, the group XP bonus, the NPC-text language garble).
- **Two examples.** `park_probe` and `login_refusal_probe` took a hard-coded local account; they now read `WOW_USER` and `WOW_PASS`.
- **Verification.** Every file strips to identical code apart from the two examples; `cargo fmt --check` is clean.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 01:29:00 +07:00
Sam 88eca028ee map: regenerate — sess/polish landing 2026-09-23 20:17:55 +07:00
Sam 0492e3499b map: regenerate — sess/gaps landing 2026-09-23 17:27:32 +07:00
SamandClaude Fable 5.1 3e12fa1d7f benilla is one repo: the working tree moves in, restructured
Until now this repo was an export of a private tree, pushed as squashed snapshots. From here on
it is the working tree itself, and the private repo is archived.

The root holds AGENTS.md and the Rust and GitHub files only. Everything an agent needs to orient
is in docs/: METHOD.md (the rules and how the project is built, 123 lines, project method only),
MAP.md (generated from the code at every land) and CONTRIBUTING.md (what gets in and how a
change is judged). One skill, `probe`, holds the procedure for running the client unattended
against the local server.

Brought over because the project needs it: reference/ (the 1.12 name catalogues 34 source
files read; the export failed its own tests without them), the gate chain and the pool
(scripts/), the hooks that enforce the install rule and the worktree rule, the full .gitignore,
the `cargo play` alias, and the original wording of eleven source files the export had reworded.

Left out: the decision records (archived with the private repo), the export tooling, the
operator's Discord and triage loop, the lab-laptop scripts and the instruments nothing
references, which live outside the repo now. What read the records is gone from the tooling:
no corpus lint, no records index, no `reserve` verb, no number-collision check.

Three hooks are gone: fmt-at-commit and the file-size nudge were redundant with the gates, and
the assets/ui guard is now a test (`assets_ui_does_not_grow`) that fails for everyone, not only
for a Claude Code session. The remaining pool hooks stand down on a machine without a pool.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-23 15:56:57 +07:00