Commit Graph

339 Commits

Author SHA1 Message Date
Sam Sung 09c276fe97 Bag and item reads answer as of their packet; logout reads no player (#2728)
Bag and item reads now answer as of the listener's own packet, not the frame's end, and the logout reads what 1.12.1 has there.

- The bag getters read the player's bags, the bank and the keyring live. Item and container writes go into the view log, so a listener sees its packet's slots, stacks and bags.
- The loot close's two unlocks fire at its packet (`0x48f2bd`, `0x4c1d36`). A wrong-bag refusal names the bag its packet found (`0x5e3a1e` → `0x5ede00`).
- At logout the object manager goes before PLAYER_LOGOUT fires (`0x46771e`, `0x490c2a`). The inventory getters then answer nothing with no active player (`0x4944a0` → `0x4c8c56`). `UnitName("player")` keeps the character's name.
- The item tooltip feed follows record changes by cursor. The suffix template is copied after the frame's Lua.

Each change has a test that fails without it. flip_readiness falls to 92 rows.

Fixes #2725
Refs #2168
Refs #2217

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 01:52:03 +07:00
Sam Sung 7c7a7f3e7c The verb census sweeps the whole suite and counts writes after a signal (#2726)
The census that finds Lua verbs whose work outruns their signals now sweeps the whole suite. It also counts state a lend writes while a handler is held, which a late listener would read and no send shows.

- Over 12,964 tests, one verb sends ahead of its signals: `UseAction`, V2's.
- An accepted item swap is in order: its send (`0x5e0fdb`) comes before the cursor clear (`0x5e0fe7`).
- One write after a held signal: a target clear's strike-cancel report waits behind the action bar's usable refresh (`0x493a08`, `0x493a18`, `0x493a69`, `0x493a73`). It is now listed, with its test.

All of it is test-only; the player build is unchanged.

Refs #2271
Refs #2202

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

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 01:29:40 +07:00
Sam Sung 39de572fc1 Transport props animate and cull with their ship, engine sound included (#2724)
Transports' props animate and cull with their ship, as in 1.12.1.

- A carried prop's world sphere comes from its host's pose every frame. The tick re-poses a transport's doodad defs before the cull (`0x6709a0` → `0x698f50` → `0x6a2070` → `0x6a81d0`), so a ship's parts stay drawn wherever it is.
- A transport's props play the ship's phase (`0x5f52f9` → `0x6709f0` → `0x6a24b0` → `0x6a20f0`), with window ends chained through `0x5f49b0`. A carried play cross-fades over its sequence's blend time (`0x6a2128`). The zeppelin's engine `$DSL` keys sit in its moving sequences (0xa2, 0xa3), so its engine is heard now. Before, its props looped Stand.
- The transport and lift loaders guard the reference's own faults as `Deviation:` lines: unwritten leg lengths, an unbounded append, a zero divide (`0x5f62a4`), an endless seek.
- A whole-period test of the Booty Bay–Ratchet path.

Each change has a test that fails without it.

Fixes #1210
Fixes #1221
Refs #1220

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 00:00:49 +07:00
Sam Sung 6ee671bcca Our player's create does its own writes inside its packet (#2723)
Our player's create now does its own writes inside the update packet, at the end of its create step (`0x5dec7b`), as 1.12.1's PostInit does:

- SelfPlayer is tagged in the create's own commands, so a later packet in the same frame finds us tagged and in control.
- The control flag and its two signals (`0x5deb56`), gated on `+0xc8` (`0x5deb49`).
- `CMSG_SET_ACTIVE_MOVER`, sent only when the mover changes (`0x6006ee`-`0x6006fb`). The mover clears at world entry, at the unit's destructor, at a release and at a control stop.
- The cascade message (`0x5deb60`), then the class family (`0x5debcc`).

The movement lock moves to its own resource. Only the arrival writes it, so a `/reload` while feared or possessing leaves it alone, since 1.12.1's reload runs only `0x4908c0`.

Each change has a test that fails without it. flip_readiness falls to 96 rows.

Refs #2168
Refs #2217

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 23:41:24 +07:00
Sam Sung beb1e124be Mouse moves and wheel turns are input records in arrival order (#2720)
Mouse moves and wheel turns are input records, run in arrival order among the keys and clicks, as 1.12.1 queues them. Kinds 0xa and 0xb dispatch 0xc and 0x10 (`0x424031`, `0x42417e`).

- Each move runs the hover walk (`0x7660d0`), so a frame crossed and left within one frame gets OnEnter and OnLeave.
- A wheel turn sweeps at the saved cursor (`0x766524`), and the world frame's binding runs inside it (`0x483d20`). One message is one step, from its sign alone (`0x76c1a9`). A trackpad's pixel carry is kept as a `Deviation:`, at most one step per event.
- During a long teleport's frozen frame the window stream waits. Released records take the live cursor, as each takes `GetCursorPos` when handled (`0x42dd00`).
- The pick order is reused until a frame changes: 2 µs a walk, 0.034 ms a frame at 1000 Hz polling.

Each change has a test that fails without it.

Fixes #2430

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 22:27:44 +07:00
Sam Sung 1343fbbd2c Helm and cloak toggles, account-data reads and the login burst's hold (#2718)
Three fixes, each with a test that fails without it.

- ShowHelm and ShowCloak send their toggle only while the live PLAYER_FLAGS bit says the opposite (`0x5ee6c4`, `0x5ee783`), and ShowingHelm/ShowingCloak read that bit (`0x48dd8b`). On the native display the call re-dresses the model at once. A display rebuild, a head or back visible-item change, or an item record landing dresses by the flag again (`0x604990`, `0x5e2541`, `0x5ed880`). The mannequin clones what the model shows (`0x476cb0`).
- Account-data readers register through `0x5afe50`. Each section is read when it settles, and its saves are skipped while it is pending. Bindings read set 1 and set 2 apart (`0x4b6240`, `0x4b64b0`), and macros merge by id (`0x4f0600`).
- The login burst is held until the entry UI has loaded, Pong excepted (`0x537b56`), as 1.12.1 loads before it logs in (`0x46c272`).

Fixes #2602
Fixes #2448
Fixes #2715

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 20:33:25 +07:00
Sam Sung 40b6613f94 Selection writes signal inside their packet or call, as 1.12.1 does (#2717)
Every write to the player's selection now signals where 1.12.1 does, inside its packet or call, not from a per-frame diff.

- The attacked and cast-at acquires select inside their packets (`0x6259fe`, `0x6e799c`). SetSelection keeps its order: the usable refresh (`0x493892`), then `PLAYER_TARGET_CHANGED` (`0x49389c`).
- Deselects (`0x493910`) run at their packet's end, on their lane, or inside the call. `CloseLoot` deselects a dead source after the loot session ends (`0x48f33d`, `0x48f369`).
- The server's swing stop and the refusal latch apply inside their packet (`0x625ab8`).
- The control flag is stored in its handler, then LOST or GAINED, then the refresh (`0x4958e0`).
- The feeds' own raises are gone, and a world-less clear fires nothing (`0x4939df`).

Each change has a test that fails without it. flip_readiness falls to 98 rows.

Refs #2168
Refs #2217

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 19:50:13 +07:00
Sam Sung 05075f092e Status lines, message frames, quest panels, page text and cache wipes (#2714)
Ten fixes, each with a test that fails without it.

- Status lines are cut at 255 bytes (`0x41a049`), banners too (`0x419f19`), and an empty line is dropped without raising the severity (`0x41a05f`).
- A message frame's `bytes=N` holds N−1 bytes. A negative N empties its lines, since `0x771692` tests only zero.
- The reward panel after a progress panel plays the completion chime (`0xbe081c`, `0x500d36`).
- A panel opens only for a held unit, game object or item (`0x500c16`-`0x500c35`).
- Readable pages are rewritten into their language for the reader (`0x49b560`).
- A page of 1024 bytes or more reads empty, as `0x4191b0` does at capacity 0x400.
- World entry wipes only the four unsaved caches (`0x555740`)., keeping saved asks.
- Two comments cite the right addresses.

Fixes #2580
Fixes #2690
Fixes #2692
Fixes #2694
Fixes #2696
Fixes #2700
Fixes #2701
Fixes #2710
Fixes #2711
Fixes #2712

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

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 18:28:42 +07:00
Sam Sung f40fc60c7c Field callbacks run inside the dispatch, in the notify pass's order (#2709)
The next piece of the event-core move (#2168, #2217). The update handler's field callbacks now run inside the packet's dispatch, as the reference's notify pass does.

- A new end-of-update handler runs each changed field's callbacks in the order the pass wrote them, inside the dispatch:
  - the aura, stack and loyalty lines;
  - the death;
  - the form release and service closes;
  - the pet rename.
- The form release comes right after its own block's signals (`0x5de010`, then `0x5de07f`), so a window closes between UPDATE_BONUS_ACTIONBAR and the next packet's events.
- The stack line reads the live descriptor at notify (`0x62b822`).
- An XP award hides a death line only when it arrived in an earlier packet (`0x62c232`).

Placed-queue readers outside the dispatch go from 5 to 0. A two-packet test fails on main's order.

Part of #2168
Part of #2217

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 16:48:54 +07:00
Sam Sung f65f4bf1b1 Grammar escapes, raid selection, world states and CVars follow 1.12.1 (#2708)
Fifteen UI API fixes.

- **Text and names.** FontString text, buttons, tooltips, names and bubbles apply the `|1`/`|2` grammar escapes (`0x7716f0`). Rank names count code points.
- **Raid.** The raid roster selection stores the member's guid, survives a /reload, and clears at world login.
- **World states.** Each world-state packet fires WORLD_MAP_UPDATE then UPDATE_WORLD_STATES at its place.
- **Events.** DISPLAY_SIZE_CHANGED fires below scale 0.64. UPDATE_CHAT_COLOR carries the caller's name.
- **CVars.** Four refuse what 1.12.1 refuses, and anisotropic and ErrorLevel print its console lines.
- **Calls.** GuildSetLeaderByName("target") sends no name. Repair costs read by row position. Quest reward slots count to the first empty one. OfferPetition checks its eight guards.
- **Glue and errors.** The glue VM has the file calls. Widget methods share one wrong-receiver text.


Fixes #2507
Fixes #2557
Fixes #2559
Fixes #2567
Fixes #2568
Fixes #2581
Fixes #2588
Fixes #2593
Fixes #2599
Fixes #2606
Fixes #2615
Fixes #2623
Fixes #2630
Fixes #2687
Fixes #2688

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 15:59:50 +07:00
Sam Sung cf90bdc592 Frames build one inherited level at a time; anchors are never retried (#2702)
1.12.1 builds a frame one inherited level at a time, template first (`0x769820`). Each level runs:
- its geometry (`0x767800`);
- its flags;
- one child loop in document order;
- its type's own part.

An XML anchor to a frame not yet built is logged, "Couldn't find relative frame", and dropped. benilla instead merged the levels and retried missed anchors later. That hid order bugs, such as Button labels built before their state textures.

- The loader now builds level by level, every frame type's part included, and the retry queue is gone.
- `<Frames>` and `<ScrollChild>` take their first element only.
- Each TitleRegion and ButtonText makes a fresh object.
- A tooltip finds its lines after OnLoad, and Minimap models read the instance's own node.

All 4,140 anchors in FrameXML and the 12 Blizzard addons resolve in this order. A test holds that.

Fixes #2574

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 15:30:17 +07:00
Sam Sung c5fc904f8c Cursor and cast drops, arrival target clear, loot and chat follow 1.12.1 (#2699)
Thirteen fixes to the cursor, world arrival, loot and chat.

- Losing control (`0x495901`) or a world port drops the cursor and the cast, inside the packet.
- Every arrival clears the target (`0x490927`), just before the world-entry events.
- A resolved roll frees its loot row, and a group coin loot prints its share line.
- A boss whisper becomes a whisper, and channel chat carries its instance number.
- The delete confirm names an uncached item.
- Bag books send CMSG_READ_ITEM and handle both replies.
- The spellbook pickup and drop sounds play.
- The quest log's gold guide arrow shows, cleared first on each select (`0x4def39`).
- A cleared loot row records its `Deviation:`.

Each change has a test that fails without it.

Fixes #2573
Fixes #2576
Fixes #2446
Fixes #2578
Fixes #2584
Fixes #2577
Fixes #2558
Fixes #2564
Fixes #2562
Fixes #2563
Fixes #2627
Fixes #2679
Fixes #2680

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 14:21:17 +07:00
Sam Sung 217a10df1b The XML loader and the SavedVariables writer follow 1.12.1 (#2693)
The XML loader and the SavedVariables writer follow 1.12.1.

- **Broken handlers.** One that fails to compile replaces the template's, reports nothing and keeps input on (`0x704c9f`).
- **Script files.** A `<Script file>` with a body runs both (`0x6ee0d4`).
- **FrameXML.log.** Load faults reach it in the client's wording. A failing script file logs at severity 2 (`0x704ae0`), and inline bodies log nothing.
- **Template levels.** Flags are read one inherited level at a time, and handlers come before a Slider's or StatusBar's values.
- **Fonts, gradients, names.** Unnamed fonts are logged, heights of a screen or more are refused, gradients take their last colours, and empty names stay unnamed.
- **Size changes.** A NaN size change fires OnSizeChanged.
- **SavedVariables.** They write bool and fractional keys, and the client's comments for skipped entries.

Fixes #2555
Fixes #2556
Fixes #2560
Fixes #2561
Fixes #2565
Fixes #2566
Fixes #2570
Fixes #2572
Fixes #2678
Fixes #2683
Fixes #2684

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 12:07:21 +07:00
Sam Sung cbd8e0fa68 Sorted particles, party snapshots, CVar floats and GetText follow 1.12.1 (#2691)
Seven issues from the UI core pass.

- Sorted particle emitters, with file flag 0x2 (`0x70fd1d`, `0x7b5d00`), now draw back to front. They use the reference's view-depth heap, so ties pop in its order (`0x7b3a5b`). Every other emitter keeps list order. (#2676)
- A party member leaving view snapshots the committed zone, `0xb4e314`, and their position at the call, as `0x5f0880` does. (#2677)
- GetDefaultLanguage answers nothing during a login's load (`0x49fcf0`). (#2665)
- GetGamma, the video getters and flat SavedVariables numbers read the CVar record's float (`0x63e135`). (#2673, #2672)
- The engine's counted and gendered text calls the Lua GetText and GetPluralIndex (`0x703bf0`, `0x703cf0`). Part of #2666.
- The pet cooldown test no longer depends on wall-clock time. (#2670)

Each change has a test that fails without it.

Fixes #2676
Fixes #2677
Fixes #2665
Fixes #2673
Fixes #2672
Fixes #2670

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 11:27:06 +07:00
Sam Sung 9f492e98f2 Held units and by-name searches read live; two frame-end feeds go (#2689)
The next piece of the event core (#2168): two more unit feeds that pushed frame-end copies into the VM are gone.

- The held-unit chain and the by-name candidates are now read live through the call host.
- The by-name searches and the emote lookup take the player's and each unit's position at the call, so a unit that moved this frame is measured where it stands.
- The object, name and record getters ask a thin held-unit query instead of building a full unit state. With the host lent, `UnitHealth("target")` drops from 3.26 µs to 1.20 µs.
- A guid that is not held as a unit is no longer a name candidate.

The frame-final feeds go from 45 to 43. `ui_unit::feed_units` stays for the next piece. Each change has a test that fails without it.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 10:17:17 +07:00
Sam Sung 403f999892 GM tickets, cursor casts, hover, gossip and plate flashes follow 1.12.1 (#2685)
What differed from 1.12.1:
- **Ticket results** show their lines: create and update codes 1, 3 and 5, a fetch status of 0 and a failed delete (`0x5e4432`, `0x5e44ff`). Update text is cut at 1999 bytes (`0x5efacd`).
- **UPDATE_TICKET** clamps a negative age to 0 and passes the client's NaN and denormal bits for the no-ticket answer. Arguments the client fills from stack residue are a `Deviation:`.
- **Ticket status** 2 sends the no-ticket answer; status 3 fires GMSURVEY_DISPLAY (`0x5e78f0`).
- **Casting** reads the cursor first: a held spell goes through pickup, and anything else is cleared (`0x4b3310`).
- **Hover:** Disable fires OnLeave first, Enable fires OnEnter after (`0x779160`).
- **Gossip:** leaving the world resets the gossip lists (`0x4e1f60`).
- **Nameplates:** a name flashes red for 5 seconds on damage (`0x7cba77`).

Each fix has a test.

Fixes #2462 Fixes #2472 Fixes #2476 Fixes #2478 Fixes #2481 Fixes #2484 Fixes #2536

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 08:55:25 +07:00
Sam Sung 6aef15f7e7 Numerals and sscanf convert as 1.12.1's C runtime does (#2675)
Lua numerals and the sscanf ports used Rust's correctly rounded parse. 1.12.1's C runtime rounds its own way: `__RoundMan` rounds up only when the round bit and some bit two past it are set (`0x40fb43`), so ties truncate and `6e23` reads one ulp low. It flushes results below 2^-1022 to +0 (`0x741ed3`) and keeps 24 digits (`0x41157f`). benilla-re holds these at proof two.

One port of that conversion now lives in benilla-lua (`strtod.rs`, `ld12.rs`). It serves `str2d` and `%f`, which rounds once to float (`0x40feef`) and consumes a digitless exponent marker (`0x7451a5`). `%d` wraps in 32 bits (`0x7456c9`). The time zone is read once per process (`0x414568`), and two client faults are written as Deviations.

A table of 3487 cases generated from benilla-re's transcription, and the exe's fixed cases, pin it.

Fixes #2647 Fixes #2648 Fixes #2649 Fixes #2650 Fixes #2651 Fixes #2652

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 08:13:32 +07:00
Sam Sung ab15e9c933 Script errors reach the error handler at the raise, as 1.12.1 does (#2674)
A script error reached the `seterrorhandler` function once per frame, from a queue, after every other listener had run. In 1.12.1 the handler is the error function of the pcall each script runner makes (`0x704e4f`-`0x704e79`; the error function `0x703b40`). So it runs at the raise, inside the failing script: `this`, `event` and `arg1` are still the failing handler's, `debugstack()` shows the failing call, and the event's next listener has not run yet.

Every benilla runner now passes that error function: handlers, OnLoad, file chunks, RunScript, key bindings and slash commands. It returns the handler's result, as `0x703b40` does. The queue is gone.

A handler body that fails to compile reaches no handler (`0x704c70`). benilla's host log keeps each original fault, and `/errors` replays the ones raised before benilla's layer loaded.

Tests cover the raise-time globals, the stack, the listener order, OnUpdate and chunks.

Fixes #2656

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 07:46:07 +07:00
Sam Sung 36406d72bb Drag raise, mouse order, anchors, scale and draw place follow 1.12.1 (#2671)
Frame layout mechanics that differed from 1.12.1:
- **Drag raise.** A dragged toplevel window rises once it overlaps another. A pass after the update recomputes each toplevel's overlap bit (`0x7657d0`). A rect change during a move raises unforced (`0x764a4c`).
- **EnableMouse.** Re-enabling a visible frame enters it behind its equals in the mouse list (`0x764b2d`).
- **Anchors.** SetPoint and SetAllPoints refuse a target that depends on the receiver (`0x768010`) and check the relative point last.
- **Scale.** An effective scale of 0 is refused before it is stored (`0x768366`), so the layout stands. GetEffectiveScale answers the stored value.
- **Draw place.** A region takes the last place in its layer each time it registers: Show after Hide, a new layer, or a frame show (`0x77fcb0`).

A scan of stock FrameXML finds nothing that visibly flips. Each fix has a test.

Fixes #2657 Fixes #2658 Fixes #2659 Fixes #2661 Fixes #2662 Fixes #2668

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 07:17:37 +07:00
Sam Sung eca3152fe2 Chat notices, markup, font sizes and script maps follow 1.12.1 (#2669)
What differed from 1.12.1:
- **Channel notices.** Moderator and voice changes print their notices (`0x49e8a0`).
- **Copy.** Copying from an edit box strips link, colour and `||` codes (`0x5c2590`, mask 0x500).
- **Links.** A `|H` token ends at the first `|`, which must be `|h` with a byte after it.
- **SimpleHTML.** A resize rebuilds the links (`0x78a320`).
- **Secure add-ons.** Their `.url` file is read at registration.
- **Font size.** A FontString refuses any scaled size of 0 or below, 1 or above, or NaN (`0x5c1afe`).
- **Message frames.** Justify lives on the frame's font, CENTER by default (`0x783a87`).
- **Script names.** Each widget type takes only its own scripts: SetScript and GetScript raise `doesn't have a "%s" script`, and HasScript answers nil.

Each fix has a test.

Fixes #2483 Fixes #2488 Fixes #2489 Fixes #2490 Fixes #2491 Fixes #2494 Fixes #2543 Fixes #2553

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 06:36:58 +07:00
Sam Sung 40e64e9449 Unit relations, reach and languages read live; six frame-end feeds go (#2667)
The next piece of the event core (#2168): six of the unit feeds that pushed frame-end copies into the VM. Their getters now read the world live through the call host:
- the relation verbs, UnitIsPVP and the tooltip gates (`0x5167e0`-`0x516c50`);
- the reach behind CheckInteractDistance, CanInspect and the slot getters, with each position computed at the call;
- the item-use control exemption;
- the languages (`0x5ec720`, `0x5ec890`).

PLAYER_FARSIGHT_FOCUS_CHANGED fires from its field watch (`0x5dda45`). LANGUAGE_LIST_CHANGED fires at the learn handlers (`0x4b2641`) and once per language spell at arrival. The client interleaves those with the form events in wire order, the same gap #2217 records for forms.

GetNumLaguages returns nothing with no player (`0x49fb5e`).

The frame-final feeds go from 51 to 45. Each change has a test that fails without it.

Fixes #2664

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 06:18:56 +07:00
Sam Sung ad0fd89729 Lua getters answer what their 1.12.1 handlers answer (#2663)
Lua getters that answered what 1.12.1 never does now read what their handlers read:
- **Bind area.** GetBindLocation and the `$z` spell token fall back to HOME_INN (`0x507f3a`).
- **Craft.** GetCraftName answers the trade-skill slot by craft type (`0x4f601c`).
- **Trainer.** The skill line and requirement come from the service's spell (`0x4d9261`, `0x4d94d6`).
- **Map.** GetPlayerMapPosition takes any unit token; GetMapInfo answers its sizes.
- **Mail.** GetInboxItem gates by quality and misses uncached items (`0x4af721`).
- **Pets.** Stabled pets pack in list order, the summoned one by its byte's bit 1 (`0x4cabe3`). UnitCreatureFamily reads any unit's record.
- **Gates.** HasFullControl and UseAction gate as the client's do; ItemTextGetItem answers nil with no text open.

Each fix has a test.

Fixes #2475 Fixes #2497 Fixes #2499 Fixes #2508 Fixes #2509 Fixes #2510 Fixes #2511 Fixes #2513 Fixes #2514 Fixes #2515 Fixes #2516 Fixes #2519 Fixes #2523 Fixes #2524 Fixes #2525 Fixes #2528

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 05:04:00 +07:00
Sam Sung b18d08f64f Lua's string, base and math libraries and the aliases match 1.12.1 (#2660)
Lua 5.0 behaviours of the client that an addon can observe, which benilla-lua's 5.1 base got differently:
- string.sub and string.find keep a position's low 32 bits (`0x7fbd55`).
- An escaped byte at or above 0x80 matches nothing (`0x7fcbc6`).
- `error()` checks its argument (`0x702993`) and adds the position at any level but 0 (`0x7029a8`).
- `__unm` gets the operand and nil (`0x6f8edc`).
- `__index` chains reach 102 tables and `__newindex` 101.
- math.deg multiplies by 180/pi (`0x7fb14d`).

The bare aliases run as their own `compat.lua` chunk (`0x703b21`), each on the client's line. Its closures keep a local `math`, so their error lines match and replacing the global `math` leaves them working.

`seterrorhandler` keeps the top value, and nil clears it (`0x702930`).

Each fix has a test.

Fixes #2632 Fixes #2633 Fixes #2634 Fixes #2635 Fixes #2636 Fixes #2637 Fixes #2638 Fixes #2639 Fixes #2640 Fixes #2654

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 04:45:57 +07:00
Sam Sung 99d87d1410 Widget setters gate and read back as their 1.12.1 handlers (#2655)
Widget methods called with a wrong argument returned quietly or did the wrong thing. In 1.12.1 they raise the client's usage line, or clear and read back, as the bytes show. This batch covers EditBox, FontString, Button, Slider, StatusBar, ScrollFrame, Model, PlayerModel and the Frame verbs.

Every method gates and reads back as its handler does at the address its commit cites. Examples: a missing frame name prints `(null)` (`0x6f5bc6`), and colours read back through f32 (`0x79ab3a`). Slider:GetMinMaxValues answers min plus the stored span.

Each fix has a test that fails without it. The tests that asserted the old behaviour now assert the reference.

Fixes #2533 Fixes #2534 Fixes #2535 Fixes #2537 Fixes #2538 Fixes #2539 Fixes #2540 Fixes #2541 Fixes #2550 Fixes #2551 Fixes #2552 Fixes #2554

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 04:16:52 +07:00
Sam Sung 6db07056ed GameTooltip setters check and answer as their 1.12.1 handlers (#2653)
GameTooltip setters accepted links, slots and indices that 1.12.1 refuses, and showed what it would not. Each one now checks and answers as its handler does.

- SetHyperlink takes only `item:` or `enchant:` (case-folded, `0x5320b1`), checks for an already-held link before the id, and raises `Unknown link type` on anything else.
- SetAction clears first. An Attack slot shows one white ATTACK line (`0x5323ad`).
- The trade skill, craft and trainer setters raise their lines on a bad index; a craft header clears and hides.
- Merchant and trade subtract 1.0 before truncating (`0x534159`).
- Any region can own the tooltip. A Font owner raises, written as a `Deviation:` (the client's `0x5313c9` is undefined there).
- Every setter checks `this` before its index.

Each fix has a test that fails without it.

Fixes #2544 Fixes #2545 Fixes #2546 Fixes #2547 Fixes #2548 Fixes #2549

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 03:58:42 +07:00
Sam Sung 9692f49350 Entering the world dead releases the body, as 1.12.1 does (#2642)
Logging in or reloading dead and unreleased left the body on the ground with a Release button. 1.12.1 releases it instead. The world-enter cascade's last test reads health (`0x490a2d`-`0x490a38`) and sends CMSG_REPOP_REQUEST (`0x490a3c` → `0x5e0ae0`). A /reload runs the same cascade (`0x490168`). The server makes the player a ghost at the graveyard (vmangos `MiscHandler.cpp:48-67`).

Changes:
- benilla sends the repop at the end of every cascade with health ≤ 0, and drops both deviations.
- UI_Init zeroes the release timer (`0x490131`), so a reload reads 0.
- The arrival fires PLAYER_DEAD or PLAYER_ALIVE once, after the cascade (`0x5deb65` → `0x495850`). A reload fires neither.

Tests cover the send order, a dead and a live body, the reload's zero timer and its silence.

Fixes #2620

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 03:33:06 +07:00
Sam Sung 92c8d2d400 Comments cite the right addresses and the facts their rulings rest on (#2641)
Comments that cited the wrong client address, or stated a fact the bytes contradict. Each one was re-read at the bytes and corrected. Comments only: no code changes.

Citations: the MCLQ cell read is `0x68b21f`, not the WMO group reader. The portrait bake has one caller, `0x713540`. Camera mode 0 keeps facing the target. The survey id call is `0x474064`, the macro setter `0x494f80`, and the empty party list jump `0x49e08e`. The clock constructor sets six fields to -1 and one to 0. A statusbar reads a numeric string as its number.

Deviation comments: each now gives the reference fact its ruling rests on, with every ruling kept. Examples: SavedVariables are kept as .bak (`0x51f794`), only flag-0x10 emitters sort (`0x7b3a1b`), the buff expiry cache clears at game entry (`0x4e40c0`), and SetTexture keeps the previous image (`0x7702ac`).

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 03:04:23 +07:00
Sam Sung 71c9adb0e9 Lua arguments coerce and gate as the client reads them (#2622)
Lua arguments are read the way the client reads them:
- numeric strings go through the VM's own number coercion instead of Rust's parse, truncating through `_ftol` where the bytes do (SetGuildRecruitmentMode `0x4a008f`, SetWorldDetail `0x488e0a`, the compare-item and model verbs)
- `debugstack` takes numeric-string levels, ports the level window's wrapping arithmetic, prints `(tail call): ?` below level 0, and joins leftover arguments top-down (`0x703760`)
- SendAddonMessage returns first with no player object (`0x49f94b`)
- the four sell-cursor verbs gate on targeting and repair mode before their arguments (`0x4fbab7`, `0x4fbac9`)
- SetPortraitTexture checks the token after blanking; 13 unit predicates take any value, and three more check the token

Each fix has a test that fails without it.

Fixes #2501
Fixes #2502
Fixes #2527
Fixes #2529
Fixes #2530
Fixes #2532

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 01:58:50 +07:00
Sam Sung 6de8e4e0eb Lua verbs raise the client's usage lines, in its gate order (#2619)
Lua verbs that raised benilla's own text, or nothing, now raise the client's usage line or message, in the client's gate order (each address read in the commits):
- chat, channel and emote verbs take their argument through the usage gate, and send "" and padded names as given
- `SetChatWindowName` stores an empty or padded name as given, and "" for a non-string
- `GuildInviteByName` shows ERR_COMMAND_NEEDS_TARGET for nil or ""; `SetGuildInfoText` raises its usage line
- `NotifyInspect`: usage, then the player, then the token
- GM ticket verbs gate type, text, then the player; -1 goes out as u32::MAX
- `PickupPetAction` truncates through `_ftol`, so 4294967297 is slot 1

Each fix has a test that fails without it.

Fixes #2500
Fixes #2503
Fixes #2504
Fixes #2512
Fixes #2517
Fixes #2520
Fixes #2521
Fixes #2522
Fixes #2531

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 01:37:00 +07:00
Sam Sung 8d4ef580f0 The quest giver reads live and fires its events at their packets (#2608)
The quest giver's getters read the panel's cells live, as 1.12.1 reads its globals (`0x501a20`-`0x501a70`), and its events fire on a lane at their packets instead of from a frame-final copy:
- each of the six texts has its own buffer; only a greeting clears them (`0x500c3b`)
- titles beside levels, nil past the pool (`0x501b05`)
- item info, link and counts read off the item cache at the call; IsQuestCompletable also checks the held counts (`0x501230`)
- QUEST_ITEM_UPDATE fires at the item answer (`0x5018c0`); a GameObject objective's name comes from its template
- a world entry and a /reload reset the cells (`0x500af0`)
- QuestLogPushQuest reads the first party slot at the call (`0x4e13a6`)

`feed_quest` is gone; the frame-final ceilings drop to 51 rows and 268 inputs. Tests cover each getter, the reset and the lane order.

Fixes #2474
Fixes #2498
Fixes #2505
Fixes #2506
Fixes #2518
Fixes #2526

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 00:47:32 +07:00
Sam Sung 9e4d240811 Cursor pickups gate on control; ammo slot, indestructible items, clicks (#2571)
Four cursor and button defects, each with a test that fails on main:

- RegisterForClicks stops at the first non-string (`0x78268f`).
- The pickup verbs (`0x4f9b87`, `0x4c7345`, `0x4f9fdb`) and every cursor setter gate on player control as the reference does (the item setter also allows a taxi flight). While feared, a player can no longer drag, split or buy into bags.
- Both destroy paths read the item record's flags at the call. Bit 0x20 refuses with ERR_ITEM_CANT_BE_DESTROYED, and an uncached record raises no popup (`0x495370`, `0x4953ac`).
- The ammo slot takes a drop. Projectiles load by entry, other items auto-equip, and an empty hand lifts the loaded ammo and sends SET_AMMO 0 (`0x4c73a6`–`0x4c7646`).

Also, the merchant toggle-off matches a held preview by entry (`0x4fb811`), and previews play the item display's grab and put-down sounds (`0x495154`, `0x49526a`).

Fixes #2468
Fixes #2469
Fixes #2470
Fixes #2471

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 23:39:06 +07:00
Sam Sung f060e0e1ee Bank, gossip, purse and repair costs read live; events at packets (#2542)
The bank, gossip, purse and repair-cost getters read frame-final copies, and their events fired from feeds after the drain.

Now they read live as of each packet:
- the purse, as GetMoney does (`0x518160`)
- the repair costs per slot (`0x4faf30`)
- GetNumBankSlots and GetBankSlotCost, from the player's own field with no banker test (`0x4f85b0`, `0x4f8630`)
- gossip's slots and text (`0x4e28d0`, `0x4e28c0`)

A bank lane carries OPENED (`0x4f8522`) and the refusal lines. PLAYERBANKBAGSLOTS_CHANGED fires at its field (`0x4f8460`). A gossip lane pushes SHOW at the draw (`0x4e22b5`), and asks the NPC's name there. A gossip draw with no player or no NPC held writes nothing and fires nothing (`0x4e20bb`), so a late greeting no longer reopens a closed window.

Four feeds are gone. Tests in `bank_live_tests.rs` and `gossip_live_tests.rs` fail on main. READS_FRAME_FINAL 56 → 52.

Part of #2217

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 23:00:30 +07:00
Sam Sung 15dd7b524c Item requirements, lock openers and sets read live as of their packet (#2496)
The item requirement state, lock openers and set blocks were read from frame-final copies pushed by feeds, so a listener saw the frame's last level, skills and bags, not its own packet's.

Now the requirement checks (`0x5eaae0`, `0x5ea930`), the known-spell test (`0x60c740`), the lock openers (`0x5da5e0`) and the set blocks (`0x52d8a0`) read the player and bags live, as of each packet. With no player held, the getters answer the reference's fixed values: GetQuestGreenRange 0 (`0x4e17f8`), GetComboPoints 0 (`0x51a261`), and the item tooltip hidden (`0x52b699`). The tooltip build asks for its creator, charter and set-member names inside the build (`0x52e204`, `0x52b93b`, `0x52df4b`). The merchant's buyback and repair reads are as of the packet too.

Four feeds are gone, including feed_player_req. Tests in `lanes/requirement_live_tests.rs` fail on main. READS_FRAME_FINAL 60 → 56.

Part of #2217

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 22:04:49 +07:00
Sam Sung 03a90574d5 Stairs climb: the floor is classified by the face, not the contact (#2473)
Walking into some stairs stalled the player until they jumped, such as the Stormwind auction house's street door. The movement code classified the floor by parry's contact normal. At a stair nosing that normal blends the faces meeting there (ny 0.38), so the tread read as steep.

1.12.1 classifies by the hit face record's own normal against cos 50° (`0x80dffc`). It does so in the step probe (`0x636592`), the ground classify (`0x636ecf`) and the step trigger (`0x6334e6`). The casts now return the hit triangle's face normal (`collision::BodyHit`), and all four sites read it. A mover with no direction or fall bit runs no resolver (`0x634100`), so a body stopped on a nosing stands there and sends no FallLand.

Tests on the real Stormwind WMO cover the climb, short taps, and a stop at every frame of it.

Fixes #755

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 20:59:38 +07:00
Sam Sung d3b5a4a569 The pet bar, menu, stats and book read live; presses signal in call (#2467)
The pet bar, menu, stats and book getters read frame-final copies. A reaction or Stay/Follow press raised PET_BAR_UPDATE from the VM's copy, even when the press was refused.

Now the pet's getters read live through the call host: PetHasActionBar (`0x4bdc20`), GetPetActionsUsable (`0x4bcf70`), GetPetActionInfo (`0x4bdc50`), HasPetUI (`0x4be670`), PetCanBeAbandoned/Renamed (`0x4be500`/`0x4be580`), HasPetSpells (`0x4b4410`) and the rest. A press writes the state word and signals inside the call, after the gate (`0x4bd1f2`) and before the send (`0x4bd444`). A stunned pet's press stays silent. The pet-teach "already known" line and CreateMacro/EditMacro read the pet's book live. `GameTooltip:SetPetAction(0)` answers nil (`0x532812`).

The bar, menu and book feeds are gone. Tests in `lanes/pet_live_tests.rs` fail on main. READS_FRAME_FINAL 63 → 60.

Part of #2217

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 19:09:35 +07:00
Sam Sung f9489c6578 A DXT or raw BLP shorter than the palette window decodes (#2466)
`benilla-blp`'s `parse_header` refused any BLP shorter than the 256-entry palette window, whatever its compression. So small DXT or raw textures failed to load, such as AtlasLoot's 516-byte `gold.blp` highlight.

1.12.1's parse (`0x5a83b0`) copies the 1172-byte header with no length check (`0x5a83da`). It fails only on the magic and the type, and only the paletted path reads the palette.

Now the palette window is required for compression 1 only. Test: `dxt_without_a_palette_decodes`, a 4x4 DXT5 with no palette.

From chairmanpow's benilla-octo `660cf760`, carried on Thorsten42/benilla@78f7362.

Fixes #2464

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 18:53:08 +07:00
Sam Sung b7eb120325 The trainer, stable, tabard and charter read live; events at packets (#2465)
The trainer, stable, tabard and charter getters read frame-final copies, and their events fired from feeds after the drain.

Now they read live through the call host, matching their reference reads: the trainer's rows (`0x4d9096`, `0x4d9d07`, `0x4d97c8`), the stable's records (`0xb72058`) and display (`0x4cb99e`), and the charter's five getters (`0x4f43d0`…`0x4f5230`). Each family's events fire on its own lane at its handlers. TRAINER_UPDATE fires at each `0x4d7d40` caller: twice for a superseded rank (`0x5e63a4`, `0x5e64f1`), and between the skill line and the profession rebuild (`0x5de42d`). A close in the same frame no longer swallows a queued SHOW. That also fixes the merchant, whose CLOSED could fire before its SHOW. A /reload keeps the trainer's rows.

The four feeds are gone. Each family's tests fail on main. READS_FRAME_FINAL 67 → 63.

Part of #2217

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 18:33:51 +07:00
Sam Sung af5bfa582f The dialogs read live; their events fire on a lane at their packets (#2463)
The dialog getters read frame-final copies, and their events fired from frame-final feeds after the drain, so a listener saw them out of packet order.

Now CheckBinderDist (`0x5dff50`), CheckPetUntrainerDist (`0x5dfd30`), the boot, spirit healer, meeting stone, summon and instance getters read live through the call host. Their events fire on a dialogs lane stamped at each handler. The duel, binder, dialog verb, meeting stone, summon and instance feeds are gone.

Fixes on the way:
- The binder and pet unlearn questions pass the range gate first (`0x5dfe3c`, `0x5dfc2d`), the innkeeper click included.
- The boot timer's delay is signed (`0x496340`).
- The duel checks the player's own challenge before ignore, and its timer runs before packets (`0x420d0c`).
- A deleted ticket signals UPDATE_TICKET (`0x5e461d`).

Tests in `lanes/dialog_live_tests.rs` fail on main. READS_FRAME_FINAL 75 → 67.

Part of #2217

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 17:32:11 +07:00
Sam Sung fe4b12bc2d The merchant and flight map read live; their events fire at packets (#2461)
The merchant and the flight map answered from VM copies pushed at frame end, and their events fired from frame-end feeds. In 1.12.1 the merchant getters read the vendor's rows at the call (`0x4fb130`, `0x4fb580`), and a close leaves no rows (`0x4fae22`). The flight map's slots are built inside `SMSG_SHOWTAXINODES` (`0x4dc380`), with each fare read at the call.

Now both read live through the call host. `MERCHANT_SHOW`, `MERCHANT_UPDATE`, the buy and sell lines, `TAXIMAP_OPENED` with its tutorial, and the taxi replies fire on lanes at their handlers. A vendor row's item query names the vendor and a buy-back row's names 0 (`0x4faf05`, `0x4fb3f0`). A new flight path closes the gossip (`0x5e3e68`). Buy-back names carry their suffix (`0x5d8bc0`).

Tests: live rows, fares and repair costs; each event's place; byte-exact query goldens. All fail on main.

Part of #2217

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 14:58:32 +07:00
Sam Sung 1f75ed73ee Inspect and honor read live; the honor reply fires at its packet (#2460)
Inspect and honor answered from VM copies pushed at frame end. In 1.12.1 another player's slot getters read its visible-item block at the call (`0x4c8328` → `0x5f0d60`). The PvP getters read the active player's honor block (`0x51a4b0`–`0x51aa90`). The inspect reply handler stores a reply only for the target (`0x4c6e58`), then signals `INSPECT_HONOR_UPDATE` (`0x4c6f56`).

Now these getters read live through the call host, and the reply's event fires on the notice lane at its packet. The inspect honor slot is built as the reference's: target, has-data and pending, with one ask per new target. The recorded deviation that clears it on a re-target stays, with its reason restated: a server that never answers would leave the last player's figures shown.

Tests: an inspected link at the call, the honor event between two packets' events, the tooltip's maker line, and a re-target clearing the slot. All fail on main.

Part of #2217

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 14:31:14 +07:00
Sam Sung 18a99340b5 The stance bar reads live; the mirror timers fire at their packets (#2459)
The stance bar answered from a VM copy pushed at frame end, and the mirror timers' events fired from a frame-end feed. In 1.12.1 the stance getters read the form list at the call: `GetShapeshiftFormInfo` (`0x4b45c0`) gives four nils past the list or with no player, and castable comes from the usability walk (`0x6e3d60`). The mirror handler (`0x5e7990`) signals in place, then the tutorial. Releasing the active player stops all three timers (`0x5e9b90`).

Now the stance getters, `CastShapeshiftForm` and `GameTooltip:SetShapeshift` read live through the call host. The mirror events fire on the notice lane at their handler, and the player's release sends `MIRROR_TIMER_STOP` for each type. Both frame-end feeds are deleted.

Tests: live list, active, castable, cooldown and hover; four nils with no player; mirror events in packet order; the release's stops; `SetShapeshift(0)` past the list. All fail on main.

Part of #2217

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 12:58:19 +07:00
Sam Sung 19b67feec6 Friends, ignore and who read live; their events fire at their packets (#2458)
The friends, ignore and who lists answered from VM copies pushed at frame end, and their events fired from a frame-end feed. In 1.12.1 the getters read the lists at the call (`GetFriendInfo 0x5ad060`, `GetIgnoreName 0x5ad460`, `GetWhoInfo 0x5ad6e0`). The handlers raise `FRIENDLIST_UPDATE`, `IGNORELIST_UPDATE` and the who answer in place, which goes to the frame or to chat by the flag at that moment (`0x5adf9c`).

Now the getters read live through the call host, and the events fire on a social lane at their handlers. Each item seats the lists as its packet left them, so a listener's `SetSelectedFriend(1)` or `RemoveFriend(1)` acts on that packet's list.

Tests: getters read lists the handlers wrote with no frame between, the events interleave in packet order, a listener's verbs act on its signal's list, and the who route follows the flag at its stamp. All fail on main.

Part of #2217

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 12:08:42 +07:00
Sam Sung 6354cd983d The battlefield reads live; its events fire at their packets (#2457)
The battlefield's status, list, scoreboard and positions answered from VM copies pushed at frame end, and their events fired from frame-end feeds. In 1.12.1 the getters read the battlefield state at the call (`0x4ab4a0`–`0x4ab820`, `0x4ab9d0`, `0x4abf90`). The status handler fires `UPDATE_BATTLEFIELD_SCORE` before `UPDATE_BATTLEFIELD_STATUS` (`0x4aaa5a`, `0x4aab05`).

Now every getter reads live through the call host, and the events fire on a battlefield lane at their handlers. The board sorts with the reference comparator through the CRT qsort (`0x4aa350`, `0x73f727`). A row with no name record counts as team 0, and a world entry clears the stat columns (`0x4a9e67`).

Tests: live getters against a written world, a frame of mixed packets firing in order, name callbacks at the name packet, and a 40-row sort. All fail on main.

Part of #2217

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 11:20:27 +07:00
Sam Sung 807067ba66 Skills, talents and spellbook read live; their events fire at packets (#2456)
The skill lines, talents and spellbook answered from VM copies pushed at frame end, and their events fired from frame-end feeds. A listener therefore saw them as the frame's last packet left them.

Now their getters read live through the call host: skills through `0x5ea3f0`, talents through `0x4f3200` with character points at `+0xe48`, the book through `0x4b3fe0`. `SPELLS_CHANGED` and `LEARNED_SPELL_IN_TAB` fire at the learn, unlearn and supersede handlers, after `UPDATE_SHAPESHIFT_FORMS` (`0x4b2904`), and also at arrival and level-up (`0x5edb7b`). `CHARACTER_POINTS_CHANGED` fires from its field watch (`0x4c7098`), its one source; the page-turn source was spurious. A talent counts a known higher rank (`0x60c8d0`). The respec question drops with no trainer in range (`0x5df9be`).

Tests: eight full-client tests cover the live reads and each event's place. Five fail on main.

Part of #2217

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 10:56:09 +07:00
Sam Sung 721c408d66 Target and npc tokens read live; a released target ends at its release (#2455)
The `target` and `npc` tokens read guids pushed into the VM at frame end. A target whose object the server released was torn down by a frame-end system, after later packets' listeners. In 1.12.1 the tokens read the target and npc globals at the call (`0x515a86`, `0x515c00`). The release (`CGUnit_C::Release 0x5fbb60`) ends a target in place (`0x5fbbe5`), except a group member's, whose guid stays (`0x5fbbcd`).

Now both tokens read live through the call host, and the npc is the raw interaction guid. For a game object, `UnitName("npc")` names it (`0x5f8100`) and `UnitExists` is nil. The teardown runs inside the release on a new lane. A reconnect clears the old selection.

Tests: a live target and npc, a game-object quest giver's name, the release firing `PLAYER_TARGET_CHANGED` between two packets' events, and a reconnect. All fail on main.

Part of #2217

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 09:18:40 +07:00
Sam Sung 5de4942fde The party colour and the meeting stone read the party slots (#2454)
Two party tests read the wire's whole member list, which holds every raid subgroup and is not emptied when the party ends. In 1.12.1 both read the four party slots at `0xbc6f48`. The selection colour asks `CGPartyInfo::IsMember` (`0x4e7f70`): us, or a guid in one of the slots. The meeting stone tests slot 1 (`0x5f6a05`), then counts the slots (`0x4e86d0`, `0x5f6a45`).

Now the target ring and nameplates use the relations' party test, and the meeting stone gates on the party slots. A raid member of another subgroup no longer shows the party colour. A raid of four or more gets NO_RAID_GROUP rather than GROUP_FULL, and a player whose party ended can join.

Test: the stone gate with a raid whose other members sit in another subgroup, and with an ended party. Both fail on main.

Fixes #2450
Fixes #2451

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 09:05:18 +07:00
Sam Sung 73c5ff8887 widescreen 0 lays the interface out 4:3, stretched across the window (#2453)
With `widescreen` at 0, the in-world interface kept the window's aspect. In 1.12.1 the game's aspect answers exactly 4/3 then (`0x4901e0`). It is stored at `0x48fe1e` for the screen basis `0x41ad10`, re-read on a resize (`0x492f13`) and for the automatic UI scale (`0x492fb4`). The interface lays out 4:3 and is drawn stretched across the window.

Now a `UiScreen` built from the window and the CVar carries the interface's size, its exact aspect and its stretch. Every window-to-UI conversion goes through it: input records, hover, the UI pass, tooltips, the minimap, the world map, nameplates, bubbles, combat text, the cursor and the probe click. With `widescreen` on the stretch is 1, so nothing changes.

Tests: a click round trip at 1920×1080, the cells' aspect at exactly 4/3, and the automatic scale at 700 and 766 tall. All fail on main.

Fixes #1577

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 08:47:50 +07:00
Sam Sung f304e3a10a A drunk player's honor title takes their real gender, a deviation (#2452)
The honor line's rank title took the active player's gender from `PLAYER_BYTES_3` byte 0, while every other gendered text read the real gender. 1.12.1's `CGPlayer_C::GetGender` (`0x5ed5b0`) does read that byte whole for the active player. But the byte also carries the drunk bits (`gender | drunk & 0xFFFE` on vmangos), so a heavily drunk female reads as male.

Now the honor line reads the real gender (`UNIT_FIELD_BYTES_0` byte 2) like the rest. One `Deviation:` line on `unit_gender` names the reference's read and why benilla differs. The byte-0 getter is gone.

Test: a female with byte 0 full of drunk bits gets the female rank title. It fails on main.

Fixes #2103

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 08:29:18 +07:00
Sam Sung e8c5b60650 The quest log reads live at the call; its events fire at their packets (#2449)
The quest log's getters read copies pushed into the VM at frame end, and its events fired from a frame-end feed. A listener therefore saw the log as the frame's last packet left it, not as the reference's slot reads (`0x4dfa41`, `0x4e01df`) find it at the call.

Now the slots, held counts (`0x622130`), standing (`0x4d6370`), coinage and the timers' clock read live through the call host. Template landings and refusals, toasts with `QUEST_WATCH_UPDATE`, rank moves and time syncs fire on a quest events lane at their handlers' stamps. The LFG quest list reads the live slots too. The frame-end clock feed and most of the log feed are deleted.

Tests: five full-client and five VM tests cover the live reads and the event order at the packets, including a refused template. They fail with the live reads off or the events stamped late.

Part of #2217

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 08:14:09 +07:00
Sam Sung fa947ea2e4 Account data syncs with the server, as 1.12.1 does (#2447)
Key bindings, macros, the window layout and the chat setup stayed on the machine and never synced with the server. 1.12.1 keeps eight sections both on disk and on the server, in step by digest. At each world entry the server's digests (`SMSG_ACCOUNT_DATA_MD5`) meet each section's own and the cached one (`0x5afa10`). A section changed here goes up 30 seconds after the interface shutdown that wrote it (`0x5aff40`), and one changed there is asked for and stored.

Now benilla does the same, with the four wire bodies byte-exact. Two recorded deviations keep data the reference would lose: no truncated upload (`0x5afb42`), and no write on a bad download (`0x5afcbc`). `SaveBindings` now writes the set it stored, not the live table at drain.

Tests: wire goldens, the section check and transfer, and the timers. A live round trip against the local server came back byte-equal.

Fixes #1337
Fixes #2368

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 07:50:09 +07:00