Commit Graph

406 Commits

Author SHA1 Message Date
Sam Sung 9923962378 Every loot close runs inside the packet that owes it (#2854)
A loot close a packet owed was made at the frame's end (`drain_loot`), so a listener between two packets saw the window open, and the release went out late. 1.12.1 closes it inside the packet, at `EndLootWindow` (`0x48f200`).

**Changed**
- A precast end, a GO, a cast result, a move start and the server's stand state each close the window inside their packet. Each release goes ahead of the handler's later sends.
- Every close cancels the rows' pending record fetches (`0x4c1cb0`). A move or a sit closes only while a loot target is latched (`0x6126b0`).
- A chest's GO arms its loot target after the close (`0x6e831b`).
- `drain_loot` is gone. With it, the last frame-end write a listener could read is placed.

**Tests:** each close inside its packet, the release order, the latch gate, the fetch cancel. Each fails on main.

Fixes #2217

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 01:52:38 +07:00
Sam Sung c2f65546f9 A move's packet seats its unit when the movement clock has it due (#2851)
A remote unit's move was seated by the frame's movement pass, so a listener between two packets read where the unit stood before. 1.12.1 seats a move inside its packet when the movement clock has it due (`0x618dd2`), and holds it for the tick when not.

**Changed**
- A due move seats in its handler, stepped on by its lateness (`0x618bb0`). A rider seats on any game object flagged as a transport (`0x630a80`).
- A create seeds the unit's relay chain with its stamp (`0x619320`). On vmangos, a player who stood still before coming into view now trails by up to a second, as in 1.12.1.
- A plain relay ends its packet; only the speed relays read on (`0x6010e0`).
- `GetBattlefieldPosition` and `SpellCanTargetUnit` read positions at the call.

**Tests:** due and held moves, the catch-up, riders, the create seed, each read between packets. 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 01:16:08 +07:00
Sam Sung 213f8e66b5 A cast's and a selection's signals run their listeners before the send (#2850)
A cast's push and bind signals, the selection's tutorial ladder and five smaller stretches still ran their listeners after the sends behind them. 1.12.1 runs each listener at its signal, before the verb goes on (`0x703e50`).

**Changed**
- A cast stops at `SendCast`'s entry (`0x6e54f0`), runs its listeners, then reads the word and the record again. A cancel there leaves the cast unsent (`0x6e5580`), and the swing waits for the sent spell (`0x6e51be`).
- The selection's ladder signals before its send (`0x49388d`).
- StartAttack's refused stop, the click's quest-giver tutorial, the soulstone's cancel, and the stand change's loot close each signal before the sends after them.
- The send census's allowlist is empty.

**Tests:** each signal's listener sends ahead of the verb's send, and a cancel at the push signal. Each fails on main.

Part of #2271.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 00:35:40 +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 fc4b59b720 A call's teardown, tick, follow and mail signal at their place (#2841)
Several calls and engine turns held their signals past later effects of the same apply. 1.12.1's `SignalEvent` (`0x703e50`) runs every listener before the verb goes on.

**Changed**
- A selection teardown's strike cancel (`0x493a08`) signals before the store, the send, the usable refresh and the pet stop (`0x4bd650`).
- SetCommands' tick reports its cancel at `0x51512a`, the AFK line before its send (`0x5eb878`), and freelook at `0x51491f`.
- AUTOFOLLOW_END comes before the follow's stores (`0x61113e`), mail's signal before its arm (`0x4add9d`), a right-click's ClearCursor before the interact, and a refused bind's signal before the deselect (`0x6e615d`).

**Tests:** the wire and census order for each, and a strike-cancel listener's pet stop signalling once. Each fails on main.

Part of #2271.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 17:18:42 +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 f7f3600e7b The CVar mirror's syncs leave the frame-final list, held by a guard (#2832)
The guard listed `sync_cvars` and `sync_input_cvars` as frame-final reads: a handler's CVar write was said to reach the VM's mirror only at the next sync. No handler writes the registry but through the call host, whose appliers sync the mirror inside the call, as `CVar::Set` runs its callback inside its writer (`0x63df7d`) and `GetCVar` reads that record (`0x488bf9`). Every other writer runs before the input sync or after the dispatch.

**Changed:** both rows move to the no-view list with that reason. The frame-final counts fall to 12 rows and 36 inputs.

**Test:** `every_cvar_write_reaches_the_mirror_before_a_packet` fails when a handler holds the registry past the host, or a writer runs between the input sync and the packets. A registry write in the weather handler fails it.

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 13:49:26 +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 3ae988ab17 A capture without the UI enters the world on a boot VM (#2823)
Since #1466 the world entry parks the glue VM. `load_ingame_ui_seated` unparks it and then returns early when a capture runs without the UI. That left the glue VM in the world, so a world capture such as a vista painted the login screen over the world.

**Changed:** in that branch, a glue VM's CVars fold and a boot VM is installed, the VM a reload already holds there. A reload is unchanged.

**Test:** `a_capture_without_the_ui_enters_the_world_on_a_boot_vm` enters the world under `CaptureMode` with the glue VM parked and checks the VM is in-game. It fails without the change (`Glue`).

Fixes #2819.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 02:36:09 +07:00
Sam Sung 593a4cce0f cvar_app tests hold the env lock with the window variables cleared (#2821)
`cvar_app()` built a client from the window and capture variables without `ENV_LOCK`, while the capture, account-data and state-folder tests set them under it. Under `cargo test`, which runs a crate's tests on threads of one process, `a_locale_default_reaches_its_knob_at_load` saw another test's `WOW_WIN` and failed.

**Changed:** `cvar_app()` now holds `ENV_LOCK` for the client's life, with every variable the build and the load read cleared and a foreground run forced. Tests that set variables of their own build under the same hold.

**Tests:**
- On 16 threads, the cvars, capture, local-state and account-data tests together failed 20 of 21 runs before the change and 0 of 20 after.
- A new test sets `WOW_WIN` on another thread while a client builds. It fails on the old helper.

Fixes #2816.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 02:03:07 +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 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 65c83b1883 A follow ends at the packet that ends it, with AUTOFOLLOW_END there (#2803)
A follow ended at frame time, in `steer_follow`, and AUTOFOLLOW_END fired there, out of order with the packets that ended it.

**Reference:** `StopAutoMove` (`0x60fb60`) ends a follow inside the packet that ends it: our death, the attack-state swing, the taxi flag, a mover switch or release, a near teleport, a knockback, feign death, a stun, a monster move on us and far sight. It signals AUTOFOLLOW_END (`0x60fb84`), then releases forward and autorun. A near teleport and a knockback also close the loot window (`0x60e990`).

**Changed**
- One stop runs at each of those handlers, with END on the notice lane and the autorun clear behind its listeners.
- The loot window closes on a near teleport and a knockback.
- Each unit holds its client-controlled bit (`0x5fa636`).
- `feed_follow` is gone.

**Tests:** each end fires END between its neighbouring packets' events, and death's END comes before PLAYER_DEAD.

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 17:39:03 +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 3d730699e7 A packet's select binds the cursor's pick; the go's swing is validated (#2800)
With the targeting cursor up, a unit a packet selected was parked and bound to the cursor a frame later. A spell's deferred swing at its go also skipped the attack validator.

**Reference**
- `SelectTarget` with the cursor up binds at once (`0x4935d5` → `0x6e5b40`) and selects nothing. The loot response, the two acquires and the go each reach it at their packet.
- The go's swing runs the validator `0x612df0` (`0x6131aa`): the actor checks, the select, then the reload from the selection (`0x6130c1`), with ERR_NO_ATTACK_TARGET or ERR_INVALID_ATTACK_TARGET.

**Changed**
- Each handler binds in place.
- The melee probe and the pet bind inside their press.
- The go runs the Attack action's validator and swings at the selection as reloaded.

**Tests:** the loot response binds between its neighbours, a stunned caster's go sends no swing, and with a cursor up the go binds one unit and swings at the selection.

Fixes #2113. 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 14:53:28 +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 4efe834bb6 Error and info lines show at the handler that raises them (#2798)
The error and info lines a packet raises showed from a per-frame feed, ahead of every lane's events, so they sat out of order against their own packets' signals.

**Reference:** each handler shows its line where it runs, through `DisplayError` (`0x496720`): a chat row, or UI_ERROR_MESSAGE or UI_INFO_MESSAGE by the message's kind.

**Changed**
- The lines ride a stamped lane, pushed at their handlers.
- Five orders now match the bytes: the guild event and invite, learn and rank-up, the party disband and the ready check.
- A rank-up shows its line with no player held (`0x5e6363`).
- A missing tame string shows ".", as `0x703bf0` does.
- A world session that ends before entry no longer leaves its lines for the next.

**Tests:** each line between its neighbouring packets' events, the rank-up line without a player, and the session that ends before entry.

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 13:12:28 +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 2f9bd96fc2 The spell packets clear and replace bar slots at their handler (#2787)
The bar's slot clears, rank-ups and learned-ability latches were rewritten once a frame by feeds. A listener inside an unlearn or a supersede read the old bar, and ACTIONBAR_SLOT_CHANGED fired ahead of every packet event.

Reference: the unlearn handler clears the spell's slots after the book removal (`0x4e5e20` at `0x5ea253`). The supersede's `0x4e5e60` clears then sets each slot, with a send and an ACTIONBAR_SLOT_CHANGED for each (`0x4e58e0`). The learn and unlearn set the latches (`0x4b262c`, `0x4b2c83`). Each game entry zeroes the bar (`0x48f5a4`).

Changed: those writes run in their handlers, with the signals on the notice lane. The rank pass's Deviation: now runs only where login slots arrive. The bar empties at game entry.

Tests: an unlearn's listener reads the slot cleared; a supersede sends clear then set; a relog sends nothing for the last character's bar.

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:10:23 +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
Sam Sung 68176302d5 A worldport frees the cooldowns and cancels open loot rolls (#2775)
A cross-map worldport kept the old cooldowns and left open loot rolls up in the new world.

Reference: the worldport's teardown timer `0x401bc0` frees every cooldown after the objects' walk (`ClearItemAndSpellCooldowns` `0x6e9ad0` at `0x401c17`). That covers the player's and the pet's lists and the item table, with no flush. Then it cancels every open roll (`CancelAllLootRolls` `0x61b290` at `0x401c2c`), signalling CANCEL_LOOT_ROLL for each one not queued, before the map load.

Changed: two legs after the objects' leg of the worldport timer, in that order. The legs run once per load.

Tests:
- a port cancels the open rolls in the next frame;
- a port empties the cooldowns, and the server's resend seeds only what it lists;
- a same-map teleport touches neither.

Fixes #2769
Fixes #2770

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

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 23:00:52 +07:00
Sam Sung e0329532cc Played time, pings, broadcasts, the clock and channels fire at packets (#2774)
/played, PLAYER_SKINNED, the game clock, members' minimap pings, world broadcasts and the chat channel list came from feeds that ran once a frame. A listener inside a packet read the last frame, and two /played answers in one frame kept only one.

Reference:
- TIME_PLAYED_MSG and PLAYER_SKINNED signal inside their handlers (`0x4018df`, `0x48f412`);
- `GetGameTime` reads the clock at the call (`0x515ee0`);
- the ping handler stores the point and signals MINIMAP_PING at its packet (`0x4ee48d`);
- the broadcast handlers add their chat lines themselves (`0x49dcc0`).

Changed: each fires on its lane at its handler. The clock and the channel getters read through the host, and the five feeds go.

Tests: per family, a neighbouring packet's listener reads the live value; 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-28 22:37:11 +07:00
Sam Sung 6b50b9dd6a A worldport's teardown runs at the next frame's start, as one unit (#2773)
A worldport tore the old world down inside its packet: the despawn, the world handle and LOGOUT_CANCEL there, then PLAYER_LEAVING_WORLD before the frame's earlier packets' events had fired.

Reference: `SMSG_NEW_WORLD` (`0x401b00`) posts its teardown `0x401bc0` as a zero-delay timer, and a tick runs its timers before its packets. So the next tick:
- zeroes the world handle (LOGOUT_CANCEL);
- walks the objects (`0x467800`);
- sweeps at the local player's turn (`0x5dd543`).

Changed: the packet only posts the load. Three legs at the next frame's start run the connection's half, the sweep and the objects' teardown. The sweep runs ahead of the objects, so the target is still there in PLAYER_LEAVING_WORLD.

Tests:
- the port frame's events precede the leave, and its listeners still see the old world;
- a port alone leaves in the next frame;
- the leave listener sees the target.

Fixes #2763

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

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 21:47:03 +07:00
Sam Sung cb949ba1a5 Player scalars and the unit tokens read live; the unit feed goes (#2771)
The player's combo points, action-bar toggles and zone PvP masks, and the four unit tokens, read copies pushed once a frame by `feed_units`. A listener inside a packet saw the last frame's values.

Reference:
- `GetComboPoints` reads the player's count and banked target at the call (`0x51a190`);
- the bar toggles read `+0x102a` (`0x4e7660`);
- `GetZonePVPInfo` reads its masks at the call (`0x48d540`);
- the tooltip's health bar is redrawn by the health field's watcher (`0x52a08d` → `0x529560`).

Changed: those getters ask the live player, and `feed_units` is gone. Health notices redraw the tooltip bar. The senders' player checks read the player object. The unit-frame and world-entry harnesses run the host-lent path.

Tests:
- the scalars read as written;
- the tooltip bar follows each health packet;
- the senders gate on the player object;
- the login's XP check runs through the host.

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 20:43:57 +07:00
Sam Sung 3242ef72cc Quest text, quest lines and shares fire at their packets (#2768)
The quest log's text, its turn-in and accept lines, and the share verdicts came from feeds that ran once a frame. A listener inside a quest packet read the last frame's text, and an accept line was lost when a later packet in the frame emptied its slot.

Reference: `GetQuestLogQuestText` expands its `$` macros at the call (`0x4dff89` → `0x506f70`). The slot watcher `0x5ddd80` shows the accept line inside the update packet. The turn-in lines are `0x5dc400`'s.

Changed:
- the text expands at the call;
- the log reads names and records through the host;
- the accept line, the turn-in lines and the share verdicts fire on their lanes at their packets;
- the share line's bounded name wait is kept as on main.

Tests: a listener reads its own packet's quest text, and the accept line shows at the packet that fills the slot.

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 19:49:41 +07:00