`SendChatMessage` with an empty line now sends nothing unless the type is AFK or DND, as in 1.12.1, where `0x49f28d`-`0x49f2a1` end the call on an empty first byte for every other type, after the chat-type check (`0x49f276`, "Unknown chat type" at `0x49f285`) and before the language, the target checks, the AFK clear and the tutorial acknowledge. Before, an empty line went out as an empty packet, and an empty SAY cleared a standing AFK.
Fixes#499
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The player's Attack (the Attack Target key, `AttackTarget()` and the Attack action button) now picks its target as 1.12.1's validator `0x612df0` does, where before it swung at whatever was selected, a friendly unit or a corpse included.
- With nothing selected, or a unit the player is friendly toward (reaction ≥ 4, `0x6130a3`), it runs the Tab cycler for the nearest enemy (`0x6130b5` → `0x493f60`), which commits the new selection at once, so `AttackTarget()` then `/cast` in one script casts at the new target.
- It then reads the selection again (`0x6130c1`): none raises "There is nothing to attack." (`0x6130d9`); a unit neither alive nor feigning, or one `CanAttack` (`0x606980`) rejects, raises "You cannot attack that target." (`0x613171`). A friendly unit with no enemy near stays selected and meets that second gate.
- The swing toggle runs only once a target is accepted (`0x6131a0`), so Attack at a corpse mid-swing gives the error and keeps the swing.
- The pet's attack order shares the same pick. vmangos stops a swing at a friendly or dead target server-side (`CombatHandler.cpp:46-59`), which the client no longer sends.
Fixes#249
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
`SendChatMessage`'s language argument is spoken, as in 1.12.1: a racial language picked from the chat menu (`ChatFrame.lua:2404`, passed on by `ChatEdit_SendText` at `:1950-1954`) or named by an addon goes out in `CMSG_MESSAGECHAT`, where before every line went in the faction tongue.
- `0x49f1e0` starts from the speaker's default language (`0x5ec890`); when argument 3 passes `lua_isstring` (a string or a number) it is matched by name against every `Languages.dbc` row, case-insensitively (`0x49f8a0`, `SStrCmpI`), and a miss raises "Unknown language" (`0x49f2e1`). nil, an absent argument, a table or a boolean keep the default. A language the character has not learned is sent and left to the server, which drops it (vmangos `ChatHandler.cpp:175`).
- The packet builder writes the type, the language and a target for a whisper or a channel only, then the text (`0x49f6c3`-`0x49f72a`); fourteen per-type writers become one, with byte-exact goldens.
- Each new UI state is seeded with the language table.
Checked live against the local vmangos: a say, a yell, a whisper to self and a GM command round-trip through the new builder; a channel line reaches the server's channel lookup; an unknown language raises in Lua.
Fixes#164
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
`UnitBuff`, `UnitDebuff`, `UnitAura`, `CancelUnitBuff` and the tooltip's `SetUnitBuff`/`SetUnitDebuff` now resolve their unit token as 1.12.1's `0x515970` does before reading any aura, so `mouseover`, `party1target`, `raid3target`, `pettarget`, `targettargettarget` and any casing (`"PLAYER"`) answer for the unit they name, where before they answered nil.
- The resolver matches the nine base names as prefixes in the reference's order, folding ASCII case (`SStrCmpI 0x64a4c0`), with `npc` whole; the number after `party`/`raid` wraps at 32 bits and counts from 1; `mouseover` counts only while the unit is loaded (`0x515bd2`); each `target` suffix hops to that unit's `UNIT_FIELD_TARGET` (`0x5159d3`-`0x515a2c`); a name none of the nine match raises `Unknown unit name: %s` (`0x515c14`), before any index check, as `UnitBuff` calls it at `0x519542`.
- The VM holds one aura list per guid, plus the player's own in cache order (`0xbc6040`); any token naming the player reads that list, so its order and durations are unchanged.
- The feed builds lists only for guids some token can name, through the existing per-guid cache. `UNIT_AURA` fires for the same tokens as before.
Fixes#488
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The auction house's Auctions tab now names the high bidder on each of your auctions, and shows "No Bids" only for an auction with none, as in 1.12.1; before, every auction read "No Bids".
`GetAuctionItemInfo`'s eleventh value follows `0x4cee40`: on the owner list, for a row the player owns (`0x4cf12a`-`0x4cf142`), it is the bidder's name from the name cache (`0x4cf144`-`0x4cf174`), nil with no bid (`0x4cf150`) or while the name has not arrived; on every other row it is 1 when the player holds the bid, else nil (`0x4cf17b`-`0x4cf1a0`). A bidder name that arrives later refreshes the list with `AUCTION_OWNED_LIST_UPDATE`, the event the reference's callback `0x4cf2b0` fires. vmangos sends the bidder guid with each row (`AuctionHouseMgr.cpp:840`).
Fixes#259
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A character's AddOns file now holds only the rows the character chose, as in 1.12.1, so a character that never chose an addon keeps following what the realm's other characters agree on (`0x51e470`) instead of freezing its inherited values at the first logout.
- Each character has an enable hash and a dirty flag: the loader `0x51ebe0` fills the hash from the file, clean; the setter `0x51ea20` inserts on a miss and dirties the hash only on a change (`0x51ebab`-`0x51ebbc`); the writer `0x51ef20` writes a dirty character's hash, a line per entry, and nothing else (`0x51ef59`).
- Logout and exit (`0x490c88`) and the character-select AddOns screen's Okay (`SaveAddOns` `0x46d990`) both write through it, so a session that toggles nothing writes nothing, and no `Blizzard_*` rows appear.
- `ResetDisabledAddOns` restores the hash as loaded, clean (`0x48e830`).
- On the AddOns screen a character with no row now follows another character's toggle at once, as `AddonList_Update` re-reads each column after every `EnableAddOn`.
Fixes#418
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The 48 functions the stock `Bindings.xml` bodies call are now Lua globals, registered from the reference's three tables (`0x8500b8`, `0x84f7a0`, `0x83de68`), so an addon, an addon's `Bindings.xml` body or a `/script` line that calls `TargetNearestEnemy`, `TargetLastTarget`, `AttackTarget` or the rest no longer raises. Each runs the code its native binding runs.
- **Targeting** goes through the ordered script-call queue, so `/script TargetNearestEnemy()` then `/cast` casts at the unit the cycle picked. `TargetNearestEnemy`, `TargetNearestFriend`, `TargetNearestPartyMember` and `TargetNearestRaidMember` share the Tab cycler (`0x493f60`, modes 1-4; party and raid modes are new: `0x493eed`, `0x493f15`), reading the reverse flag as `0x6f1c10` does. `TargetLastTarget` (`0x489b00`) swaps to the last-target pair `SetSelection` stamps, and deselects when it is empty. `AttackTarget` (`0x489b50`) shares the ATTACKTARGET key's body.
- **Movement:** 23 of the 26 movement-table functions open with the gate `0x494a50`, which lets a call through only while a binding runs; here that is a binding body running, stock or an addon's. Inside one, `MoveForwardStart`/`Stop` and the rest set and clear the same held state the key does (`0x515090`); a Start with no Stop keeps moving, and the window deactivate (`0x514490`) and the loading cover (`0x5144c0`) clear it. `Jump`, `ToggleRun`, `ToggleAutoRun`, `SitOrStand` (`0x48b920`) and `ToggleSheath` (`0x48a070`) fire their key's command. `IsMouselooking` (`0x514270`) answers 1 or nil.
- **Camera:** `CameraZoomIn`/`Out([yards])` (`0x50b400`, `0x50b450`) take a number or numeric string, 1.0 when absent, into the wheel's zoom.
- Not built, so they answer and do nothing: the pitch pair, `TurnOrAction*`, `CameraOrSelectOrMove*`, `MouselookStart`/`Stop`, the `MoveView*` pairs and `ToggleMouseMove`; each is named with its address where it is registered.
Fixes#334
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
`EnableAllAddOns` and `DisableAllAddOns` now switch only the addons in the Lua index array, as 1.12.1's `0x48e720` and `0x48e7f0` do: bound `0x51def0`, each name from `0x51df00`, into the enable setter `0x51ea20`. That array leaves out the records `SMSG_ADDON_INFO` hid (`0x51dc4f`), which on vmangos is every `Blizzard_*` addon (`AddonHandler.cpp:129`), so an addon manager's disable-all no longer leaves the auction house, talents, trainers, trade skills, inspect, `/macro` or Key Bindings unable to load.
- The per-addon verbs and the all verbs share one setter, standing for `0x51ea20`.
- Before the server's addon reply the index is empty and both verbs change nothing, as the reference's count is 0 until then.
Fixes#414
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Item tooltips in repair mode show the repair cost: SetInventoryItem and SetBagItem return each item's cost after the open vendor's discount
- Bank items price in their tooltips too, the repair-cost gate is tested input by input, and logout keeps the last push
- The repair-cost tests share one fixture for the feed's world and the VM's sword
- The repair-cost comments say one fact each, and two that restated the code are gone
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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>
Targeting an NPC whose faction you are Exalted with no longer opens a script error. `UnitReaction` answered 8 there, and stock `TargetFrame_CheckFaction` indexes `UnitReactionColor[reaction]`, which has seven entries (`TargetFrame.lua:6-14`, `:183`).
- **The cap.** `UnitReaction 0x6061e0` takes `0x606530`'s answer through `min(rank, 6)` at `0x606439` (`cmp esi,6` / `jl` / `mov esi,6`), so Revered and Exalted both read 6, and the Lua binding adds one (`0x51683e`), so Lua sees at most 7. `ring_reaction` now caps its answer there.
- **The ladder stays whole.** `reputation_rank` is untouched: the reputation pane and the item requirement lines still read Exalted.
- **Every caller** of `ring_reaction` models `0x6061e0` and tests at 1, 3, 4 or 5, so none changes but the four Lua unit feeds (target, targettarget, npc, mouseover), which now share one `unit_reaction` helper. The vendor discount's Exalted row reads as Revered, as in the reference, and still takes 10%.
- **Comments** that gave the scale as 0..7 or 1..8 now say 0..=6 and 1..=7.
Checked: `the_reputation_rank_is_capped_at_revered` walks both sides of the Honored, Revered and Exalted thresholds; `an_exalted_npc_target_reads_revered_and_its_plate_is_green` runs stock `TargetFrame` on an Exalted Stormwind unit. With the cap loosened to 7 both fail, the second with the issue's `TargetFrame.lua:183: attempt to index field '?'`; restored, both pass. Gates green on macOS; no platform seam touched.
Fixes#440🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A friendly or resurrection spell that binds nothing now enters the unit targeting cursor, as `TryCast` does for any residual word without the enemy bit (`0x6e507b`–`0x6e50ab`). `autoSelfCast` boots at its reference default of `"0"`, and its `Deviates` row is gone.
- **Binding.** The cursor binds a unit through a world click, and through `SpellTargetUnit` from the unit frames (`BindTarget 0x6e5b40`). The selection never moves.
- **Relation checks.** Heals and buffs ask `CanAssist 0x6066f0` instead of reaction alone, so a friendly NPC without the PvP flag is no longer assistable. That is part of #50.
- **Range.** `SpellCanTargetUnit` and the cursor answer per unit through `0x6e6460`'s unit leg, including the spell's min/max range (`6e677c`–`6e6802`). A click on a unit out of range raises "Out of range." or "Target too close" and keeps the cursor armed. The reference's abort there also strands the cursor, and that is not copied.
- **Your own model** joins the world pick only for a word `0x6e61a0` admits (`0x850e`, no `AttributesEx 0x80000`), and never in first person.
Fixes#136
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Sam <samuv.eth@gmail.com>
- A repair the purse cannot cover is refused as in 1.12.1: the not-enough-money error and its voice line, no repair sound, nothing sent; Repair All tests the worn gear alone
- Tidy three comments: the gift-wrap getter's reader, the repair-reset test's claim, the merchant module doc's width
Fixes#412
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Casting a targeted spell ends repair mode, as 1.12.1's targeting arm writes its cursor over Repair
- A right mouse-down on the world ends repair mode, as 1.12.1's WorldFrame hook resets the cursor to Point
Fixes#420Fixes#422
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Right-clicking a bag or worn item puts a held item back first, as 1.12.1 clears the cursor before any use
- The bag frame's end-to-end test: a right-click with a held item puts it back, then uses the slot
Fixes#413Fixes#421
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- fix: repair mode
- repair mode: a right-click puts a held payload back, then repairs the item
- repair clicks sound ITEM_REPAIR as they are sent, under the kit's no-duplicates gate
- the repair-all button is no longer driven from Rust
- repair mode: the click bindings' order, for every cursor payload
- worn-item repair clicks drain beside the bag's, in no system of their own
Fixes#147Fixes#148
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Sam <samuv.eth@gmail.com>
One comment corrected: the stock unit menu greys Trade and Duel on `HasFullControl()` (`UnitPopup.lua:474-477`, `:502-505`), not follow and trade.
Comment only.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The comment and string passes' last leftovers, and one lookup made to match the reference.
**Comments that stated a wrong reference fact**, now stating the right one:
- A cast failure clears the GCD, and a failed cooldown-on-event spell also drops its parked record (`0x6e73cc`).
- `SetUnit` is `0x505d70`; `0x476cb0` is the component clone it reaches.
- Interior sound follows the character, where the listener sits, not the camera.
- Template inheritance re-runs `LoadXML` on the one object against each template (`0x76985c`), and reads `name` and `id` from the instance alone (`0x769770`).
- On the ghost-to-alive edge the reference clears its corpse cache and sends nothing (`0x5ee990`, `0x491f50`).
- The name cache has no high-bit miss. An empty-name answer evicts the record in the reference (`0x55f6f0`).
- The combat-log deferred queue (`0xc4e208`) waits only on an item name. A unit name never waits.
- A flying spline's segments are paced by curve length (`0x454320`, `0x453760`), not by chord length.
- A stop within 3 yd of its stop point keeps the arrow nocked (`0x6019e4`).
- A missile's sound id is at `CMissile+0x40`.
- A nil or unresolved `AssistUnit` token is a gap against `0xb8` ERR_GENERIC_NO_TARGET, not a deviation.
**Strings:**
- Test, log and tool messages lose leftover history wording and placeholder labels.
- The extractor's scan summaries describe the current tint and sequence-clock paths.
- The smoke script's failure text carries no private numbers.
**Line continuations:** twelve log, panic and assert strings get back the `\` continuations they had lost. Each printed a run of spaces mid-message. Fixes#106.
**One lookup:** a region's `inherits=` is looked up as one verbatim name, matched case-insensitively (`0x6ee6f0`, `_strnicmp` at `0x6ee747`), as `framexml::expand` already did for frames. The gate before it split on commas and matched case-sensitively, so a texture or font string whose `inherits=` spelled its template in another case never received the template. `a_region_takes_a_mis_cased_template` fails before this change and passes after.
**Tests:** the quest tests stop loading three FrameXML files that no quest file uses.
**In game:** nothing changes for the stock UI. Log lines read as one sentence, and an addon texture that names its template in another case now gets it.
Verified with `scripts/check.sh` on the changed crates, plus the loader and quest tests with the install present.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Quests printed no chat lines: no "Quest accepted: …", "… completed.", "Experience gained: …" or "Received …". Nothing raised the reference's quest messages.
Fixes#69.
- **Accept** (`0x5ddd80`): a quest-log slot watcher raises "Quest accepted: <title>" when a slot gains a quest, or keeps its quest and loses the fail bit (`0x5dde17`-`0x5dde34`); a slot that swaps quests says nothing. An uncached quest prints when its record arrives. The QUESTADDED sound now comes from that message row.
- **Turn-in** (`0x5dc400`), in order: "<title> completed." only when the quest has no follow-up in its chain; "Experience gained: N." when the XP is non-zero; "Received …" with the paid coins from the COPPER/SILVER/GOLD globals; then one "Received item: [link]." per fixed reward, and one for the chosen reward, which the client remembers from `GetQuestReward` (`0x50161a`) because the packet does not carry it (vmangos `Player.cpp:14500`).
- Every line comes from the install's strings. A line whose quest or item record is still in flight waits for it; an unknown record is dropped.
- The money line joins only the paid coins ("1 Gold, 5 Copper"). The reference's separator test prints "1 Gold, , 5 Copper" when the middle coin is zero, a quirk not copied.
Tests: six new tests across the quest log, the turn-in handler and the line queue; `scripts/check.sh` is green.
To see it: accept a quest from any giver and read "Quest accepted: <title>"; turn it in and read the completed, experience, money and item lines.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Typing into a numbered channel (`/1 hello`, or a line under a "[2. Trade]" header) sent nothing: the channel number went to the wire as the channel's name, so the server found no channel called "2".
Fixes#222.
- `SendChatMessage` reads a CHANNEL target the way 1.12.1 does (`0x49f4d9`-`0x49f4ea`): `SStrToInt` on the text, then the by-number slot getter (`0x49be50`), and the packet carries that slot's name (`0x49f70b`). A number that names no joined slot sends nothing, before the AFK clear and the tutorial acknowledge. There is no by-name leg, so an addon that passes a channel name sends nothing, as in 1.12.1.
- The binding keeps the target as Lua's `tostring` gives it, so 2.7 reads 2 instead of rounding to 3. `LeaveChannelByName` shares the parse and the getter.
- The send drains run before the zone-channel walk, so a `/N` resolves against the roster the script read the same frame.
Test: `a_channel_send_carries_the_numbered_slots_name` runs the real drain over a slot roster. It failed before (the wire target was "2") and passes now; holes, suspended slots, names and missing targets send nothing.
To see it: two characters of one faction in the same zone, both in General. On the first, `/1 hello`: the second now sees it.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Part of #41: the last of five `benilla-app` pieces. This one is everything the others did not cover: the addon harness, the settings registry, the glue screens (login, realm list, character select and create), key bindings, the minimap, nameplates, the loading screen, transports, the performance instruments, the smaller modules, the integration tests, and the UI asset files (`OptionsFrame.xml`, `benilla.toc` and the rest). Their comments are rewritten to the rule in `docs/METHOD.md`, which is what the code does and the reference fact behind it, in a line. 27,351 comment lines become 11,010.
Fixes#33. Fixes#103.
- **The mechanical strip.** The citations that were only a private record number go first, and each edit is shown to be a pure removal.
- **Wrong facts corrected.** The rewrite corrects the comments that stated wrong facts. Among them:
- The weapon trail's emissive is 0 (`0x70ca30`): the trail is a type-5 render record, so its ambient-only light term is exact.
- The camera-style dropdown writes "1", "2", "0" (`UIOptionsFrame.lua:525,536,547`); several register-site addresses in the settings table are corrected.
- The graphics settings are held until `RestartGx`, as in 1.12; `Fonts.xml`'s early load no longer carries a false reason.
- vmangos sends a player-name invalidation after a rename (`CharacterHandler.cpp:839`), and the cinematic camera's announce packet has an empty handler there (`MiscHandler.cpp:919`).
- Stock FrameXML and GlueXML line citations are checked against the stock 1.12 files, and vmangos line citations against the pinned revision.
- **Deviation labels.** "Deviation:" now marks only a choice with its recorded reason. Declining to copy a reference defect is stated plainly, and so is an unbuilt behaviour or an untraced one.
- **Verification.** Every file strips to identical code, apart from test literals in `login/`, `death/` and `ui_script/death_tests.rs` that now use placeholder account and character names, and `cargo fmt --check` is clean. Six second readers compared every file old against new, restored the facts the rewrite had dropped, and checked each new citation against the code, the stock files, vmangos and the reference.
A second commit corrects one comment in `benilla-ui` (`unit_is_grey`: the reference's exclusion is players only). A third lowers `ui_script_wall`'s ceiling from 45 to 40: five gameplay files named `UiScript` only in comments the rewrite removed, and the ratchet asks for the number to follow.
Behaviour differences the rewrite surfaced are filed as their own issues (#339 to #353 among them). String literals are untouched; they are #92.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- OptionsFrame.xml and options_tests.rs: the stock camera-style dropdown writes "0" for Never
(UIOptionsFrame.lua:525,536,547), not "3"; the `alt` entry only shows a stored "3" as Never.
- unit/bindings.rs: TargetByName's silent miss is a gap (#52), not a deviation; the reference's
messages are 0x127 ERR_UNIT_NOT_FOUND and 0xb8 ERR_GENERIC_NO_TARGET.
- fields/unit.rs: the melee reach's 5.0 is a floor (0x6e35bf-0x6e35ce), not a cap.
- tests/nameplate.rs: pfUI's click-through is nameplates.lua:1228.
Comments only; code unchanged. Built on macOS.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Code health in `benilla-ui`, found while its comments were rewritten. No behaviour a player can see changes.
- **Unused globals and queues removed.**
- The Rust `ChatFrame_OpenChat` and `ChatFrame_SendTell` queued text that nothing drained. The stock `ChatFrame.lua` defines both globals and replaces them when it loads, as the reference's FrameXML does.
- `BenillaGetItemStats` had no Lua caller. Its test of the item cache's miss, ask and push now runs through `GetItemInfo`, which shares that path.
- `UiScript::queue_social_request` had no caller.
- **One true/nil helper.** Seven modules each carried their own copy of `binding_abi::flag`, the reference's `1`-or-nil boolean. They call it now.
- **Three tests test their claim.**
- `click_focuses_regardless_of_autofocus_…` built both boxes with autoFocus on. One is now off.
- `level_shift_saturates_at_zero` never saturated. Its child now drops past 0.
- The layout profiler's `plan` bucket was never set, so it always printed 0. It is gone.
- **Smaller fixes.** An empty `if buyback {}` branch is gone, and a doc comment no longer sits on a `use` line instead of on `install_justify`.
Checked with `cargo clippy -p benilla-ui --all-targets -D warnings` and `cargo test -p benilla-ui` (1513 passed) before the land gates.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Part of #92, for `benilla-ui`, `benilla-world` and `benilla-formats`.
- 88 string literals drop private record and bug numbers, the dates of private incidents, and the workspace's name for the maintainer. They are assert and panic texts, log and printout lines, the `--` comments of the Lua prelude and of test fixtures, and one Lua assert. Each keeps its fact in its own words.
- Two test names lose their numbers: `burning_steppes_lava_builds_a_magma_surface_at_the_reported_pin` and `join_channel_by_name_answers_the_row_custom_and_nil_legs`.
- A test-only event, `B271_PROBE`, is now `ERROR_PROBE` in the two crates that register and fire it. A fixture's error text in `benilla-app`'s `ui_script` tests drops its bug number, together with the assertion that reads it.
- Doc comments that named a client function by a disassembler's label (`FUN_0068a830`) now give its address, in `benilla-formats`, `benilla-assets` and one `benilla-app` file.
- Every literal change keeps each literal's line count, proven with comments stripped and every literal masked. The two renamed tests differ only in their names.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Fixes#32. Every vmangos `File.cpp:N` citation in the crates whose comments have been rewritten now reads in one revision, vmangos core `6b34a36`, the one the comments are written against, a few commits from the server benilla is run against. Some citations had been taken from a newer vmangos checkout, where the lines have moved. #32's own targets came from that newer checkout too, so where this PR and #32 differ, this PR follows `6b34a36`.
- 53 citations in 31 files are re-pointed: most in benilla-protocol, a few in benilla-ui and benilla-app. Each new line was read and is the construct its comment names.
- #32's cases:
- The root-ack kick is `MovementHandler.cpp:715-722`; three comments cited the falling deferral after it.
- Movement is discarded before `CMSG_SET_ACTIVE_MOVER` lands at `:291-293`, through `Player::GetConfirmedMover`.
- The released mover's parting stop is `:955-964`.
- The mount-special echo is sent with `self` false at `:972`. Whether the sender gets its own echo depends on the server's broadcaster (`Object.cpp:2273-2280`); that is stated once, where the packet is parsed, and the opcode comment points there.
- Ten more citations were a line or two off in every revision. One comment also stated a wrong fact: vmangos's ignoring-you party refusal names the invitee, and the empty name is the raid-convert confirmation's.
- Comment text only. Every file strips to identical code, and `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>
Part of #41: the comments of `benilla-ui` (the Lua UI engine, its FrameXML loader and its widgets) are rewritten to the rule in `docs/METHOD.md`, which is what the code does and the reference fact behind it, in a line. 43,140 comment lines become 15,161.
- **The mechanical strip.** The citations that were only a private record number go first, and each edit is shown to be a pure removal.
- **Wrong facts corrected.** The rewrite corrects the comments that stated wrong facts. Among them:
- `0x64a480` is the case-sensitive compare (a byte `repz cmpsb`), and `0x64a4c0` the case-insensitive one.
- A frame's id sits at `+0xb0`, not `+0xb4`. `EnableKeyboard`/`IsKeyboardEnabled` are `0x776ec0`/`0x776f90`.
- A region's unpinned edge has no fallback to its owner's rect: the owner supplies only the scale.
- `$parent` expands to the nearest named ancestor, or "Top" when none is named (`0x76c5b0`), not simply to the parent.
- A StatusBar fills from its internal normalised fraction, not from `GetValue()`.
- The addresses of `StartDuel` and `StartDuelUnit` were swapped.
- Many "untraced" notes now give the traced reference fact: the drag threshold, the per-grip sizing edges, the empty-`autoFocus` skip, and `SetTexCoordModifiesRect`'s default.
- Stock FrameXML line citations are checked against the stock 1.12 files and corrected. `ref-` file names become the stock file names.
- **Deviation labels.** "Deviation:" now marks only a choice with a reason. A surface that exists only for the retired Era addon target says so plainly: it is not 1.12.
- **Verification.** Every file strips to identical code, and `cargo fmt --check` is clean. Nine second readers compared every file old against new, restored the facts the rewrite had dropped, and checked each new citation against the code, the stock files, vmangos and the reference.
Behaviour differences the rewrite surfaced are filed as their own issues (#131–#142), and more are being validated. The comments here state what the code does. String literals are untouched; they are #92.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Comments that named a chat reporter, cited a chat channel or message id, or quoted a report word for word now state the symptom in their own words. The test fixtures that used real character names use invented ones.
Verified: 52 of the 56 files strip to identical code with their comments removed; the other 4 differ only in the fixture names, and with those mapped back they strip identical too. `cargo fmt --check` is clean, and the `ui_party`, `unit_frame_tests` and `capture` tests pass with the install present and no skips. Record numbers still in these comments go with the comment pass (#41).
Fixes#34🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Comments that pointed into the maintainer's private reverse-engineering notes (a note by name, a
path, a section or finding id, the analysis's own name for a client function, a process word) now
carry the fact itself, cited by the reference client's address, by vmangos file and line, or as
"the reference". Nobody outside could follow those pointers.
Comment text only, plus the test-assert and log strings that carried the same pointers. No claim
changes: a comment the notes have since corrected keeps its wording here, and correcting it is
separate work.
The two reference generators, `scripts/gen-reference-globals.py` and
`scripts/gen-reference-verb-events.py`, named the private notes' layout to find their inputs. They
now take the input files as arguments (`--capture`, `--framexml`, `--names`, `--extents`,
`--disasm`). Run on the same inputs, both write the committed tables row for row; only the two
header lines that named the old environment variable change.
Checked per file:
- the code is identical: each file with its comments stripped compares equal before and after; a
changed string literal is compared under a mask that proves only literal text moved and every
literal kept its line count;
- no client address the old text cited was dropped;
- a second reviewer checked every added address against its derivation;
- a detector for the notes' names, paths, ids, function names and process words finds none left;
- `scripts/gates.sh` is green.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
What a clone lacked: the gates went green with a thousand skipped tests and said nothing, every
login script assumed a worktree-pool slot and its probe account, the client defaulted to the
maintainer's account, a docker path and a home directory were in the code, the temp files broke
on GNU coreutils, and two generators could only run against a sibling repo.
Now the test gates print how many data-gated tests skipped and why; a scripted run logs in as
the account its checkout declares in `.probe-identity` or as the three variables name, never a
default, and the client refuses a scripted login on any other account; the event and message
tables regenerate from vendored inputs, byte for byte; the docs state the real prerequisites.
Out of the tree, into the maintainer's own tooling: the worktree pool, its hooks, the perf leg
runner, the cross-platform compile and the Vulkan container leg — a way of working, and setups
not every machine has. One hook stays: no writes into the install.
Two tests that could never run are gone with their subject: the quest-timer geometry diff
compared a copy of the window that no longer exists (and joined a chain path onto a folder, so
it always skipped), and the FrameXML flag gate now reads the stock files off the player's own
patch chain instead of an extracted folder only one machine had.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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>
- a Vanish, a stealth or a unit leaving range clears your target at once, not two seconds later, and TAB reaches a mob behind you (2345)
- a fully painted terrain layer covers fully: roads and rock no longer let about 6% of the grass underneath show through (2345)
- companion pets show their names, the saved camera pitch survives a login, and a missed resurrect offer no longer blocks later ones (2345)
- ESC on a quest giver closes it, browsing the auction house by category searches the right items, and abandon removes the quest you chose (2345)
- a profession window open at logout no longer reopens for the next character (2345)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- a creature walking a path no longer cuts its first corner: the path's first waypoint was dropped at decode (2342)
- two characters whose names differ only in accented or non-Latin letters no longer share macros, bindings and SavedVariables; a file that failed to load is not overwritten at logout (2342)
- custom chat channels survive a logout, a collapsed quest header keeps its quests watched, and a trackpad swipe scrolls as far as it adds up to (2342)
- a hearth off a zeppelin no longer lands back on its deck, and an opened lockbox with loot left in it unlocks (2342)
- every snapshot runs on Vulkan, in a container, before it ships (2344)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- an item is an object in the one index, and its enchant and lifetime countdowns are its own (2334, 2340)
- the server's stand state applies, a bag equipped into a bag slot fires BAG_OPEN, and the inspect echo is dropped on purpose (2339)
- the schedule census counts declared orders only, and the architecture walls gate leaks rather than the API (2337, 2338)
- one queue for every event into the UI is not built: the reference orders its events per mechanism, not by the wire (2341)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- the cast ladder moves under the spell: try, target bind, the usable walk and the refusal rungs live in one place, and the cast reply sends a chained cast itself (2330)
- every server packet decodes through one exhaustive match, and an opcode-coverage script joins the reference's 387 handled opcodes against ours (2331)
- a landing that touches a platform seam compiles Linux and Windows first, and the seven WMO portal-cull audits join the tests (2331)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- PlayMusic and StopMusic are in, one engine verb with a NULL arm as the reference has it: the track loops on the Music slider, the zone music fades out under it over 4 s and comes back 6 s after a stop (2309)
- a PlayMusic whose file does not open leaves the zone track alone, and the server-pushed music slot is named — three fades the reference keeps apart that our one slot folds together, the next move (2315)
- an addon's own sound file plays: every by-path asset read walks the patch chain, then the loose AddOns folder — the rule art and fonts already had, now one function that audio shares
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- a frame's __index is its kind's method table and the Region map's 19 shared verbs dispatch in Rust: a widget method call costs one table index instead of a Lua re-entry — the idle UI tick ~20 % lighter, client-wide (2310)
- SetParent names the subtree it moved instead of giving the layout ledger up: a pooled map note's recycle no longer re-derives the whole interface, and a Questie quest-log change's worst frame halves, 166 → 83 ms (2314)
- thirteen single-store windows — honor, talent wipe, the dialog verbs, scoreboard, battlemaster, battle map, book, mirror timers, minimap ping — register their handlers; 18 kinds leave the match (2313)
- the addon-enable RE lands: DefaultState is a seeded byte, the node set is the character list, and a character the store has never seen inherits nothing — 2311's one wrong branch corrected (2316)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- the CVars are one engine-side table that outlives every interface reload: a change fires its observer, the eight latched rows wait for RestartGx, and /console gains set, cvar_reset, cvar_default, cvarlist and help (2303)
- every push into the interface runs in one feed phase after the net drain and before the tick, held by a structural test on the built schedule (2304)
- subsystems own their packet handlers, dispatched in packet order; the auction house is the first family out of the 273-arm drain (2305)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- inside a battleground the battle map draws its terrain, overlays, arrow and blips: the world map's selection carries the instance maps as the reference's third cell (2298)
- talent spell modifiers land: the 45 packets a level-60 login threw away fill the reference's two tables, and a spell's power cost reads them, so a cast you can afford is no longer refused (2298)
- a ghost's Accept on the spirit-heal dialog can no longer be eaten by frame order, and a latched spirit guide no longer follows you into the next character's session (2298)
- the battleground probe refuses GM mode, exits when done, and reads the scoreboard before toggling it (2298)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- a panic writes benilla-config/Diagnostics/crash-<time>.txt with the build id, the location, a backtrace and the last 200 log lines, and names the file as its last line, so a bug report can attach it (2266)
- every wire-count allocation hint is capped and the compressed update object reads through the cursor, so a malformed packet can no longer ask for gigabytes (2266)
- the 23 in-world run conditions are one set, the 45 probe variables one registry, the install-gated tests say when they skip, and every gate is timed (2266)
- one workspace-level lint allowance replaces 413 per-site attributes, and the decision corpus gets a lint in the gate (2266)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- your character's own name no longer goes missing on the first login of a fresh start, so KLHThreatMeter stops erroring: the four "player" verbs read the login record, as the reference does (2260, 2261, 2263)
- four pipelines that compiled live mid-play now warm under the loading screen: the model tile atlas's orthographic view, the minimap composite's quad, the glow combine pair and the indoor pool's liquid layout (2262, 2264)
- a hoverable sign seats its tooltip where its template says, at the cursor or in the corner, not by its object type: Brill's signpost still follows the cursor (2259)
- three warnings that fired when nothing was wrong are quiet, and the lane-coverage gates walk every crate instead of one (2264)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- a right-click on a nameplate selects and interacts again: the plate registers the right button, as the reference's does (2233)
- the verb-events table files each fire site against its function's verified extent, and four rows it had misattributed are gone (2257)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- a signpost or mailbox tooltip shows with Questie loaded, and after opening the map: the cursor-seated plate carries an owner, so Questie's OnShow hook no longer hides it (2255)
- picking up a quest whose template is not cached no longer blanks the quest log or jumps the selection: the missing quest is skipped, not the pass (2256)
- the spellbook's skill-line tab flashes when a spell lands in it (2252)
- the GPU meter's resolve rides the frame's own encoder, with no submit or poll of its own: −1.8 ms on Intel's DX12 driver when armed (2254)
- the audio starvation watch no longer charges a stream's own spin-up as silence (2253)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- a quest objective that carries both a creature and an item is two lines in the quest log, as the reference's four passes enumerate it: Graverobbers shows its Embalming Ichor (2249)
- the quest log's detail follows the live selection inside a frame, so a quest addon's log walk reads the right quest; Questie no longer files a chain quest under its shortest sibling (2247)
- a UI tint change is not a material change: a run's colour rides the batch's instance tag, so the PlayerFrame glow no longer re-prepares a material every frame (2245)
- the workspace lints clean on rustc 1.98: constant-size chunks become as_chunks, one slice fill, two unused globs
- WOW_HOVER_PROBE aims the pick in a headless run, and the inspect card names the tooltip's ladder stage (2248, 2250)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- learning a spell prints "You have learned a new spell/ability/recipe" in chat, and unlearning one prints its line, on the reference's own gates (2243, 2246)
- the world map's dark backdrop is back: the screen size is seeded before the interface loads, so BlackoutWorld is sized from the real window, not 1024x768 (2242)
- the trainer filter repaints again: the engine fires TRAINER_UPDATE from inside the filter verbs, as the reference does (2244)
- the zone channels, the keybinding table and the default language seed at the entry edge, before any addon asks (2241)
- WOW_UI_COST=1 counts the frame's UI material asset events and names the first hundred modified (2236)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- entering the world builds its own Lua state instead of adopting the character screen's, so a login one-shot fired into the frameless glue VM is spent again against the interface (2226)
- after training, the trainer's detail pane moves on: the selection is the service's spell id, re-found by scan, and a filtered row is hidden, not removed (2231)
- a spell's miss or resist floats in spell gold, and a travelling spell's word waits for the projectile; only a white hit's miss is white (2229)
- a press that lands on a nameplate is the plate's: no freelook, no camera turn, the plate's own click slot (2233)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- right-clicking a nameplate acts again: both buttons read the press pick, as the reference picks once on the down edge and never re-picks on release (2230)
- the honor tab's Highest Rank and title name the rank: a rank title's team digit is the race, not the faction group, so a GM-flagged character keeps its rank (2227)
- a raid's party feed watches only its members' descriptors, and the empty 2D opaque pass is skipped: ~0.7 ms a frame in a crowd (2225)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- player and pet names are a world session, not a file: the reference never persists them, so a character created after a server wipe no longer wears a deleted character's name (2223)
- Martin Fury and every item whose spell targets where you stand raise the cursor and cast: the targeting word is a mask, not one bit (2218)
- the guild line prints ahead of the realm's welcome lines by declared order, as the reference's chat latch paints it (2221)
- the performance journal's CPU clocks answer on Windows, and a run with no install no longer panics on the cinematic narration (2219)
- SetInventoryItem's nameOnly argument builds the reference's compact tooltip (2224)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- the "Guild Message of the Day:" line prints in chat at login: our own guild id arriving is not a guild change, and the guild feed waits for the interface it fires into (2214)
- maximizing or resizing the window after entering the world no longer crashes the client: the dismissed loading tip asks for a re-shape (2212)
- the vendor's compare plate is the worn item's full tooltip with one gray "Currently Equipped" line first, its name in its quality colour (2216)
- the login chat drain waits for the chat frame it prints into, so a realm's welcome line is never lost one login in six (2220)
- the GPU meter never resolves a query set nothing has stamped yet — the Vulkan device loss and hang the lab met (2211)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>