Commit Graph

652 Commits

Author SHA1 Message Date
Sam Sung 7361ddac1f The workspace tests pass on Windows: zone, census path and origin name (#2933)
Twelve workspace tests failed on Windows, from three causes:

- The census cut a site's path at `src/`, so a Windows path kept its whole prefix. It now cuts at either separator and prints `/`.
- The raise-origin panic named its caller from the backtrace, which a build without debug info resolves wrongly. It names the raise by its line, and lists callers only from frames that carry one.
- Ten zone tests set `TZ` through `std::env`, which never reaches the Windows C runtime's own copy of the environment. In 1.12.1, Lua's `date` and the screenshot names read the zone through the CRT's `_tzset`, which honours `TZ` (`0x4145d6`). The logon bias reads `GetTimeZoneInformation` alone (`0x4297c0`). The test helper now writes `TZ` where the CRT reads it, and the logon bias calls `GetTimeZoneInformation` directly, as the reference does.

Cross-checked for `x86_64-pc-windows-gnu`.

Fixes #2927
Fixes #2928
Fixes #2929

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 05:47:43 +07:00
Sam Sung 36e41ecf42 Item-text queries carry a u64 key; stats ask per table; console repeat (#2926)
Three gaps:

- CMSG_ITEM_TEXT_QUERY sent the text id, the mail id and a u32 0. `0x569980` writes the text id and a u64 key (`0x569a99`): the mail id under high dword 0x70000000, an item's guid, or 0 for the invoice. benilla now sends that key. vmangos reads only the first dword after the text id, which is unchanged.
- A group list and a deactivate asked for stats once per member. `0x4e85d0` and `0x4badc0` each send their own request, party first, so a subgroup mate is asked twice. benilla now runs both legs.
- `/console repeat N <command>` (`0x639250`) was missing. It now runs the command once a frame through the console executor and counts down (`0x63bd50`).

Tests: goldens for the three keys, the request count and order, the repeat count.

Fixes #2601
Fixes #2604
Fixes #2587

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

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-01 04:18:27 +07:00
Sam Sung 8df5ff2286 The pointer is one flag the last call sets; merged status lines reformat (#2923)
Two UI gaps:

- The pointer stayed hidden while any of mouselook, a playing cinematic or the glue's `HideCursor` held. In 1.12.1 the pointer is one device flag (`[dev+0x2880]`, `0x595c20`), and the last call of `0x58b590` wins. Its eight sites are the glue's `ShowCursor`/`HideCursor`, a cinematic's start and end, leaving the world, mouselook's start and end, and the glue build's `SetCursor`. benilla now keeps that one flag, set by each site in order. So releasing mouselook during a cinematic shows the pointer.
- Merging one load status into another copied lines verbatim. `0x41a100` re-adds each line through the target's Add with its text as the format, so a `%%` collapses, a line over 255 bytes is cut and an empty one is dropped. The merge now does the same.

Tests: `the_last_call_on_the_device_decides`, `a_merge_formats_each_line_again`. Built for Windows too.

Fixes #2575
Fixes #2621

🤖 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-11-01 02:33:57 +07:00
Sam Sung e475fb8e2b The console commands gxRestart and bind are registered (#2919)
Two console commands 1.12.1 registers were missing, so each ended in `Unknown command`.

- `gxRestart` (`0x63b360`, handler `0x639f60`, category 1, no help) applies the latched gx CVars. A console gx write printed `set pending gxRestart` and then never applied. It now raises the same restart `RestartGx()` does.
- `bind` (`0x4b6050`, handler `0x4b60b0`, category 5, no help) takes the key as the first space-split token, cut at 31 bytes, and binds the rest of the line on the live set with no `UPDATE_BINDINGS`. An empty rest unbinds.

Both are listed by `help`, which answers them with "No help yet".

Tests: `help_knows_gx_restart_and_bind`, plus `ConsoleExec` tests for each command.

Fixes #2585
Fixes #2586

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

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-01 01:21:24 +07:00
Sam Sung 7df7e418b3 A verb's events run before its next send, and the census enforces it (#2915)
1.12.1 runs a signalled event's handlers inline, before the verb that raised it goes on. The census that refuses a send, lock or cursor write made while such a handler is still held now panics by default in a debug build (`BENILLA_SEND_CENSUS_PANIC=0` turns it off), after a survey of the whole suite came back clean.

The survey found one real ordering defect: `OpenItem` sent `CMSG_OPEN_ITEM` before its lock's `ITEM_LOCK_CHANGED` listeners ran. In the reference the lock signals at `0x495415`, before the send at `0x5edd26`. The send is now owed past them, as the loot release is.

Two cells, the used mailbox and the item-text reader's queue, were marked written by a use that touched neither, and now are not. The survey's listener frame is hidden, so it no longer moves frame levels.

Tests: `an_open_items_lock_listeners_run_before_its_send`, `an_item_use_that_opens_no_window_writes_none_after_its_held_report`.

Fixes #2271

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 00:02:37 +07:00
Sam Sung 135ce88892 The raise-origin check reads the open scope, never the backtrace (#2913)
The debug-build check on where an event is raised passed an allow-listed site by finding its function's name in a captured backtrace. CI builds tests without debug info, and on x86_64 an allowed function inlined away leaves no frame. So #2900's Linux run failed 368 tests: 367 on `minimap::feed_minimap_inside`, 1 on `drive_glue_mgr`. Apple silicon keeps those frames, which is why the Mac gates stayed green.

Each of the 13 allowed sites now opens `Origin::Allowed("<site>")` around the part that raises, and the check reads only the open scope. The backtrace is kept only to name callers in a failure.

Tested:
- `an_allowed_site_passes_by_its_scope_never_by_the_stack` fails under the old verdict;
- a guard ties every allow row to a scope in the function it names, and every scope to a row.

Fixes #2901

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 23:08:14 +07:00
Sam Sung 50342dd6b0 Shadow, blood and water CVars take effect; the last glue verbs register (#2906)
Four registered CVars had no effect, a default was off, and eight glue verbs were missing.

- `shadowLOD` 0 draws no unit blob shadow (`0x6d7904`). Its handler refuses values outside 0..1 (`0x6d8450`).
- `shadowBias` scales the ring's and blob shadow's depth bias by value/0.1, with none at 0 (`0x6d7506`).
- `violenceLevel` drives the blood spurt (`0x60a39d`), clamped to the locale's cap (`0x6c5af0`).
- `MapWaterSounds` 0 hard-stops the liquid loops and arms none (`0x462af0`, `0x462b50`).
- `mouseSpeed` registers "1.0", the OS speed × 0.1 as `%1.1f` (`0x402c4d`).
- The glue registers the billing, patch download, subtitle and survey verbs, with the reference's answers when no such state exists. `SurveyNotificationDone` reports the login authenticated, as EndSurvey does (`0x473ef5`).

Each fix has a unit test, and the glue surface test lists no verb as unbuilt.

Fixes #346
Fixes #361
Fixes #409
Fixes #344
Fixes #2592
Fixes #2607

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

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-31 20:20:49 +07:00
Sam Sung 134f037600 Looking-for-group replies fill the list; group, guild and target fixes (#2892)
Five group, guild and spell-target defects in the Lua API.

- The looking-for-group reply (`0x4e8dc0`) filled nothing and `UPDATE_LFG_LIST` never fired. The list now fills with the reference's caps (50 records, name 0x30, comment 0x80) and signals once its quest titles are known. Deviation: a login starts with an empty list, where 1.12.1's getters read leftover memory.
- `SMSG_GROUP_CANCEL` reads the name it drops and fires `PARTY_INVITE_CANCEL` (`0x5e6770`, `0x48f440`).
- `GuildSetMOTD("")` sends no string (`0x48b2c1`-`0x48b2da`).
- `SpellCanTargetUnit` passes a unit on any arm (`0x6e6460`). A unit or corpse click that leaves bits in the word holds the spell, and the send carries the mask its binds gathered (`0xceac5c`, `0x7e4e10`).

Each new wire body has a byte-exact golden, and each fix has a unit test.

Fixes #2583
Fixes #2569
Fixes #2609
Fixes #2133
Closes #2666 (fixed by #2691)

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 16:50:14 +07:00
Sam Sung c7c868a41a Loot closes unlock and signal before their release goes out (#2889)
Part of #2271. `EndLootWindow` (`0x48f200`) unlocks an item source (`0x48f2bd`, `ITEM_LOCK_CHANGED`) before it sends `CMSG_LOOT_RELEASE` (`0x48f311`). The teardown's second unlock and `LOOT_CLOSED` follow (`0x4c1d36`, `0x4c1dac`). Taking the last row signals `LOOT_SLOT_CLEARED` (`0x4c1f77`) before that close.

benilla sent every release ahead of those listeners. Now each close's unlocks are turns of their own, and every source's release waits behind what was signalled ahead of it. A nested close sends its own release, and a dropped loot lane still sends its queued ones. A Shift-open's lock and send wait for the sweep's `LOOT_OPENED` listeners.

With every lock and cursor write at its point, their rewinds are deleted. The census refuses such a write ahead of a held or lane-queued signal, in tests or when asked.

Each order has a test that fails on main.

Fixes #2877
Fixes #2888

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 12:56:15 +07:00
Sam Sung 851a6f9993 The create screen draws each new look whole, as 1.12.1 does (#2887)
The create screen's paint calls `UpdateCustomizationScene` (`0x471300`), which forces the character's composite (`0x477860(1)`, `0x471308`) and then waits on `IsReady(1, 1)` over the scene (`0x471320`). So a race, sex, class or dial change draws whole in the first frame that shows it. benilla built the create look at the asset server's pace, so a change could keep the old look, or show the new one without its textures, for several frames.

The select's stage wait (#2886) now runs for every glue look. A dial loads no model, only its new textures (fur `0x478160`, hair `0x478220`), so its wait holds for those alone.

Tested by a create look and then a dial change, each waiting for its stage; the test fails on main.

Fixes #2884

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 11:48:04 +07:00
Sam Sung 6a3f89fa9f Held event stretches become steps that fire in place (#2885)
Part of #2271: in 1.12.1 a verb's events fire at their instruction, and every listener runs before the verb goes on (`SignalEvent` `0x703e50`).

- Every stretch that held its events to the end of its turn is now a step whose listeners run in its place. These cover our death and a unit's (`0x5e9a45`, `0x6058e0`), the selection's teardown (`0x493910`), the release and the seven window closes, the tradeskill recast, the bind confirm, the dressing room and the quest feed. The holding they used is gone.
- A cast refusal's error line fires its listeners before its cancel goes out (`0x6e21dd` before `0x6e2218`), at the arm, the range reread and the same-spell rungs.
- The send census panics in tests over a send past a waiting signal, and in a live debug build when `BENILLA_SEND_CENSUS_PANIC=1`.

Each order has a test that fails on main.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 10:49:33 +07:00
Sam Sung d0f09f93fb CreateMacro keeps the non-local count; derived macro lines cut at 255 (#2876)
Two macro gaps against 1.12.1:

- Each scope keeps a count of non-local macros (`0xbdcd04`). A load and a create add one for a non-local macro (`0x4f09b5`, `0x4f124d`), a delete takes one off unsigned (`0x4f13a5`), an edit moves none, and at 18 or more `CreateMacro` forces the macro local (`0x4f1055`-`0x4f106b`). benilla kept no count.
- The bound-spell derivation tokenizes each body line into a 256-byte buffer, so it reads at most 255 bytes (`0x4efe18`-`0x4efe2f`, `0x64af4b`). benilla read whole lines.

Both are now built, each with a test that fails on main. #2273 already fixed #2108 and #2109: the header tokenizer stops at the next quote, and `GetActionTexture` answers the icon directory for a macro with no icon.

Fixes #2110
Fixes #2112
Closes #2108
Closes #2109

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 08:08:46 +07:00
Sam Sung d5e37dd975 Every deviation names its reason, and a gate keeps it so (#2868)
`docs/METHOD.md` asks each deviation to name the reference fact and why benilla differs. About 130 `Deviation:` comments gave no reason, and the Warden comment lost its reason when the refusal became a read-past.

- `scripts/genmap.sh --check`, now a gate, fails a `Deviation:` comment without "because" and a `Deviates` row without a Why.
- `scripts/deviation-why.allow` lists the 43 comments whose change recorded no reason. It only shrinks: an entry that gains its reason, or is new against main's copy, fails.
- About 190 comments that gave their reason in other words now say "because", with their meaning kept.
- Nine reasons, the Warden one among them, come back from the commits and pull requests that made them.

Tested by the gate on the full tree, and by its failure on a comment with no reason and on a stale entry.

Fixes #2856
Fixes #2857

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 06:23:34 +07:00
Sam Sung 52936104ab Item records ask in their call and answer in their callbacks' order (#2849)
Item records were fed to the interface at the frame's end, and their answers ran there too. So a listener between two packets read a record as it stood before. 1.12.1 asks inside the call and runs each answer's callbacks in their list.

**Changed**
- A getter's miss asks inside its call (`0x55ba30`), and a new interface's copy is seated at its load.
- The answer callbacks run in registration order (`0x55bf12`), for the try-on, the auction lists and the combat log. Combat lines on one item are written one per answer, newest first (`0x6295b0`), through their formatter's gates again. An enchantment line asks for its item before its gates (`0x628f83`).
- An item text read and the mailbox open inside the click or the call (`0x4acd42`).

**Tests:** each read between two packets, the callback order, the combat-line order, the mailbox's MAIL_SHOW. Each fails 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-31 00:07:18 +07:00
Sam Sung bf1ea25f58 Nameplates move at their units' callbacks, in their watchers' link order (#2847)
Nameplates were driven from the frame's end, so a listener between two packets saw a plate as it stood before them. 1.12.1 moves each plate at its unit's field callbacks, inside the packet.

**Changed**
- A plate's bar, level and colour change at their unit's callbacks. The bar reads health signed (`0x7cc41b`) and the level drunkenness lowers (`0x7cbce0`).
- A unit's watchers run in link order (`0x467f00`): its event bridge, its plate bars, the tooltip's bar. The walk reads the live list (`0x4655df`).
- The token flags follow their writers in place: selection, party, raid, create, pet, return. They survive a reload, as they do on the unit (`+0xc90`).
- A tooltip links a watcher only on a unit in view (`0x467e86`).

**Tests:** each read between two packets, the watch order across a reload, the party recolour. Each fails 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-30 23:05:43 +07:00
Sam Sung c137bdd551 Trade halves, bind answers, StopAttack and chat lines signal in place (#2845)
More calls and turns held a signal past a later effect of the same apply. 1.12.1's `SignalEvent` (`0x703e50`) runs every listener before the verb goes on.

**Changed**
- Each queued trade half signals before the next one sends (`0x4bfa70` per item).
- A bind answer frees its record after its listeners (`0x5e1d09`).
- StopAttack runs as head, send and save with points between (`0x5ecb17`, `0x5ecb5b`, `0x6e6ff3`), and a call's tutorials fire at its points.
- A move's cast cancel signals before its channel cancel and the tick (`0x51512a`, `0x515193`, `0x5151a0`).
- SendChatMessage's AFK and DND lines signal before the packets after them (`0x5eb801`, `0x49f5fc`).
- A pick made at a point signals its target change once.

**Tests:** the wire order for each, the census over queued reports and tutorials, one target change per pick. Each fails on main.

Part of #2271.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-10-30 19:56:08 +07:00
Sam Sung 6ee575b019 The world map, readout and chat walks read their state at the call (#2844)
Fixes #2838
Fixes #392

The world map, its landmarks, the world-state readout, the recruitment cascade and the channel walk were fed at the frame's end. 1.12.1 reads each at its call or runs it at its packet.

**Changed**
- `GetCorpseMapPosition` and the map setter read the corpse, the explored bytes and the landmarks as they run (`0x492090`, `0x4a6bf4`, `0x4a6819`).
- The readout's rows are read at the call (`0x4c5a70`), and its flip signals inside its notice (`0x49bdd6`).
- The recruitment cascade (`0x49ea90`) runs at each caller, the chat cache's AUTO line included (`0x498e35`). The taxi flag's walk runs at its watcher (`0x5ff56e`).
- A corpse answer is kept only for a ghost (`0x48f766`). A ghost-bit edge resets it in its packet and asks only for a ghost (`0x491f50`).

**Tests:** each read between two packets, the readout's signal, the walk, the cascade, the corpse on both edges. Each fails on main.

Part of #2217.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-10-30 19:15:26 +07:00
Sam Sung 2731b7a1ba LoggingChat and LoggingCombat open their log inside the call (#2842)
Fixes #2836

`LoggingChat` and `LoggingCombat` answered their flag before the app tried the open on a later frame, so a failed open still answered 1. 1.12.1's verb (`0x49fe50`) opens the log inside the call (`0x65a0c0` at `0x49fea2`), clears the flag when the open fails (`0x49feab`), and only then answers it.

**Changed**
- The verb's open runs through the call host, and a failed open clears the flag before the answer.
- Writes gate on the flag (`0x49acde`, `0x49b1d3`). Turning it off keeps the file open, and the chat teardown closes both (`0x498700`).

**Tests:** an unwritable path answers nil for both verbs. A writable one answers 1, writes the same chunk's line, stops on off, appends on on, and closes at the teardown. Each fails on main.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 17:40:37 +07:00
Sam Sung 5899c9847b Item locks, record views and equip records read at their place (#2840)
The item rows read the frame's final state: locks were forgotten as the frame left the index, tooltip records and the bind point were fed after the packets, and equip callbacks were replayed at the frame.

**Changed**
- A lock lasts until its item is freed (destroy `0x4674a0`, reuse `0x466e55`, teardown `0x467800`). A release keeps it.
- An item's deactivate (`0x5d8660`, from the release at `0x46496f`) lets go of its loot, cast, cursor and reader, and cancels its record wait. A brought-back item sets up again (`0x5d8430`).
- A tooltip build and `GetBindLocation` read at the call (`0x5075f0`, `0x48dae3`). A cached equip record acts inside the call (`0x5e163b`).

**Tests:** each lock path, each deactivate leg, a bring-back's set-up, a tooltip between two writes, the equip send in the call. Each fails on main.

Part of #2217.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-10-30 16:44:49 +07:00
Sam Sung ef2f445f96 Account-data readers run inside their section's settle (#2839)
Fixes #2833
Fixes #2834

The bindings, layout and macro readers reran at the frame's end off the sections a download stored. 1.12.1 runs each inside the settle its digest or download handler makes (`0x5afaca`, `0x5afd19`): the bindings `0x4b6240`, the layout `0x490380`, the macros `0x4f0600`.

**Changed**
- Each reader runs at its section's settle, ahead of the next packet.
- The interface load registers the readers after VARIABLES_LOADED (`0x4900c2`-`0x4900d1`), so a `/reload` reads the bindings and signals UPDATE_BINDINGS there. File scope sees the fresh, empty binding table.
- A 0.2 macro file's button moves wait for the bar's arrival (`0x5deae2`). The file is rewritten only after them.
- A save queued before the set choice is dropped (`0x4b6506`).

**Tests:** a download between two packets for each reader, the `/reload` order, the macro moves with the bar before and after the settle, a save at file scope. Each fails 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-30 15:48:18 +07:00
Sam Sung 6028b6ce1c A nameplate's click selects and interacts inside the click (#2837)
Fixes #2202
Fixes #2831

A nameplate's click was queued and applied by frame systems after the click returned. 1.12.1's click slot `0x7cb910` runs `0x4949f0` at `0x7cb92a`, ahead of `RunClick` (`0x7cb936`): the left leg's trade, pet feed, greeting and `SelectTarget` (`0x4925d0`), or the right leg's select and interact (`0x492820`).

**Changed**
- Both legs run inside the click, for a press and a scripted `Click()`, on buttons 1 and 4 only, a disabled plate included.
- A select's re-swing is judged on the unit (`0x5ecb70`: alive, `CanAttack`), not the cursor, at every select.
- A retired plate keeps its guid (`0x608a10` never writes `+0x4e8`), so its click still selects that unit.

**Tests:** `Click()` then a same-chunk target read, the select's events ahead of `OnClick`, the trade, feed and bind order, the re-swing under any cursor, a retired plate's click. Each fails on main.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-10-30 14:58:59 +07:00
Sam Sung 892abefc08 Lanes fire after each packet's handlers, not once at the frame's drain (#2835)
Fixes #2168

Listeners fired once a frame, at a drain after every packet, so one packet's events could fire after a later packet's writes. 1.12.1 runs each packet's handler whole, in wire order (`0x5384d0`, `0x537ae5`), and a signal runs every listener before it returns (`0x703e50`).

**Changed**
- The lanes flush after each packet's handlers, and before the first packet for what input and the tick stamped.
- An update packet's blocks, a speed packet's pose and a saved answer's bytes stay inside their packet.
- Log starts become span starts. Chat's age pass runs once a frame.
- The guard holds each lane's own reads.

**Tests:** a listener's send ahead of the next packet's query, per-log reads between packets, close shapes across lanes. Each fails with one drain. Twelve suites now run in the world.

Part of #2217.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-10-30 14:19:56 +07:00
Sam Sung b4aad19569 The mouseover tooltip rebuilds at its callbacks inside the packets (#2829)
The world mouseover tooltip was rebuilt once a frame from the frame's final state. 1.12.1 picks and publishes it at frame time (`0x481790` → `0x482090` → `0x492890`), but rebuilds it inside the packets, at its callbacks:
- a unit's name and creature records and its skinnable flag (`0x4919d0` via `0x491a00`, `0x5ff7be`);
- a game object's lock key (`0x52ad10`);
- a corpse's owner name (`0x52afe0`).

**Changed**
- Each rebuild runs at its callback and reads through the host. Other field changes don't rebuild, and a rebuild neither re-anchors nor raises UPDATE_MOUSEOVER_UNIT, as in 1.12.1.
- A record-less locked object shows "Locked" (`0x52aae2`).
- A `UnitName` miss signals UNIT_NAME_UPDATE when the name lands, whether or not the unit is held (`0x517290`), and the wait ends with the world session.

**Tests:** each rebuild read between two packets, the locked line, an out-of-view member's name update. Each fails on main.

Part of #2168.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-10-30 11:00:34 +07:00
Sam Sung 6bc2e31b4b The worn slots, enchants and countdowns read live; the doll's feed goes (#2828)
The character doll's feed pushed the worn slots, weapon enchants and combat stats into the VM once a frame. A listener for an equip packet read the frame's last item, where 1.12.1 reads the item list at the call.

**Changed**
- Worn and bank-bag slots read live (`GetItemInSlot` `0x6228a0`, at `0x4c8cd3`), each slot built only when asked.
- `GetWeaponEnchantInfo` reads live (`0x4c97ca`), with six nils and no player (`0x4c9965`).
- Item countdowns read as of the listener's packet.
- The feed goes, along with its gate, the name cache's counter, and an ammo-hit `UNIT_INVENTORY_CHANGED` that 1.12.1 never raises: a cache hit signals nothing (`0x4c83ce`).
- New guards list the host's reads of handler-written state and its raw store reads.

**Tests:** an equip listener reads its own packet's item, and a line between two writes reads the first charges and enchant time. Each fails on main.

Part of #2168.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 09:21:35 +07:00
Sam Sung 745e8d673a Movement, camera and mouselook verbs act inside their calls (#2827)
The movement, camera and mouselook verbs queued their work for the frame's input pass. 1.12.1 does it in the call: the tick, the stand-up and AFK clear (`0x514e23`), freelook's entry and leave, the sheath, the zoom and the loot close (`0x60e990`).

**Changed**
- Each verb acts inside its call.
- The mouse's primary bits live in one store, which IsMouselooking reads live (`0x514278`).
- Freelook entry moves the mouse focus to the world frame (`0x764dc0`). Its records walk no hover (`0x766250`).
- A press goes to the frame holding a button (`0x76637f`), and in freelook to the world (`0x492bd9`).
- The mouse turns the camera only while the command word holds a bit (`0x51440c`).
- `/follow` ends at the mouse hand-off (`0x5145fa`).

**Tests:** each verb's effect read on the next line of its chunk, the focus change's order, routing, and rebinding. Each fails on main.

Part of #2202.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 08:56:45 +07:00
Sam Sung 9cd691f630 Far sight, the npc token and small reads happen in place; a wider guard (#2826)
Several small engine reads used copies a feed wrote once a frame, and the guard missed whole classes of such reads.

**Reference and changes**
- The "npc" token reads the interaction latch (`0x515c00`, `0x5f0251`).
- The camera pose loads at the interface load (`0x481b4e`).
- The display modes and the language skill are read at the call (`0x48bc90`, `0x4e3837`).
- Far sight:
  - starts or ends at its field change (`0x5de144`, `0x5de15d`) and at the object's create (`0x5fbb1a`, `0x5d552d`);
  - resets where our player is freed (`0x5dd33d`);
  - its bit, not the camera, tears input down (`0x514537`).
- The guard now scans every system that writes the VM. It matches by full name and counts read-filled queues and writes through `&self`. That surfaced 10 honest rows.

**Tests:** each moved read between two packets, far sight at create, at loss, and across sessions, and every guard gap shown to fail.

Part of #2168.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 05:44:07 +07:00
Sam Sung d9736a9878 Token verbs and map positions read the group live at the call (#2825)
The unit-token resolver's inputs were pushed into the VM once a frame. The verbs that read the push answered with the frame's last group, where 1.12.1 resolves at the call (`0x515970`).

**Changed**
- The feed is gone. The views' rewind and targeting walk the tokens' reach off app state (`ui_unit/reach.rs`).
- `InitiateTrade` resolves at the call (`0x48a120`).
- `UnitIsFriend` and `UnitCanCooperate` test membership through the host (`0x491900` → `0x4e80a0`, `0x4bafd0`).
- `GetPlayerMapPosition` reads at the call (`0x4a8610` → `0x4a7870`): a held Player's position, else the group record. The record is skipped when status bit 0x20 is set, and placed under its zone's own map, so a member in an instance reads (0,0).

**Tests:** trade, the friend test, and map position between two group lists, mid-frame joins, an instanced member, bit 0x20. Each fails on main.

Part of #2168.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 04:20:26 +07:00
Sam Sung 03b99f4942 The party feed goes; a new interface is seeded at its load (#2820)
The party feed pushed the roster to the interface once a frame, seeded a new interface late, and printed the ready check's summary a frame after its answer.

**Reference**
- Every party and raid getter reads the engine at the call (`0x4e86d0`, `0xbc6f48`, `[0xb713e0]`, `[0xb712a8]`).
- A `/reload`'s interface sees the group and lockouts from its first file (`0x490bd0`, `0x48fbf0`; `[0xb6e5e8]`).
- The ready check's not-ready and away lines print inside the answer's handler (`0x4bb310` → `0x49a870`).
- The friend list signals only from its own handlers (`0x5ae867`, `0x5ae92d`).

**Changed**
- The roster push and its catch-up are gone.
- The seed runs at the load, before the files.
- The ready-check lines print in place.
- The social feed's dead seed is gone.

**Tests:** a new interface's first lists signal at their packets, a reload's files read the lockouts, the ready-check lines between their packets, no stray list event.

Part of #2168 and #2217.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 00:47:06 +07:00
Sam Sung 24cd215ba6 The cast getters read live; a refused reagent line shows at its answer (#2818)
The cast state's getters read a copy a feed pushed once a frame, and a refused cast line that missed the item cache retried at frame time, so both lagged their packets.

**Reference**
- IsCurrentAction, IsAutoRepeatAction, SpellIsTargeting, SpellStopCasting and the targeting word's checks read the cast state at the call (`0x4e54c1`, `0x6e48e0`, `0xcecac0`).
- A reagent or totem refusal that misses the cache registers `ShowItemCastFailure` (`0x6e1e82`-`0x6e1ea4`, `0x6e29b0`). The item's answer shows it, with UNKNOWN on a refusal (`0x6e2a49`). With no item word no line ever shows (`0x55be5d`).

**Changed**
- The getters read through the host, and `feed_cast_reports` is gone.
- The refused line rides the item cache's callback list.
- The "Missing reagent" fallback that 1.12.1 never shows is removed.

**Tests:** the line between its neighbours for a record and for a refusal, the getters between packets, no line with no item word.

Part of #2168 and #2217.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 00:06:35 +07:00
Sam Sung 7e66ef52a0 The bar's state feed goes; macro faces, reload seed and staged buttons (#2817)
The bar's last per-frame feed is gone, along with four mismatches with 1.12.1 its audit found.

**Reference**
- A macro or item slot's texture runs the attack and ranged face tests on its spell first (`0x4e6ab2`, `0x4e6acb`, `0x4e5a50`).
- A `/reload` keeps the bar in the engine, so the new interface reads it from its first file.
- SMSG_ACTION_BUTTONS only stages its words (`0x5e6680`), and the arrival loads them, skipping unknown spells (`0x5deaf5`).
- The usable walk asks with the bar's callback, behind its player and count gates (`0x4e5b30`, `0x4e5080`, `0x4e50ab`).

**Changed**
- Each of those, and a worldport's book no longer reruns the rank pass over loaded words.
- `feed_action_state` is deleted, and its tests read live.

**Tests:** an Attack macro's weapon icon, the bar during a reload's load, an unknown spell's empty slot, the walk's ask, the worldport's book.

Fixes #2810. Fixes #2811. Fixes #2812. Fixes #2813. Part of #2168.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 23:26:36 +07:00
Sam Sung a84dbd3ab1 Inventory alerts recompute and signal at their item watchers (#2815)
UPDATE_INVENTORY_ALERTS fired from the character feed at frame time, and GetInventoryAlertStatus read its copy, so an alert lagged the durability or ammo packet that changed it.

**Reference:** `0x4c7ee0` recomputes the 12 statuses, including the temporary-enchant arm (`0x4c8035`). A break's tutorial fires inside the loop (`0x4b5390`), and the signal at `0x4c8085` on every pass. It runs from the item field watchers inside the update packet (`0x5d952a`, `0x5d97aa`, `0x5d9920`, `0x5d9b2f`, `0x5dee7a`) and at world entry. Any player's visible item also rebuilds the profession and craft lists (`0x5dee30`).

**Changed**
- The recompute runs at each watcher's step and reads live, including the temporary enchant.
- Its tutorial fires in place.
- Another player's visible item rebuilds both lists.
- The feed only stores.

**Tests:** a durability change's signal between its packets, the getter between packets, the enchant statuses, the tutorial ahead of the signal, the rebuilds.

Part of #2168.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 21:00:13 +07:00
Sam Sung e8e9969f91 The bar's icons, counts, aura notifies and SetAction read live (#2809)
The bar's icon, count and consumable getters, its aura notifies and GameTooltip:SetAction read a copy a feed resolved once a frame, so a call between two packets read the frame's last state.

**Reference**
- GetActionTexture, GetActionCount and IsConsumableAction read at the call (`0x4e6a50`, `0x4e6c70`, `0x4e5250`), with the counts at `0xbc6390` zeroed at game entry (`0x4e4f50`).
- An item answer runs the bar's callback (`0x4e5b80` → `0x4e5c70`), firing ACTIONBAR_SLOT_CHANGED per slot.
- An aura add or removal notifies its slot around its log line (`0x4e5de0`).
- SetAction runs the cooldown query and attack test at the call (`0x5323a6`, `0x5323b4`).

**Changed**
- The getters and SetAction read through the host.
- The callback rides the item cache's list.
- The aura handlers notify in place.
- `feed_actions` keeps only the engine's refusals and a new VM's seed.

**Tests:** an item answer's slot event between its packets, the seed's counts, the aura notifies, SetAction inside its packet.

Part of #2168.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 20:10:15 +07:00
Sam Sung bf8c54b1fe The vendor sale and the map's landmarks signal in place, not at the tick (#2808)
Five engine calls queued their event for the VM's next tick, behind every packet, where the reference signals in place.

**Reference**
- A vendor sale clears the cursor and signals CURSOR_UPDATE inside the click (`0x5df5f0`, `0x494b60` → `0x495190`), unless the player lost control (`0x494b8d`).
- The landmarks rebuild and signal WORLD_MAP_UPDATE (`0x4a67a0`, `0x4a6ce4`) at the gossip marker's set, at its deadline, and at an arrival tested on a moved update (`0x6dac93`, `0x6d9411`, `0x6d9a4c`).
- World entry drops the marker but keeps its deadline (`0x6d8fb0`).
- The catalog's load signals nothing (`0x4a5d00`).

**Changed**
- The sale signals in its click.
- The landmarks signal on their own lane.
- World entry and `/reload` drop the marker.
- The seeds are silent.
- The world map's Deviation keeps only the world-enter sync.

**Tests:** the sale before the binding's next line, the gossip set between its packets, the deadline, a reload's marker.

Part of #2168.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 18:53:00 +07:00
Sam Sung b4ea5df281 Debug builds check every event raise's origin at run time (#2805)
The guards' source scan misses a raise reached through a helper in another module, so a frame-time feed could raise outside every lane unseen.

**Changed**
- In debug builds, every raise into the VM goes through one check: the immediate fire (`tick::fire_event_into`) and the queue (`Model::queue_event`, now its only push).
- A raise outside a script call, a lane or the VM's tick fails with the event and its caller chain, unless an allow entry names its guard row or reference place.
- The app's tests fail hard. A live run warns once per site.
- Its first run found `feed_char` raising UPDATE_INVENTORY_ALERTS at frame time, where the item field watchers signal it in the packet (`0x4c8085`). That is now a guard row.

**Tests:** a frame-time raise fails naming its caller, the same raise in a lane passes, and a live run reports each site once.

Part of #2168.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 18:18:28 +07:00
Sam Sung 3d7383b710 The bar's usable pairs store at each write; refreshes run in place (#2802)
The bar's usable pairs were stored by a per-frame diff, and EditMacro's and a bag update's refresh fired at frame end, so ACTIONBAR_UPDATE_USABLE and a slot's IsUsableAction lagged their cause.

**Reference**
- Each slot write stores its pair (`0x4e58e0`, `0x4e590a`).
- The refresh `0x4e5c00` runs at world entry, inside EditMacro (`0x4f132e`) and in the item watcher's run before BAG_UPDATE (`0x4f9374`).
- Game entry zeroes the cache (`0x4e4f50`).
- The reactive list keeps learn order (`0x5dea86`, `0x4b27b3`).

**Changed**
- Every writer stores its pair, and the refreshes run in place.
- The cache zeroes at game entry.
- The reactive list moves to its own resource, in wire order.
- The per-frame diff and its event queue are gone.

**Tests:** EditMacro's update inside the call, a bag run's between its packets, PlaceAction's listener reading its pair, the reactive order.

Part of #2168.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 16:07:53 +07:00
Sam Sung b686ad253b The Craft window reads live and signals at its packets (#2801)
The Craft window answered from a copy a feed refreshed once a frame, and rebuilt and signalled there.

**Reference**
- The build (`0x4f60c0`) zeroes its counts and, with no craft type open or no player, leaves before CRAFT_UPDATE (`0x4f661a`). The open, SMSG_PET_SPELLS, the record callback, learn and unlearn, and the item watchers rebuild it.
- The getters read at the call (`0x4f6c3b`, `0x4f6d52`, `0x4f6fe6`, `0x4f76b2`, `0x4f7a3c`).
- GetCraftDescription falls back to the taught spell's text (effect 36 or 57), with the pet flag (`0x4f7b53`).

**Changed**
- The build and the getters read through the host, and a build with no player empties silently.
- The description takes the fallback and the pet flag; the VM fills its templates in the call.
- `feed_craft` is gone; the trainer and the window share one token helper.

**Tests:** each getter read between two packets, CRAFT_UPDATE between its neighbours, the silent build, the description fallback.

Part of #2168 and #2217.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 15:43:25 +07:00
Sam Sung 4b6ef4b2c6 The profession window reads live and signals at its packets (#2799)
The profession window answered from a copy a feed refreshed once a frame, and rebuilt and signalled there, so a getter or TRADE_SKILL_UPDATE between two packets read the frame's last state.

**Reference**
- An item answer runs the getter's or the build's record callback (`0x4fdc30`, `0x4fc8e0`), which rebuilds and signals at its packet.
- The build reads the book at its call (`0x4fca20`), and with no player it empties the list and signals nothing (`0x4fca52`, `0x4fcb1b` → `0x4fd170`).
- GetTradeSkillLine reads the open line at the call (`0x4fdd40`).

**Changed**
- The callbacks go on the item cache's list.
- The build and the getters read through the host.
- A build with no player empties the book silently.
- `feed_trade_skill` is gone.

**Tests:** the line getter between two packets, a reagent's answer between two falls, a learn banded at its packet, a product answer after `/reload`, and the silent empty build.

Part of #2168 and #2217.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 13:48:12 +07:00
Sam Sung b52f2bf110 The movement verbs and DropItemOnUnit apply in their calls (#2797)
The movement verbs and DropItemOnUnit queued their work for frame time, so a cast a movement press should cancel was still running on the script's next line.

**Reference**
- Every movement verb goes through `SetCommands` (`0x515090`). It sets the bit, and a new press in mask 0x10f0, or the press completing both mouse bits, cancels the cast inside the call (`0x51512a` → `0x6e4940`), then the channel, then runs the tick.
- ToggleAutoRun reads the bit at the call (`0x513de7`).
- DropItemOnUnit fills the trade or starts one in its call (`0x48da50`, `0x48da62`).

**Changed**
- With a host lent, the 23 movement verbs and Jump apply in the call.
- The autorun bit flips there.
- DropItemOnUnit applies in the call.
- The mouselook verbs still queue.

**Tests:** a press mid-cast cancels before the next line, two toggles in a frame cancel once, and a drop on a player trades in its call.

Part of #2202.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 12:29:30 +07:00
Sam Sung d12054da65 Death, logout and the taxi flag read live and signal at their packets (#2796)
Three windows still answered from a copy a feed refreshed once a frame, and fired their events there.

**Reference**
- The cursor's control gate reads our unit's taxi bit at the call (`0x494b97`-`0x494ba6`).
- PLAYER_CAMPING, PLAYER_QUITING, LOGOUT_CANCEL and the logout-failed line are raised by their packets' handlers (`0x5aaf17`, `0x5aaf72`, `0x5aaf26`).
- The death signals fire at their packets, and the corpse range is checked each frame (`0x492130`).

**Changed**
- The taxi gate reads the flag off our object.
- The logout replies go on the notice lane at their handlers.
- The death getters (GetCorpseRecoveryDelay and the rest) read live, and the death signals fire at their packets.
- `feed_on_taxi`, `feed_logout` and `feed_death` are gone.

The fake world server logs a session before answering its authentication, which a loaded test run read behind.

**Tests:** each getter read between two feeds, and each event between its neighbouring packets' events.

Part of #2168 and #2217.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 11:55:07 +07:00
Sam Sung 5d1f7ad395 The bar's state getters and cooldown queries read live at the call (#2793)
The action bar's state getters and cooldown queries answered from a copy the feed refreshed once a frame, so a call made between feeds read the last frame's state.

Reference: each getter reads its slot at the call. A cooldown query that misses the item cache registers a callback on the record (`0x6e2f70`) and answers when the record arrives. A pet spell list derives its macros where it loads (`0x4bdae0` → `0x4f0ce0`). The go handler sweeps both cooldown lists every 120 s (`0x6e1910`); the insert sweeps nothing.

Changed: the bar getters (IsUsableAction, GetActionCooldown and the rest) resolve and read live. The cooldown query uses the record's callback list. The pet list derives its macros in its place. The sweep moves to the go.

Tests: each getter read between two feeds, the callback answered at the record, the macros derived after UPDATE_MACROS, and the swept GCD.

Part of #2168.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 11:17:12 +07:00
Sam Sung db8a28ec2c Try-ons, slash targets, RestartGx and the video verbs apply in the call (#2790)
Several verbs did their work in a later drain: try-ons, the chat input's /target, /assist and /follow, RestartGx, and the engine CVar verbs (SetGamma, the video pairs, SetMultisampleFormat, the minimap zoom, the glue writes). A script's next line read the old state. LoadBindings also switched the current set, so a Character Specific tick then Cancel left the character set live.

Reference: a try-on reads the cache and lands or refuses in its call (`0x504540`, `0x504b2d`). RestartGx runs gxRestart synchronously (`0x48dab0` → `0x63ce00`), and SetMultisampleFormat restarts in its call (`0x48c79b`). LoadBindings never writes the current set (`0x4b8249`-`0x4b8252`).

Changed: those verbs apply in the call, and the slash commands call the stock verbs. The drains left at the frame each name why; SaveBindings only marks its set until shutdown (`0x4b6310`, `0x4b65e0`).

Tests: per verb, a same-chunk read of its effect.

Part of #2202.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 09:19:04 +07:00
Sam Sung 0259668162 Tutorials trigger where they happen, on a stamped lane (#2789)
Tutorials were judged once a frame by a feed. A tutorial a packet raised fired behind every packet's events. Looting fired at a corpse's first sight, even one already dead at login, and Backpack fired before its item's record arrived.

Reference: each trigger fires in the handler or input that raises it.
- the vendor and trainer lists (`0x4fad32`, `0x4d74ae`);
- Looting on the dynamic flags' bit 0 rising (`0x600482`-`0x60049b`);
- the item tutorials after the received line, once the record is known (`0x491cec`);
- Raid Groups at the roster rebuild (`0x4ba60d`).

Changed: tutorials are a stamped lane, and each site triggers where it happens. TutorialsEnabled reads live. `feed_tutorials`, `judge_selections`, `watch_windows` and `TutorialEvent` go.

Tests: a packet's tutorial falls between neighbouring packets' events, and a ClearTutorials in an earlier listener stops a later trigger.

Part of #2168.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 08:41:10 +07:00
Sam Sung 76874a1510 Profession books open in their cast's call; Create All recasts at the go (#2788)
A profession's book opened from a frame feed, after the cast verb had returned, and its "Create All" repeat recast from a drain. A listener read the book shut inside the call that opened it, and a repeat on a recipe with a cooldown refused its own recast.

Reference: `TryCastSpell` opens the book inside the call (`0x6e4bce` → `ToggleTradeSkill` `0x4fc910` / `ToggleCraftWindow` `0x4f6000`). `HandleSpellGo` runs the repeat hook (`0x6e81e7`) before its cooldown inserts (`0x6e8425`). The repeat disarms only at a failure report, a failed cancel, or the close (`0x4fd790`'s four callers).

Changed:
- the toggles run in the call;
- the repeat runs in the go ahead of the cooldowns;
- `drain_trade_skill` and `open_trade_skill` go.

Tests:
- a listener's CastSpellByName shows the book between two packets;
- Create All recasts at the go, also with a cooldown.

Part of #2168.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 07:45:43 +07:00
Sam Sung 26655c88a6 WORLD_MAP_UPDATE fires from the explored-bits watcher at its packet (#2786)
WORLD_MAP_UPDATE for a newly explored zone was queued for the VM's next tick from a frame feed, so it fired behind every packet's events. A listener read the map's overlays before the explored bits were written.

Reference: `0x4a6430` arms a field watcher on the player's explored bytes (section 4, `+0xe6c`, `0x100` bytes). Its callback `0x4a6460` re-selects the shown map: `0x4a67a0` reads the bytes, rebuilds the overlay and landmark lists, and signals WORLD_MAP_UPDATE once (`0x4a6ce4`), inside the update's notify pass.

Changed: a field notice for the explored bytes, fired on the field-notices lane at its packet. It writes the bits, rebuilds the landmarks, then signals. The tick queue's row goes.

Tests: the event falls between neighbouring packets' events; a listener reads the new overlay; an update that moves no explored byte fires nothing.

Part of #2168.

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

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-29 06:16:32 +07:00
Sam Sung 4b322073b4 SetCVar runs its callback in the call; the view verbs seat in theirs (#2785)
SetCVar ran its change callback and its effect in the frame's later sync, and SetView, SaveView and ResetView queued their camera writes for a drain. A script that read the effect on its next line saw the old value.

Reference: `CVar::Set` (`0x63df50`) runs the row's callback inside the call (`0x63df7d`), and a refusal drops the set. CVAR_UPDATE follows from `SetCVar` (`0x488cad`). SetView writes `cameraView` in its call (`0x512f17`), and SaveView writes its view there too (`0x50fa09`).

Changed:
- the 23 callbacks, once observers, are plain functions the host runs in the call;
- the knobs they write became host state, with their readers ordered around the VM pass;
- `drain_view_requests` is gone.

Tests: SetCVar("deselectOnClick","0"), then a click on empty world on the chunk's next line, keeps the target. A view verb's CVar reads on the next line.

Part of #2202.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 05:35:50 +07:00
Sam Sung eee9d1441c An item record's answer walks its callback list at the packet (#2784)
An item record's answer ran its callbacks from hand-placed hooks. The equip replays ran in the drain, the quest log's refire at the frame's end, and the tooltip rebuilds at the tick, so a replay's send followed later packets' sends. A refused record also replayed every frame.

Reference: `ItemCacheReceive` (`0x55bdb0`) stores the record, then walks one callback list per record, oldest first, filled by each lookup's miss (`0x55ba30`). A refusal calls each callback with found 0 and frees the list (`0x55bea8`, `0x55bed2`).

Changed:
- `Items` keeps that list, registered at each miss and walked at the answer's handler;
- a deferred stretch's calls apply at once and its signals park.

The late callbacks stay named in the `Items` row.

Tests: an equip replay sends before a later packet's send; two parked swaps keep their order; a refused record sends nothing.

Part of #2168.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 05:10:21 +07:00
Sam Sung e41ddb36d3 Party, raid and pet tokens and the roster read live at their packets (#2783)
The party, raid and pet tokens read copies pushed once a frame, and so did the roster copy, the event-token walk and the dungeon latch. A listener inside a member's update read the last frame's party.

Reference:
- the tokens look their member or pet record up by guid at the call (`0x496400`, `0x496420`);
- the event-token walk names player, pet, target, party row, raid row, npc, mouseover (`0x515c50`);
- the dungeon latch is set at the port's new object manager (`0x401c22` → `0x495d10`), and at login with the character list's map (`0x4015f3`);
- a ready check closes at the raid rebuild's end (`0x4bac5a`).

Changed: the tokens, the token walk, the roster, the group predicates and the relations read live, and the latch moves to those places. The frame-final party and pet pushes go.

Tests: a member listener reads party1 and raid3 from its own packet, through a same-frame roster swap.

Part of #2168.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 04:27:08 +07:00
Sam Sung cdebda36fb Loot roll getters read live; roll signals fire at their packets (#2781)
The loot-roll getters read a copy pushed once a frame, and START_LOOT_ROLL, CANCEL_LOOT_ROLL and the roll lines fired from that feed. A vote also deleted its roll, where the reference keeps it listed until the result.

Reference: START fires at the start packet with the item cached, or at the item answer (`0x61b44d`, `0x61b7a7`). A vote sets only the closed byte after its CANCEL (`0x61bf2a`, `0x61bf32`). A win's line waits for the winner's name (`0x61b96a`). Time left is an unclamped 32-bit difference (`0x61b404`).

Changed: the getters read live, the rolls get their own lane, and each signal fires at the handler whose lookup lands last. One Deviation: a refused record closes the roll, where 1.12.1 leaves a dead frame.

Tests: a listener reads the live roll; START falls between neighbouring packets' events; a start and a win in one frame leave nothing listed.

Part of #2168.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 01:50:05 +07:00
Sam Sung 77cea16e72 Closes run their engine half at the packet and signal at its place (#2779)
A close that a packet handler ran fired at once when its own lane was empty, ahead of earlier packets' events on other lanes. benilla also closed the binder, pet-untrain and talent-wipe confirms itself when you walked away.

Reference: each close core releases, clears its cell and sends in the handler, then signals. The stock OnHide's close verb re-enters it: trade's release re-enters at `0x493365`, so a latched partner gets three CMSG_CANCEL_TRADE. The confirms' latches are never cleared (`0x5dfa21`); StaticPopup.lua hides them.

Changed:
- the engine half of every close runs at the handler, and the signals park behind the lane;
- one shared guard keeps a re-entered close verb off a newer session, replacing auction's own;
- the range-close systems go, and the polls test range at the call.

Tests: quest detail Accept sends its NPC, and the trade cancels and Begin go in order.

Part of #2168.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 00:18:47 +07:00
Sam Sung e23368a777 The item-text reader reads live and signals at its packets (#2778)
The book, letter and plaque reader read a copy pushed once a frame, and opened on its lane. A bank or gossip open in the same frame lost to the book, and a listener read the last frame's page.

Reference: the open, the page and record answers and the turn each run in their handler. ITEM_TEXT_BEGIN (`0x4e349f`), READY and TRANSLATION are signalled there, and a waiting open finishes in its record's callback (`0x4e3280`, `0x5f59d0`).

Changed: the getters read live, each step runs in its handler, the events go on the lane. A nested close acts only on its read. Deviation: 1.12.1's reopen closes the book when an uncached page or a letter's author lands, and benilla keeps the book open.

Tests: a listener reads its packet's page, a same-frame bank open wins, and a book read before its template arrives opens when it lands.

Part of #2168.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 23:46:11 +07:00