Commit Graph

425 Commits

Author SHA1 Message Date
Sam Sung fe7148b043 Mac key citations point at the Mac client's published key input (#2912)
benilla's Mac key-handling comments cited the 1.12.1 Mac client as `0x5bf320` and `0x8a4aa`. The first resolved to an unrelated `WoW.exe` function, and nothing documented the second. benilla-re now publishes the Mac client's key input with `mac:` addresses (`systems/core/mackeys.md`), and every citation points there; `cited` finds all 15 backed.

Read against it:
- Caps Lock queues nothing on macOS, as the Mac client's modifier handler tests only Shift, Control and Option (`mac:08a06c`).
- F17-F20 are named by the OS layout there, so benilla's names are noted as its own.
- Deviation: Z keeps its capital name, where the Mac client's fill uppercases only a-y (`mac:08a3df`), because the fault leaves the stock sheathe binding unreachable.

Tested: Caps Lock is a key only off the Mac.

Fixes #2895

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

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-31 22:07:14 +07:00
Sam Sung 4cc50bc5ab A two-texture M2 batch draws its second stage on UV set 1 (#2908)
A two-texture M2 batch drew its second stage only when it was sphere-mapped, and a lookup past its table was taken as generated or untransformed.

- **#1831.** The stage's coordinate and transform lookups are unchecked (`0x70b8bd`, `0x70b8aa`). Past its table each reads the file's next u16, and benilla now does the same. A transform index at or above the count gives identity (`0x70b90b`).
- **#1832.** Every lit `Model2.bls` program passes `texcoord[k]` to stage k, and vertex format 12 carries set 1 at `+0x28` (`0x71de2d`). So stage 1 reads UV set 1, through its own transform, rotation and scale included (`0x70b91f`). This applies in every M2 lane and the skybox.

No stock model changes, since set 1 is zero in every stock vertex. Custom and HD models draw their masks.

Tests cover the past-table reads, the set-1 parse and the slot rules.

Found in Adrinalin4ik/manila.

Fixes #1831
Fixes #1832

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Thorsten42 <3400178+Thorsten42@users.noreply.github.com>
2026-10-31 21:15:25 +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 39e671290e Model textures resolve as TextureCreate does, and BLPs keep their 1x1 (#2904)
Model textures loaded by their literal name, and BLP mip chains stopped one level short.

- **#610:** a model texture resolves as `TextureCreate` (`0x449d90`) resolves it. A three-character extension is stripped (`0x449590`), then `.blp` and `.tga` are tried in the reference's order, and a `.tga` the size gate refuses (`0x448450`) falls to the next. This covers M2, WMO, clutter, skybox and baked NPC textures. A `.tga` gets its full chain from `_mipN.tga` files, then box-filtered (`0x4491f0`); one that fails to decode is opaque white (`0x44a2eb`). Found in Adrinalin4ik/manila.
- **#1680:** a BLP keeps its 1x1 level (`0x5a4ac0`), and ground layers take 9 levels.
- Bake, hair and extra-skin textures clamp on both axes (`0x4795ed`, `0x4782a5`, `0x4781dd`).

Tests run the loader and the readers over in-memory sources: the candidate order, the gate's fall-through, the mip chains and the white fallback.

Fixes #610
Fixes #1680

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

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-31 19:48:17 +07:00
Sam Sung 3bee60cb71 Models and buildings are lit per vertex, the colour inside the clamp (#2899)
1.12.1 lights models and buildings per vertex and interpolates the result. benilla lit them per pixel, and clamped the light sum before the material colour multiplied it.

- **#509:** M2 lighting (the `Model2.bls` vertex programs under the stock `M2UseShaders` 1, `M2UsePixelShaders` 0) and WMO exterior T&L now run in the vertex stage.
- **#530:** the vertex and model colour is the lit material inside the one clamp. For WMO this is MOCV, through GL colour material (`0x59cfec`, `0x59cff3`). For M2 it is `c28`, the model colour times the batch's M2Color (`0x70c4d3`-`0x70c712`). The programs clamp E first only when a point light is bound.
- **#115:** the clutter grey (`0xffc0c0c0`, `0x6b2615`) sits inside the clamp.

Tests:
- an interpreter runs `Model2.bls` programs 135 and 136 against the shader's mirror;
- both program levels pin the clamp law per light count;
- per-vertex and clamp-order checks run on both shaders.

Fixes #509
Fixes #530
Fixes #115

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 18:30:09 +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 f2efc34a72 A character select draws its whole stage at once, as 1.12.1 does (#2886)
A first click on a character at the select screen lagged; a repeat did not. In 1.12.1 the select's model build ends on `IsReady(1, 1)` over the scene (`0x473083` → `0x710450`), which loads the scene, the character, its items and the pet before returning. A failed read counts as done (`0x4434a8`). So the stage draws whole in the frame after the click.

benilla built the stage one asset step per frame, so a first select took 4 to 10 frames. A select now holds the next frame until the whole stage has loaded or failed, uncapped. A mesh built in that wait is built whole; the old vertex budget had cut the human booth model's skinned forms on first spawn.

Measured with the new `gsel` trace: a first select now draws whole in 3 frames. Tests cover the wait, a failed read and a return.

Fixes #2365

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 11:10:59 +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 e3097df6a4 The deviation list writes each comment's whole paragraph (#2883)
`docs/METHOD.md` asks each deviation to name the reference fact and why benilla differs, and `docs/DEVIATIONS.md` is generated from those comments. The generator wrote only a comment's lead: its first sentence, extended to at least 70 characters. A comment that gives its reason first lost the reference fact from the list, as the Warden entry did after its reason was restored.

The list now writes each comment's whole paragraph, the one the reason check already reads. The lead and sentence helpers are gone with it.

Tested by regenerating the list: every entry keeps its full paragraph, and the Warden entry shows the reference's handling of `SMSG_WARDEN_DATA` again.

Fixes #2880

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 10:32:45 +07:00
Sam Sung 9f5d8c44eb Remote stops, ridden create paths and lost targets follow 1.12.1 (#2881)
Three remote-unit gaps against 1.12.1:

- A monster-move stop 3 yd or more from the unit runs a one-leg path there (`0x6019be`), at 4× the unit's run speed over `len / speed · 1000` ms, at least 1 (`0x601f49`-`0x601fcc`). benilla left the unit standing.
- A create installs any path its block carries (`0x619371`); one already ridden ends at once, placing the unit at its last point from 3 yd (`0x61a874` → `0x616d60`) and writing its end facing (`0x61a882`-`0x61a8db`). benilla dropped such a path and kept the create's spot and facing.
- The facing goal looks up a nonzero target, and one not found keeps the raw facing (`0x600e62`); only a zero target reaches the interaction row. benilla turned the unit to face us.

Each has a test that fails on main.

Fixes #2871
Fixes #2872
Fixes #2873

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 09:38:45 +07:00
Sam Sung da8d1f4c73 A death, a leave or a destroy releases a unit's effects at once (#2879)
In 1.12.1 a unit's death releases every effect on it at once (`OnDied` → `ReleaseEffects`, `0x605922`). So does its leave (`0x5fbc84`), any object's destroy (`0x613be2`), and a lootable flags write once the death clip has ended (`+0xcb4`, `0x5fc95a`). Each freed effect fades its sound over 0.15 s (`0x61f57d`) and ends a persistent chain beam (`0x61f591`). The loot sparkle hangs as the death is stamped or at the model's `$DTH`, and goes only when the lootable bit falls or the unit leaves. Game objects and corpses never rebuild their model on a display write: their watchers cover other fields only.

benilla kept effects through a death, swapped a game object's or corpse's model, and hung the sparkle in the killing frame. Each now follows the reference, tested to fail without its change, except the guard against despawning one beam twice.

Fixes #2855
Fixes #2862

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 09:08:11 +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 e93c2e2129 Remote units dead-reckon from their create and each move's fire time (#2870)
In 1.12.1 every unit has mover state from its create (`0x619456` → `0x617b00`). A unit created moving is dead-reckoned from the create, and a move the tick fires mid-frame steps on from its fire time (`0x618d67`). benilla left such a unit standing until its first movement packet, and stepped a queued move over the whole frame.

Now every remote unit gets mover state at its create and is dead-reckoned from it and from each fire time. The facing system keys on the mover's bits and the unit's type (`0x600cd0`). A relay during a server path applies at once, and every monster move holds the spline that forces the next relay (`0x618c43`). The flush acts only on root and teleport (`0x618080`). A unit created airborne goes on along its arc, its start height rebuilt from the fall time.

Tests: one per behaviour, each failing on its parent.

Fixes #2853
Fixes #2852

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 07:31:22 +07:00
Sam Sung b90ab800ab Zone channels walk at the zone commit and its three other callers (#2869)
The zone-channel walk `0x49a210` reads the stored zone `0xb4e314`, and runs from four places only: `UpdateZoneText` `0x494780` on the area poll's tenth tick, behind its taxi and cinematic gates; the cinematic's end; the chat settings' load; and the flags reflex when the taxi bit clears (`0x5ff56e`). benilla walked whenever its own per-frame copy of the live area changed, so channels left and rejoined across a line crossed and recrossed between polls, and at each zone crossed in flight.

Now `CommittedZone` is the walk's one input, and each of the four callers walks at its place. The real zone text (`0xb4b404`) outlives a reload, an empty name keeps it, and its compare ignores case. The indoor dedup stores no zone.

Tests: each walk site, the flight, the reload, the empty name and the dedup.

Fixes #2846
Fixes #2791

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 06:59:51 +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 f55684b8ee A cast's arm signal sees targeting up, with a park of its own (#2866)
A cast's push and arm signals shared one park, so the arm signal's listeners never saw targeting up. A TargetUnit or a cast there acted as if no cursor stood. 1.12.1 writes the targeting word between the two signals (`0x6e525e`), so only the arm's listeners see it.

**Changed**
- The attempt parks after its push (`0x6e5026`) and again after its arm (`0x6e5333`).
- The targeting word is its own state, apart from the cursor mode. A send lowers the mode (`0x6e5547`), but the long-range send leaves the word standing (`0x6e5454`-`0x6e5467`).
- The ground decal follows the word (`0x4820f9`, `0x48383b`).
- A cursor re-armed at the arm signal keeps its cast in flight.

**Tests:** a TargetUnit, a cast and a cancel at the arm signal; SpellIsTargeting() after a far send; the decal. 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 05:29:45 +07:00
Sam Sung 1a11ef8319 A display rebuild ends a unit's spell effects before its body goes (#2864)
A display swap despawned a unit's spell effects with its old visual while their instances still named them, so a later teardown despawned a dead entity. 1.12.1's rebuild ends every effect on the unit first (`0x60abe0`, `0x60ac10`), then replays the auras' and the channel's kits.

**Changed**
- A rebuild runs only when `0x60ae10` says the display changed. It ends the effects started before it on the wire, and replays the auras' and the channel's kits on the new body.
- Kit sounds fade over 0.15 s as they stop (`0x6204b6`), and each loop is tracked per kit.
- benilla's rig heal keeps what the unit had.

**Tests:** a swap and a heal with effects up, the wire order within a frame, the gate, the replays, the fade. The swap tests fail on main.

Fixes #2843

Found in Adrinalin4ik/manila@cd8790bc

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 04:58:29 +07:00
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