Commit Graph

1155 Commits

Author SHA1 Message Date
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 383cf93c00 Off macOS the pointer systems run after the VM pass, as on macOS (#2939)
Off macOS, the cursor chain #2923 added ran the pointer tracking and the cursor driver unordered against the VM pass. The macOS chain orders them after it, since a Lua call in the pass can move the camera rig whose look the cursor shows. Linux's Update schedule gained 40 undeclared orders (1507 against the ceiling's 1467), so the Linux gates failed on every pull request.

The non-macOS chain now runs `track_pointer_device` and `other::drive` after `VmPass::End`, as macOS does.

Test: the schedule ceiling test passes on Linux (aarch64, in a container) and on macOS.

Fixes #2938

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 07:56:42 +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 e6ed2a2a9d The field of view follows the aspect; a missing model draws the cube (#2930)
Three gaps:

- The world camera's vertical field of view was a fixed 45°. 1.12.1's camera holds π/2 (`0x50a707`), and its projection takes the vertical half-angle as fov / (2·√(aspect² + 1)) (`0x5c3cc0`). That gives 54.0° at 4:3, 47.7° at 16:10, 44.1° at 16:9 and 35.0° at 21:9. benilla now derives it from the viewport's aspect and re-derives it on a resize. The spyglass keeps its 6× narrowing.
- A unit, item, effect or doodad whose model file is missing drew nothing. `CM2Scene::CreateModel` (`0x707350`), which every lane creates through, opens `Spells\ErrorCube` instead (`0x70737f`). benilla's asset readers now do the same for a missing `.m2`.
- `view::msaa_from_env` was dead code that defaulted to 4x, and is deleted.

Tests: the field of view at each aspect, and `CreatureDisplayInfo` 4 (`CrystalSpider.mdx`, absent) loading the cube.

Fixes #124
Fixes #330
Fixes #129

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

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-01 05:00:44 +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 d69afa8155 Shapeshifted item use is refused locally; a helm reads its race prefix (#2924)
Two gaps against 1.12.1:

- A shapeshifted player's item use went to the server and could ask to bind the item first. In 1.12.1 `0x5d9105` refuses it when the form is no stance (`0x60e9f0`) and its `SpellShapeshiftForm` flags lack 0x8, or carry 0x40 and the item is not equippable. It shows ERR_NO_ITEMS_WHILE_SHAPESHIFTED and sends nothing, before the bind ask. So cat, bear, travel and aquatic form, and ghost wolf, allow only equippable items, Tree of Life none, and stances all. benilla now refuses the same way.
- A helm's model took its race prefix from an eight-entry table clamped to Troll. `0x4799a0` reads the `ChrRaces` row's client prefix and attaches no helm for a race with no row. benilla now does the same, so a goblin's helm loads `_Gb`.

Tests: the route order, the stock forms and the stock race prefixes.

Fixes #2477
Fixes #611

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

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-01 03:08:15 +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 9084eacd7f The loose-source test builds on Windows, its unreadable file unix-only (#2921)
`the_loose_source_falls_through_per_candidate` made a file unreadable through `std::os::unix`'s `PermissionsExt` with no `cfg(unix)`, so the Windows test build failed and no test ran there.

The permission calls are now unix-only. Elsewhere the file stays readable, and the test already skips its second assertion in that case. A cross-check of every workspace test for `x86_64-pc-windows-gnu` finds no other break.

Fixes #2916

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 02:05:45 +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 fa11bd66a6 The effect lane holds a dead slot still instead of taking slot 0's loop (#2914)
A spell effect whose texture transform has per-sequence tracks resolved its UV loop by the playing slot, then fell back to the slot-0 loop when that slot had none. So in a sequence with no track it kept scrolling.

In 1.12.1 a transform animates by the sequence the model plays, and a sequence with no keys holds still. The per-placement lane and stage 1 already do this.

The effect lane now takes the playing slot's loop when per-slot loops exist, and the single loop only when they do not. With no player yet it reads slot 0, as before. The probe's resolver does the same.

Test: `a_dead_slot_holds_an_effect_still`, a two-slot effect with a dead slot 1.

Fixes #2909

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 23:31:49 +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 ad44a7208a Time<Real> is stamped on the main thread, so a stall reaches GetTime (#2891)
1.12.1 stamps its frame time once per tick on the main thread (`ContextUpdateFrameTime`, `0x420f70`), so a stall in one frame shows in the next frame's `GetTime()`. Under pipelined rendering, Bevy's `time_system` stamped `Time<Real>` with the instant the render world sent after presenting the frame before. A main-thread stall in frame F reached `Time<Real>`, the frame step, `GetTime()`, aura stamps and the session clock only at F+2, so a timer reading `GetTime()` in F+1 lost the stall.

`frame_pace::plugin` now drops the render world's `TimeReceiver` at `PreStartup`, so `time_system` reads `Instant::now()` as each frame starts. A manual `TimeUpdateStrategy` is unaffected.

Tested by emulating the render thread's lagging instant: a 600 ms stall moves `GetTime()` by 600 ms in the next frame.

Supersedes #2890, by kalistoking.

Co-authored-by: kalistoking <148718830+kalistoking@users.noreply.github.com>

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: kalistoking <148718830+kalistoking@users.noreply.github.com>
2026-10-31 16:21:21 +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 d882f21df3 The map's zone, the book's rank order and empty combat-log names (#2882)
Three answers Lua reads, against 1.12.1:

- `SetMapToCurrentZone`'s resolver matches the map rows against the stored zone `0xb4e314` (`0x4a6765`), which only the area poll's commit writes. benilla read the live area every frame, so the map could pick a zone up to nine poll ticks early.
- The spell book's rank parse skips zeros and falls back to the spell's level when the number is 0 (`0x4b3c30`). Its comparator compares numbers only when both are nonzero, else the rank strings (`0x4b30c0`), and the book sorts with the CRT `qsort` (`0x73f727`).
- The combat log prints `UNKNOWNOBJECT` for a unit whose name is empty (`0x6265eb`). benilla printed a blank.

Each has a test that fails on main.

Fixes #2867
Fixes #2079
Fixes #2095

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 10:06:46 +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 4d6ecd59b4 Tests that dispatch packets run in the world, on the per-packet path (#2861)
Since #2835, the lanes fire after each packet's handlers while the in-game interface is up, which is every packet in play. 162 tests in 30 files dispatched packets with the interface down, so they exercised the frame-end path, which ships only for the loading screen.

**Changed**
- Headless test clients boot in the world with the interface up. The lanes and chat harnesses do too.
- Tests read a packet's lane items at its end, through a tap, not after the frame's drain.
- Four expectations now hold the in-play order. For example, `SetPlayerControl` resets the cursor before PLAYER_CONTROL_LOST (`0x495901`, `0x49590b`).
- Tests of the session end, the hello and the world entry stay out of the world on purpose.

**Tests:** the suite passes. No product code changed, and the move found no product defect.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 04:04:54 +07:00
Sam Sung 0fb932f958 A cast binds its target after its push and arm listeners run (#2858)
A cast fixed its target before the push and arm listeners ran, so a listener that retargeted, raised a cursor or refilled the record could not change what was sent. 1.12.1 binds after them (`0x6e5338`).

**Changed**
- Every attempt parks at `0x6e5338`. On resume, the bind leg reads the word, the cursor and the record again (`0x6e5359`-`0x6e5477`), then TryCast's tail.
- An item use keeps the selection it passed (`0x4fa163`); a press re-reads it (`0x6e539f`).
- An out-of-range unit falls through to autoSelfCast (`0x6e53d7`).
- "You have no target" or "Invalid target" follows the selection (`0x6e5084`).
- Feed Pet's arm listeners run before the food is bound.

**Tests:** a retarget, a listener's cursor, a refill, an item's fixed target, the self-cast fall-through, Feed Pet's cursor. 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 03:18:07 +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