An edit box always drew its text left-aligned: `SetJustifyH("RIGHT")` or `("CENTER")` moved only the `OnCursorChanged` x.
In 1.12.1 the box's `SetJustifyH` writes the box's own font (`0x797a17`, `+0x324`) through the shared setter `0x79fc20`, which pushes the change to the label linked to that font (`0x779c27`). `SetMultiLine` cuts off only the label's vertical bits (`0x77a509`, `0x77a599`), so the horizontal justify draws.
The box's font now exists with its label, justified `LEFT` as the constructor leaves it (`0x779bd2`). The four justify methods act on that font, the label no longer pins H, and the caret reads the label's justify. The separate justify word on the box is gone.
Test: `the_text_draws_by_the_boxs_justify_h_and_its_own_justify_v` checks the drawn H follows `SetJustifyH` while V stays the box's own. Without the fix, RIGHT draws LEFT.
Fixes#2977🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`ipairs(t)` answered nil once a script had cleared the global `ipairs`, a collection had run, and the global was set again.
The reference reads the global `ipairs` raw as the loop's iterator (`0x702d15`), by name. We read it by a string id interned once at start, but the collector's root list of those names was kept by hand and left `ipairs` out. A collection could free the string, and setting the global again interned it under a new id.
`Names::all` now lists every field through an exhaustive pattern, and the collector roots that list, so a name added later does not compile until it is rooted.
Test: `ipairs_finds_its_global_after_it_is_cleared_and_collected` runs the issue's sequence. It answers nil without the fix and the function with it.
Fixes#2974🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
M2 translucent batches sorted by their own centre with a file-order bias, so a glow could draw under the part it lights.
1.12.1's transparent order `0x70ae10` compares the instance's squared distance, the fill copy, the signed priority plane (texture unit `+0x02`), the vertex shader under the default mode, the batch depth, then the unit's layer (`+0x0c`). A model frame's scene skips depth fill (`0x76ccf5`). benilla now reads both unit words and re-sorts the M2 batches in that order, merged with the other transparents by depth. Texture handles are stood in for by file order, a recorded deviation.
Mount children are no longer grounded or swum as free units (`0x607a00`), and worn items take their idle sequence, or the first one, as `0x710153` arms it.
Tests use the install's HangingSkullLight and a staff whose sheen orders by shader.
Fixes#2922Fixes#2931Fixes#1208Fixes#327🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A body placed flush on a slope at a tile seam fell through the far tile: the backface band tested a facet's raw vertices, so it dropped the far floor once the body had passed part of it.
1.12.1 clips each facet into a leading face's swept prism of its own volume (`0x632700`, `0x6318c0`). It keeps the facet when a clipped vertex is ahead of −2^-20 (`0x6328a1`), or within 1/36 behind after a second clip (`0x632910`). The band now builds that volume at the capsule's feet (`0x631440`, `0x631be0`) and runs those steps; it matches benilla-re's tested port on 399,550 random cases. A facet ahead is held at any distance, recorded as a deviation, since the capsule bulges past the volume.
A real-terrain test walks a body flush on the Valley of Trials ramp over the seam: main sinks it 6.48 yd, the fix holds it.
Fixes#1121Fixes#2958🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A release bump changes only `[workspace.package] version` in `Cargo.toml` and the lock's entries for the workspace's own crates. The gates' memo counted only docs as unread, so the 0.3.0 bump would have rerun the whole chain on a tree whose parent had just passed it.
No gate reads the version: it reaches only the launchers' `env!("CARGO_PKG_VERSION")`, and the build-id tests pass their own strings.
`docs_only_delta` now also keeps the verdict when `Cargo.toml` and `Cargo.lock` differ in nothing but those version lines, compared with each file's workspace versions blanked. Checked on history: the 0.3.0 bump keeps the verdict; a code commit, a dependency version in the lock, a changed edition, and the bump together with its parent's code each rerun.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Under Proton-based Wine, `tabtip.exe` is a UI Automation client, and `bevy_winit` gives every window an AccessKit provider. That traffic faults in `rpcrt4` on blocks the game has reused, which damages the heap and later crashes the client at varying addresses. Blocking `tabtip.exe` stops the faults and the crash, and other programs in the same bottle show no such faults.
The reference's window has no provider: `WoW.exe` imports no accessibility library and names none of its calls. benilla builds no AccessKit tree either.
`VideoPlugin` now drops the adapter when Windows reports a new window, which restores winit's window procedure. Other platforms keep theirs.
No test: the adapter needs a real window and the fault a UI Automation client. Built for macOS and Windows.
Refs #2932🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`docs/DEVIATIONS.md` never said why the login screen has a realmlist control: the reason sat in a test comment, and the list leaves tests out. It is now a `Deviation:` on the control, in `AccountLoginRealmlist.xml`. The host-name port's reason, "everyday use", said nothing, so it now names the case and why it's safe: an address 1.12.1 can dial means the same here.
Comments only.
Refs #2860
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`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#1697Fixes#1699Fixes#1706Fixes#2948
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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#1314Fixes#1316Fixes#2946
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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#1197Fixes#1198Fixes#2482Fixes#2487Fixes#2591Fixes#2614Fixes#2945Fixes#2947
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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#2078Fixes#2384Fixes#2480Fixes#2485Fixes#2920Fixes#2943Fixes#2944
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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@af86636a54Fixes#7Fixes#10Fixes#14Fixes#67Fixes#1660Fixes#1661Fixes#2941Fixes#2942
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
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#119Fixes#65Fixes#2492🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
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#2927Fixes#2928Fixes#2929🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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#2601Fixes#2604Fixes#2587🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
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#2595Fixes#2598Fixes#2603🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
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#2575Fixes#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>
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#2585Fixes#2586🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
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#2716Fixes#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>
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#2681Fixes#2582Fixes#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>
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>
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>
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>
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#1831Fixes#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>
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#346Fixes#361Fixes#409Fixes#344Fixes#2592Fixes#2607🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
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#610Fixes#1680🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
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#509Fixes#530Fixes#115🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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#2583Fixes#2569Fixes#2609Fixes#2133Closes#2666 (fixed by #2691)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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#2877Fixes#2888🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
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>
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>
`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>
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#2871Fixes#2872Fixes#2873🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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#2855Fixes#2862🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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#2110Fixes#2112Closes#2108Closes#2109🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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#2853Fixes#2852🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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#2846Fixes#2791🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`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#2856Fixes#2857🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
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>
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>
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>
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>
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>
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>
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)
Fixes#2838Fixes#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)