Commit Graph

442 Commits

Author SHA1 Message Date
Sam Sung dabb331bc6 Camera verbs blend and hold; a /reload builds a new camera (#2962)
`SetView` snapped pitch and yaw, `FlipCameraYaw` was undone by the auto-follow, and a spyglass zoom left the camera at distance 0.

`SetView` blends all three by `cameraViewBlendStyle` (`0x512eb4`), and a new camera counts as moved (`0x50c400`). The flip sits outside the follow's yaw offset. The spyglass's start and end select their views, a held scope refuses, and arrival ends it (`0x5deb44`). Freelook's entry and leave stop a view's yaw and bias (`0x50f97e`, `0x50f984`).

A `/reload` builds a new camera (`0x481b4e`, `0x50c0e0`): offset, flip, moves and shakes start over, and the views reread their settings (`0x50c3db`).

Each has a test.

Fixes #1697
Fixes #1699
Fixes #1706
Fixes #2948

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 18:24:33 +07:00
Sam Sung a3530a64b0 Select screen idle and emblem; a teardown frees UI callbacks (#2960)
The select screen looped one Stand clip and never showed a guild's emblem. Select models now replay Stand with a fresh pick at each window's end, and a guild tabard asks for its guild and wears the emblem.

An interface teardown kept every store's pending callbacks, so an answer after a `/reload` raised events in the new VM. The reference frees them unrun (`0x490c35`, `0x5556c0`, `0x5615b0`), and the score board's count stands until a packet zeroes it (`0x4aabb5`). What would strand the player stays or ends cleanly, recorded as a deviation.

Tests cover the item, name and guild stores through a real `/reload`, the board's hold, the tabard and the replay.

Fixes #1314
Fixes #1316
Fixes #2946

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 17:41:32 +07:00
Sam Sung 570ccf4f4b Casts, item use, TAB and the map arrows behave as in 1.12.1 (#2957)
Casting, item use and targeting differed from 1.12.1 in eight places.

A ready cast leaves the wand's repeat running (`0x609570`), and a local refusal of the repeat spell stops it (`0x6e1cd9`). A broken use item is refused and one not worn must be equipped (`0x5d8fd6`). An item with no use spell leaves silently, ahead of the form and disarm refusals (`0x5d8fc1`).

TAB reads `targetNearestDistance` at each press (`0x494350`). TargetLastEnemy's memory is stamped as the select makes it (`0x49372f`). The minimap and world map arrows take the facing of the object the camera follows (`0x4eb07e`, `0x4a7c7c`).

Each has a test.

Fixes #1197
Fixes #1198
Fixes #2482
Fixes #2487
Fixes #2591
Fixes #2614
Fixes #2945
Fixes #2947

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 15:51:11 +07:00
Sam Sung 9136e8aaaa Seven small 1.12.1 gaps in the log, tooltips and screenshots (#2953)
Seven small differences from 1.12.1, in the log, tooltips and screenshots.

Combat log: a line needs its target held, through the gate at `0x626630`; environmental damage and four spell lines need a held unit (`0x5e8145`, `0x5e8178`, `0x5e82a8`); the enchantment log reads its owner first (`0x628f23`).

Item tooltips and trade skills walk ChrRaces and ChrClasses at the install's locale (`0x52ce14`, `0x4fea2e`) in place of English literals. The lockout warning divides signed and keeps an empty map name. A screenshot is the frame of its ask (`0x442a10`, `0x442350`).

Each has a test against those addresses.

Fixes #2078
Fixes #2384
Fixes #2480
Fixes #2485
Fixes #2920
Fixes #2943
Fixes #2944

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 14:52:24 +07:00
Sam Sung afda4539e1 Damaged files and malformed lists behave as in 1.12.1 (#2951)
A damaged file or a malformed list took the client down, or was shown as it came.

Files: a DXT width of 0, a zero-sided BLP, an M2 under 0x11c bytes, a first sequence that ends before it starts and a large geoset group each panicked. A DXT level past the file failed the whole texture, where `0x5a8558` tests its size for zero alone. Geoset ids are 32-bit (`0x477592`, `0x47869a`).

Lists: the realm list's size and string caps are checked (`0x5ba9e7`, `0x5b22d9`), a character list over 10 or unread to its end fails (`0x5b42a0`), and a failed re-dial empties the list (`0x5b2560`).

Each has a test.

Found in Thorsten42/benilla@af86636a54

Fixes #7
Fixes #10
Fixes #14
Fixes #67
Fixes #1660
Fixes #1661
Fixes #2941
Fixes #2942

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 14:08:55 +07:00
Sam Sung 514b7d32b2 Every launcher that calls the build stamp gets the 8 MB main stack (#2940)
The Windows main-thread reserve was set only in `crates/benilla/build.rs`. So `benilla-worldview`, and every launcher built on benilla by the recipe in `extended_launcher.rs`, linked with the linker's default: 1 MB on MSVC, 2 MB on mingw. That is less than the login screen needs.

`benilla_buildstamp::emit()`, which every launcher's build script already calls, now asks for 8 MB through `rustc-link-arg`: `/STACK` on MSVC, `--stack` on mingw. Unlike `rustc-link-arg-bins`, cargo accepts it from a package with no binary, so a library that calls `emit()` still builds. The recipe and CONTRIBUTING's "Building on top" say so.

Test, mingw cross-build: `SizeOfStackReserve` is 0x800000 for `benilla.exe` (unchanged) and `benilla-worldview.exe` (was 0x200000). A library-only crate calling `emit()` checks clean on MSVC and builds on mingw.

Fixes #2934

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 08:17:05 +07:00
Sam Sung ed4ca02a88 A building's pool draws only when its room is visited; type from row one (#2937)
A building's pool drew forever once its room had been visited, and a portal-less building with an area id never drew its pool, Northshire Abbey's wall fountain among them. In 1.12.1 the flood's visit `0x685d70` links a visited group into the liquid list (`0x685f6b`, the only link), and the liquid pass `0x684cd0` unlinks each record as it draws (`0x684e1d`). So a pool draws exactly in the frames its group is visited. The latch is gone, and the fountain draws. Across 184,624 camera poses around the Great Forge, benilla's flood keeps the lava wherever the reference's portal graph does.

A pool's liquid type read every tile row and the group's override. `0x6ba970` scans only the first row and takes the first wet nibble, and its caller never reads the override. benilla now does the same.

`WOW_LIQUID_TRACE=1` prints each pool's per-frame verdict.

Fixes #119
Fixes #65
Fixes #2492

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 07:12:21 +07:00
Sam Sung dddc644771 Units in sunlight light at 2.5 and ramp in from 1.0, as in 1.12.1 (#2935)
Units, players, creatures and game objects in sunlight were lit at ×1.0, where 1.12.1 lights them at ×2.5. The cap `min(I, 1)` was recorded as a deviation on the premise that lifting it takes sun-facing surfaces past 1.0. That premise is false: the per-vertex sum is clamped in both clients.

In 1.12.1 a node is created at intensity 1.0, and its first sync sets the target: 2.5 lit, 0.5 in the chunk's shadow, 1.0 as an interior (`0x670db0`, `0x69e280`). Each tick steps toward the target at 3.3333 × the frame time (`0x69e770`), so a unit appears at 1.0 and ramps to 2.5 over about 0.45 s. The intensity reaches the SH unclamped (`0x71c4e0`).

benilla now does the same, in the entity shader and the static one, and the two deviation notes are gone.

Tests: the ramp from 1.0, the three targets, the uncapped shader line.

Fixes #506

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-01 06:24:47 +07:00
Sam Sung 7361ddac1f The workspace tests pass on Windows: zone, census path and origin name (#2933)
Twelve workspace tests failed on Windows, from three causes:

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

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

Fixes #2927
Fixes #2928
Fixes #2929

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

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

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

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

Fixes #2601
Fixes #2604
Fixes #2587

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

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-01 04:18:27 +07:00
Sam Sung 01ef631ab3 Area triggers heartbeat first; far swings stop attack; ignored GMs speak (#2925)
Three wire gaps:

- An area trigger sent CMSG_AREATRIGGER alone and at any health. `0x5e2110` runs only while health is above 0 (`0x5e215e`). On entry it sends a heartbeat from the live pose (`0x5e221e`), then the trigger (`0x5e225d`), so the server checks the trigger against a fresh position. benilla now does both.
- A landed swing on a far victim kept auto-attack on. `0x5ea800` sends CMSG_ATTACKSTOP when the 3D distance is at or beyond 4 × max(both combat reaches + 4/3, 5) (`0x5ea899`, `0x5ea8a8`), as after a knockback or a blink. benilla now does the same.
- An ignored GM was silenced, and every ignored whisper drew CMSG_CHAT_IGNORED. `0x49d736` drops an ignored sender only under a non-GM tag, and replies only to such a whisper. benilla now does the same.

Tests: the heartbeat-then-trigger order, the reach boundary, and the tag and type matrix.

Fixes #2595
Fixes #2598
Fixes #2603

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

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

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

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

Fixes #2575
Fixes #2621

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-01 02:33:57 +07:00
Sam Sung e475fb8e2b The console commands gxRestart and bind are registered (#2919)
Two console commands 1.12.1 registers were missing, so each ended in `Unknown command`.

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

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

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

Fixes #2585
Fixes #2586

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

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-01 01:21:24 +07:00
Sam Sung 7043c1f440 A missing base archive stops the boot, and only 0x200 sectors decode (#2918)
Two archive-chain gaps:

- The MPQ reader decoded a shrunken sector under block flags `0x300`. The one sector reader in 1.12.1 (`0x651450`) masks the flags with `0xff00` and reads such a sector only when the result is exactly `0x200` (`0x651993`-`0x65199f`). Any other codec flags fail the read. benilla now fails it the same way. A sector that did not shrink is still copied under any flags (`0x651982`).
- A base archive missing from `Data` was mounted around. In 1.12.1 the first base archive that will not open, `model` first, ends the client with `Failed to open archive %s: %s` (`0x4038ef`). benilla now stops at mount and names it in the existing fatal dialog. `speech2.MPQ` and the patches stay optional. The no-install note's premise, that the reference goes on past a missing archive, is corrected.

Tests: `a_shrunken_sector_reads_only_under_exactly_the_compress_flag`, `a_missing_base_archive_is_an_error_naming_it`.

Fixes #2716
Fixes #2616

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-01 00:56:36 +07:00
Sam Sung 91d92a2714 A short AUTH_OK tail logs in; object reset-state and self-control fixed (#2917)
Three wire and unit-state gaps:

- An AUTH_OK or queue reply with a 5 to 8 byte billing tail failed the world connect. `0x5b41b0` reads each field through `GetUInt32` (`0x418eb0`), which writes nothing on a short read, and admits the player anyway. A short field now keeps its old value. Test: every tail length from 0 to 9.
- `SMSG_GAMEOBJECT_RESET_STATE` (`0x6e9790`) was unhandled. An object now re-applies its descriptor state, undoing the open-lock and loot predictions. Test: a byte-exact golden test of the body and a handler test.
- `IsSelfControlled` (`0x5fa550`) now reads flags 0x2 and 0x1, the charmer, and possession, which answers with the charmer's or creator's own reading. A moving charmed player's emote is no longer refused.

Fixes #2681
Fixes #2582
Fixes #2682

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

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

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

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

Tests: `an_open_items_lock_listeners_run_before_its_send`, `an_item_use_that_opens_no_window_writes_none_after_its_held_report`.

Fixes #2271

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

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

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

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

Fixes #2901

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 23:08:14 +07:00
Sam Sung 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