Экран входа MoonWell и синхронизация с Benilla 0.3.1 #15

Merged
sindoring merged 628 commits from login-screen into main 2026-11-08 18:55:51 +03:00
Owner

Новый экран входа MoonWell: отдельная 3D-сцена лунного колодца, вода с отражением и рябью, падающие листья, лесное окружение, освещающий сцену висп и музыка Song of the Falling Stars. Автоматический вход из лаунчера показывает локализованные этапы со спиннером и выдерживает время показа сцены. Сохранены ручной вход, ruRU, выбор реалма, переключение интерфейса и исходный протокол авторизации.

Интегрированы 17 изменений Benilla от d9c64230 до e5651f0d, включая порядок прозрачных моделей, анимацию idle, вход в мир, шрифты nameplate и Lua. Сохранены графические расширения MoonWell. Версия workspace — 0.3.1.

Снимок upstream: e5651f0ddd. Изменения применены трёхсторонним слиянием от ранее синхронизированного снимка d9c64230: предыдущая синхронизация попала в main через squash, поэтому обычный merge-base был устаревшим.

Проверено на Windows x64:

  • Полный scripts/gates.sh: ALL GATES GREEN — форматирование, Clippy, reference, workspace-тесты и doctests, повторный прогон без Data, документация, каталог сообщений, render-pass lint, Clippy и тесты без dev, оба запуска движка.
  • cargo check -p moonwell --locked и release-сборка без dev прошли.
  • Обычный запуск release-клиента: сцена и ruRU отрисовываются, применены 4/4 патча шейдеров; журнал подтверждает совпадение касания листа и ряби, включение света виспа. Скриншот сохранён.
  • Пакет содержит прежние 25 файлов; изменён только MoonWell.exe. Dry-run S3 подтверждает повторное использование остальных 24 файлов. Производственный auth endpoint доступен по TCP без авторизации.

Ограничения: в workspace-прогоне пропущены 37 проверок внешних аддонов/роликов, в прогоне без dev — 32 проверки внешних аддонов. Тестовая учётная запись не задана, live login/logout не выполнялся. Запуск проверялся без звука. Нативный ввод через инструмент автоматизации в этой сессии не подтвердился также у предыдущего релиза; интерактивная проверка не засчитывается.

Новый экран входа MoonWell: отдельная 3D-сцена лунного колодца, вода с отражением и рябью, падающие листья, лесное окружение, освещающий сцену висп и музыка Song of the Falling Stars. Автоматический вход из лаунчера показывает локализованные этапы со спиннером и выдерживает время показа сцены. Сохранены ручной вход, ruRU, выбор реалма, переключение интерфейса и исходный протокол авторизации. Интегрированы 17 изменений Benilla от d9c64230 до e5651f0d, включая порядок прозрачных моделей, анимацию idle, вход в мир, шрифты nameplate и Lua. Сохранены графические расширения MoonWell. Версия workspace — 0.3.1. Снимок upstream: e5651f0ddddabcb33f8a9b32e9095065bffc82ef. Изменения применены трёхсторонним слиянием от ранее синхронизированного снимка d9c64230: предыдущая синхронизация попала в main через squash, поэтому обычный merge-base был устаревшим. Проверено на Windows x64: - Полный scripts/gates.sh: ALL GATES GREEN — форматирование, Clippy, reference, workspace-тесты и doctests, повторный прогон без Data, документация, каталог сообщений, render-pass lint, Clippy и тесты без dev, оба запуска движка. - cargo check -p moonwell --locked и release-сборка без dev прошли. - Обычный запуск release-клиента: сцена и ruRU отрисовываются, применены 4/4 патча шейдеров; журнал подтверждает совпадение касания листа и ряби, включение света виспа. Скриншот сохранён. - Пакет содержит прежние 25 файлов; изменён только MoonWell.exe. Dry-run S3 подтверждает повторное использование остальных 24 файлов. Производственный auth endpoint доступен по TCP без авторизации. Ограничения: в workspace-прогоне пропущены 37 проверок внешних аддонов/роликов, в прогоне без dev — 32 проверки внешних аддонов. Тестовая учётная запись не задана, live login/logout не выполнялся. Запуск проверялся без звука. Нативный ввод через инструмент автоматизации в этой сессии не подтвердился также у предыдущего релиза; интерактивная проверка не засчитывается.
sindoring added 628 commits 2026-11-08 17:34:25 +03:00
- A loot window that opens while you move closes, as in 1.12.1 (#782)
- Set auto_loot in the LootConfig initializer (clippy field_reassign_with_default)
- Close a window opened on the move in the feed's pass, so it never paints

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Sam <samuv.eth@gmail.com>
- spell text: $g/$G take the active player's gender and the chosen form is space-trimmed, so a female character reads "her" (0x508180, 0x508214)
- spell text: trim the $g docs, drop the colon-less $g pin
- spell tooltips: $g reads the view's player store; nets cover gender
- item tooltips: re-substitute $-texts when the gender byte moves
- tests: share one real-spells loader beside Spells

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Sam <samuv.eth@gmail.com>
- A loot right-click while moving does nothing, as in 1.12.1 (#760)
- loot gate: name the emitter's drain and write the landing deviation

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Sam <samuv.eth@gmail.com>
- Change `LevelUpInfo` to signed to match vanilla behavior
- Add parse test for negative values in SMSG_LEVELUP_INFO, update comments
- grammar
- level-up gains: cite the %d fire and vmangos's wrapped deltas; pin the reported line

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Sam <samuv.eth@gmail.com>
- capture: a name-rank scenario shoots a ranked player's overhead name line
- Overhead names carry the player's PvP title under UnitNamePlayerPVPTitle, as 1.12.1 does (#550)
- review: bundle the rank-title inputs; doc the snapshot and rank-19 limits
- nameplates: read rank strings per VM; test the composed name line
- pvp: correct the rank-title docs; convert the credit team without a cast
- Fix the titles row comment; name-rank reuses WOLF_POS, cites :900
- rank strings: the honor feed reads them off the VM; nameplates only reads the value

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Sam <samuv.eth@gmail.com>
`TargetUnit("npc")`, `AssistUnit("npc")` and `SpellTargetUnit("npc")` did nothing with a merchant, gossip or quest window open: the app's unit-token resolver answered nobody for `npc`.

In 1.12.1 the resolver (`0x515970`) matches `npc` as a whole token (`0x515bec`, an unbounded compare, so `npctarget` stays unknown) and takes the interaction NPC's guid `[0xb4e2d0]`, which `0x4930d0` writes when the window opens.

`npc` now resolves to the held unit `InteractNpc` names. Tests cover the three verbs, a closed window and a streamed-out NPC, and `/script TargetUnit("npc")` typed into the stock chat box.

Fixes #707

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A unit tooltip's title read the bare name where 1.12.1 decorates it.

The tooltip calls the name builder `0x609370` itself (`0x52a1ab`, or `0x52a1db` when there is no second part) with the decoration flag a literal `1`, the same call `UnitPVPName` makes, so no CVar gates it. With the flag on, a ranked player reads `UNIT_PVP_NAME` of the gendered `PVP_RANK_<rank>_<team>` and the name, a city protector's `PVP_MEDAL<n>` follows on a second line (`0x6093ef`), and a civilian kill (`0x612550`) reads the `PVP_RANK_CIVILIAN` prefix (`0x609461`).

The title now runs the builder `UnitPVPName` already uses. The test covers rank, gender, medal, civilian, an unranked player and a creature; an on-demand `ui-tooltip-rank` capture shows a ranked player's tooltip.

Fixes #794

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Sam <samuv.eth@gmail.com>
At login benilla sent `CMSG_SET_ACTIVE_MOVER`, `CMSG_QUERY_TIME`, `MSG_QUERY_NEXT_MAIL_TIME` and `CMSG_BATTLEFIELD_STATUS` right after `CMSG_PLAYER_LOGIN`, before the server had seated the player. cmangos drops them; after a worldport or `/reload` the time and mail queries never went again.

In 1.12.1 all four follow the local player's own create (`0x5dea50`): `SetActiveMover` (`0x6006e0`), then the world-enter cascade (`0x5deb60 call 0x4908c0`), whose inits send `0x1CE`, `0x284` and `0x2D3` in that order. A cross-map worldport re-creates the player and takes the same route; `/reload` re-enters the cascade alone (`0x490168`).

The four now ride the self create, in that order, and `/reload` re-sends the three. Tests cover login, the create, a worldport, the schedule order and the reload; a live trace shows the order at login, after `.tele` and on `ReloadUI()`.

Fixes #789
Fixes #100

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Elias Miettinen <eliaspmiettinen@gmail.com>
Co-Authored-By: Sam <samuv.eth@gmail.com>
The `out` trace tag logged only what the long-lived writer sent, drained after each command. Everything the session sends before the reader and writer split never reached it: `CMSG_AUTH_SESSION`, `CMSG_CHAR_ENUM`, `CMSG_PLAYER_LOGIN`, character create and delete, the cinematic ack and logout. So a login trace could not show where `CMSG_PLAYER_LOGIN` sits against `SMSG_LOGIN_VERIFY_WORLD`, which wire-timing work needs.

The world socket has one write path, `send_packet`. It now reports each packet to an observer right after the write, and the app installs the trace there only when the `out` tag is on. The writer's sent-log and its drains are gone; the line format is unchanged.

A protocol test over the fake server checks that the handshake, a pre-split send and a writer send are all observed, in order. A live login trace now opens with `CMSG_AUTH_SESSION`, `CMSG_CHAR_ENUM` and `CMSG_PLAYER_LOGIN`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A crate built on benilla through `run_with` has its own `benilla-config/`, but could not ask where it is: `local_state::home` is crate-private. A crate that ships an addon has to write it into that folder's `AddOns/`, where benilla discovers addons and resolves their art and fonts, so without the path it needed a fork.

- `benilla_app::config_dir()` answers `local_state::home()`, asked from `run_with` on, since a dev build resolves it from the launcher's folder set there.
- CONTRIBUTING's "Building on top" says a crate's files and addons go there, and names `run_with` and this folder as the two seams opened for crates alone.
- The example launcher logs its `AddOns/` folder through it, so the example, which clippy and the tests build, is the outside caller that keeps it public.

No 1.12.1 behaviour changes.

Fixes #807

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The meeting-stone status query (`CMSG 0x296`) and its text reset ran once per UI VM, and the text drop only on leaving the world. So after a cross-map worldport benilla kept the old text and never asked again.

In 1.12.1 the world-enter cascade's meeting-stone init (`0x490a14` → `0x4c9f40`) resets the text to `UNKNOWN` and sends `0x296`, right after the battlefield init, on every pass: login, every cross-map worldport and every `/reload`. The leave-world sweep `0x490a80` drops the text (`0x490b2f` → `0x4c9f80`) on a worldport, a `/reload` and leaving the world.

The enter leg now runs on the world-enter cascade after the battlefield status. The sweep is one edge, written whenever its latch is spent, and the drop reads it. Tests cover the cascade, a worldport, a reload and the schedule order; a live trace shows `0x296` after `0x2D3` and its reply at login, on `ReloadUI()` and after `.tele`.

Fixes #99

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- capture: trainer scenarios shoot the service description field (#793)
- fix: trainer services show their spell's description, as 1.12.1 does (#793)
- review: reuse the caster closure, cut repeated comments, pin the profession rows
- trainer: the pet's skill value is the tooltip's one helper, not a copy
- trainer: the recipe row's real-data test reads a product that has text

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Sam <samuv.eth@gmail.com>
The quest log never showed the `[n]` count of party members on a quest, because `IsUnitOnQuest` was a stub that always answered nil.

1.12.1 (`0x4dfe10`) checks both arguments first. It reads the row's quest id (`0x4df150`; a header or out-of-range row answers nil before the unit is resolved), resolves the token, and admits only you or one of the four party slots, by guid (`0x4e7f70`). It then needs the unit held as a player and scans the 20 `PLAYER_QUEST_LOG` ids for that quest. The answer is 1 or nil. The ids reach party members on the wire. `UNIT_QUEST_LOG_CHANGED` (`0x51bc63` → `0x51bd90`) fires once whenever any slot of a unit's window changes, so an open log recounts.

Unit snapshots now carry the window, the binding follows those steps, and the event fires. Tests cover each path and run the stock `QuestLogFrame.xml` to a `[2]` row; they fail against the stub.

Fixes #818

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On a PvE realm, zone names were still coloured friendly, hostile or contested.

In 1.12.1, `GetZonePVPInfo` (`0x48d540`) answers a type only when its display gate is open (`0x48d5c2`/`0x48d5cc`). The gate opens on the realm's PvP flag or in a zone with Flags `0x10`, which is the six capitals. The realm flag is read from `Cfg_Configs.dbc` for the selected realm's type and stored once per world entry (`0x4015de`). A type with no row skips the store. Otherwise the type is nil, so the stock zone splash shows cream and the minimap zone text gold.

benilla now reads `Cfg_Configs.dbc` instead of a transcription that stopped at type 8, so realm type 10 lists as PvP. It latches the realm flag at world entry and applies the gate.

Tests cover the gate, the six capitals and every shipped Cfg_Configs row; each failed before the fix.

Fixes #816

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Escape on the realm list closed the list and then also quit the game from the login screen, or went back to login from character select.

1.12.1 walks the keyboard frames once per press, strata high to low, and stops at the first handler that takes it (`0x765f10`, leaving at `0x765f64`/`0x765f86`). The realm list is DIALOG strata (`RealmList.xml:246`), so its Escape never reaches the screen beneath, even though it hides itself.

benilla's list and screens read the keys in no fixed order. A screen only checked whether the list was still shown, and the list's own Escape had just hidden it. Who owns a frame's keys is now decided once, before any of them runs (`Realms::owns_input`, set in `PreUpdate`).

Tests run the real list and screen systems in both orders, and fail before the fix with the reported symptoms.

Fixes #817

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In a free-for-all area such as the Gurubashi battle ring, your own overhead name and selection ring turned red.

1.12.1 colours a player's name and ring through `0x605960` by mutual attackability: `CanAttack` (`0x606980`) in both directions. Both true reads red, only theirs soft blue, only yours yellow. Neither splits on PvP: yourself green or blue, a party member pale, others green or blue. `UnitReaction` (`0x6061e0`) answers friendly for a unit toward itself before the FFA rung (`0x606200`), so you can never attack yourself.

benilla stood in a reaction-rank threshold for that matrix, and its reaction lacked the identity rung. The selector is now the reference's four-arm matrix, `CanAttack` runs in both directions, and a unit is friendly to itself. `UnitReaction`, `UnitCanAttack` and the cursor follow for yourself.

Tests pin each arm and yourself in FFA, on the shipped faction templates too; they fail before the fix.

Fixes #814

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reopening the world map after zooming out showed the old map for a frame before the current zone.

In 1.12.1, every Lua verb that moves the map selection (`SetMapZoom`, `SetMapToCurrentZone`, a `ProcessMapClick` hit) goes through the setter `0x4a67a0`. The setter stores the selection, rebuilds the landmark list, then fires `WORLD_MAP_UPDATE` in place before returning (`0x4a6ce4`). So `WorldMapFrame`'s OnShow repaints the tiles before the first draw.

benilla queued the event to the next tick, so the first frame painted the previous map. The three verbs now run the setter whole. The landmark list is projected from per-row sources inside the setter, so the new tiles never show the old map's markers. The engine's own pushes still queue, as a narrowed deviation.

Tests replay the report's steps on the stock UI and check the event fires inside the verb with landmarks rebuilt first; they fail before the fix.

Fixes #811

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An instance with no `name=` took its template's name: `framexml::merge` laid the instance's attributes over the template's, so `name` and `id` carried through. Stock `UIOptionsFrame.xml`'s unnamed `<Button inherits="UIPanelCloseButton">` became the global `UIPanelCloseButton`, with `GetName()` answering it, and any unnamed addon frame did the same to its template's name.

In 1.12.1, `PreLoadXML` (`0x769770`) reads `name` and `id` from the instance node before `LoadXML` walks the templates (`0x76985c`). The texture and font string factories also read `name` once, outside that walk (`0x6f2751`, `0x6f27e1`). So neither is ever inherited.

The merge now drops the template's `name` and `id`. Tests cover an unnamed frame, a named frame (no template id), an unnamed texture and `CreateFrame` with a nil name. All four fail without the fix.

Fixes #394

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`PLAYER_LOGIN` fired at the end of the UI load, before the player existed: its handlers saw `UnitExists("player")` nil and level, health and XP 0, at every login and `/reload`. Ace2's `OnEnable` runs there.

In 1.12.1 the load only arms a one-shot (`0x49011d`). The world-enter cascade fires `PLAYER_LOGIN` if armed (`0x49094b`), then `PLAYER_ENTERING_WORLD` (`0x49096a`), inside a sound-suppression bracket (`0x4908d5`–`0x490a56`). It runs from the player's own create (`0x5deb60`), or on `/reload` from the load (`0x490168`) with the player still in the world. A worldport re-arms nothing.

Now the load arms it, and the unit feed fires both, silenced, once the player is seated. A `/reload` loads under the live player and no longer plays the target-select sound. Tests cover login, worldport and reload; they fail without the fix.

Fixes #189
Fixes #191

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The UI scale was folded into the pixel seam, with the root and `UIParent` at scale 1. Frames outside `UIParent` (dropdown menus, need/greed rolls, the world map) shrank with it, `UIParent:GetScale()` answered 1, and `GetCursorPosition()` was off by the scale.

In 1.12.1 the scale is `UIParent`'s own frame scale (`0x494550` → `0x76ac10`, floored at 0.64) over a 768-unit root. `GetScreenWidth`/`GetScreenHeight` divide by it (`0x494590`), and `GetCursorPosition` (`0x48b820`) does not.

Now the seam is `height/768`, and the scale is applied to `UIParent` at the load's end and on a resize. The nameplates' compensation is gone. Backdrops, hit insets and minimap pings now follow a frame's own scale (`0x76b580`, `0x77e8d0`); tooltips at the cursor and `OnCursorChanged` divide by it (`0x530b20`, `0x77db23`). Tests cover each, and a stock-UI comparison shows nothing inside `UIParent` moves.

Fixes #172

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Show and hide propagated a whole subtree first, then fired from a snapshot, parent first: a parent's `OnShow` ran before its children's, and a child hidden by an earlier handler still got its `OnShow`. A frame visible at the end of its load got no `OnShow` at all.

In 1.12.1 show `0x76ae10` and hide `0x76ad50` mark the frame, walk the live child list re-checking each child's gate, and fire the frame's own handler last (`0x76aef5`, `0x76adfd`). The post-load hook fires `OnShow` after `OnLoad` for a visible frame (`0x76a3b3`), from XML and `CreateFrame` alike.

Now handlers run inside a live post-order walk, and the post-load `OnShow` fires. Tests cover the order, a sibling hidden mid-walk, and one or two `OnShow`s after a load; they fail without the fix. Four existing tests' expected orders flip to the reference's.

Fixes #350
Fixes #59

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An incremental rebuild of `benilla-app` spent most of its time in ThinLTO: above opt-level 0 rustc runs it over a crate's own codegen units by default, and incremental compilation redoes it whole. `cargo play` already turns it off, which left its dependencies without cross-unit inlining.

`[profile.dev]` now sets `lto = "off"`, and dev and play build dependencies as one codegen unit each.

Measured on an M4 Max, one-constant edit in `benilla-app`, cpu_ms at the leg pin:

| | before | after |
|---|---|---|
| `cargo build -p benilla` | 24.4 s, 5.2 GB | 5.6 s, 2.7 GB |
| `cargo test -p benilla-app --lib --no-run` | 25.8 s, 7.0 GB | 8.3 s, 4.0 GB |
| cold dev build | 5:19 | 4:06 |
| dev cpu_ms | 11.06 | 11.71 |
| play cpu_ms (release: 10.1) | 11.74 | 10.63 |

Release and ship are unchanged.

Fixes #852.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Add bevy/dynamic_linking feature forward for benilla-app for faster dev builds
- bevy_dynamic_linking says the binary starts only through cargo run

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Sam <samuv.eth@gmail.com>
A target whose faction template the server swaps live (35 to 14, neither naming a faction group) kept its friendly plate colour: `UNIT_FACTION` fired only when the pvp, FFA, faction-group or tapped state moved.

The reference's per-field watch fires `UNIT_FACTION` on any change of `UNIT_FIELD_FACTIONTEMPLATE`, as it fires `UNIT_FLAGS` for that field. `fire_transitions` now also fires it on the faction template's edge.

Test: `a_live_faction_template_change_recolours_the_target_plate`.

From #855 by @kalistoking, landed here because the fork refuses maintainer pushes.

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>
An addon's Lua and XML files were read with the exact case its `.toc` or `<Script file>` spelt. On a case-sensitive filesystem, Linux's, a file named in another case never loaded: Questie 3.7.1 lists `Astrolabe\Astrolabe.lua` from a folder shipped as `astrolabe`, so `Astrolabe` stayed nil and its OnUpdate raised every frame.

The reference is a Windows client, and every path it opens matches without regard to case. Addon textures and sounds already resolved that way through `loose_addon_file`; addon scripts, XML and `Bindings.xml` now go through the same resolver, and the second, case-sensitive one is gone. The shared resolver also refuses a component the host reads as more than a plain name (a Windows `C:`), so it still cannot leave the AddOns root.

Test: a file asked for in three spellings resolves to the one on disk; it fails without the change on a case-sensitive volume.

Fixes #851

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Script errors reached the Lua error handler as mlua's display text: a `runtime error: ` word and a `stack traceback:` block, which overflow the stock `ScriptErrors` dialog. And a raise in a `CreateFrame` template's OnLoad was logged as a warning and never reached the handler at all.

In the reference, handlers run under `lua_pcall` with the error function `0x703b40`, which calls the `seterrorhandler` function with the bare message. `CreateFrame` builds its template through the XML instantiator `0x6ee280`, whose OnLoad runs under that same function (`0x704d50`).

Now every caught error records two texts: the decorated one for benilla's log and the bare message for the handler. A load report pairs the two per row, and `CreateFrame` records its template's errors as script errors.

Tests: the handler hears bare messages from handlers, and a template OnLoad raise reaches it.

Fixes #697
Fixes #859

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
OnUpdate ran in frame-creation order, from a list built before the walk, so a frame hidden by an earlier handler still updated, and strata and level played no part.

The reference's update walk `0x765650` goes through the nine strata from the bottom, each stratum's levels from 0, and each level's list oldest link first. It saves the next frame before each call, so a hide skips a frame before its turn. A frame linked into the level being walked waits a tick; one linked into a level still ahead runs there.

The walk now follows that order, using the link stamps shows and level changes already keep. It checks each frame at its turn and places frames linked or given an `OnUpdate` mid-walk. An EditBox still flushes right after its own `OnUpdate`.

Tests cover the order, mid-walk hides and shows, and a box's flush.

Fixes #742

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`cargo test` stops at the first test binary that fails, so a red `scripts/check.sh` or gate run showed one crate's failures and hid the rest: the next run found more. Both scripts now pass `--no-fail-fast`, so one run reports every failing test across the workspace. The exit status is unchanged: any failure still fails the step.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A frame hidden while a button was pressed on it still got OnMouseUp on the release, and its stale capture aimed the next chorded press at it. And showing any frame, even one that takes no mouse input, scheduled a hover re-pick, so a still cursor could fire OnEnter or OnLeave a tick early.

In the reference, the hide's input-index removal clears the capture slot when it holds the hidden frame, and the release goes only to the capture, so it reaches no one. The hover re-pick is armed only when a show inserts a frame into the mouse index.

The hide now drops the capture and the pressed frame; the release then runs nothing, no world click either. A show arms the re-pick only for a frame that takes the mouse.

Tests cover hides while pressed and shows with and without mouse input.

Fixes #844
Fixes #845

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Raising an overlapped toplevel frame compacts its stratum's levels, and benilla wrote the new levels directly, so the moved frames kept their old link order: which of two frames on one level draws on top, takes the click and runs its OnUpdate first did not change as in 1.12.1.

The reference's compaction moves each frame of a shifted level through the non-propagating level setter, levels from the bottom and each in creation order, and the setter relinks a visible frame at its level's tail. So a shifted level's visible frames end in creation order.

Compaction now moves each frame through `set_frame_level` the same way, in that order; frames carry a creation stamp, since arena order is not creation order once a slot is reused.

Tests cover the relink order, the swapped draw and hit order after a raise, and a mid-walk raise.

Fixes #862

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A ScrollFrame built its `<ScrollChild>` after its `<Frames>` children and so after their OnLoads: under a scroll frame template, the template's scroll bar loaded before the instance's scroll child, and an OnLoad that read the other frame saw it unbuilt.

In the reference, the ScrollFrame's load runs the base load, then builds the scroll child with its whole lifecycle and its own OnLoad, then stores it as the scroll child, all before the post-load hook builds `<Frames>` and fires the ScrollFrame's OnLoad. A template's scroll child is built at the template's level, ahead of the instance's, which wins.

The loader now builds `<ScrollChild>` in that place, on the one path XML and `CreateFrame` share, and only for a ScrollFrame.

Tests cover the order directly, through a template, and with a scroll child in both template and instance.

Fixes #846

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The tooltip fade, a model's animation clock and its OnAnimFinished ran after the whole OnUpdate walk, and OnUpdateModel fired in creation order. A tooltip hidden through its parent also kept its owner and fade.

In the reference, the walk makes one call per frame: its OnUpdate, then its kind's engine update (a tooltip's fade step, a model pane's clock and completion test). OnUpdateModel fires later, from the paint, in draw order. A tooltip's own hide and show run from any caller, its parent's cascade included: the hide un-owns and clears it; the show needs an owner.

The walk now runs a per-kind update in each frame's slot, OnUpdateModel fires per stratum in draw order after the hover re-pick, and show and hide dispatch to a kind's override. CharacterModelBase's override is left for #890.

Tests pin each order and a parent's hide and show.

Fixes #861
Fixes #863
Fixes #864

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`EnableMouse(false)` only flipped the bit, so a frame disabled while pressed still got OnMouseUp and OnDragStart, and a hovered one kept its hover. A second mouse button pressed while one was held fired on the frame under the cursor rather than the held one.

In the reference, a mouse disable on a visible frame runs the same removal a hide does: the capture and the press drop, the hover ends with OnLeave at once. The down handler takes the held capture first and fires there, and a Button clicks on release only while PUSHED and with the cursor inside its own rect.

`EnableMouse` now runs that shared removal, the drag arm included. Presses go to the capture when one is held, and the Button release tests pushed and inside rather than topmost.

Tests cover a disable while pressed or hovered, chorded presses and the release gate.

Fixes #886
Fixes #887

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Fourteen bindings answered Lua `true`/`false` where 1.12.1 pushes the number 1 or nil: the loot slot, resurrect and distance predicates, the talent flags (`isExceptional` read 0, which is true, for every ordinary talent), the cursor predicates, and `GetLootRollItemInfo`'s bind flag, whose count also echoed the stack size where the reference answers 1. `GetRepairAllCost` returned two values with no vendor open, and `GetLootRollItemInfo` a sixth value.

The reference pushes 1 or nil for each and returns exactly the values its catalogue row records.

All of them now answer through `binding_abi::flag`, and `GetRepairAllCost` answers the one value 0 unless a repairing merchant is open. The boolean gate, which missed these, now probes every row whose kinds are nil or number whatever its name or arity confidence, over seeded talent and roll state.

The widened gate fails on the parent for all fourteen.

Fixes #159
Fixes #900

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A backdrop was data drawn in the frame's own slot, so `GetRegions` and `GetNumRegions` never saw its pieces, and addons that skin by walking a frame's regions missed them.

In the reference, installing a backdrop creates nine Texture regions on the frame: the background on BACKGROUND, then eight border pieces on BORDER, linked into its region list in that order. A resize re-runs their texcoords and colours, and the colour setters tint the background or the border alone.

The pieces are now real regions, drawn through the region path, so edges draw over another frame's BACKGROUND at the same level. `<Layers>` and `<Backdrop>` load in document order. A region leaving its parent empties any Button, CheckButton, StatusBar, Slider or ColorSelect slot holding it, and a dead layout node now seeds only its dependents.

Tests pin the order, colours, resize, teardown and stock quads.

Fixes #883

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A raise's level compaction counted and packed every level any frame of the stratum held, hidden or shown, so a frame given a high level while hidden was pulled down and later showed under a dialog it should cover.

In the reference, each stratum keeps a level bound that grows only when a visible frame links into a level, and the compaction examines and moves only the levels below it, then sets the bound to its count, which the raised frame takes. A frame above the bound keeps its level.

The arena now keeps that bound per stratum, grown on every visible link and set by the compaction, which packs only below it.

Tests cover the hidden frame above the bound and a high show raising the bound for the next raise.

Fixes #885

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
GameTooltip's lifecycle took shortcuts the reference does not: a 0.5 s linear fade armed once, a fade cancelled by new lines, cursor-anchored plates that faded instead of hiding, a SetOwner that never hid first, the last anchor kept after a hide, and a health bar shown and hidden without its handlers.

In the reference, FadeOut re-arms a 2 s countdown that holds full alpha for half and then ramps, or hides at once at the cursor anchor. Only the owner core, the show and FadeOut write the fade. SetOwner runs the tooltip's own hide first. The un-own leaves ANCHOR_NONE. The clear hides the bar through its own transition.

Every builder, verb and world or minimap seat now goes through these functions; an uncached item hides and un-owns as in the reference.

Tests pin each of these.

Fixes #889
Fixes #134
Fixes #896
Fixes #893
Fixes #895
Fixes #843

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A texture object handed to a widget's texture setter stayed on its old frame and layer: Button and CheckButton slots only stored it, and StatusBar, Slider and ColorSelect ignored a texture object altogether.

In the reference, each setter deletes the slot's old texture and re-links the new one into the widget: Button states on ARTWORK, the highlight on HIGHLIGHT with its blend, checked textures, thumbs and markers on OVERLAY, the bar on the given layer. The link writes the texture's shown bit, and the old parent's removal hook runs. Thumbs and markers lose their anchors.

All these setters now go through one install that re-links through the region owner path and writes the shown bit; a non-current state texture answers `IsShown()` nil until its state comes, and the raise texts are the client's.

Tests cover each setter, both shown-bit directions and the backdrop teardown.

Fixes #902

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Hidden chat frames kept fading their lines, a scroll frame recomputed its range on every read and fired `OnScrollRangeChanged` on every `UpdateScrollChildRect`, hidden or not, and `OnUpdateModel` fired for panes with no size or off screen.

In the reference, each of these runs in the frame's own update slot, only while shown: message frames trim and fade there; a scroll frame recomputes a range its writers marked, stores it and notifies only on a real move. The model paint fires only for a resolved rect whose screen-clamped viewport has size.

Message frames and scroll frames are now walk kinds, the range is stored and marked, and the paint applies the rect and viewport gates. benilla's options scroll measures its body's own overhang.

Tests pin the hidden fades, the stored range and its notify, and the paint gates.

Fixes #182
Fixes #175
Fixes #892

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Any write that changed the layout graph's roster (a frame or region created, freed, re-owned or re-pointed) made the next resolve derive the whole graph: about 1.35 ms for one `CreateTexture` or `CreateFrame` on the stock UI, against 0.05 ms for a no-op.

A write now names the nodes it touched, and the resolve re-derives just those in the cached graph: a joining node is added with its edges, a leaving one is removed and its dependents seeded, a staying one has its edges and inputs re-synced. The full derivation stays the fallback. A region's owner and an art change are now layout inputs that name their region. Verify now also compares the graph itself.

A randomized test checks rects and graph against a scratch solve after every batch of creates, frees, re-owns and re-anchors. `CreateTexture` now costs 0.14 ms, `CreateFrame` 0.15 ms, a backdrop rebuild 0.22 ms.

Fixes #903

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A Button state texture's visibility was kept apart from its region's shown bit, so a script `Show()` on a non-current state texture drew nothing. The bar, thumb and ColorSelect setters took a colour form and accepted arguments the reference rejects, the loader built their textures from `file=` or `<Color>` alone, and a bar texture answered nil to `GetTexCoordModifiesRect`.

In the reference, a region has one shown bit that `Show`, `Hide` and the state switch all write. The setters fork on object, path, nil or usage. The loader builds each widget texture as a full Texture element, and installing a bar texture sets its texcoord flag and gives it all points.

All of these now match: one shown bit, the reference's argument forms, full-element textures with their names and texcoords, and no tint without a bar.

Tests pin each.

Fixes #909
Fixes #908
Fixes #575

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The tick ran the OnUpdate walk without resolving the layout around it, so a resize made in an event handler fired `OnSizeChanged` after the walk, and some walk slots resolved on their own to compensate. Separately, `OnSizeChanged` gated on 2⁻²² in benilla's units, and an emptied FontString kept its old measured box in the incremental layout.

In the reference, the pump drains the layout queue before the walk and again after it, each drain firing `OnSizeChanged`, then re-picks the hover. ApplyRect compares sizes against 2⁻²² in its own units, where the screen diagonal is 1.

The tick now drains before and after the walk and drops the stand-in resolves. Size gates scale 2⁻²² by the layout unit, and a FontString's text going empty or back names it on the layout when its node changes.

Tests pin each.

Fixes #912
Fixes #741
Fixes #914
Fixes #911

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A Button release un-pushed the button before `OnMouseUp` and `OnClick` ran, so both read NORMAL. A plain click on a floating chat tab then took the pick-up branch and left the window following the cursor.

In the reference, the mouse-up handler fires `OnMouseUp` first, then reads the state: a PUSHED button with the cursor inside it and the Up registration fires `OnDoubleClick` or `OnClick`, and only then is it set NORMAL, unless locked. A failed gate still un-pushes; an `OnMouseUp` that un-pushes cancels the click.

The release now runs that order after `OnMouseUp`, with the started-drag skip kept.

Tests read the state inside each handler and drive the stock chat tab.

Fixes #699

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Chat bubbles were quads drawn straight into an overlay with no widget behind them, so `WorldFrame:GetChildren()` never returned them and bubble-skinning addons such as pfUI's found nothing.

In the reference, a bubble is an anonymous Frame under WorldFrame from a reused pool: eleven regions (the nine backdrop pieces, the tail, the text in the `NAMEPLATE_FONT` global), the text's bottom on the speaker's projected point, its own fade, and an OnShow on each reuse. It fades out and hides when the speaker is out of range or was not drawn last frame.

Bubbles are now built that way, fed by the app and drawn by the frame pass; the overlay path is gone. Item links show their bare names, and a speaker behind the camera fades out.

Tests cover the child, its regions, a pfUI-style skin, reuse and the fades.

Fixes #884

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A release that ended a started drag fired the frame's `OnMouseUp` before `OnDragStop`, so a frame that both drags and acts on mouse-up ran its mouse-up action at the end of every drag.

In the reference, the mouse-up first checks the frame's drag registration for the released button and its started drag: if both hold it fires `OnDragStop` and the hover frame's `OnReceiveDrag`, and returns before `OnMouseUp`, which only a release without a started drag reaches.

The release now follows that: a started drag fires `OnDragStop` and the drop in `OnMouseUp`'s place, and any other release fires `OnMouseUp` as before.

Tests cover a drag, a plain click, a button the frame is not registered to drag, and a resize grip.

Fixes #920

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Every resolve stored the rect it computed, however small the move, so float noise in anchor chains moved rects, hit edges and size-change notifications where the reference keeps the old ones.

In the reference, the layout drain compares each of a node's four new edges with the rect it last stored. If every edge moved less than 1e-5 in its units (where the screen diagonal is 1), it keeps the stored rect and skips the store, its dependents and `ApplyRect`. A scale change, a new font or new text clears the resolved bit, so the next resolve stores without the test.

A resolve now keeps a frame's or region's rect under that threshold, scaled by the layout unit, and a scale change or a new measure stores the exact rect.

Tests cover jitter, a move just over, slow drift, a region and the forced stores.

Fixes #919

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Any press during a drag abandoned it with `OnDragStop`, so touching the right button while dragging an item or a window with the left stopped the drag.

In the reference, each press on the capture runs the drag arm: a button in the frame's drag mask re-arms it, started back to 0, with no `OnDragStop`, and any other button leaves the drag running. The release of a button in the mask ends a started drag with `OnDragStop`, or disarms an unstarted one. `OnDragStart` names the lowest held button.

Presses now re-arm or leave the drag as the reference does, the release tests the released button against the mask, and `OnDragStart` names the lowest held button. A release the client lost still stops the drag.

Tests cover a right press on a left-only drag, a re-arm, the first release after it, an item drag and the start's button.

Fixes #924

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A drag started past a fixed 4 pixels from the press. In 1.12.1 the threshold is 0.01 layout units, where the screen diagonal is 1: `0x76b300` stores `0.01f × 0.01f` (`0x81c468`) as an f32, and `0x76b320` starts the drag when the squared distance from the press point is not below it, comparing at x87 double precision. The converters `0x425610`/`0x425710` scale the cursor into those units (`0x41ad80`).

The drag test now divides both points by the layout unit and compares the squared distance in f64 against the f32 threshold. At 1024×768 a drag needs 12.8 px, at 1920×1080 about 22 px; the UI scale does not change it.

Tests: just under and over 1% of the diagonal, 12 and 13 px at 1024×768, the exact f32 boundary, two screen sizes and three UI scales.

Fixes #157

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

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Anchors were a list: SetPoint re-applied an unchanged anchor, GetPoint counted in the order points were set and answered five nils past the last, and the cursor tooltip replaced every anchor each frame.

In 1.12.1 a frame holds one anchor per point. SetPoint (`0x767c70`) returns at `0x767d5e` with no store and no layout queue when that point's anchor has the same target and relative point and both offsets differ by under 2⁻²² layout units (`[0x8029d4]`). GetPoint (`0x7a2340`) walks the nine slots in point order and pushes nothing past the last (`0x7a240d`). The `ANCHOR_CURSOR` update sets BOTTOM alone (`0x530b20`).

Anchors are now nine slots, and SetPoint, GetPoint and the cursor update follow those rules.

Tests: an unchanged re-set and an in-threshold offset touch no layout, GetPoint order and indexes, a cursor tooltip keeps an addon's second point.

Fixes #926
Fixes #131
Fixes #879

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Moving from the world onto a window, the world mouseover's loss reached the tooltip a frame after the window's OnEnter, so a gate kept the fade off any tooltip the world had not put up.

In 1.12.1 the hover walk runs the old frame's leave before the new frame's enter (`0x766229`, `0x766239`). The WorldFrame's leave (`0x483b10`) runs OnLeave, then publishes the empty mouseover, whose publisher fades GameTooltip whoever holds it (`0x492890` → `0x530ae0`). A nameplate's leave publishes first (`0x7cb8b0`); the Minimap's hides the tooltip (`0x4eb880`), and its probe drops a held blip on a miss (`0x4eb0c0`).

The VM now holds the published mouseover and runs these leaves in the walk, the hide and every mouse disable, freelook's plates included (`0x60f830`); the ownership gate is gone.

Tests: OnEnter no longer sees "mouseover", an addon tooltip fades on a change, blip and Minimap leaves hide it.

Fixes #901

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A FrameXML template registered under a name already taken replaced the first, nothing was logged, and names differing only in case were stored twice.

In 1.12.1 the registrar `0x6ee500` folds case (`0x64b3f0`, `0x64a4c0`) and keeps the first entry, logging "Virtual object named %s already exists" (`0x871188`), unless the old entry carries a non-zero script context and the new one zero (`0x6ee594`). The stock files load at context 0; `AddOn_Load` (`0x51f240`) loads an addon under its name unless its signature checked SECURE (`0x51dbfb`).

Templates are now keyed case-folded with their context, an addon's load sets the context, an addon counts as secure only from the player's own data with `## Secure: 1`, and the refusal and the unnamed-node line (`0x871020`) reach FrameXML.log.

Tests: duplicates and case variants refused, the context rule, the log lines.

Fixes #556

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An XML state texture on an instance over a template went through the Lua path leg, which reuses the populated region, so the template's TexCoords, size, tint, blend and anchors stayed on the new art.

In 1.12.1 `Button::LoadXML` (`0x7788c0`) builds a fresh region for each state tag through the `<Layers>` adder (`0x6f26f0`) and hands it to the slot store (`0x778fd0`, `0x779110`; the checked pair `0x785330`, `0x785440`), which destroys the region it held. A fresh region has TexCoords 0..1 and BLEND (`0x76fc40`, `0x76fc64`), and an XML highlight keeps its own blend (`0x7789a4`).

The XML leg now builds the region as a `<Layers>` texture and installs it, freeing the old one; the Lua leg is unchanged. An XML highlight with no `alphaMode` now draws BLEND.

Tests: all six tags on a CheckButton template, the name kept, the Lua leg's region kept.

Fixes #875

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Four pointer defects: OnEnter and OnLeave set `arg1 = true`; a frame being moved or sized still ran the hover walk; the pointer leaving the window fed `f32::MIN` to a move in flight, flinging the frame off-screen; and the mouseover token read the world pick, not the published mouseover.

In 1.12.1 both scripts run through `0x702690`, which sets no arg global. While `[root+0xcfc]` holds a move or size target, the walk pumps it (`0x7655b0`) and returns before any probe (`0x766164`–`0x7661a0`); a hide clears that slot (`0x76492e`). The pump only sees real cursor events. The token resolver reads the published pair `[0xb4e2c8]` (`0x515970`).

OnEnter and OnLeave fire with no arguments, `mouse_move` pumps a move or size and returns, a hide ends it, the pointer leave has its own call, and the aura feed reads the published mouseover.

Tests bite on the parent for each.

Fixes #932
Fixes #933
Fixes #936
Fixes #938

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Region names lived in one first-wins name → id map that also served `GetName`, so a second region given a taken name answered `GetName()` with nil and was `<unnamed>` in its error strings.

In 1.12.1 the name lives on the region: `SetName` (`0x76c650`) keeps the region's own copy at `[+0x98]`, freed by its destructor (`0x76c560`) and read back by `GetName`. Only `_G` is first-come (`0x701bd0`, `0x701c8a`).

Each region now carries its name; `GetName` and the error strings read it directly, and the name map is a first-come lookup only, whose entry a free drops only when it holds that region.

Tests: same name twice, `$parentText` under colliding parents, the second region's error string, a free leaving a same-named sibling's name.

Fixes #939

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`SetFont` raised on a `false` flags argument and wrote the height even when the load failed, and only SimpleHTML had `SetSpacing`/`GetSpacing`, so SpellAlert's message frame raised in its OnLoad.

In 1.12.1 `SetFont` (`0x79f210`, the FontString's `0x79d4f0`) reads flags only when `lua_isstring` (`0x79f287`), writes them on every successful load (`0x79f334`), and writes nothing on a failed load or a height ≤ 0 (`0x79f361`, `0x79f2f4`, `0x771571`). Every text table has the spacing pair (`0x79fb40`/`0x79fbe0`; the FontString's clamps at `0x772240`); the line step adds the gap rounded up to a device pixel (`0x5c2070`, `0x5cdc20`), as do the message frames (`0x78899e`, `0x785f06`) and EditBox rows (`0x77d133`).

All seven SetFont bindings share these gates and ask the host's font probe; spacing is stored, inherited, measured and drawn on all six tables.

Tests bite on the parent for each.

Fixes #873
Fixes #692
Fixes #945

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`ClearAllPoints` invalidated the node, so an anchorless frame lost its rect at once, with everything anchored to it, and any region whose resolve failed stopped drawing.

In 1.12.1 `ClearAllPoints` (`0x7a2b70` → `0x767620`) only frees the anchors, queuing no resolve, so the rect stands. Only the node's own invalidate re-resolves it; a failure (`0x768d20` at `0x768d55`) clears the valid bit and skips slot 0, so its dependents keep their rects. A Texture keeps drawing its last vertices (`0x7706e0` checks only its texture); a FontString stops (`0x773080`, `0x7730b7`). A new string, font or spacing forces a store (`0x771ea4`).

Each node now carries an entry state, and takes a recomputed result only when invalidated or a target stored a new rect; a failed Texture's last quad is kept, and the full derive and the incremental pass read the same state.

Tests bite on the parent; the verifier is green.

Fixes #942
Fixes #943

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A drag kept the frame's authored anchors: `GetPoint()` answered them afterwards, the frame kept following its old target, and a resize grip on a CENTER- or TOPLEFT-anchored frame moved the wrong edges.

In 1.12.1 every drag enters at `0x7652b0`, whose first act pins the frame (`0x768430`): `ClearAllPoints`, one TOPLEFT on the screen root's TOPLEFT at the current rect, and an explicit size. The pump `0x768710` moves that anchor (case 4) or, for each of the eight grips, computes the clamped size and re-pins shifted by the left and top travel only (`0x76898a`). A frame with no rect starts no drag (`0x7652cd`).

Drags now pin and pump that way, with one drag slot; `StartSizing` defaults to BOTTOMRIGHT (`0x776913`).

Tests: the pin and `GetPoint` after a drag, the eight grips on a CENTER-anchored frame, the clamps, the layout cache round trip, and stock chat-window resize and off-screen drop.

Fixes #173

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Text measured `ceil(sum of steps) + 1`: no kerning, float advances, the last glyph priced by its step, and no outline or shadow lead.

In 1.12.1 the kernel (`0x5c6940`) seeds a line with the outline lead plus the shadow (`0x5c69c0`, `0x5c69a4`), steps each glyph by `ceil(floor(adv) + 1 + min(kern, 0))` on FreeType's 26.6 advances (`0x5ca2d0`, `0x5d1298`), prices the line's last glyph by its ink (`0x5c6b70`, `0x5cb080`) and carries the previous glyph across a break (`0x5c6aa0`). The draw rows use the same law, shadow-seeded, wrapping at `ceil(width)` (`0x5c7470`, `0x5cddb1`).

The measure, the wrap, the justify and the EditBox now follow it, reading the face's own metrics, kerning and composite build (`0x7d40e6`).

Tests pin widths computed outside the code base from FRIZQT and ARIALN at 12–34 px, with and without outline and shadow.

Fixes #427

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A title-region press refused while another drag held the slot, `SetLayoutMode` stored nothing, and a press on a chat link made the chat frame itself the target, so every link click started a title move of the window.

In 1.12.1 the drag entry takes the slot unread (`0x7652b0`); a mid-drag press goes to the frozen hover (`0x76637f`). With `SetLayoutMode` (`0x488540`) on, a Ctrl or Alt press drags the nearest movable toplevel (`0x765490`) from a 3×3 grip grid (`0x765320`) and is consumed, world included; its release only ends the drag (`0x7664e0`). Each link span is its own Button (`0x7a3240`) routing `OnHyperlinkEnter`/`Leave`/`Click` to the parent (`0x7a3780`–`0x7a37e0`).

These now hold; a release goes to the capture whatever its button (`0x76642b`), and number arguments keep `_ftol`'s low dword.

Tests bite on the parent for each.

Fixes #951
Fixes #952
Fixes #957

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The first piece of running the stock GlueXML: benilla had no glue Lua state, and no reference data for its surface.

In 1.12.1 the glue state holds the base set (`0x7039e0`) and seven registrars (`0x46abb0`): 119 functions, 105 of them glue-only, with 26 glue events (`0xb41e70`, `0x46a88f`). `RegisterEvent` matches names case-insensitively and drops an unknown one (`0x702140`). The glue builds the 13 base frame types plus `ModelFFX` (`0x46a4fe`), a Model with its own show and paint (`0x46fa00`); the in-game types come only at world entry (`0x495940`).

`reference/` gains the glue event and surface tables, generated and checked against the shapes table. `UiScript::new_glue()` builds that state; a gate holds its globals to the table, with the 108 unbuilt names listed. Nothing in the app changes yet.

Part of #856

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The solver took any node's recomputed rect whenever it resolved, because about 55 layout writes named a node without invalidating it. So a freed region stayed in its dependents' anchors, `SetTexture` re-resolved a failed texture, and a scroll child kept its authored anchors behind a per-solve override.

In 1.12.1 a node re-resolves only when invalidated or a target stores a new rect (`0x768d20`, slot 0 `0x768cf0`). `SetTexture` only dirties the draw (`0x770200`). A destructor frees its dependents' anchors to it (`0x768e20` → `0x767e70`, no invalidate). A scroll child is re-anchored to one TOPLEFT (`0x787100`).

Every write now invalidates exactly where the reference's does, and the solver's guess, the hold and the art latch are gone. The tooltip invalidates only on its relayout (`0x530640`).

Tests bite on the parent; the verifier is green; the stock UI's quads are byte-identical.

Fixes #947
Fixes #949
Fixes #958

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With `WOW_GLUE_STOCK=1` (dev builds), benilla now loads the stock GlueXML off the player's own patch chain into the glue state and draws it; with the switch off nothing changes.

In 1.12.1 the glue builder (`0x46a7b0`) opens `Logs\GlueXML.log` (`0x46a8ce`), runs `GlueXML.toc` through the shared toc runner (`0x6edb90` at `0x46a9b3`), fires `FRAMES_LOADED` (`0x46aa34`) and opens on "login" or "movie" (`0x402b3b`), or "charselect" back from the world (`0x402084`). Glue `GetScreenWidth`/`GetScreenHeight` answer the root unscaled (`0x46dd30`, `0x46dd80`).

Under the switch the glue state replaces the boot state, takes input, and fires `SET_GLUE_SCREEN` on those edges; the native screens are hidden but still drive the network. A data-gated test loads the stock files with only seven unbuilt verbs raising, a list that may only shrink.

Part of #856

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Under `WOW_GLUE_STOCK=1` the stock login screen now logs in. Separately, realmd's 0x0E answer was refused by the native login too.

In 1.12.1 the glue manager runs the screen states (`0xb41da0`): `DefaultServerLogin` (`0x46afb0`) needs the agreements accepted and opens the status dialog; each tick re-texts it from the logon's state and result (`0x46b140`, tables `0x836b40`, `0x836b78`); the world connect re-texts it every frame (`0x46be8b`). The agreements seed from `readTOS`/`readEULA` (`0x46a400`). Realmd 0x0E is a success (`0x5b2cd4`); a proof's 0x0A changes nothing (`0x5bada2`).

The glue core verbs, the glue manager over the native login, the agreement CVars, `SimpleHTML file=`, case-insensitive loose files and a benilla Realmlist layer file are built; the protocol accepts 0x0E. A recorded `Deviation:` keeps the interface signature checks off.

Tests: fake-realmd refusals and 0x0E; stock-screen login reaching the native submit.

Part of #856

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A hidden frame resolved and fired `OnSizeChanged` at once, a getter's resize fired at the drain, and `GetWidth`/`GetHeight` answered the rect over scale.

In 1.12.1 hiding sets the deferral flag `0x2` (`0x76ad50`); while set, an invalidate marks the frame pending and passes it to its dependents without a resolve (`0x768178`). Show resolves it first (`0x76ae58`), a getter resolves a pending one and fires `OnSizeChanged` inside the read (`0x7a1980`, `0x76b580`), and an anchor read pulls it for good (`0x7a2fc0`). `GetWidth` answers the set size when non-zero, else the rect span, unscaled (`0x7a1e93`, `0x7a1ee6`).

The deferral is modelled for frames and regions, getters fire inside the read, layout ids mint in handle order (deterministic), and the options window measures its scroll by its edges.

Tests bite on the parent; the verifier is green; stock quads match.

Fixes #948
Fixes #971

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Player reports and the trace, in one batch.

- SetFactionInactive/SetFactionActive raise `UPDATE_FACTION`, so the list moves at once.
- NPC greetings: the latch, a dead unit, then the reaction pair with the active mover (`0x60c270`); a dead or ghost player hears none. Only a passing click re-tracks the NPC, and a window hello resets it (`0x60c3d1`).
- "You are now <rank> with <faction>." prints on first contact and rank change (`0x62c670`), in packet order, with the real gender.
- A standing packet prints its "increased by" line for the first entry only, never a hidden one (`0x4d57dd`).
- `WOW_MOVE_TRACE` carries each packet's bytes as hex; chat bodies are opt-in, and dot/bang commands and channel passwords are always redacted.

Tests cover each gate, the line order and the redaction.

Fixes #927
Fixes #882
Fixes #881
Fixes #867
Fixes #965
Fixes #966
Fixes #967

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An unattended run (`WOW_UNATTENDED`, `WOW_RIG`) resolved the same `benilla-config/` as a person. In a dev build, every linked worktree maps that to the primary checkout's folder. So a probe read the player's CVars, bindings and layout, and wrote its realm, settings and window positions back over them.

`local_state::home` now gives an unattended run a fresh folder of its own (`$TMPDIR/benilla-run-<pid>/benilla-config`) and prints it at boot. `BENILLA_HOME` still pins one, and a capture still resolves none. CONTRIBUTING's "Running it unattended" says so.

Test: `an_unattended_run_keeps_its_own_state_folder` checks the rig path, the override and the capture, and that the folder is never the resident one.

Fixes #975

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- **The layout file** now saves what the reference's writer does (`0x490e60`): `X`, `Y`, `W`, `H` and `FrameLevel` from the frame's held rect, rows in creation order, and only frames that are user-placed and movable or resizable. The restore (`0x4905e0`) pins TOPLEFT to the screen, sets the size and level, and resolves each frame as it goes (`0x490710`). Old anchor-form files still load and are rewritten once.
- **Chat bubbles** re-rank only on a camera move or a new line (`0x4b11ff`), and a repeat speaker moves to the tail, as `0x4b1870` links it.
- **The live re-layout on a window resize** gets its `Deviation:` line. 1.12.1 reloads the UI on an aspect change of 0.001 or more (`0x492f26`).

Tests cover parent/child level restore, the held rect, the hidden-frame resolve, the junk lines, the migration and the bubble tie order.

Fixes #954
Fixes #955
Fixes #847

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Part 3b of the stock GlueXML (`WOW_GLUE_STOCK=1`): the stock `RealmList` and `RealmWizard` run on the reference's ten realm verbs (`0x46f480`).

- The realm park is now a cancellable operation. Each dial carries the cancel generation it was asked under, so no Cancel is lost, and a status Cancel closes the logon (`0x5b3320`).
- Change Realm with the world up drops the session first (`0x46b22b`).
- The roster arm (`0x4724d0`) sets the chosen realm, or asks for the list when the roster is empty.
- An empty realm list is shown, never dialled at the world port.
- The native type sort puts PvP before RP.

Deviation lines cover the re-dial's full logon, comparator ties, a missing Region row and the native close.

Part of #856 and #834

Fixes #833
Fixes #974
Fixes #982

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Lua bindings now read their arguments the way the 1.12.1 client does:

- Numeric strings parse as Storm's `SStrToInt` (`0x64ac60`) and `SStrToFloat` (`0x64aaa0`). That covers channel keys, `GetNumber` and `item:` fields.
- Flag arguments go through `GetBoolOrDefault` (`0x6f1c10`) with each site's default. That is about 40 bindings, `SetChecked()` and `ShowHelm()` among them.
- Colour channels take numeric strings (`lua_tonumber`), and alphas are `isnumber`-gated: no raise, 1.0 by default. A three-argument `SetVertexColor` keeps the current alpha (`0x79ae0a`). So action-bar rings dimmed by a drag stay dimmed, as in 1.12.1.
- `SetTexture("1")` takes the colour form. MessageFrame channels follow `0x795590`.
- `LoggingChat`/`LoggingCombat` return the flag.

Binding-level tests use the reference's values.

Fixes #135
Fixes #144
Fixes #160
Fixes #161

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Addon loading follows the client's `AddOn_Load` (`0x51f240`) and `.toc` reader (`0x51c9b0`):

- One driver serves the startup walk, `LoadAddOn` and the survey harness. It marks the addon loaded before its dependencies (`0x51f313`), so a requirement cycle loads both and `IsAddOnLoaded` answers during an addon's own load. It loads `## LoadWith` dependents after `ADDON_LOADED` (`0x51f5b2`).
- Every directive is matched by its own literal. `LoadOnDemand`, `Secure` and XML bool attributes parse with the client's string-to-bool (`0x6f1b30`), so `hidden="1"` works. Values are not trimmed, and only an unindented `##` line is a directive.
- `GetAddOnMetadata` reads the metadata map (`0x51d580`): Title defaults to the folder, the last value wins, and locale suffixes apply. The url comes from the server, never the `.toc`.
- `EnableAddOn` keeps a toggle for an addon that isn't installed.

Tests are mutation-checked against the reference order.

Fixes #192
Fixes #193
Fixes #405
Fixes #986
Fixes #987
Fixes #990

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Lua bindings now return what 1.12.1 pushes, in type and count:

- Flags are 1 or nil, never a Lua boolean. This covers `SetInventoryItem`/`SetBagItem` hasCooldown, `GetQuestLogLeaderBoard` finished, `GetContainerItemInfo` locked/readable, `AtTop`/`AtBottom`, `IsOwned` and `SetAlphaGradient`.
- Eleven verbs whose handlers end `xor eax,eax` return nothing. That covers `PickupAction`, `PlaceAction`, `PickupSpell`, `PickupInventoryItem`, `EquipCursorItem`, `SplitContainerItem`, `PickupMacro`, `DeleteMacro`, `ResetChatColors` and others.
- The miss tuples: `GetInboxHeaderInfo` gives 13 values (`0x4aeff0`), `GetInboxText` four nils, and `GetQuestLogLeaderBoard` three.
- `GetTrainerServiceDescription` is nil with no text (`0x4d9d44`). `GetZonePVPInfo`'s faction is `""`, not nil (`0x48d633`).

The boolean gates now probe every global and widget method.

Fixes #442
Fixes #574
Fixes #577
Fixes #578
Fixes #822
Fixes #823
Fixes #872

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- `RegisterEvent` and `UnregisterEvent` match a name against the client's event table case-insensitively and drop a name that isn't in it (`0x702140`). A new gate checks that every event name the code fires is in the table.
- A visible XML frame resolves its layout at the post-load hook (`0x76a310` → `0x7680e0(1)`). So its first `OnSizeChanged` fires before its `<Frames>`, `OnLoad` and `OnShow`, as in 1.12.1. A hidden frame still waits for its show.
- `pending_events`' doc now says what the queue is: `SignalEvent` (`0x703e50`) runs handlers in place, and the Lua verbs that still queue are a gap.
- #890 was already fixed by #907; two stale comments around it are corrected.

Fixes #959
Fixes #972
Fixes #560
Closes #890

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Part 4a of the stock GlueXML (`WOW_GLUE_STOCK=1`): character select's roster.

- The select verbs run on the reference's roster (`0x4736c0`): `GetCharacterInfo` (race and class from `Name_Lang`), `SelectCharacter` with its clamp and event, the facing gate, and `GetCharacterListUpdate`. They are backed by the roster load and teardown (`0x4724d0`, `0x472580`) and screen state 3.
- Under the stock screens the character list is fetched only when the screen asks, as `0x46b880` does. The world connect completes on the auth response (`0x5aab80`), not on a roster.
- Every character-park request goes through one ordered channel. A roster reply completes only the current link's query. A late auth after a cancel is dropped, Warden stays terminal, and a failed list shows `CHAR_LIST_FAILED`.

The native screens are unchanged. Fake-server tests cover the ordering, the cancel, the realm change and the native policy.

Part of #856

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

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
In 1.12.1 an event a Lua verb raises runs its handlers before the verb returns: `SignalEvent` (`0x703e50`) walks the listeners in place. About 35 benilla binding sites queued theirs for a later drain instead.

- Verbs now raise inside `tick::verb` steps, and the events fire as each step's model borrow drops. A step follows the reference's split wherever it signals before changing state a getter reads: the money pickup, bag pickup and place, merchant grab, repair cursor, item use, action and pet-bar pickup and drop.
- The grid events fire before `CURSOR_UPDATE`, and a clear's unlock before both, as at `0x4952b3`/`0x495455`. `ITEM_LOCK_CHANGED` carries no arguments.
- `pending_events` is host-only now.

Tests pin handler order per family, nesting from a handler, and the order changes.

Fixes #999
Fixes #582

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In 1.12.1, logging out or a refused character login returns to character select on the same world connection and re-lists there (`0x5aaeb0` → `0x401ee0`, then `0x46b880` → `CMSG_CHAR_ENUM`). There's no realmd logon and no new `CMSG_AUTH_SESSION`. Enter World closes the realmd link (`0x46b70c` → `0x5b3320`), and only the glue's own re-dials reopen it.

- benilla used to drop the socket and log on again from cached credentials. Now the world session splits and rejoins on one connection, and the header cipher carries its state across.
- The realmd link closes at the pick.
- A leftover packet at character select is skipped rather than fatal.
- The write thread applies a pending writer swap before any command.
- A kept-link teardown ends the character without a "server down".

Fake-server tests pin one logon, one auth and one more enumerate. The logout smoke fails without a same-link relist.

Fixes #984
Fixes #983

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1.12.1's MSG_MOVE_TELEPORT_ACK handler (0x603f90) reaches none of the
loading screen's raises: 0x406800 is entered only from SMSG_TRANSFER_PENDING
(0x401900), the glue EnterWorld (0x46b6f1) and 0x407de0. benilla raised the
cover on a same-map snap twice over: the "focus not resident" backstop on the
snap frame, and a >=100 yd relocation raise. The relocation raise goes, and
the backstop stays off while a snap's settle hold is up; the body is held over
the destination as before.

Verified live against the local realm (map 0, Gmagent): .tele ironforge from
Northshire (~3960 yd) and back covered 50 and 93 frames (0-916 ms, 0-1680 ms)
before, 0 and 0 after; cross-map .tele valley and back still raise the cover
("worldport"). New tests in loading_screen.rs fail on the old code at the snap
frame. benilla-app and benilla-world lib tests green, install-reading tests
green with BENILLA_REQUIRE_DATA=1 (the addon corpus is absent on this
machine). Built on macOS (arm64).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Key input follows the client's walks: key-down (`0x765f10`), char (`0x765df0`), key-up to the recorded consumer (`0x765fd0`) and repeat (`0x766070`).

- EditBoxes are in the walk from creation (`0x779ced`), so the stock login's boxes take typing; only a focused box consumes. A box declines PRINTSCREEN and unaltered arrows on an `ignoreArrows` box, so stack-split steps under a focused chat box. A binding's press records the world frame.
- Repeats follow the pressed-key list, so a held key resumes after alt-tab. Chords reach a box only through the walk. Keys the client can't name walk nowhere.
- The stock login reads the agreements before its first screen. Cinematics returns to login (no movie player; Deviation). The Realmlist box has its middle border.
- Alt+F4 closes the window on Windows and Linux (Deviation: the reference relies on the OS).

Fixes #179
Fixes #1003
Fixes #1004
Fixes #1005
Fixes #1019

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Lua bindings answer the reference's shapes on misses, bad indices and tooltips:

- Indexed getters push their full miss tuple (`GetCraftInfo`, `GetPetActionInfo`, the trainer requirements and others). Indices go through `ftol` and an unsigned compare, so a negative misses instead of raising. Verified slot readers keep their own raises (`IsInventoryItemLocked`, `0x4c8520`).
- The eight GameTooltip setters return the builder's refresh flag, gated as `0x53267c` gates it, so a hovered cooldown tooltip refreshes. `LOCKED` follows the item object (`0x52bdb3`).
- Quality goes through the InventoryType gate (-1), and a pending template shows the question-mark icon.
- `GetLootSlotInfo` returns four values. `SetSpell`, `CreateMacro`, `JoinChannelByName`, `GetQuestReward` and the shapeshift and trade-skill filters raise or answer as the reference does.
- The shape gate probes each getter's miss legs through an argument ladder.

Fixes #145
Fixes #993
Fixes #994
Fixes #995
Fixes #996
Fixes #997
Fixes #998
Fixes #1016

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The cursor now runs on the client's two primitives. `ClearCursor(a,b)` (`0x495190`) cancels an armed gift wrap, then runs the payload's arm (an item's unlock fires `ITEM_LOCK_CHANGED`; coins fire `PLAYER_MONEY`), then the grid hide and `CURSOR_UPDATE`. Each setter runs that clear first, then its own tail (`0x494b60`, `0x4950f0`, `0x494d20`, `0x494f80`, `0x494e20`, `0x495010`). Every pickup, place, split, sale, trade, mail and auction path goes through them. So a `CURSOR_UPDATE` handler reads the emptied cursor, and `PickupSpell`/`PickupMacro`/`PickupPetAction`/`SplitContainerItem` clear what is held instead of refusing.

An item attached to mail or an auction stays locked until it leaves the slot (`0x4adef0`). Attaching to an occupied slot puts the old item back on the cursor.

Event-order tests cover each setter, the coin clear, and the attach lock and swap.

Fixes #1011
Fixes #1012
Fixes #1006
Fixes #1007
Part of #156

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Part 4b of the stock GlueXML (`WOW_GLUE_STOCK=1`): the stock screens' ModelFFX scenes now go through the glue booth instead of the model-tile path, which drew them black.

- The scene, look and facing come from the glue VM. `SetCharSelectBackground` runs the frame's own SetModel and swaps its fill callback (`0x472128`, `0x472144`), so the select scene fogs until a character sets its stage.
- An ModelFFX pane draws the booth's scene at its own bucket's ARTWORK layer (`0x46fa90`), framed at the pane's aspect with the diagonal law (`0x7ada40`, `0x5c3cc0`). The native box Deviation now holds for the native screens only.
- Each SetModel restarts the stage. The select glow holds through an empty roster until the screen shows again.
- An empty roster shows the Orc scene fogged, and after the last delete the last stage stays, as `CharacterSelect.lua` leaves them.

Part of #856

Fixes #978

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The stock login's Breaking News box (`WOW_GLUE_STOCK=1`):

- At glue init, as `0x46a400` does, the client reads the Lua global `SERVER_ALERT_URL`, which the player's own GlueStrings or a server's patch sets. It fetches the URL once on a background thread: plain HTTP, 5 s timeouts, redirects followed.
- The body builds the reference's buffer (`0x46a580`: 0x7ff cap, appended chunks). A case-insensitive `SERVERALERT:` prefix fires `SHOW_SERVER_ALERT` once from the update (`0x46b939`). Every failure is silent.
- `LaunchURL` opens the browser only for URLs that pass the reference's gate (`0x5abc10`): `http://`, a strict charset and the 10-host whitelist. An over-long host is refused rather than cut.
- No fetch or browser ever runs in a test, capture or unattended run.

Tests run against a local fake server: prefix, 404, redirects, chunked, timeouts, caps, and the frame shown end to end.

Part of #1026

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

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The cursor verbs follow the client's arms, on top of the primitives from #1038:

- **Action bar.** PickupAction places only what the bar can take (`0x4e615d`) and picks up over anything else. Bar spells and macros lift with their own setters, so `CursorHasSpell` sees them. Dropping the slot's own action, or a spell its macro or item casts (`0x4e5a50`), only clears the cursor.
- **Bags and doll.** Clicks pick up over a held spell, macro or coins, and clear a held bar item (`0x4f9de9`, `0x4f9f41`). A misfit doll drop auto-equips (`0x4c78ef`).
- **Trade.** A slot click swaps the old item back (`0x4c0036`). An offered item stays locked until the server closes the trade, and the close fires the unlocks between its two signals.
- `SplitContainerItem` refuses a count outside the stack.

Fixes #1031
Fixes #1032
Fixes #1034
Fixes #1035
Fixes #1036
Fixes #1037
Fixes #156

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The stock glue AddOns screen had no verbs to call, and SMSG_ADDON_INFO was read only for its status.

The glue AddOns verbs now run over the addon registry and an enable store kept in benilla-config, out of the AddOns folder; old enable files move there once. Reading SMSG_ADDON_INFO fires ADDON_LIST_UPDATE. Each record's status, key, revision and url go to the addon sent in the same position (0x51da70). The url is a C string of up to 256 bytes (0x4191b0), and addons past the last record keep its status. A banned addon and those that depend on it read as banned on both screens. IsAddonVersionCheckEnabled tests the CVar's integer (0x51f180). LaunchAddOnURL keeps 0x429ff0's decode. Deviation: it then re-encodes everything outside the URL characters and opens only http(s) with a host.

Tests cover each record shape, the pairing, the decode, the migration and the banned gate.

Part of #856
Fixes #1013

🤖 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>
The stock character select had no DeleteCharacter or RenameCharacter.

DeleteCharacter opens CHAR_DELETE_IN_PROGRESS and sends the delete (screen state 6). On success it fires SELECT_FIRST_CHARACTER and fetches the list again; a refusal shows OKAY with the status text (0x46c173).

RenameCharacter follows 0x473520 and 0x46b340:
- it needs flag 0x4000;
- the same name, or a failed name check, reopens the dialog with the key;
- otherwise it sends CMSG_CHAR_RENAME (state 7).
The reply (0x46b440) tells apart only result 0x31. Success renames the record, clears the flag, moves the character's local files (all or none, as one folder move) and fires CHARACTER_LIST_UPDATE.

The client's name check (0x5abd50 → 0x6c96c0), with NamesProfanity, ChatProfanity and NamesReserved compiled once, is shared with create.

Tests cover each verb arm, the wire, each name code, and both flows end to end.

Part of #856
Part of #79

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The stock character select had no EnterWorld.

EnterWorld follows 0x46b500:
- it returns silently with no realm, no record or no world link;
- flag 0x4 opens OKAY CHAR_LOGIN_LOCKED_FOR_TRANSFER and flag 0x4000 fires FORCE_RENAME_CHARACTER, neither sending;
- otherwise it writes and saves lastCharacterIndex, enters state 8 and logs in, the last call of a frame winning.
A refused login (0x5aad70) sets state 9, whose OKAY button re-enumerates on the same link.

The rebuilt glue is now seated before SET_GLUE_SCREEN, so a logout's select asks for its list. A refusal queued in the same drain as the entry now wins, natively too. The login-send doc is a Deviation line stating the reference's load-then-send.

Tests cover each guard and arm, and the entry, refusal and logout flows over the fake world.

Part of #856
Part of #977
Part of #79
Fixes #979

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Item locks came from pending-op bookkeeping, so unlocks reached the wrong items or none.

Now a lock is a bit per item guid, set by LockItem (0x4953e0) and lifted by UnlockItem (0x495420) at the client's own sites:
- pickups, places, swaps, the delete and the wrap;
- UseContainerItem's arms, taken in the call in 0x4fa0e0's order (bank, deposit, sell, equip, open);
- the watchers: a seat's guid changing, an arrival, a restack;
- the inventory and sell refusals, loot closes, the bind-confirm cancel, the revive.
Readers refuse a locked item where the client does (0x4f9e76, 0x5d8d32, 0x4fa1cd, 0x4fa4bf). Ammo equips by class and unlocks.

A /reload carries the locks and the cursor, minus what the sweep's loot, mail and auction legs unlock. A despawned item loses its lock.

Tests cover each site, reader, watcher and the reload.

Fixes #1033
Fixes #242
Fixes #228
Fixes #354
Fixes #227

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The stock character create screen had none of its 24 verbs.

They now follow table 0x8380f8:
- the reset draws sex, race, then the five customizations, with class 1 (0x470900);
- a race change redraws and takes the old list's first class (0x470cf0);
- the cycle lands on an end rather than wrapping by modulo (0x470f80);
- GetRandomName builds names from NameGen trigrams (0x475480);
- CreateCharacter checks the name (0x4711e2), then shows CHAR_CREATE_IN_PROGRESS and sends CMSG_CHAR_CREATE in screen state 5.
Success fires SELECT_LAST_CHARACTER and returns to the select; a refusal shows OKAY with its text. The preview stands in the glue booth.

Hair styles and each style's colours now come from CharSections (0x47a3e0, 0x47a400), so an Orc female has 8 styles. A NameGen that fails to load empties only the random name.

Tests cover each verb, the wire and the create flow end to end.

Part of #856
Fixes #1073

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Verb events fired from app feeds a frame late; 1.12.1 signals inside the call.

Now each verb raises its events in place. Feeds push only what their own server source changed, so a verb's write is never rolled back.

Also, from the bytes:
- The score board waits on pending names (0x4aa216).
- The recruitment cascade runs inside its verb.
- Seven mail verbs share the request lock 0xb6efc8; the mailbox keeps its inbox across a close.
- StartAuction runs its gates, sends the attached guid and empties the slot in the call.
- The tradeskill repeat counts, disarms and resets as 0x4fd7b0 and 0x4fc750 do.
- Quest folds carry across a /reload.

Tests cover each event's timing and the feed rule.

Fixes #1008
Fixes #1009
Fixes #1010
Fixes #1045
Fixes #1046
Fixes #1047
Fixes #1050
Fixes #1051
Fixes #1059

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Five item-use paths differed from 1.12.1's bytes:
- PutItemInBag swapped a worn item instead of storing it (0x4c7d44), and sent a bag into itself (0x4c7cea).
- UseSoulstone skipped the cast cancel before its use (0x48ae6b → 0x6e4940).
- The bar's equip left a held item on the cursor; it now runs 0x5e1840 → SetCursorItem.
- The bind-on-equip confirm emptied the cursor; the item now stays held under the question (0x5e16ce).
- Item use ignored loss of control. The dispatcher's requirement and control gates (0x5d8e0e, 0x5d8eaa), the equip and SetCursorItem gates, the fear/charm exemption (0x6e9d70) and the trade-window refusal (0x1b4) now apply. They read our own character's control only (0x5fa600).

Tests cover each path and gate.

Fixes #1057
Fixes #1075
Fixes #1078
Fixes #1076
Fixes #1077

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The glue manager's verbs queued their work for a later frame. In 1.12.1 each verb calls CGlueMgr's function, which writes screenState and signals its dialog before the call returns, then begins its operation. So a dialog that its own OnShow dismisses was drawn for a frame here.

Now each verb runs its synchronous half inside the binding, against the one host GlueMgr the VM shares. Network work stays asynchronous. The Update arms (0x46b930) fire their signals before their later state writes. A failed world connect's "Retrieving realm list" closes on its reply, and a failed logon closes the link but keeps an unattended resubmit.

GetServerName reads the realmName CVar and the selected realm's flags live (0x46d280).

Tests cover each verb's events inside its call, a verb nested in another's fan-out, each arm's order, and the refused login never showing its first dialog.

Fixes #1065

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A scroll child drew by its own strata and level. So on the stock create screen the info text drew over the icon frames raised beside it.

In 1.12.1, SetScrollChild (0x786ce0) flags the child's subtree. The flat pass skips flagged frames (0x7659df). The ScrollFrame's HIGHLIGHT callback (0x787220) draws the subtree clipped to its hit rect, frame by frame in link order (0x76b3f0), and draws nothing for an empty clip. Picking keeps each frame's own level, clipped to the first unflagged ancestor (0x76b020).

benilla now draws and picks the same way. A frame shown or hidden inside the subtree keeps the others' order. The options window's scroll trough sits at its ScrollFrame's level, so the page still draws over it. Deviation: a scroll child reparented out draws once, not twice.

Tests cover the order, nesting, clip, pick and the stock create panels.

Fixes #1084

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Four stock glue screen defects:
- Credits raised a Lua error: PlayCreditsMusic was unbuilt. It now fades the theme and plays the seven city intro tracks in turn (0x45b000, table 0x836028).
- HideCursor/ShowCursor (0x46db70) only set a flag. The pointer now hides over the credits and the movie on every platform.
- A world status past the 83-row table read RESPONSE_FAILURE's text; GetStatus formats it "(N)" (0x5aa960).
- A plain Model pane (the realm wizard) was lit only by the widget's light. It now takes its M2's own lights, as 0x718960 emits them, under the widget light and fog.

Also writes the Deviation line for the config save: benilla saves a quiet second after a change, where SaveConfig (0x63d980) writes only at world entry, logout and shutdown, so a crash keeps the settings.

Tests cover each.

Fixes #1070
Fixes #1069
Fixes #1058
Fixes #1082

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SMSG_SET_FORCED_REACTIONS (0x2A5) was never read, so a forced-reaction aura such as the Gordok Ogre Suit left the ogres hostile.

The client stores the table whole (handler 0x4d59a0) and looks a target faction up in it (0x4d6490). Unit-to-player reactions consult it at 0x6065e6, behind the leading rungs and capped at 6. Player-to-unit reactions consult it at 0x6063e0. On arrival the handler fires UNIT_FACTION for every bound unit. ShutdownGame clears the table, never a worldport.

benilla now decodes the table onto Reputations, answers both reaction directions from it, fires UNIT_FACTION on each arrival, and clears it at the session's end.

Tests cover the decode, both directions against an at-war faction, the caps, the replace and clear, and the target frame recolouring.

Fixes #78
Supersedes #1080

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: Claude Sonnet 5.5 <noreply@anthropic.com>
The glue booth threw its model away where 1.12.1 keeps it. So the create preview restarted its animation on every customization or class change, and the login scene restarted after Credits.

In 1.12.1, the create setters (0x478790 and its siblings) and the re-dress 0x470800 update the same CharacterModel. Only a race or sex change builds a new one (0x470b70). A hidden ModelFFX is unlinked from its strata bucket, so its OnUpdate (0x76d7f0) and its clock stop until it shows again, and the clock steps in whole milliseconds (0x76d846).

Now the booth re-dresses its body in place and keeps a hidden or left scene parked, resuming it on return. Each scene has its own clock (one SceneClock on its root), its own materials, so two scenes on one file stay independent.

Tests cover the keep, the park, the clocks and the cleanup.

Fixes #1083
Fixes #1068

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A failing test gate printed only its log's last 30 lines. Those are the doc-test runs after the failure, so the failing test's name and its panic were missing from both the gate output and the land log.

scripts/gates.sh and scripts/check.sh now print the panic lines, each test's FAILED line and any compiler errors from the log first, then the tail as before.

Checked on a sample failing log; both scripts pass `bash -n`.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Leaving the world (a /reload, logout or worldport) kept loot, trade, mail, the auction house and the other NPC windows open, and a reload kept the target. A loot release for another object, or with result 0, closed the open window.

In 1.12.1, the sweep 0x490a80 closes each window in its legs, in the dying interface: loot with its release, trade cancel, mail, auction, gossip, trainer, taxi, merchant, tabard, stable and the rest. A /reload first drops the target loudly and ends a follow; a logout runs the sweep first and clears the target silently. The release response closes only for the latched guid with a nonzero result (0x5ec090).

benilla now runs those legs in that order on each root, gates the release, and lets each leg drop its own item locks.

Tests cover each leg, each root's order and the release gate.

Fixes #1066
Fixes #1044
Fixes #209

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On the stock credits screen the fades drew as solid black boxes over the text, and the black bars covered the borders.

A texture's gradient now tints its four vertices over the texel (0x77f910, TL BL TR BR). A gradient alone draws nothing, and any vertex-colour write collapses it (0x77f750).

A layer batch sorts its quads by texture handle at End (0x7731a0), with an exact port of the client's qsort. Its tie order was checked against the client's own code. The client's handles are heap addresses, so different textures rank by a creation stamp that follows allocation order. That is a narrowed Deviation.

Extract costs what it did before. The options window's ground dim moves to BORDER to keep its look.

Tests cover the gradient, the sort and the stock credits frame.

Fixes #874
Fixes #1095
Fixes #1092

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Seven Lua API and event edges departed from 1.12.1:
- SetTradeSkillSubClassFilter and SetTradeSkillInvSlotFilter now raise their errors (0x4ffc70, 0x4ffe60).
- RollOnLoot sends any roll type (0x61bdf0).
- TogglePetAutocast drops a held pet action instead of toggling (0x4be290).
- GetInboxHeaderInfo's hasItem is 1, not the count (0x4aef38).
- UPDATE_BINDINGS fires only from SetBinding and the copy into set 3 (0x4b8056, 0x4b649d).

Mail now follows the result handler 0x4ad050. Take, return and delete edit the rows in place, with no re-list, and each arm fires its own MAIL_INBOX_UPDATE. MAIL_FAILED fires for every result, after the request lock lifts (0x4ad164). Every list result fires MAIL_INBOX_UPDATE. Taking a template mail's item keeps the letter (0x4ad7fd).

Tests cover each.

Fixes #1048
Fixes #1049
Fixes #1055
Fixes #1072
Fixes #1071
Fixes #1053
Fixes #1052
Fixes #239

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
benilla's reference facts come from reading the 1.12.1 client at its bytes. That work is now public as [benilla-re](https://github.com/samwhosung/benilla-re): one row per function, global and field, a doc per system, and the data tables, each cited by client address.

This links it from the README, from METHOD's "The reference first" and from CONTRIBUTING's step on stating the reference fact, so a contributor knows where a client address leads. Docs only; no code changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every shown EditBox now keeps the client's text window, focused or not. An unfocused box used to cut long text to "prefix..."; it now shows where its caret last left it, re-windowing at 0.25 or 0.75 of the width when the caret leaves (0x77d705–0x77d858, 0x77d320). A password box never re-windows (0x77d769) masks one `*` per byte, and copies as an empty string (0x77e1f8). A window starting inside a coloured run keeps its `|c` code (0x77d8ba).

OnCursorChanged reports the font's line height as `h` (0x77b970), and measures `x` from the window's start (0x77dcd8) by the text's justify (0x77db63).

Paste types the clipboard one character at a time through the typed-character insert (0x77e250 → 0x77c200), so `|` doubles, a tab is four spaces, and OnChar fires per character.

Tests cover each through the Lua surface, plus an app test drawing an unfocused window.

Fixes #728
Fixes #739
Fixes #740
Fixes #744

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A MessageFrame's link is text; only chat lines take link buttons
- set_spans' doc says only a ScrollingMessageFrame's spans take buttons

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Sam <samuv.eth@gmail.com>
In 1.12.1, `TargetByName` (`0x489d60`), `TargetUnit` (`0x4899d0`) and `ClearTarget` (`0x489ff0`) commit through `SetSelection` (`0x493540`) before they return, so the next line of the chunk reads the new target. benilla queued them, so `UnitName("target")` on the next line read the old unit.

The bindings now update the `"target"` view and the resolver's guid at once. They rank over a per-frame candidate feed that shares the app scan's candidate test and rank. A pick `SetSelection` refuses (not selectable, `0x4935ee`) leaves the view alone. The unit feed re-pushes the committed selection the next frame. A full tie goes to the lower guid, written as a deviation.

Tests: the chunk reads the pick; namesake ties, refused picks and the re-push, in benilla-ui and through the app's real feed.

Supersedes #1225, whose branch GitHub would not let us push to.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Contributors and their agents had no word on how to use the reference beyond a link.

- `AGENTS.md` sends an agent to benilla-re before it builds: clone it beside the repo and grep it, or look an address up on its site.
- `docs/CONTRIBUTING.md` gets "The reference": how to look a fact up (the site, `symbols/`, `tables/`, `systems/`), how to weigh a row's confidence, and that a gap or an error goes to benilla-re as an issue or a finding.
- `docs/METHOD.md`, the pull request template and the issue chooser say the same in a line each.

Docs only; `scripts/check.sh` is green.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`reference/` held benilla's own copies of the reference's tables, some derived by its own scripts from older inputs: the fire-site table kept one event id where a site fires two, and credited glue-screen sites to in-game events.

Now `reference/` is benilla-re's tables at the commit `reference/PIN` names, byte for byte: bindings, events, glue events, messages, event arguments, verb events, the glue surface, the Lua globals, chat types and groups, and CVars. `scripts/sync-reference.py` copies them, and its `--check` runs in the gates and in CI. Four generators and nine old files go, and every reader moves to the new columns.

New tests hold the chat type, chat group and CVar tables to the copies; the CVar rows already filed are listed as known.

Fixes #245
Fixes #960
Part of #244

🤖 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>
A fresh-clone walk-through as a new contributor found four gaps in the docs:

- No quick test loop: `scripts/check.sh` has nothing to check on a clean clone, and `cargo test -p <crate>` was not mentioned. "The loop" now says both.
- The doc-link lint is a full `cargo doc` of the workspace; METHOD and the script's header now say so.
- What to cite while a benilla-re gap is still open: the client address, with the benilla-re issue or pull request linked.
- When `reference/PIN` moves: only with a change that needs a newer benilla-re row, to a commit on its main. Stated in CONTRIBUTING and `reference/README.md`.

Docs only.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Four UI-core fixes, each against the client's code:

- Text breaks on `\r` and `\n` before any flag is read (0x5c286e); `|n` is refused only under flag 0x200, which no stock style sets.
- A texture or sound open with flag 1 or 2 asks the install's loose-file index first (0x647f89), then the archives, so an addon's loose file shadows the archive copy.
- A tiled backdrop divides the frame's own rect by the tile size (0x77f0c0), in FrameXML and glue alike.
- A chat frame releases every live link button before placing any (0x788c00), and a resize in screen units, insets times scale (0x788342), relays the lines and rebuilds them.

Each has a test that fails without it.

Fixes #877
Fixes #880
Fixes #898
Fixes #962

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CVars and Lua arguments, each against the client's code:

- A CVar row carries the reference's refusing callback, run before the value is stored (`CVar::Set`, 0x63df7d): the video rows, six camera rows, `farclip` (NaN passes, as 0x688d40's unordered compare lets it), `frillDensity`, `gxTripleBuffer` (0 or 1, 0x63afe0) and `gxRefresh` (the 11 rates at 0x8645e0).
- `RegisterCVar` creates at most 30 CVars per process (0x488b25); `GetCVar` and `GetCVarDefault` raise their usage lines.
- `Minimap:SetMaskTexture`, `CloseLoot(1)` (ERR_LOOT_NO_UI, 0x48f27b) and the onSelf of `UseContainerItem`/`UseInventoryItem` follow the client.
- Lua's numeral reader has no hex and takes `d` as an exponent, as the client's strtod (0x4112d1).

Each has a test that fails without it.

Fixes #558
Fixes #559
Fixes #567
Fixes #696
Fixes #988
Fixes #989
Fixes #1025
Fixes #302
Fixes #366
Fixes #1275

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
NPC windows share 1.12.1's one interaction latch (0xb4e2d0). Every opener closes the current holder through the dispatcher 0x493310, in its order, before it opens (0x4930d0); each close core releases on its own cell, so a direct close while latched fires its `*_CLOSED` twice (quest once: it zeroes its cell first, 0x501201).

All 13 windows are on it: gossip, quest, merchant, taxi, trainer, bank, stable, registrar, tabard designer, mailbox, auction, trade and item text. Opening a vendor during a trade closes the trade and sends one `CMSG_CANCEL_TRADE` (0x4bf527); walking away closes a trade at the interaction leash (0x493230). A second gossip menu from the same NPC closes nothing (0x4e1fb8). A chained quest turn-in leaves the panel up (0x5dc6d7).

Tests pin the event order and count for every pair of windows, and the packets sent.

Fixes #1054
Fixes #1099

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The gates memo kept its verdict for a change under `docs/` or in a top-level `*.md` only, so a change to an issue or PR template ran the whole chain again. No gate reads the templates; the workflows under `.github/workflows` stay code.

Scripts only.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FontString text is laid out by 1.12.1's line kernel (0x5c7470) instead of a whitespace-only wrap.

- Breaks: the classifier 0x5c7780, after `-`, `:` and `/`, before whitespace, and between CJK and Hangul characters, with its 25 openers and 57 closers. Whitespace above U+0100 is Windows' C1_SPACE set, as the client asks it.
- A row ends at the last break, else at the glyph that did not fit; the skip after it crosses blank lines and stops at a colour code; a glyph wider than the row ends the layout.
- `nonspacewrap` (off by default, the XML attribute and `SetNonSpaceWrap`) carries a force-cut row into the next (0x5c76ea).
- `maxLines` cuts with "..." at maxLines × the line pitch, also for an auto-height string (0x771ec0, 0x7729b0).

An emulation of the client's draw loop, fit walk and EditBox row table matches on 400k fuzzed strings.

Fixes #217
Fixes #218
Fixes #219

🤖 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 follow-ups on the interaction latch:

- The NPC's hello and goodbye come from the latch: a window registering for a different non-player unit plays the hello (0x49318f), a release to nothing plays the goodbye (0x493210), and a swap or a walk-away (0x493305) plays none. Every window greets alike; a game object or a player says nothing.
- Death and shapeshift close the window: the player's death releases with the goodbye (0x5e9a51), the latched unit's death releases without it (0x605950), and a shift into a form where IsShapeShifted holds and the row lacks flag 0x8 releases (0x5de07f).

Tests pin the hello and goodbye per window, the swap and walk-away, and each release, and fail on a goodbye where none plays.

Fixes #1287
Fixes #1288

🤖 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>
44 Lua functions 1.12.1 registers were undefined, so an addon calling one raised.

- Units: `UnitCanAssist` (0x516bb0, computed as the first unit toward the second) and `UnitIsTrivial` (0x519d70). The assist rule now runs player-to-unit where the client does (0x6066f0), which corrects UnitBuff's and the TAB scan's answers for reputation factions.
- Quests, trainers, crafts: `GetActiveLevel`, `GetAvailableLevel`, `OpenTrainer` and seven skill-line functions.
- GM survey, LFG, lottery, minigame, skill points and the file verbs, from the install's tables: the survey (id 6) and LFGDungeons are loaded, and `GMSurveySubmit` (0x32a), `LFGQuery` (0x1ff), the lottery verbs (0x334, 0x336) and `BuySkillTier` (0x21f) send what the client sends.

Every send has a byte-exact golden; usage strings are the client's.

Fixes #333
Fixes #335
Fixes #338

🤖 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>
Five fixes to how `SMSG_UPDATE_OBJECT` is applied, each against the client's code:

- An out-of-range block never releases the active player (0x465f39–0x465f45).
- A movement block for the unit we move, the player or a possessed unit, is dropped (0x4670a5–0x4670bb).
- A create spline reads its three final-facing fields independently, in the order point, GUID, angle (0x47f0bf, 0x47f0e8, 0x47f0f9), so a server that sets two no longer breaks the packet.
- A game object is placed by its create block's position over its position fields (0x5f7046), and a re-create re-poses units only (0x46638c).
- The leave-world sweep's comment names the destroy path alone.

Byte-exact fixtures for the spline; each change has a test that fails without it.

Fixes #1307
Fixes #1308
Fixes #1309
Fixes #1310
Fixes #1311

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

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The in-flight cast slot is pushed and popped where 1.12.1 does:

- Every cast attempt pushes it (0x6e5026), and only an auto-repeat spell is popped after the send (0x6e5935 → 0x6e5989), so a ranged shot holds the slot until its result and a press meanwhile is refused with 0x61.
- An item use holds it only with `ITEM_FLAG_PLAYERCAST` (0x6e500b–0x6e501f).
- The slot is popped by the spell's `SMSG_CAST_RESULT`, success or failure (0x6e7408 → 0x6e741a), never by the GO.
- `SpellStopCasting` with the targeting cursor up aborts it and returns 1 (0x6e6e80).
- A same-spell re-press bails with no red line (0x6e4d43).

Each change has a test that fails without it.

Fixes #1291
Fixes #1295
Fixes #1303

🤖 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>
The event gates now read every place benilla fires a Lua event. One scanner finds firing functions by signature, with `queue_event`, `fire_global` and the glue closures included, and resolves names bound to locals. Tests fail on any other path into the queue. The shape gate refuses a list or name it cannot read. The `BENILLA_` exemption and the weaker exe-string gate are gone.

What the gates exposed, each against the reference:
- `GOSSIP_SHOW`, `MERCHANT_SHOW`, `MERCHANT_UPDATE`, `TRAINER_SHOW` and `TRAINER_UPDATE` fire with no arguments (`SignalEvent 0x703e50`).
- `PARTY_MEMBER_ENABLE`/`DISABLE` pass the slot as a number (`%d`, `0x4e869d`).
- `FRIENDLIST_SHOW`, which 1.12.1 lacks, is no longer fired; `FRIENDLIST_UPDATE` is.
- The three bind-confirm events count as produced. Two gap rows are reworded.

Fixes #86
Fixes #202
Fixes #313

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

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The Corrupted Ashbringer's glow bones carry constant Stand rotation keys that set the
plane of its XY-quad emitters (flag 0x1000); at rest pose the quads lay 90° off. The
client arms every M2 with animation 0 at load, so a held item with emitters on its own
bones now gets a skinless RigPose and an idle player, and its emitters ride their bone
anchors. The character window and dressing room carry the item's skeleton and idle
(EffectRig) and pose the effect host the same way.

Built and tested on macOS.
Lua errors and chat gates where 1.12.1 has them:

- `GetCVar`, `SetCVar` and `GetCVarDefault` raise `Couldn't find CVar named '%s'` (`0x488be7`). Every script-visible CVar of the reference is now registered, at its default, so only a name 1.12.1 lacks raises.
- `SendChatMessage` raises on an unknown chat type (13 tokens, `0x49f7a0`), and on a whisper or channel line with no target (`0x49f310`, `0x49f4ca`).
- A non-string type is SAY.
- With no player, the call returns quietly.
- Party, raid and raid warning lines are refused outside their group (`0x49f33e`–`0x49f4c7`). A group leader's raid and battleground lines go out as the leader types (`0x49f3bf`, `0x49f48b`).
- A dead or ghost player's say, yell and emote are refused with `ERR_CHAT_WHILE_DEAD` (`0x49f592`).

Each change has a test that fails without it.

Fixes #1284
Fixes #373
Fixes #380
Fixes #1335
Fixes #1333

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

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Loose files in the install now answer the opens 1.12.1 flags, ahead of the archives (`0x647e60`, benilla-re `storm/mount`).

- The index walks the install folder, then `Data\`, keyed case-insensitively; the first entry wins.
- It answers interface files and textures, fonts (bare name first), cursors, sound, model frames and the minimap arrows.
- A `.MPQ` file answers its name from inside it.
- A name written with `/` misses, as in 1.12.1.
- A found file that fails to read does not fall back.
- The index empties where the reference empties it (`0x646fc0`): after saved variables, `AddOns.txt` and the glue sweep.
- A `Blizzard*` addon folder holding its `.toc` is skipped unread (`0x51fb50`), so the archive's copy loads. Nothing in the install is moved.

Each change has a test that fails without it.

Fixes #1144
Fixes #1145

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`docs/DEVIATIONS.md` lists every place benilla deliberately differs from 1.12.1, generated by `scripts/genmap.sh` at every land beside `docs/MAP.md`:

- The settings that ship off the reference default, with each one's reason, from the `Deviates` rows.
- Each `Deviation:` comment's first sentence, linked to where it lives, grouped by crate and module.

A test fails if a `Deviates` row takes a shape the generator would misread. README, METHOD and CONTRIBUTING link the doc.

Fixes #1289

🤖 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>
Cooldown reads, the GCD after an event, and lost control, as 1.12.1 has them:

- A cooldown held until an event (Stealth, Shadowform) reads start now, duration 1 ms, enable 0 (`0x6e1503`, `0x6e1517`). Enable stays 0 once a held leg sets it, and the Lua getters answer the call's own time for a held start.
- An item's own category overrides its spell's (`0x6e1457`).
- `SMSG_COOLDOWN_EVENT` restarts the GCD from the event, which shares the recovery start (`0x6e15d8`, `0x6e185a`).
- While control is lost, spells, macros, spell-less items and forms grey unless the spell's immunity scan exempts it (`0x6e3dc3`–`0x6e3e4c`).
- Every self create gives control back (`0x5dea50` → `0x5fa600(1)`), firing `PLAYER_CONTROL_GAINED` when it was lost.

Each change has a test that fails without it.

Fixes #1296
Fixes #1297
Fixes #1298
Fixes #754

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

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Tooltip lines, EditBox byte caps and button slot textures, as 1.12.1 has them:

- A tooltip line with no text on either side is not added, and only a non-empty side is written (`0x530270`).
- A tooltip holds as many lines as it has `TextLeft<n>`/`TextRight<n>` pairs at load (`0x529650`), so a bare `GameTooltip` holds none. `AddFontStrings` adds one (`0x529940`).
- A clear blanks the left text and keeps the right (`0x530050`).
- An EditBox's `<FontString bytes="N">` and `SetMaxBytes(N)` cap it at N−1 bytes (`0x77a225`, `0x798f30`); `GetMaxBytes` answers N. Over the cap, the box drops a character or a whole link. The chat and who boxes stop at 255 bytes.
- A button texture path that does not load makes no slot region and keeps the old art (`0x7790ce`).

Each change has a test that fails without it.

Fixes #165
Fixes #177

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

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The Warden `Deviation:` lines now give the reason, not only the fact. Warden's answers come from a module, native Win32 x86 code the server sends and the client maps, links and runs in its own process (`0x7a79d0`). The client parses none of the payload itself (`0x6ca5c0`). benilla runs no such code on any platform, so it has nothing that could answer, and a server that requires Warden would kick it 30 s in (`Warden.cpp`).

Fixes #1349

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every character model frame, named or anonymous, now draws the unit its own `SetUnit` names, in a booth of its own. The seven stock-name booths are gone: the character window, pet doll, inspect, stable, dressing rooms and tabard designer are ordinary instances, driven by their FrameXML calls.

- `SetUnit` stores the GUID and builds a copy when shown and the unit is held (`0x505b50`).
- `RefreshUnit` rebuilds from the stored GUID (`0x505e40`); hide drops the copy (`0x505ce0`).
- `SetRotation` and the paint timer turn it, with the model's fallback ids (`0x505bb0`, `0x505c50`, `0x711bf0`).
- The constructor's directional light lights a fresh pane (`0x505680`).
- The stable shows the live pet's creature-template display (`0x4cb99e`).

Each change has a test that fails without it.

Fixes #1346
Fixes #1347

Co-authored-by: migwynkriid13 <335116281+migwynkriid13@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: migwynkriid13 <335116281+migwynkriid13@users.noreply.github.com>
Five Lua API gaps, settled at the bytes.

- `FrameXML_Debug` sets its flag only from a number (`0x488440`); it always returns the flag.
- The eight debug globals exist and return nothing (`0x83de68` rows).
- `GetTalentTabInfo` returns the tab's SpellIcon path (`0x4f30d0`).
- `SetEuropeanNumbers` stores its flag (`[0xcf4d24]`). The flag survives a reload. FontString and Button `SetText` swap `.` and `,` inside a number under the exact byte tests of `0x771c50`.
- The protected-action gate (`0x494a50`) fires `MACRO_ACTION_FORBIDDEN` or `ADDON_ACTION_FORBIDDEN`. It covers the 23 tag-0 movement calls, PitchUp/Down included, by context: a `RunScript` chunk, an insecure addon's files, inline scripts and binding bodies, or the client's own. Stack scan; the unmodelled taint cases are listed in `taint.rs`.

Tests: a test for each fact, including the gate through `LoadAddOn` of a real chain addon.

Fixes #60
Fixes #410
Fixes #162
Fixes #566
Fixes #486

🤖 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>
benilla read every DBC string from the enUS column and answered enUS everywhere. 1.12.1 reads its locale from `Wow.ini` in the archives (`0x40249d`). It joins `Language` and `Country` into a tag at `0xc2a2a4` (`0x402513`). It looks the tag up in the eight locale names to get the index at `0xc0e080` (`0x402562`); enGB keeps index 0.

- The chain reads `Wow.ini` with the client's ini rules: no trimming, and the first value wins.
- Every localized DBC read takes column `first + index`.
- `GetLocale` and the TOC `Title-xxXX` pick follow the index (`0x48d8b0`, `0x46ce40`).
- The logon challenge (`0x5b2115`) and the name-cache header carry the tag, so this install sends enGB.

Tests:
- A synthetic WDBC per locale.
- 43 loader probes at an empty locale.
- A byte-exact golden for the challenge.
- GetLocale in glue and in game.

Smoke: green.

Fixes #1338

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Nothing listened to the player's item objects after they changed. Action and trade slots diffed snapshots, so an addon missed restacks, enchants and destroys.

- **The seam.** `item_watch` emits one notice per reference handler run, in wire order. The handlers are destroy `0x5d8660`, stack `0x5d9400`, charges, entry, enchant `0x5d9a90`, record `0x5d8440`, seats `0x4f8db0`/`0x4f90d0`/`0x4f8ec0` and visible items `0x5dee30`.
- **Action bar.** Each notice recounts every item slot holding the entry and fires `ACTIONBAR_SLOT_CHANGED`, with no change test (`0x4e5c70`, 16 callers). Arrival recounts quietly, then fires slot 0 (`0x5dea50`). The per-frame poll is gone.
- **Trade.** Our slot re-sends or clears its item and fires `TRADE_PLAYER_ITEM_CHANGED` (`0x4bfb10`, `0x4bfb60`). Slot 7's enchant spell signals its side and names the enchantment (`0x4bf2b0`). `ClickTradeButton` refuses bound, quest and bag items (`0x4bfeb5`).

Tests cover every watcher, consumer order and refusal.

Fixes #1064
Fixes #1062
Fixes #1063
Fixes #1041

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Re-vendors `reference/` at benilla-re `c854c4e`, which now resolves computed event ids at their fire sites. 97 events gain a producer: the 93 `CHAT_MSG_*` events and the player-field watcher's `"player"` events.

The fires change to match:

- `PLAYER_XP_UPDATE`, `PLAYER_PVP_KILLS_CHANGED` and `PLAYER_PVP_RANK_CHANGED` carry `"player"`, once per changed watch (`0x5de4b0`). A level-up fires twice.
- `UPDATE_EXHAUSTION` fires bare from the rest byte (`0x5de4e0`) and `"player"` from the pool. `UPDATE_FACTION` fires bare from the list, twice on initialize, and `"player"` from the watched index. Everything fires in field order.
- `PLAYER_DAMAGE_DONE_MODS` is produced (`0x5ddbc0`, `0x5ddbe7`, `0x5ddc0e`), and `UNIT_DAMAGE` keeps only its own fields.
- The rank watch covers all of `PLAYER_BYTES_3`, so its deviation goes.
- The shape gate reads the chat fire's shared ten-value list.

Tests: the shape gate, plus feed tests for each watch and its order.

Fixes #237
Fixes #244
Fixes #296

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The protected-action gate (`0x494a50`) now runs its input arms. Tags 2, 4 and 10 pass tainted code only under an input flag:
- the edit box's Enter (`[0xcf4dc4]`);
- a button's click or double-click (`[0xcf4da0]`);
- a running binding.
Without one they refuse silently. This covers casts through `TryCastSpell`, the pet actions, `SpellStop*`, `SpellTargetUnit`, item targeting, `TargetNearest*` and `AttackTarget`, and `ReloadUI`.

Branches the reference runs before the gate still run when the cast is refused: aura and form cancels, and the bind-on-use and soulstone legs. Queued calls replay the input events they ran under, around their own events too. So a TAB-driven `PLAYER_TARGET_CHANGED` handler casts as in 1.12.1.

`SetEuropeanNumbers` now also swaps separators in tooltip lines, both message frame classes (at show for scrolling ones), SimpleHTML, chat bubbles and nameplates.

Tests: a 30-verb census, and the stock buttons, bindings, `/cast`, spellbook and macros.

Fixes #1354
Fixes #1355

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
**Problem.** `localhost` resolves to `::1` first. benilla dialled every resolved address in order, so a realmd bound only to IPv4 left a Windows login waiting about 2 s on `::1`.

**Reference.** 1.12.1 resolves the realmlist host to one IPv4 address and dials it alone (`WowConnection::ConnectToHost` `0x5b6e40`): `inet_addr` (WSOCK32 ordinal 10), else the first entry of `gethostbyname` (ordinal 52). It never dials IPv6.

**Change.** The dial takes the resolver's first IPv4 address and nothing else. A host with no IPv4 address fails as unresolved.

**Tests.** `localhost` dials `127.0.0.1`. The first IPv4 address of several is dialled. A host that resolves only to IPv6 gives no target.

Smoke: green.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Sam <samuv.eth@gmail.com>
Item counts and trade state, as 1.12.1 holds them.

- Counts walk mask 0 (`0x622420`): worn gear, bags, backpack and keyring, skipping broken and flagged items. A charged item counts its uses left (`0x4e6d20`). A reagent is red only when no copy is carried (`0x52f49f`). The equipped border asks the walker at mask 1.
- The trade keeps both item offers after a close until the next open (`0x4bf549`). Money zeroes on the ending statuses (`0x5d4884`), and the player getter is gated (`0x4c0706`). Traded-away items clear before `TRADE_CLOSED`.
- `TRADE_UPDATE` fires only from a getter's landed item record (`0x4c034f`).
- A stale own-side status is dropped (`0x5d49ba`). Every edit sends whatever the window state and bumps the counter, money verbs included.

Tests: each change, including the stock money re-entry.

Fixes #1356
Fixes #1357
Fixes #1358
Fixes #1359

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In 1.12.1 the stat, XP, rest, honor and faction events come from field watches. The notify pass (`0x465330`) runs each watch once per values block, in field order, only when its bytes changed (`0x4655bb`). A first create and `/reload` run none. benilla diffed snapshots per frame, fired on first sight and lumped fields together.

`field_watch` now writes one notice per block per watch from the object handlers. The events fire from those notices:
- the unit-field bridge (`0x51bbb0`/`0x51bd50`), one per field, to every token of the guid (`0x515c50`);
- the player watches (`0x5dd8a0`);
- the skill watches (`0x5ed990`: UNIT_ATTACK, UNIT_DEFENSE, UNIT_RANGED_ATTACK_POWER);
- the weapon slots (`0x5dee30`).

The feeds only push. POSSTAT/NEGSTAT and `has_offhand` fire nothing, and the AP multipliers fire.

Tests:
- field order and per-block counts;
- silence on create;
- bridge ids pinned to `events.tsv`;
- push-before-fire order.

Fixes #207
Fixes #286
Fixes #1379
Fixes #1381
Fixes #1382
Fixes #1383
Fixes #1384

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`SelectTarget` (`0x493540`) checks two things before it selects:
- a charmed player returns (`0x49359c`);
- with the spell targeting cursor up, a unit is bound to the cast and nothing is selected (`0x4935ca`–`0x4935d5`).

Every benilla selector skipped this. Now one select front models it, and every caller runs through it: the script verbs, TAB (which still advances), `AttackTarget` and the Interact attack, nameplate and body clicks (both greet first, `0x4927fd`), loot select, `/target`, and the attacker acquire. With the cursor up, `AttackTarget` and the pet Attack send the cast first.

`CastBookSpell` (`0x4b3474`) stops targeting when the cursor is the same spell.

Tests: each verb with the cursor up and down, a stock TAB with a heal's cursor up, the charmed cases, and the wire order.

Fixes #1380
Fixes #1378

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Places that read the client's locale in 1.12.1 now follow it:

- **Creature type words.** `CreatureType.dbc` at the locale column (`0x51a2dc`). The tooltip gates type 10 itself (`0x52a2c6`), so `UnitCreatureType` answers it and that deviation is gone.
- **Race and class names.** `ChrRaces`/`ChrClasses` at the locale column (`0x518301`, `0x518451`), feeding `UnitRace`, `UnitClass`, `$R`/`$C`, who, friends, guild, raid, the battlefield score and the glue screens.
- **Name check.** Charsets come from the Wow.ini Region or the locale (`0x6c91b8`, `0x86ca0c`), with the Hangul syllable list (`0x86ca30`) and the per-charset caps.
- **Display.** koKR chat bubbles are left-justified (`0x4b16e7`). The loading tip font is picked by locale (`0x82f018`).
- **CVar defaults.** AutoInteract on koKR, gxWindow and gxMaximize on zhCN (`0x603368`, `0x63a738`). A saved value still wins.

Tests run at non-zero locales throughout.

Fixes #1361
Fixes #1362
Fixes #1363
Fixes #1364
Fixes #1365

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
**What was wrong.** A nameplate created during a frame drew one frame late.

**Reference.** The plate update (`0x764340`) draws at `0x764347`. Building a plate (`0x765896`) drains its batch records at `0x7658e2`. The world render runs inside the WORLD callback (`0x482d70`). So a new plate's first WORLD batch draws in the frame it was made.

**Change.** The first WORLD batch boundary is kept for a new plate. A plate released and retaken in the same sync keeps its batch records (`0x772fd0`).

**Tests.**
- `a_slot_retired_and_retaken_in_one_sync_draws_at_once`.
- The contributor's first-draw tests.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Sam <samuv.eth@gmail.com>
The README still asked for an English client. Since #1360, benilla reads the client's locale from `Wow.ini` and shows each game-data string in that language, so any 1.12.1 client works. The line now says so, and notes that development runs on English clients.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Sam <samuv.eth@gmail.com>
**What was wrong.** The loose-file index walked every folder below the install on the first open. The walk followed links and blocked the frame. A large tree under the install, such as a Wine prefix whose `z:` links `/`, left the window empty while memory climbed (6.6M keys, about 4 GB).

**Reference.** The walk at `0x646ea0`/`0x646910` follows links and keeps no record of visited folders.

**What changed.**
- A lookup lists only the folders along its name, each once, and caches answers until the index empties.
- Keys and skip rules are unchanged.
- A folder reached through a link now answers, as the reference's does.
- Listing on first reach is recorded as a deviation.

**Tests.** Lookups match a full walk over 1.9M probed keys. The prefix install lists 8 folders, down from 207, and reaches the menu in 0.77 s.

Fixes #1398

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Unit events fired from a per-frame diff of each token's values: a new target re-fired its events, a roster packet fired member events, a `targettarget` token appeared, and cost-modifier changes fired no UNIT_MANA.

In 1.12.1 they come from field watchers (`0x51bbb0`, `0x5e2510`, `0x5dd8a0`) and the flag callbacks `0x5ee990` and `0x5ff580`, signalled per values block through `0x515e50`, and from party records compared on member stats (`0x5f0b80`).

Now every unit event fires from a field-watch notice or its packet. That covers health, power, flags, auras, rest, PvP, play time, regen, pet attack, the pet bar and the guild tag. Name answers signal only on a cache miss. The per-frame fires are gone.

Tests: each arm's notice and order, and no-fire tests that fail on the old feeds.

Fixes #1389
Fixes #1390
Fixes #1393
Fixes #1394
Fixes #713

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Three defects a fork found.

- A worn item the server answers as unknown left the player unattached. 1.12.1 holds the player while a query is out (`0x5ed771`, `0x5ec8f0`) and releases it on a refusal without applying the item (`0x55bdf5`). Now the slot stays bare.
- Templates and names were read by the guid's entry bits. 1.12.1 asks the GameObject cache by the descriptor's `OBJECT_FIELD_ENTRY` (`0x5f7d77`) and keys a creature's record by it too (`0x604629`, `0x60b151`). Every reader of a streamed object now goes through its descriptor; guid bits are left only for a guid not in view.
- A negative aura duration read as 49 days. The span is signed: below zero is lapsed (`0x4e4472`), except `i32::MIN`, which reads as 2^31 ms, as the client's wrapped compare does.

Tests: readers keyed by a field entry unlike the guid bits; the refusal; the signed span.

Fixes #1402
Fixes #1404
Fixes #1405

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The wheel did nothing over action buttons, unit frames or the minimap. benilla gave the notch to the topmost wheel frame or its parents, and zoomed only off the UI. A sideways scroll also reset the idle timer.

In 1.12.1, `0x7664f0` walks the wheel list under the cursor, strata then level then age, and the first visible frame with `OnMouseWheel` consumes it (`0x76c180`). The world frame's slot runs the zoom binding (`0x483d20`), and an unbound chord passes the notch on. Only `WM_MOUSEWHEEL` stamps idle (`0x76651f`).

Now the wheel walks once, and the world takes what is left. Scroll frames are no longer born wheel-enabled (`0x786a40`). On macOS a Shift+wheel, which the system turns sideways, counts as vertical again.

Tests: no-handler frames reach the zoom, handlers consume, off-cursor parents get nothing, a sideways scroll leaves idle alone.

Fixes #176
Fixes #224

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Three defects on the stock glue screens.

- After a trip into the world the login or character scene came back black or frozen. The glue booth resizes its image in place, which gives Bevy a new GPU texture, but the UI pass's cached material kept the old one. Materials over a modified image are now re-prepared.
- `reloadUI` and a resolution change did nothing at the login screens. In 1.12.1 they set a flag (`0x4035f0`, `0x46abe0`, which reads `gxResolution`) that rebuilds the glue when idle and signals the current screen again (`0x46b930`, `0x46b840`). Now they do the same; a window drag does not.
- The agreement header measured its title as 0, because the glyph cache was built after the first glue screen. It is built first now.

Tests: the material re-prepare, the rebuild and its trigger, the startup order.

Fixes #1147
Fixes #1313
Part of #1002

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Taxi route lines drew tiled copies beside each line. benilla chose repeat or clamp from a texture's current UVs. Lua-set textures with UVs past [0,1] tiled.

In 1.12.1 the wrap is fixed when the file loads (`0x770200`, both axes, sampler table `0x80a254`: clamp or wrap). An XML `<Texture>` passes 1 only when its own `<TexCoords>` leave [0,1] (`0x76fe20`, each template level with its own flag). Lua `SetTexture` passes 0 (`0x79bda5`). `SetTexCoord` never changes it. A backdrop's background follows `tile`, its sides wrap, its corners clamp. A same-file call keeps the loaded texture (`0x770225`).

Now each texture carries its load-time flag and the sampler follows it. Route lines draw as one line. The stance bar's middle strip clamps, as its XML loads it.

Tests: the Lua, XML, template, same-file and backdrop flags, and the sampler pick.

Fixes #870

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A creature not in view is named by the entry in its guid. 1.12.1's combat-log namer reads guid bits 32-47 for it (`0x6265c4`–`0x6265e0`). The servers in use put the entry at bits 24-47, so the client's read would name every such creature wrongly. benilla reads bits 24-47; this records that as a deviation, so `docs/DEVIATIONS.md` lists it. Comment only.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The in-game EditBox caret was a 1-pixel white bar, the PC keymap lacked 1.12.1's Ctrl editing letters, and a word was an alphanumeric run.

In 1.12.1 the caret is 4 script units wide (`0x77ba2d`, the `SetWidth` scale) in the box's text colour (`0x77e2a0`). `0x77b160` binds Ctrl+B/F/D/W/U/K/P/N beside A/C/X/V (tables `0x77b6cc`, `0x77b69c`), and tests Shift before Ctrl on Delete, so Ctrl+Shift+Delete cuts. A word is a run of non-spaces (`_iswspace`, `0x77c3f0`, `0x77c7a0`), so Ctrl+Right ends at the next word's start.

Now the caret, the chords and the word rule follow the reference. Ctrl is its own key on the Mac, so it takes the same letters and word deletes there. The Cmd and Option chords are unchanged.

Tests: caret width and colour, each chord's edit, the word rule.

Fixes #183
Fixes #749

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Two UI tests passed without checking what they claim.

- `every_global_a_migrated_window_calls_is_answered` read each window's entry and its same-name `.lua` only. A chain file can load Lua under another name (`ActionBarFrame.xml` loads `ActionButton.lua`) and include templates. Its locals scan also took `F` in `local a = F(x)` for a local. It now reads every `<Script file=>` and `<Include file=>`, as the loader does. The hand-run `shadowed_reference_functions` follows the same files.
- `no_bottom_band_frame_overlaps_a_raised_bar` checked bag frames no file defines, and its pair floor passed on the chat frame alone. It now opens the stock `ContainerFrame1`-`5`, and every tenant must meet every raisable bar.

Bite: a global missing from `ActionButton.lua` or an included template fails the first test; bags that do not reseat on a raise fail the second.

Fixes #311
Fixes #312

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Two pieces of stock UI state were set at the wrong point of the load.

- The buff rows were repositioned right after the manifest loaded, before any addon. In 1.12.1 `CGGameUI::Initialize` loads FrameXML, then the addons (`0x4900a3`), then the saved variables and `VARIABLES_LOADED` (`0x4900b2`), whose stock `UIOptionsFrame.lua` arm runs `BuffButtons_UpdatePositions`. The early pass is gone, so addons load against `BuffFrame.xml`'s anchors.
- An addon's minimap mask survived a relog. 1.12.1 reloads `Textures\MinimapMask` (`0x4ed810`) only from `InitializeGame` (`0x48f590`) at world entry, and `ReloadUI` (`0x48fbf0`) keeps it. World entry now restores the default; a reload keeps the addon's.

Tests: an addon sees the XML anchor at file scope and in `ADDON_LOADED`; a relog restores the mask; a reload keeps it.

Fixes #314
Fixes #695

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Three defects in the stock glue path.

- `SetPreferredInfo` turned pvp and rp into booleans. 1.12.1 reads them as integers (`0x6f1c10` returns a number unchanged) and compares them raw with each realm's 0 or 1 (`0x46ea20`), so a 2 matches nothing.
- The queue's "Estimated time" climbed while queued. `0x46ae50` stores an estimate per packet, only when the position moved forward against the oldest sample (`0xb41df0`), and the dialog counts down from it (`0x46bcb8`). Now it does the same.
- The realm walk and the glue round-trip took an empty roster for "not at character select". Both now key on the roster arriving, time out, and run under the stock screens.

Tests: raw integer compares, the countdown and its keep rule, empty-roster arrival and the timeouts.

Fixes #1315
Fixes #1312
Fixes #458

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Under the stock glue screens a scripted run ignored `WOW_REALM` and parked at the realm list, because the realm policy lived in the native screen's module and ran only under `native_glue`.

The realm tables and the run-mode realm choice now live in `glue_session`, which serves both paths. The stock manager's connect dials the realm already chosen, then `WOW_REALM`, then `realmName` (`0x5ab6f0`), then the unattended pick. A run-mode pick sets `realmName` (`0x5ab7b0`) and the chosen cell (`0x46f1d1`), as a selection does. A player's run is unchanged and still gets the list or the wizard. `ClientState` moves out of `char_select` into `client_state`.

This is the first step toward the stock screens as the default.

Tests: the connect ladder; a scripted run with no `realmName` reaches character select over the fake realmd.

Fixes #1433
Part of #856

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An item tooltip's Locked line was always red, and no tooltip drew "Cooldown remaining".

- In 1.12.1, the lock check `0x5da5e0` colours Locked (`0x52bdde`): red when nothing opens it, the skill ramp `0x529fa0` for a known open-lock spell, green for a held key or an empty lock. benilla now feeds Lock.dbc and the player's openers and runs the same check.
- The spell builder draws the line when its recovery is at least a minute (`0x52f537`). The item builder draws it with no floor (`0x52e0fd`) for `SetBagItem`, `SetInventoryItem` and the `SetAction` item leg, which are the three setters that pass a cooldown. Worn items now carry theirs, and the line counts down while hovered.

Tests: each colour and ramp edge, the unit and rounding, the spell floor, and the line order.

Fixes #1024
Fixes #1017

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The character roster, the pick, `lastCharacterIndex` and the session edges (logout, connect, refused login, disconnect) lived in the native character-select screen's module, mixed with its UI. They now live in `glue_session`, ungated, so the stock screens own them. The native screen keeps only its UI and the refused-login dialog.

The logout smoke under the stock screens re-entered by sending the pick straight to the server, skipping `CGlueMgr::EnterWorld` (`0x46b500`): no state 8, no `lastCharacterIndex` save. It now selects the row and presses Enter World, as a player does.

Second step toward the stock screens as the default.

Tests: the refused-login dialog opens only off the stock screens; the smoke's stock re-entry goes through Enter World.

Part of #856

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`GetInventoryItemCooldown` answered `(0, 0, 1)` for every slot, so worn trinkets had no cooldown spiral. A charged item at 0 charges kept its cooldown.

In 1.12.1, `0x4c8a60` answers the item cooldown query (`0x6e2ed0`) for the active player's slot. It needs a use spell in the template's first block (`0x4c8b47`) and a charge left on a charged template (`0x4c8b55`). Anything else answers `(0, 0, 0)`. `SetBagItem` and `SetInventoryItem` take the plain leg for a spent charged item (`0x5347e3`, `0x53318a`), as does `GetContainerItemCooldown` (`0x4f9a48`).

Now the verb answers from the worn slot's cooldown under those gates. The slot feeds read `ITEM_FIELD_SPELL_CHARGES[0]`, and a held item with no use spell answers enable 0, as the query leaves it.

Tests: the verb's gates and errors, the spent-charge rule over five cases, and the container triple.

Fixes #1439
Fixes #1442

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Under `WOW_GLUE_STOCK=1` the login engine still wrote the hidden native `GlueDialog`: `drive_policy`, `to_select_on_roster`, `tick_login_caret`, `answer_dialog` and `exit_login` all ran ungated.

The engine moves to `glue_session/login.rs` (intent, attempts, the env fast path, the account guard, resubmit, quit) and tells a screen what it decided as a `LoginNotice`. The native login screen's `show_login_notices` turns each notice into its dialog, in the slot `drive_policy` held. `LoginPlugin` is now native-only, and the stock manager's frame moves into `UiScriptPlugin` with its ordering kept. `login/queue.rs` becomes `glue_session/queue.rs`.

Test: a schedule census fails on any login or glue-session system that writes `GlueDialog` without the native gate; it names the five above on main. Policy tests now assert notices. Live: native smoke, stock login, stock logout round trip, and a wrong password on both paths.

Part of #856.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Item tooltips lacked the lock's sub-line and a wrapped gift's giver.

The reference: the lock check (`0x52bd57`) takes the Lock row's slots in place. The first slot picks the arm: a key names its item in white (`0x52bf30`), and a skill names the LockType and the required value, coloured by the walk at `0x5da5e0`. A blank LockType name prints blank; only a missing row reads `UNKNOWN` (`0x52bede`). A wrapped item (flag `0x8`) prints `ITEM_WRAPPED_BY` with `GIFTCREATOR`, from the object or, for the trade partner's slots, from the block (`0x534559`, `0x534574`). Real lockboxes use locks whose first slot is empty, so they show no sub-line, as in 1.12.

The rows keep all eight slots, and both trade sides carry the wrapped word and the giver. Tests cover each arm, a blank name, and gifts in bags and trade.

Fixes #1440
Fixes #1441

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After login, quest givers showed no mark and could not be talked to until a quest update or a GM toggle.

1.12.1 asks a giver's status at every create (`0x5fba72`, `0x607380`) and when a released object comes back (`0x5fbe2f`). The right-click takes the quest service only with a cached status (`0x5df490`), so the missing mark and the failed interaction are one defect.

`query_statuses` ran before our own entity was tagged `SelfPlayer`, returned early and used up every change, so no giver was ever asked. A relog brought givers back under stale guid keys. Now:
- the ask key lives on each creature (`AskKey`);
- a new self resets it;
- the markers run after `WorldStage::Net`.

Test: givers spawned a frame before the tag are asked on entry and on relog. Live: 0 queries before; 11 asked and answered on each login after, and on a teleport and back.

Fixes #918

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With `WOW_GLUE_STOCK=1`, character select, realm list, create and the glue strings still ran native systems. They spawned hidden trees and could write the hidden `GlueDialog`. A refused scripted login showed nothing on the stock screens.

Every native glue system is now gated on `native_glue`. The cross-switch instruments (round trip, login shot, realm smoke) move into `glue_session`. Under stock, `WOW_CHAR` enters by the stock Enter World. The account guard's refusal opens the stock `OKAY` dialog, the path `CGlueMgr::Login` takes for an empty account (`0x46b090`). The schedule's ambiguity ceilings drop to 3939 and 372.

Test: a census fails on any system that touches the native screens' components or resources without the gate; it lists 44 on main. Live: native smoke, a stock login to create and back, a stock logout round trip, the stock realm smoke, and a guard refusal.

Part of #856.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Under #918, a system that read our own player ran before the player was tagged `SelfPlayer`. It returned early and used up its change ticks, so it lost every change that frame for good. Nothing in the schedule stopped that shape.

A schedule census now reads each system's access set. Any system that reads `SelfPlayer` or filters on it must be reachable from `tag_self_player`, or carry a row in `UNORDERED_AFTER_THE_SELF_TAG` with its reason. Of 153 such systems, 142 are ordered and 11 are argued: each returns early with no self and holds no change-tick state. The census also fails on a stale row.

Test: with `quest_markers`' `.after(WorldStage::Net)` removed, it fails naming `query_statuses`.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
benilla-lua is a Lua 5.1.5 interpreter in Rust that speaks the 1.12.1 client's grammar. It is opt-in behind the `benilla-vm` feature, with `benilla-mlua`, the slice of mlua that benilla-ui uses, over it. The C build stays the default. Its front end ports 5.1.5's `lparser.c` and `lcode.c`, so registers, limits and messages match the C build's.

Our finish on top:
- the collector counts growth as C does, off the write path;
- frames under a leaky generic `for` start nil, as `luaD_precall`;
- the budget hook counts what C dispatches, and costs nothing when unset;
- `__index` chains stop at 100;
- mlua 0.11's conversions;
- `vm_parity` answers recorded per platform.

Tests: `vm_parity` on both VMs; the front-end corpus (8,543 chunks) against `string.dump`; 7,613 app and UI tests on benilla-lua. Built on macOS; Linux by CI.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Sam <samuv.eth@gmail.com>
A fifth open loot roll was announced at once. The stock frames hold four, so its frame never opened and it could not be voted.

1.12.1 starts a roll through `0x61b430`: below four open it counts up and fires `START_LOOT_ROLL` with the wire countdown. Otherwise it defers the roll. Every close (own vote, WON, ALL_PASSED) fires `CANCEL_LOOT_ROLL` and then promotes the oldest deferred roll (`0x61b760`). An uncached item takes its turn when its record arrives (`0x61b460`).

benilla now does the same. Each roll waits, defers, starts or is open; at most four are open, and every close promotes oldest first. The client's server-close paths decrement the count even for a deferred roll that never counted (`0x61b6f8`, `0x61bc17`), so a fifth frame can open there. benilla keeps the count exact, as a `Deviation:`.

Tests: five announce four; each close path promotes; the wire countdown; a deferred close.

Fixes #1061

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The stock 1.12 GlueXML screens are now the default in every build: login, realm list, character select and create. benilla's native screens run only in a dev build with `WOW_GLUE_NATIVE=1`, or under the rig and capture runs, which still drive them, until the native screens are deleted. `WOW_GLUE_STOCK` is gone.

On the way:
- An unattended reconnect re-dials the kept realm and lists the roster. Under the stock screens it stalled at the realm list.
- `WOW_CHARSELECT_PICK` selects on the stock screen.
- `smoke.sh` counts the stock run's VMs and fonts.

The stock screens fix the native screens' defects listed below.

Tests cover the switch, the pick and the reconnect. Live: smoke, a kick and re-entry, the player build's boot.

Part of #856.

Fixes #849
Fixes #850
Fixes #834
Fixes #812
Fixes #360
Fixes #463
Fixes #79
Fixes #977

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Nameplates took a fixed Friz Quadrata path whatever `NAMEPLATE_FONT` held, so an addon or a localized GlobalStrings could not restyle them.

1.12.1's plate constructor reads the global for each FontString it builds: the name at `0x7cb3f9` and the level at `0x7cb49b`. The read is `lua_gettable` on the globals, so `__index` runs (`0x704373`). A number reads as its text; anything else, or a missing global, gives "". `SetFont` refuses "" (`0x44d05c`) and leaves the string with no font.

Plates and chat bubbles now read the global through one lookup, when each is built. The read happens before the UI model is borrowed: an addon's `_G` `__index` that touched the model panicked the bubble's read.

Tests: a plate built after the global is set takes it, an older plate keeps its own font, the non-string cases, and an `__index` that calls `CreateFrame`.

Fixes #921

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`GetTradeSkillNumMade` answered 0, 0 for a header row or an index past the rows.

1.12.1's `GetTradeSkillNumMade` (`0x4fdc50`) sends a bad index, a header (spell -1, `0x4fdcaa`) and a missing spell record to the fallback at `0x4fdd13`, which pushes 1 twice. A recipe with no spell record never becomes a row in benilla, so the past-the-rows case covers it.

The miss now answers 1, 1. Tests: the header, no-window and past-the-rows cases pin 1, 1.

Fixes #1262

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The stock 1.12 glue screens are now the only glue screens, so benilla's native login, realm list, character select and create screens are deleted (−17k lines). So are the native switch, its census tests, the art-extent module and its extract command.

What still drove the native screens now presses the stock ones through their own Lua verbs (`capture/glue_press.rs`):
- the rig;
- `WOW_PROBE_CHARCREATE`;
- the `glue-login`, `glue-charcreate` and `glue-realmlist` captures.

The three world-entry tests run on the glue VM. The schedule's ambiguity ceiling drops from 3939 to 3348.

Live: smoke, a glue round trip, a stock pick, a rig teleport, a create and delete, all three captures, and the player build's boot.

Fixes #856

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Several script verbs read a GlobalStrings text with `lua.globals().get` while holding the UI model's borrow. An addon's `_G` `__index` that called the UI there panicked the client: the ready check, `GetFactionInfo`'s headers, `SetTalent` and `GetTradeSkillInvSlots`.

1.12.1 reads these through `0x703bf0`, a `lua_gettable` on the globals (`0x704373`), so `__index` runs. Its ready-check worker disarms (`0x4bb377`), reads `RAID_MEMBERS_AFK` (`0x4bb381`), and reads `READY_CHECK_NO_AFK` too when no name is pending (`0x4bb48e`).

Each read now runs with no model borrow, and the model is borrowed again after. The ready check reads both strings in the reference's order.

Tests: an `__index` that calls `CreateFrame`, for each verb and each ready-check path. Each panics without the fix.

Fixes #1460

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Two addon-harness tests called `std::os::unix::fs::symlink` with no gate, so benilla-app's test target did not compile on Windows. That stopped clippy `--all-targets` and every benilla-app test there.

A small test helper, `addon_harness::test_support::link_dir`, links a folder with `symlink` on unix and `symlink_dir` on Windows, copying when the link is refused. Both tests use it. No other unix-only call sits outside a `cfg` gate.

Test: `cargo check --workspace --tests --target x86_64-pc-windows-gnu` is clean, and fails at the old call site. Built on macOS; Windows compile-checked, not run.

Fixes #1463

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A FontString with no font took Friz Quadrata at size 12, so it took text, measured and drew where 1.12.1 has none.

In 1.12.1, `SetText` is the only FontString method that checks the font (`0x79d760`): with none it raises `%s:SetText(): Font not set` and sets nothing. The width and height measure only with a font (`0x7728b6`, `0x7729d4`), and the string builds and draws only with one (`0x7724f8`, `0x772e5b`). A string gets a font only from `inherits` or `font=`, never from its parent.

benilla now models "no font" as its own state: `SetText` raises, the measures answer 0, and nothing draws. XML `text=` is stored without the check, as `0x77103a` does. `<ButtonText>` now reads its own `inherits=` and `font=`.

Tests: a bare string raises, measures 0 and draws nothing. Across 219 addons and 4,168 stock FontStrings, nothing new raises.

Fixes #1461

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
No gate built the shipped player graph outside tests. The player-tests gate compiles with `cfg(test)`, and clippy ran only on the dev feature set, so the player build's own warnings landed unseen: four unused re-exports that only dev probes used.

A new `player-clippy` gate runs `cargo clippy -p benilla -p benilla-app --no-default-features -- -D warnings` in `scripts/gates.sh`, in CI, and in `scripts/check.sh` when it reaches those crates. `docs/METHOD.md` names it.

The four re-exports are deleted: `ground_derived`, `GroundClamped`, `BodyPartsDesc` and `ATTACH_SLOT_NAMES`. Their dev users import them from the defining modules, with no `allow` left.

Test: an unused import added to `net.rs` fails the new gate.

Fixes #1465

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An XML `font=` naming a file with no `<FontHeight>` still got that font at a default height.

`CSimpleFontString::LoadXML` (`0x770f40`) reads `FontHeight` and refuses a height that is not above 0 (`0x771146`–`0x771154`). It warns `FontString %s: Missing font height in %s element` (`0x878e10`) and returns at `0x771191`, setting no font. SimpleHTML's element fonts go through `CSimpleFont::LoadXML`, which has the same check (`0x783d6d`, `0x87a4f0`).

Both loaders now refuse a missing, zero or negative height with the reference's warning and set no font or outline, so `SetText` on such a string raises "Font not set". None of the 219 corpus addons trips it.

Tests: absent, 0 and negative heights, the ButtonText message, and a SimpleHTML element; all fail without the fix.

Fixes #1469

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A dev build maps a linked worktree's `benilla-config/` to the main checkout's, so a developer's worktrees share one settings folder. That included a worktree that declares its own account in `.probe-identity`: an attended run from it, or a scripted one without `WOW_UNATTENDED`, read and wrote the main checkout's CVars, bindings, macros and saved variables while logged in as another account. A branch with a wrong settings migration or scripted key presses changed the player's own client, with nothing to show it.

`local_state::shared_root` now keeps such a worktree's state in its own `benilla-config/`. A worktree without `.probe-identity`, and the main checkout, resolve as before. CONTRIBUTING and `.gitignore` say so.

Test: `the_state_root_is_the_folder_or_its_primary_checkout` adds a worktree with a `.probe-identity`, which keeps its own root, and a main checkout with one, which is unchanged; it fails without the change. `the_state_folder_lands_where_the_build_says` follows the rule.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A shown item tooltip whose line waited on a cache kept missing that line until something re-ran `OnEnter`. Examples are a lock's key, a "made by" name and a set member. Bags re-run it every frame; trade slots, `ItemRefTooltip` and addon tooltips never do.

1.12.1 registers `CGTooltip::OnCacheAnswer` (`0x52e530`) on every record, name, petition and item-name miss, with the tooltip as cookie. On each answer it rebuilds from the inputs the last build kept (`0x52b6cc`–`0x52b70a`), reading the live object, and sizes the plate (`0x52e5ac`). A clear zeroes the kept entry (`0x5300dd`), so a cleared tooltip stays down.

Every item `Set*` now goes through one build that keeps its inputs and records its misses. Each answer rebuilds whatever the tooltip keeps by then; compare asks stack and builder asks deduplicate, as in the reference.

Tests cover each cache and a cleared tooltip.

Fixes #1452

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
At character select the IO thread waited only on the app's requests, so a server that dropped the link (vmangos kicks a session idle there after `CharacterScreenMaxIdleTime`) went unnoticed until Enter World started a load.

1.12.1 keeps servicing the connection on that screen, and a drop runs `CGlueMgr::NetDisconnectHandler` (`0x46c540`): `DISCONNECTED_FROM_SERVER` and back to login.

The park now looks at the socket twice a second without blocking. A closed link ends the session as a failed roster ask does; a packet sent unasked is read and handled as `char_enum` handles its strays, in one shared step.

A fake-server test drops the link while the client is parked with no request pending and expects the session lost; it times out without the change.

Fixes #1020

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

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Splitting a quest item's stack could flash its objective line ("Wolf Pelt: 4/5"). The toast rode a per-frame diff of the bag count, and a split dips it for a frame: vmangos sends the shrunk source and the new stack's create at once, and the slot naming the new stack later, in the batched update (`Player.cpp:11100`, `10316`–`10327`).

1.12.1 toasts an item objective only from `SMSG_QUESTUPDATE_ADD_ITEM` (`0x5e5ad0`, case 0x19a). It walks the log to the first quest needing the item, and `0x5dd060` shows `ERR_QUEST_ADD_ITEM_SII` with min(held + added, need), then `QUEST_WATCH_UPDATE`. An uncached quest record defers the pair to its arrival (`0x5dd180`).

Item lines now toast from that packet alone; kill and complete toasts are unchanged. Tests replay a split's update sequence (no toast) and a pickup (one toast); both fail without the change.

Fixes #916

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

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
With no WoW install found, or a `Data` folder whose archives will not open, benilla booted into a window of textless buttons, since fonts, art and the stock interface all come from the install.

It now ends before any window exists. A native error dialog (`rfd`: AppKit, Win32, `zenity` on Linux) names every folder searched, or the folder and the archive error, and says where to put the install; then it exits 1. An unattended run prints the same text to stderr and exits without a dialog.

1.12.1 ends at a fatal dialog for a present but corrupt base archive (`0x403b6f`) and only logs a missing one (`0x403b00`); ending at no install at all is recorded as a `Deviation:`.

Tests cover both messages and the failing exit; both cases were run live unattended. Built on macOS, Linux and Windows.

Fixes #836

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A hunter shot the server refused (out of range, line of sight) still sent `CMSG_CAST_SPELL` for Auto Shot: `cast_result` chained `modalNextSpell` on any result whose spell was in flight.

In 1.12.1 a failure's report pops the in-flight slot (`0x6e2201` → `0x6e2218` → `0x6e4ad0(0, 0)`), so the handler's compare at `0x6e7408` misses and the chain at `0x6e74aa` is skipped. It still chains when the report leaves before that pop (reason 0x17; 0x56 with no named form; an uncached 0x5c or 0x78 item, `0x6e1eab`) or when the save held the same spell.

`cast_result` now reads the slot pair up front and chains a failure only in those cases. The test that asserted a failed sting chains is flipped, and each remaining case has its own assertion; each fails with its rule undone.

Fixes #1594

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

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
In the 17 instances built as one WMO (Wailing Caverns, Blackrock Depths, Molten Core, Dire Maul, the Deeprun Tram and others) benilla had no area: entering by portal kept the outside zone, no `ZONE_CHANGED_NEW_AREA` fired, and logging in inside left the zone texts empty.

On a map whose WDT carries MPHD bit 0 (`0x694904`), 1.12.1's area query `0x6703a0` answers the map's `Map.dbc` field 19 for every entity, ahead of any WMO or terrain read; a 0 there fails the query.

`MapCatalog` now reads field 19, the WDT exposes the bit, and `update_current_area` takes that leg first on such a map. A worldport also drops the old map's held area, which otherwise survived the transfer. Tests cover the leg, the flag's map, a zero field and the worldport; a live portal into Wailing Caverns fired `ZONE_CHANGED_NEW_AREA` with "Wailing Caverns".

Fixes #1636

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

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Print screen on a glue screen (login, realm list, character select or create) raised a Lua error on a nil `Screenshot` and saved nothing: the stock GlueXML calls it from each screen's `OnKeyDown`, and benilla bound it only in the in-game VM.

1.12.1 binds `Screenshot` in the glue table too (`0x46d3d0`). It arms the same capture (`0x442a10`) and answers with the glue's own events, `GLUE_SCREENSHOT_SUCCEEDED` or `GLUE_SCREENSHOT_FAILED` (`0x46c520`, glue events 14 and 15).

The glue VM now installs the same verb on the same capture ask, and the writer answers with whichever VM is up. Tests cover the glue call arming one capture and the glue events answering it, and the in-game names staying in-game; they fail without the binding.

Fixes #1649

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

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
`SPELLCAST_STOP` fired only at `SMSG_SPELL_GO` and only for a timed cast, so instants, channels and item uses never sent it to addons.

In 1.12.1 it fires only from the cast result: the handler's pop `0x6e741a` → `0x6e4940` (`0x6e4a4b`) while the pending record's caster word `0xceac50` is the player's, filled by the attempt (`0x6e4fb1`) and cleared when the event fires (`0x6e4a5a`); and `0x6e74cb` for a non-failure result with nothing in flight. The go handler fires none. vmangos sends START, RESULT, GO, so a timed cast's bar still ends at completion.

The attempt now fills the caster word, the result fires STOP as those two sites do, local cancels fire only while the word holds, and the go-time raise is gone. Tests replay each cast kind in vmangos order; a live run logged STOP for an instant, a timed cast, a channel and an item use.

Fixes #1604

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A dead player or ghost could use any GameObject: the cursor stayed lit and a right-click opened a mailbox or walked to a chest. In 1.12.1 `usable` (`0x5f3130`) tests `IsDeadOrGhost` (`0x605f30`, at `0x5f315f`) ahead of its lock and range arms and refuses with error 0x7e, `ERR_PLAYER_DEAD` (`0x5f33c6`); the cursor asks the same slot (`0x5f3093`, `0x5f58a8`, `0x5f6858`) and grays.

`cursor_mode::go_player_refusal` now holds the player arms, dead and the existing mounted gate (`0x5f31a8`), and both the cursor and the click use it: dead grays the cursor and the click raises `ERR_PLAYER_DEAD` with nothing sent. Mounted now also grays a lock-less object's cursor, as the same slot does in 1.12.1.

Test: dead, ghost, mounted and live players at a chest and a mailbox, checking the cursor, the use and the error.

Fixes #830

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`cameraYawSmoothSpeed` was clamped to 90-270 when applied, the Auto-Follow Speed slider's range, so `/console cameraYawSmoothSpeed 360` followed at 270. In 1.12.1 its validator `0x50c000` refuses only outside [0.1, 360] (`0x50b330`, `Value out of range` at `0x50b36c`) and the follow reader takes the stored rate as is (`[0xbe1070]` at `0x512d75`).

The camera now uses the stored value; the registry's existing check, which already matched `0x50b330` and also runs on the config load, does the refusing. `FOLLOW_SPEED_RANGE` is gone.

Test: 270, 360, 0.2 and 30 are stored and applied as given; 360.5, 400, 9000, 0, 0.05 and -30 are vetoed and keep 30. It fails with the clamp put back.

Fixes #323

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A refused query answer (top-bit entry, empty name) was stored as a record, saved to the cache file and never asked again. In 1.12.1 the refusal leg (`0x556e63`, items `0x55bdf7`, objects `0x558927`, names `0x55f6f0`, guilds `0x561390`, quests `0x563150`) runs the callbacks with status 0 and frees the node; the next lookup with a callback (`0x556aa0`) asks again.

Now a refusal frees the record, is never persisted, and redisplays nothing. Unit, object and slot/crest changes look up at once (`0x60b15b`, `0x60463f`, `0x5f7c40`, `0x5ed764`, `0x5e078f`), and a dropped ask is re-sent. Unit-name reads follow `GetUnitName` (`0x609210`): creatures are read from the unit, never asked. Readers that poll every frame wait 30 s after a refusal (a `Deviation:` until they are split). An empty quest title is a refusal.

Tests cover each path; each fails with its change undone.

Fixes #1674
Fixes #393

🤖 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>
At the world view of the world map, pointing at a continent showed no name and no outline: `UpdateMapHighlight` answered nothing at world level. In 1.12.1 the verb (`0x4a7fa0`) finds the continent whose sheet rect holds the point (`0x4a7620`) and returns its Map.dbc name (`0x4a825b`), its WorldMapArea file, the texture share (`0x4a83b9`: 1 by H/W when wider, else W/H by 1) and the quad from its WorldMapContinent row (`0x4a8407`: W·s/62.625, H·s/41.75, the corner by the builder's sheet law).

`worldmap::world_hover` now builds those eight values, and the catalog carries each continent's WorldMapContinent row. The click's citation moves from the unreached `0x4a7100` to its inline loop `0x4a755a`.

Tests: the eight values for Kalimdor, the Eastern Kingdoms, the sea and a wide continent; and the stock WorldMapFrame over Kalimdor on the install, checking label, texture and quad. Both fail without the change.

Fixes #866

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On a window narrower than 4:3 the loading screen ran off both sides: its area was always 133.3vh × 100vh. In 1.12.1 the layer `0x406a60` fits the picture through the viewport (`0x58af60`) from `a = 0.75 × aspect` (`[0x832a4c]`): width 1/a, centred, above 4:3; full width and height a, centred, below it; the whole window at a == 1, which `widescreen` off gives. The bar draws in that viewport's [0,1]; the tip is placed in window pixels (`0x5c70b0`, sizes from `0x5c2b50`), its baseline raised by the letterbox band (`0x4066c6`).

The area is now that rect, refitted each frame, and the tip is laid out in window terms. The live window's aspect stands in for `gxResolution` (a `Deviation:`, as for portraits).

Tests: the rect at 1280×1024, 1920×1080, 4:3 and `widescreen` off, a resize, and the tip in each case. The rect tests fail with the old fit.

Fixes #355

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A server that sent `SMSG_WARDEN_DATA` ended the login at character select with a terminal failure. Servers that send it without enforcing it were unplayable, and vmangos serves the roster and the world entry regardless (no handler gates on Warden; `World.cpp:325-338` starts it after AUTH_OK) and only kicks a silent client after `Warden.ClientResponseDelay` (`Warden.cpp:686-691`).

benilla now reads the message past, answers nothing and goes on; the server's policy decides what follows, and a kick is the ordinary lost link. This is a `Deviation:` at the read: the reference hands the message to its loaded module (`0x6ca2f0`, `0x6ca5c0`) and sends its answer (`0x6ca6a0`). `WardenRequired` and the terminal-failure path it alone used are gone.

Tests: a login past a Warden request reaches the world with nothing sent back; a kick after it is a lost link; the handshake reads one past. Each fails with the old refusal.

Fixes #854

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A server that sent BillingTimeRemaining got no warning: benilla dropped the auth response's billing minutes and plan flags. In 1.12.1 the handler (`0x5b41b0`, gate `0x5b41f9`) keeps the group from AUTH_OK or the first AUTH_WAIT_QUEUE; `InitializeGame` arms a deadline at each world entry for 1-4320 minutes on a non-recurring plan (`0x48f636`-`0x48f679`); the game UI's per-frame poller (`0x495679`-`0x495826`) fires `BILLING_NAG_DIALOG` once at 30 minutes or fewer and prints `BILLING_NAG_WARNING` each 5 minutes, then each minute from 5 (`IGR_BILLING_NAG_DIALOG` at 600 for an internet-cafe plan).

The protocol keeps the group across the queue; `ui_billing` transcribes the arm and the poller. `GetBillingTimeRested` was set on the glue VM; it is now seeded into every in-game VM.

Tests: the four vmangos body shapes byte-exact, the queue's group outlasting a bare AUTH_OK, the cadence, the stock popup and chat line, and the arm at entry only.

Fixes #813

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Lazy Peons stood upright under their sleep aura. In 1.12.1 an aura's state kit (kind 2) sets flag 0x1000 (`0x60eecd`) on an effect at the head of the unit's `+0xb4` list (`0x620360`), and every play reaches `0x5fde80` (`0x5feab5`): a still unit (`[+0x9e8] & 0x20000f` clear) plays the first flagged kit's animation above 0 instead of what was asked, on body and mount alike (`0x5fdeee`, `0x5fdf08`). Spell 17743's kit names 71, which OrcMale plays as Sleep.

benilla now keeps those pins per unit, newest slot at the head (`0x604e7b`), and the driver plays the head pin for a still unit's poses, one-shots, jumps and landings, rider and mount both; the clip plays out its window after the aura goes. A still stunned unit now plays Stun, as in 1.12.1.

Tests cover each path; each fails without its part.

Fixes #837

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The chat and name filters compiled `\<`, `\>` and `\s` with Unicode classes, so a ChatProfanity word right after Cyrillic or CJK letters went unmasked. In 1.12.1 the filters run PCRE in UTF-8 mode without Unicode properties (`0x71fba0`, options `0x2801`); `\<` and `\>` are `\b` (`0x812330`), and `\b` and `\s` read the ctype table at `0x873e80` for characters below 0x100 only (`0x724fc0`, `0x7252b8`). Its word set is ASCII alphanumerics, `_` and 0xC0-0xFF except `× Þ ÷`; its space set is tab, LF, FF, CR, space and NBSP; everything from 0x100 up is neither.

Boundaries now test that word class by look-around, through `fancy-regex` for the rows that have one (`regex` has none); `\s` becomes the client's space class. Caseless matching was already the client's.

Tests: letters above Latin-1 beside a word, every Latin-1 character against the table, the space class. Each fails with the old translation.

Fixes #1056

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`BenillaScrollBarTemplate`'s own OnValueChanged was a faux-list handler that both instances replaced with the real scroll, so a new bar that forgot the override wrote `offset`/`updateFunc` on its parent instead of scrolling. The reference's `UIPanelScrollBarTemplate` carries `this:GetParent():SetVerticalScroll(arg1)` itself (`UIPanelTemplates.xml:156-158`).

The template now calls `BenillaScrollBar_OnValueChanged(this, arg1)`; the two instance overrides, the dead faux handler and its header text are gone.

Test: a bare bar from the template scrolls its ScrollFrame parent with no `offset` or `updateFunc` write; it fails with the old template.

Fixes #359

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The billing code called plan flag 0x08 an internet-cafe plan. The client's own name for it is IGR (`IGR_BILLING_NAG_DIALOG`), Internet Gaming Room, as benilla-re now says; the docs and one test name follow. No behaviour changes.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The README asked for a server with Warden off, which stopped being true when a Warden request began to be read past (#1717): benilla now plays on a server that sends Warden without enforcing it, and one that enforces it disconnects the session after its own timeout. The requirement now says that.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An M2 batch with two textures drew only its first stage, so Cupid's bow, the two Stratholme maces and the obsidian mining node lost their reflection. In 1.12.1 the element binds one stage per texture (`0x70b740`-`0x70b823`); a version 0x101 model takes each stage's combine from its header array (`0x70b7c2`, `[md+0x148][k]`), else from the blend table `0x811f8c`; a coordinate lookup above 2 generates the view-space reflection (`0x70b8bd`, the same map as stage 0's env path). These five are the only 0x101 models, each stage 1 preset 1, modulate on the default Direct3D device (`0x80a25c`).

benilla-m2 reads the combiners; the second stage is parsed, loaded and multiplied over the lit first stage before fog, with all six presets on D3D's ops.

Tests: the combine source, a synthetic two-texture parse, the obsidian node's stages off the install, the shader's coordinate and presets. Each fails without the change.

Fixes #1722

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A fading opaque M2 batch blended by `texel alpha × fade`, so where its texture carries alpha (helms, shoulders, the Wind Rider's skin, the Blackfathom pots) it thinned or vanished while fading. In 1.12.1 a blend-0 batch takes combine preset 0 (`0x811f8c`), whose alpha op is SELECTARG2 on the current (`0x80a274`; ALPHAARG is never set), and the diffuse alpha is the element alpha (`0x70c73a`, c28.w): the fade alone, blended SRC_ALPHA/INV_SRC_ALPHA (`0x811fe0`).

Each batch now carries its stage-0 combine (a 0x101 model's from its array), and the fade twin applies that preset's alpha op to the fade. Unfaded batches, cutouts and other blends are unchanged.

Tests: the shader's alpha op for presets 0 and 1, the batch's stage-0 combine, its uniform. The shader test fails with the old line.

Fixes #1732

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Cyrillic in a chat bubble drew as boxes: Friz Quadrata, the bubble's font, has no Cyrillic, and benilla drew the face's `.notdef` glyph for each missing character. In 1.12.1 the glyph load asks the face's Unicode charmap first (`FT_Get_Char_Index`, `0x7ce960` at `0x5d1159`) and returns with nothing loaded on index 0 (`0x5d1162`); the line builder then draws no quad and adds no advance (`0x5cced6` → `0x5cd280`), and kerning spans the gap (`0x5cd27a`). The charmap is the first record FreeType calls Unicode (`0x5d015d`, `0x81eb70`).

Each face now carries that charmap, and a code point it lacks is an empty cell on every surface. The chat frame keeps Cyrillic, which Arial Narrow carries.

Tests: a missing code point in Friz takes no room and draws nothing; the charmap pick skips (3,10) and takes the first Unicode record. Each fails without the change.

Fixes #832

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The script host now runs on benilla-lua alone. The vendored C Lua 5.1.5 (`third_party/lua-src`) and mlua are removed, about 17k lines. `vm_parity` checks benilla-lua against the answers the C build recorded on Linux and macOS, and `front_end` checks every corpus chunk's compiled shape against the C build's digest. `GRAMMAR.md` records each grammar change from 5.1, by client address.

A handler's `this`, `event` and `arg1`… are bound in one VM call. An event fire saves and restores `event`, while the handler runner saves only `this` (`0x704d50`).

Ours on the branch: VM fixes from review (hook dispatch counts, registers and frames around a collection, protected-call handlers, library errors) and `vm_parity`'s two-VM leftovers removed.

Tests: the full gate chain on the merged tree, `vm_parity` and `front_end` among them. Live: smoke and a stock-glue login. Load time is level with C Lua, and memory is within 7%.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Sam <samuv.eth@gmail.com>
A spell visual kit naming animation 0 (Stand) was read as no animation, so 41 kits lost their play. In 1.12.1 the field is skipped only when negative (`0x60f366 jl`): a state kit naming 0 recomputes the base (`0x60f383`-`0x60f399`) and never pins (`0x5fdeb5 jle`); a cast or impact kit plays it through `0x5fe2f0` (`0x60f3c5`); the holds and the missile release need above 0 (`0x612a0a`, `0x6e8295`).

Playing it needed two driver rules benilla lacked: PlayTrack drops a request for the id a track already asks for (`0x5fdcab`), so a recompute no longer restarts an unchanged gait; and a Stand request over a swing, ready stance or cast is dropped (`0x5feb00`-`0x5feb56`).

Tests: the parse, the recompute without a pin, the holds and release, the dedup, each demotion arm, and Stand ending at its window. Each fails without its part.

Fixes #1719

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
benilla-lua now matches the client's Lua 5.0 wherever an addon can see the difference:
- Number keys hash as `hashnum` does (`0x6fa260`), so `pairs` walks them in the client's order.
- Calls nest at most 4096 deep (`0x6f61c0`).
- `^` calls the global `__pow` (`0x6f9b5e`).
- The parser's limits and messages are the client's (`0x6ff440`); the client has no assignment limit.
- A numeric for's variable is its counter (`0x6fe380`, `0x6f9262`).
- A generic for uses `forlist`'s layout (`0x6fe540`): the first name is the control, and closures share the variables.
- Only calls name a callee (`0x6fc499`), and a stray token reports `<eof> expected` (`0x871cf4`).

Each fix has a test that fails without it. Two audits re-derived every address from the bytes. The bench is faster or level.

Fixes #1369
Fixes #1370
Fixes #1371
Fixes #1372
Fixes #1374
Fixes #1377
Fixes #1752
Fixes #1753
Fixes #1754

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In the client, a QueryCache lookup (`0x55ba30`) either passes a callback, which queries on a miss, or passes none, which only reads (`0x55baac`). A refusal frees the key, so the next callback lookup asks again (`0x556f35`). benilla re-asked every miss on a timer, through `get_or_ask`.

Now every reader says how it reads, as `Ask::Read` or `Ask::Event` at the client's trigger. The trigger is a packet, a window show, an item's create (`0x5d8300`) or a getter the stock UI polls. The timer, its `Deviation:` and the duplicate lookups are gone. Key 0 is never asked (`0x55ba42`). A recipe cooldown no longer fires `TRADE_SKILL_UPDATE` each second; the client's senders are only the list rebuild and the filters (`0x4fd164`).

Each reader family has a test: one ask at its trigger, none between, a refused key asked again next. An audit re-derived the core and 29 classifications from the bytes.

Fixes #1714

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
benilla-lua's string and number library now answers as the client's:
- `string.byte` returns one byte (`0x7fc210`).
- `gsub` takes only a string or a function (`0x7fd163`), and `%0` raises (`0x7fce20`).
- `lower` and `upper` case-map UTF-8 (`0x7fbeb0`).
- `tonumber` with a base is MSVC's 32-bit `strtoul` (`0x40db09`), on every platform.
- Chunk names cut at a NUL and at `\n` (`0x835144`).
- `string.format` is the client's native one (`0x7fd3d0`): `__ftol` conversions, `%q`'s table, one-digit `n$`, and the client's errors.
- Number text is the MSVC CRT's, ported down to `$I10_OUTPUT`: `1.#INF`, `-1.#IND`, `1e+020`.
- A NaN made from numbers is the x87 indefinite, and the math library returns the runtime's own answers at infinities.

A fuzz against the client's own `sprintf`, run in emulation, matches 1.5M cases. Every fix fails its test without it. `tostring` and `%g` got faster.

Fixes #1367
Fixes #1368
Fixes #1373
Fixes #1375
Fixes #1376
Fixes #1450
Fixes #1481
Fixes #1486
Fixes #1770

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The table library and `unpack` now run as the client's 5.0 natives (`0x822d40`). benilla's closure layer over them is gone. `unpack` refuses past 2,048 slots (`0x6f2f43`) and reads its table alone. `getn` converts a numeric-string `n` (`0x6f4f80`). `next` says "for" (`0x871aac`).

The bit library answers unsigned except `arshift` and `mod` (`0x7faa6d`). Its folds take every argument, and `math.pow`, `atan2` and `mod` check argument 1 first.

A Rust callback's error now carries the caller's `file:line`, as `luaL_error` does (`0x6f4940`); only the sound verbs raise bare. Error text keeps its bytes, from `assert` to the error handler.

An audit re-derived every commit from the bytes. Each fix fails its test without it.

Fixes #1435
Fixes #1448
Fixes #1449
Fixes #1480
Fixes #1482
Fixes #1483
Fixes #1484
Fixes #1485
Fixes #1760
Fixes #1761
Fixes #1762
Fixes #1763
Fixes #1764
Fixes #1765
Fixes #1766
Fixes #1771

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Seven places where benilla chose to differ from 1.12.1 said so without a `Deviation:` line, so `docs/DEVIATIONS.md` missed them. Each now has its line, with the client address and the reason:
- targeting stays armed after a range failure (`0x6e6089`);
- a kit's first shake reads its node before it is filled (`0x620e0e`);
- screenshots are PNG and can report a failure (`0x4426f0`);
- UnitPVPName of a guid-0 token is nil (`0x517343`);
- an aura's visual holds while any slot keeps the spell (`0x5ff2d2`);
- text input is Unicode, not the ANSI code page (`0x42d185`);
- benilla runs no code a server sends: no patch, no survey and no Warden module (`0x474610`, `0x5bad53`).

The login queue now follows the client: a leading blank line (`0x837230`) and a 32-bit estimate that wraps (`0x46aed3`), with tests. The environment-map comment cites the shader the client picks (`0x71de50`).

Fixes #821
Fixes #1599

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Three tests failed on a Windows checkout:
- one split a source path on `/`;
- one split its own `include_str!` on `"\n}\n"`, which a CRLF checkout lacks;
- one captured a traceback's addon folder with a greedy `.*`.

Each now normalises the separator or line end, or bounds the capture. A sweep found the same path forms in four more source-walking tests (`fire_scan`, `world_api_wall`, `reference_strings` and `front_end`), now normalised too.

`.gitattributes` pins Rust sources to LF and leaves `third_party/kira`'s CRLF files as they are.

Built and tested on macOS. The Windows cases are reasoned from the path and line-ending forms, not run.

Fixes #798

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Dev builds get three tools, all behind the `dev` feature:
- **Hot reload:** an edited addon `.lua` is swapped into the closures already running, keeping state and event registrations. Edited XML handlers and benilla's own `assets/ui` layer reload too.
- **An addon profiler:** Lua time and heap per addon.
- **A Lua inspector:** tables, frame scripts and upvalues, with scalars editable in place.

benilla-mlua's `unsafe raw_vm` becomes a safe lent `with_vm`.

Ours on the branch:
- the top level is compared off the compile path, in a scratch heap;
- watched files are polled in slices, and each addon's XML is walked once per session;
- no lend while a callback of the state runs;
- profiler owners are cached per source epoch;
- key-ups reach the UI while an egui field has focus.

The player build carries none of it. Tests: the full gate chain on the merged tree.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Sam <samuv.eth@gmail.com>
benilla-lua's lexer and compiler now behave as the client's 5.0:
- A control byte is "invalid control char" (`0x6ffabb`).
- Every chunk lexes as source, binary ones too (`0x6f6a15`).
- Lines end at `\n` alone (`0x6ffb94`); a dot is never a NUL (`0x6ff98a`).
- A `while` condition over 100 instructions is too complex (`0x6fd03b`), counted as 5.0's code generator counts it.
- Upvalues get no name in errors (`0x6fc5e0`).
- `string.dump` refuses closures with upvalues, and dumps the stack top (`0x6f438a`, `0x6f4373`).

The client installs no Lua hook (`lua_sethook` has no caller), so the load's runaway guard has no client mechanism to follow. It is now an abort no `pcall` can catch, recorded as a deviation.

An audit re-derived every commit from the bytes. Each fix fails its test without it.

Fixes #1755
Fixes #1756
Fixes #1757
Fixes #1758
Fixes #1759
Fixes #1767
Fixes #1434
Fixes #1445
Fixes #1777
Fixes #1778

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The AddOns screen's script memory setting did nothing.

In 1.12.1, `scriptMemory` (KB, default 49152, `0x402e42`) is one limit for the whole Lua state. It is checked each frame in the world by `0x495590`:
- Over the limit, the client does a full collection and counts again (`0x4955d5`).
- Still over, it fires `MEMORY_EXHAUSTED` with the limit in MB (`0x49563d`), so the stock popup shows, and starts a 15 s deadline.
- Still over at the deadline, it quits (`0x495602`).
- Back under, it fires `MEMORY_RECOVERED` (`0x495656`).
- A limit of 0 or less turns the watch off.

benilla now does the same. Its count still uses 64-bit object sizes (#1779), recorded as a deviation.

Tests: the state machine, plus two full stock-UI logins. A 1 MB limit shows the exact popup and freeing memory hides it. The default shows nothing (the stock UI is 13 MB).

Fixes #1081

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On some Linux setups the game was silent. The device opened, but its data callback ran once and stopped, with no error. The music position never moved.

This is cpal 0.17.1's ALSA bug (RustAudio/cpal#1099, "Data callback called only once on ALSA"), fixed in 0.17.3 by setting playback's `avail_min` to one period. benilla's stream starts only at the start threshold, which is exactly that case. cpal goes to 0.17.3.

alsa 0.11 and bevy_audio's unbuilt cpal 0.15 cannot share a lock (`links = "alsa"`). A lock-only stand-in for alsa 0.9 settles that, documented in `third_party/alsa-0.9`.

The realtime-fallback log line now says what happens on each platform. Following the default device gets its `Deviation:` line: 1.12.1 initialises sound once (`0x7a4330`).

Built on macOS, with Linux and Windows linted.

Fixes #820

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Lua memory now counts as the client's does:
- Every object is charged at its 32-bit Lua 5.0 size: a string is 17 bytes plus its length (`0x6f9da1`), a table header 32 (`0x6fa500`), a node 40, and a closure 36 plus 4 per upvalue (`0x6f9ebf`).
- Prototypes count 5.0's instructions and constants (`0x6fcb2b`).
- The string table and the concat buffer are counted as 5.0 sizes them.

The stock UI reads 10,618 KB, against 13,294 KB before.

The host's old C-host shim overrode benilla-lua's natives. It is gone:
- `collectgarbage` now moves the real collector, taking `__ftol`'s low word (`0x6f4400`);
- `gcinfo` reports that collector;
- `^` is the client's `math_pow` (`0x7fb4bd`);
- `loadstring` returns one value (`0x7032ba`).

An audit re-derived every size from the bytes. Each fix fails its test without it.

Fixes #1779
Fixes #1780

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The collector now paces as the client's Lua 5.0 does.

The threshold:
- The state opens at 4× its count (`0x6f6ec8`), and InitLua then sets 256 KB (`0x7039f2`).
- After a collection it is the count plus the larger of the old headroom and a quarter of the count (`0x6fae00`). The headroom is capped at the free space in the client's six block pools, which a new book models (`0x6fae90`).
- The memory watch collects from a threshold of 0 (`0x703bd6`), and varargs never collect.

The stack and call records start at 45 and 8 slots and grow by 5.0's rule (`0x6f5f60`). They are checked once per collection, during mark, and halved under a quarter's use (`0x6f7949`). A tail call checks the stack where the call is made (`0x6f922c`).

A collection of a 10 MB heap takes about 1 ms; each change fails its test without it.

Fixes #1783
Fixes #1784

🤖 Generated with [Claude Code](https://claude.com/claude-code)
benilla-lua's tables and globals now follow the client's Lua 5.0, not 5.1.

Tables:
- They size, fill and rehash as 5.0's ltable.c does: the hash part doubles (`0x6fab90`), and a log size of 0 is the shared dummy (`0x6fa601`).
- A hash part past 2^24 nodes raises `table overflow`.
- Constructors store every 32 items.
- A weak table's clearing removes the key, so `next` from it raises (`0x6f79d0`).
- A weak-keyed table keeps its string keys (`0x6f79f0`).

Globals and libraries:
- `ipairs` returns itself, and a second argument steps once.
- `setglobal` assigns the top of the stack.
- The state opens in the client's order, with no `_G`.

Each new test fails without its fix.

Fixes #1487
Fixes #1789
Fixes #1790
Fixes #1169

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A ScrollingMessageFrame now lays out, pages and scrolls as the client's relayout does.

- A message that does not fit whole, past the anchored one, ends the layout (`0x7889b3`-`0x7889c5`). A half-fitting top line is hidden, not clipped, and the shown count that paging reads agrees.
- PageUp and PageDown take one line step per shown message, then one step back, and stop on a refusal (`0x7885b0`, `0x7885e0`). One shown message nets no move.
- ScrollUp is refused once the oldest message shows, and AtTop reads that flag (`0x78861c`, `0x7931b5`). It is nil on an empty frame (`0x787775`).

A character pane's camera takes its aimed pose with its spawn, so a pane is framed on the frame it opens.

Each change fails its test without it.

Fixes #1786
Fixes #1787

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The quest log getters now read live state at the call, and QUEST_LOG_UPDATE fires where the client fires it. Before, every log quest's reward items were asked up front, and a per-frame snapshot rebuilt the log.

- World entry registers the log watcher and rebuilds (`0x4de430`). The rebuild holds while quest records are pending, and the last landing rebuilds it (`0x4de510`, `0x4de8b0`).
- The getters ask on a miss (`0x4e0461`, `0x4e05e0`, `0x4e0b9e`). One update fires per missed record that lands, a recorded deviation from the client's per-call callbacks.
- An item a listed quest needs fires one update (`0x4df7b0`). The time response and a faction rank change fire one each (`0x4def1b`, `0x4d58f3`).
- Cached names and templates seed each new VM.

On an idle frame with 20 quests: 46.5 µs before, 1.0 µs after. Each change fails its test without it.

Fixes #1209

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`leave_drops_the_world_session_and_keeps_the_logon` failed now and then at its first `assert_eq!(world.open(), 1)`.

The fake world server counted a session as open only after it had sent `SMSG_AUTH_RESPONSE`. A client that read the reply and reported `WorldAuthenticated` could therefore reach the assertion before the count rose.

The count now rises where the link is held, before any reply goes out, so `open()` never trails what the client has seen. Only the test fixture changes.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A ScrollingMessageFrame now fades, keeps its view and spaces its messages as the client's does.

- Only the shown messages fade (`0x7884a5`-`0x788582`). A relayout off the bottom re-arms what it walks, including the first older message, before the fit test (`0x788af0` at `0x7888be`). That covers a scroll, a resize, a font change, a show and UpdateColorByID.
- A scrolled-up view stays on its messages as they arrive. It moves down a line only when a full ring overwrites the topmost shown message (`0x78819e`-`0x7881bb`).
- The gap between messages is the snapped unscaled spacing times the scale (`0x788954`-`0x788975`). Rows within one message snap their scaled gap (`0x5c2143`-`0x5c215c`). Each message must fit whole.
- ScrollToTop and ScrollToBottom do nothing on an empty frame (`0x788690`, `0x7886d0`).

Each change fails its test without it.

Fixes #1796
Fixes #1797
Fixes #1798

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Quest completion and timed-quest failure now follow 1.12.1.

- **isComplete runs the client's turn-in test (`0x4df580`).** The complete bit counts only when the template's flags ask for it, and a quest with nothing to check is never complete. Counters, held items, both standing bounds and money are read live. A pass fires the "Quest Completion" tutorial (`0x4df792`).
- **A timed quest that runs out latches failed.** The latch is set by the rebuild, by GetQuestTimers and by `SMSG_QUESTUPDATE_FAILEDTIMER` (`0x4df4f0`, `0x4df550`). GetQuestTimers prints the failure line and then fires QUEST_LOG_UPDATE inside the call (`0x4e1529`, `0x4e1554`).
- **Timers count by the local clock until the first sync,** and a zero-difference sync reads a second more (`0x4def00`).
- **Quest-giver failures wait for the quest record** and skip a latched quest (`0x5dc898`). A plain update failure no longer re-asks the givers.

Each change fails its test without it.

Fixes #1793
Fixes #1794

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The ESC menu's Support rung was greyed with nothing behind it. The 1.15 era menu that the layer's ladder follows opens the help window from Support. In 1.12 that window is the stock `HelpFrame`, opened by the "?" micro button's `ToggleHelpFrame()` (`MainMenuBarMicroButtons.xml:217`).

Support is now live: it plays the menu click, closes the menu as the Macros rung does, and toggles the help window. Edit Mode stays greyed.

Test: `support_closes_the_menu_and_toggles_the_help_window` clicks the rung and checks the toggle and the closed menu; the live-entries test now lists Support.

Fixes #1800

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On Linux, sound stayed silent for the whole session when ALSA's `default` would not open. On the reported machine, PipeWire holds the card and `default` is left as the stock `dmix`, which cannot open it.

benilla opened only the host default and gave up when that failed. Now, when the default will not open, the first other output that does stands in: `pipewire`, then `pulse`, then each card's `sysdefault` and `plughw`, with HDMI cards last. It never picks `null` or the rate and mix plugins. The device watch now compares against the default the device was chosen against, so a stand-in is not torn down every poll. A `Deviation:` line records that 1.12.1 runs silent when FMOD's driver 0 fails (`0x7a445f`).

Tests: the ranking over three `aplay -L` lists, including the reported one. The Linux crosscheck is green.

Fixes #1806

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The red "GM mode is ON" line did not show on a session's first login, only on a relog.

In 1.12.1 the interface and the world load before `CMSG_PLAYER_LOGIN` goes out (`0x46c272`), and the loading screen drops when the player is created (`0x5debbd`). vmangos sends that create ahead of the login notices (`Handlers/CharacterHandler.cpp:675`), so the line is added after the reveal and gets its full time on screen. benilla's cover stays up through its own warm-up, about 6 s on a cold login, and the line faded underneath it. A relog is warm, so the line survived.

Server notice lines (`SMSG_NOTIFICATION`, area-trigger messages) now wait while the cover is up and fire on the frame it drops.

Test: a notice under the cover fires once, at the reveal. A live cold login shows the line the frame after the cover clears.

Fixes #1252

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#1808's stand-in search ranked ALSA devices only when the host's name read "ALSA", but cpal names it "Alsa". So the ranking never ran, and the first device ALSA lists won. On the reported machine that was `null`, which discards every sample, so sound stayed silent behind a warning that it was playing.

The check now compares the host's `HostId::Alsa` variant, so the ranking applies: `pipewire`, then `pulse`, then each card's own outputs, and never `null`.

The Linux crosscheck compiles it. The ranking's tests are #1808's.

Fixes #1806

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The loading screen's "Tip:" could read "Tlp". benilla drew the tip with Bevy's own text raster at a fractional size, which hinted the i's dot onto its stem at some heights, 1080p among them.

1.12.1 draws the tip through its font engine (`0x5c1c30`, from `0x40679c`). The tip now goes through benilla's font engine, the one every FontString uses, at the reference's size, line spacing, bottom-left justification, trimmed line ends and pen step. The shadow takes the body's alpha (`0x5cd65b`), and the shadow offset and the anchor snap to device pixels (`0x5c7010`, `0x5cdf70`). The Bevy text reshape plugin, now unused, is removed.

Tests: the i keeps a gap row at six window heights, the layout matches the engine's for the DBC line, and placement holds at DPI 1 and 2.

Fixes #1089

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A jump on a descending lift (Undercity, Gnomeregan) played the hard-landing grunt and dust. While airborne, benilla carried the rider with the deck but kept the jump's start height in world coordinates. So the lift's drop during the arc, about 24 yd on Undercity's 29 yd/s cars, counted as a fall, and FALLINGFAR latched early.

1.12.1 measures the fall height (`0x7c60c0`) in the mover's own frame: transport-local while aboard, re-mapped when the mover boards or leaves (`0x7c6620`). `Player::carry_to` now moves the start height with the deck. A remote mover's arc is measured in the deck's frame when it takes off and lands on the same transport.

Tests: a 60 fps jump on a deck dropping 28.95 yd/s lands with no fall height and no FALLINGFAR, for us (23.64 yd before) and through packets for a remote (23.88 yd before).

Fixes #1250

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Action-bar hotkey digits sat on black boxes. Their font inherits `monochrome="true"`, and benilla had no monochrome mode, so it baked them anti-aliased with a THICK outline that filled the whole small cell.

1.12.1 rasterizes a monochrome font 1-bit (`0x5d1300`, render mode 1). `0x5ce440` then draws it with a one-pixel black ring, plus a 7/15-alpha second ring under THICK. benilla now carries the flag from Font and FontString XML and from every `SetFont`, `Button:SetFont` included. It rasterizes such glyphs with FreeType's mono rules (drop-out mode 2, span jitter) and bakes the ring.

Also fixed: a FontString's XML `outline="NORMAL"` or `"THICK"` with a font file now outlines (`0x771214`). It had been parsed with the Lua spellings.

Tests: the hotkey numerals match a 1.12.1 capture texel for texel, a THICK mono cell is ringed rather than filled, and the flags round-trip.

Fixes #1811
Fixes #1812

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Moving fast raised the loading screen whenever the body outran terrain streaming. 1.12.1 never shows a screen there. Its tile pass (`0x6985d0`) blocks on each missing inner-window tile (`0x698689` → `0x443bd0`), so the frame hitches.

benilla now does the same. It requests every missing inner-window tile at once, waits for them in the frame, and builds them and their colliders untimed, during play and cinematics. The loading-screen backstop is gone. A read still hung after 10 s is waited on once, then streams on. A second defect fed this: a settled stream re-walked its window only at a tile crossing, so tiles ahead were requested late. It now re-walks on every chunk step.

Tests: outrunning the stream raises no screen, and a hung read costs one capped wait. Live, at speed ×100 across Kalimdor, no screen came up.

Fixes #1028
Fixes #1823

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Text glyphs were rasterized hinted and seated 1 to 3 px left of 1.12.1's. The client loads every glyph with `FT_LOAD_NO_HINTING` (`0x5d1324`, flags `0x208a`/`0x20c2`). It places each quad at the pen plus `ceil(horiBearingX)` (`0x5cd099` → `0x5cb080`), with the ink 0, 1 or 2 columns into the cell for plain, NORMAL and THICK (`0x5ce481`, `0x5ce48a`). benilla hinted the anti-aliased cells and seated them at `floor(xMin)` less the outline padding.

Anti-aliased cells now come from the face's unhinted 26.6 outline, the one the measure already reads, through `zeno`, swash's own scan converter. Every bake is seated at the rounded-up bearing with its ink column. The drawn ink now meets the measure.

Tests: Arial Narrow `e` at 18 px matches FreeType's unhinted raster byte for byte, and the five bakes seat at the bearing. Fitted to a 1.12.1 capture, a chat line lands on the client's columns.

Fixes #1813
Fixes #1822

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Two glyph-cell differences from 1.12.1.

Rows: the client keeps at most the font's pixel em of a glyph's rows. `0x5d1120` stores `min(em, rows)` (`0x5d1195`-`0x5d11a2`), and every bake reads that many rows from the top. benilla packed the full raster, so unhinted glyphs one row taller than the em (FRIZQT `ý þ` at 14 px, Arial Narrow `Ò Ó Ù Ü` at 14 px) drew a faint extra row. Cells are now clipped to their first `ppem` rows before the ring is built.

Alpha: the client's glyph texels are ARGB4444. The plain bake keeps coverage's top 4 bits (`0x5cf298`-`0x5cf2a1`), and the outlined fill's gray is `(cov·0xff) >> 12` (`0x5cf1b8`). benilla packed 8-bit coverage. Coverage cells and outlined fills now pack 4-bit values, so coverage under 16 draws nothing, as in 1.12.1.

Tests: an over-tall `Ò` keeps its top 14 rows, and every packed alpha is 4-bit exact.

Fixes #1827
Fixes #1828

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The quest log left out two kinds of objective line. A quest with an end text, such as an escort or event quest, showed no objective and couldn't be watched. A quest that requires a standing with a faction lacked its reputation line.

1.12.1 counts both (`0x4e0000`). `GetQuestLogLeaderBoard` (`0x4e0110`) answers line 1 with the raw end text, typed "event" and finished by the slot's complete bit (`0x4e02a2`), and the other lines move down one. It ends with a "reputation" line: `QUEST_FACTION_NEEDED` with the faction's name and the current and required standing labels (gendered), finished at the required standing (`0x4e0787`). Both are now built in that order.

Tests: an event-only quest, an event plus kill quest, and a reputation-gated quest's lines, text and finished state.

Fixes #1205
Fixes #1837
Fixes #1834

Co-authored-by: Thorsten42 <3400178+Thorsten42@users.noreply.github.com>

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A player could walk or swim past the edge of the world and keep going. 1.12.1 stops every movement step whose swept box touches terrain that isn't loaded. The query `0x6aa8b0` fails on a null tile or chunk (`0x6aadfa`, `0x6aae1f`), and the substep loop then runs no resolver and reports the time skipped (`0x634154` → `0x615ae0`).

benilla now builds the reference's walk, swim and fall boxes and holds the body when its box touches a tile that isn't resident. Position, fall clock and FALLING are kept, and `CMSG_MOVE_TIME_SKIPPED` goes out for the held time. A tile whose terrain file fails to load counts as resident, so it is never a wall.

Tests: a walk stops at the edge and walks back, and a jump hangs. Live, a teleport past the edge held in place.

Fixes #1830

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Widening the window on a glue screen stretched the login, select and create scenes past 16:9.

1.12.1 lays the glue out at `gxResolution`'s aspect (`0x41ad10` at `0x46a828`), and `GlueParent_OnLoad` (GlueParent.lua:74-84) boxes GlueParent to a centred 16:9 rect on a wider screen. The size callback `0x46abe0` only re-scales and re-lays GlueParent, and its window keeps gxResolution's shape while dragged. benilla's glue root is the window, but GlueParent's box was set only at build.

Now a resize of the glue's screen re-runs the chain's own `GlueParent_OnLoad` from its declared `setAllPoints`, so the box follows the window without a rebuild, which would reset the create screen and the typed login (a `Deviation:` line). A new test resizes a built glue through 21:9, 2:1, 16:10 and 4:3, and checks the box, the scenes and that the screen stands.

Fixes #1844

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`Model:SetCamera(n)` on a PlayerModel did nothing: the character pane always drew through its fixed pane rig.

1.12.1's PlayerModel registers no SetCamera of its own (`0x506230`), so the Model verb (`0x76e0e0` → `0x76cec0`) installs camera n of the copy; one past the model's count drops to the orthographic leg (`0x76ce80`). Every new copy gets camera 1 back at the ready hook (`0x505890` → `0x76cec0(1)` at `0x50589c`), and is armed with Stand (`0x505a2f`).

Now the pane draws through the installed camera, live or frozen as the reference does, with the ortho leg's particles at its unit (`0x7b2a50`). A camera or arm held for a pending copy dies when a new build is asked. The API-wall scanner now follows `#[path]` test modules. Tests cover each leg, the reset and both held-state sequences.

Fixes #1835
Fixes #1847

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A `RegisterAllEvents` frame sat on a list of its own: it heard every event after all named listeners, even untabled names, and `UnregisterEvent` could not take one event off it.

In 1.12.1 there is no all-events flag. `RegisterAllEvents` (`0x774c20` → `0x7023e0`) appends the frame to each tabled event's list where it is absent, as one `RegisterEvent` per event would, so dispatch (`0x703e50`) runs in registration order and `UnregisterEvent` (`0x702280`) removes that one event. The walk sets `event` once and restores it after (`0x703ea4`, `0x703f04`); `this` and the args stay per listener (`0x704f10`).

Now registration goes through one per-event list and the second pass is gone. Tests cover the interleaving, the per-event unregister, a re-register going last, a mid-dispatch registration, a frame registered both ways, and the `event` global.

Fixes #1165

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The flat saved-variables file ran as Lua, and an add-on's saved file was skipped when its toc declared no scope.

1.12.1 reads `SavedVariables.lua` as lines (`0x4913b0`): a name, `=`, then a quoted string (raw, empty is nil) or a number read by `SStrToFloat` (`0x64aaa0`) and rounded to f32; anything else is skipped and no Lua runs. An add-on's account and character files run whenever they exist (`0x51f4a2`), and at save a scope that declares nothing removes its file (`0x51f859`). `RegisterForSave` is refused outside Blizzard code (`0x4884e0`).

Now the reader parses lines, unescaping the writer's escapes (a `Deviation:`). An old-format file with lines the reader drops is copied once to `.bak`. An undeclared scope's file read cleanly this session is renamed to `.bak` instead of deleted (a `Deviation:`), and `RegisterForSave` raises 1.12.1's two errors.

Fixes #1166
Fixes #1167

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Six places where benilla's Lua answered unlike 1.12.1's:

- The lexer took `[=[` long-string levels; 1.12.1's `luaX_lex` tests one byte after `[` (`0x6ff793`) and `--[=[` is a line comment (`0x6ff6c9`).
- `math.random` ran a glibc-style `rand` seeded with 1; now MSVC's (`0x7400e5`, `% 0x7fff` times 1/32767 at `0x7fb310`), clock-seeded as the client is (`0x5d1c8b`), fixed in captures and the addon harness.
- `getfenv`/`setfenv` honour a raw `__fenv` field (`0x702ba0`), and `setfenv` returns nothing.
- An object's text is `type: %08X` in the embedding API too (`0x74301d`).
- `time` converts a date table through MSVC's `_mktime` (`0x74066b`).
- `date` formats with MSVC's C-locale patterns (`0x8323a0`): `%c` is `09/09/01 01:46:40`, `#` drops zeros.

Each has a test that fails without it; the addon corpus parses as before.

Fixes #1257
Fixes #1258
Fixes #1510
Fixes #1511
Fixes #1503
Fixes #1504

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The UI ticked on raw `Time<Real>`, which under pipelined rendering mis-splits shown frames (26.4 and 8.0 ms for two 16.7 ms frames), so a held paperdoll arrow turned in uneven steps while the bones, on paced `Time<Virtual>`, did not.

1.12.1 hands `OnUpdate` the loop's own frame time, unclamped (`0x420f70` → `0x885608` → `0x76b2c0`).

Now the pacer publishes one step per frame (`FrameStep`) that `Time<Virtual>` and the UI both read: `OnUpdate`'s elapsed, `GetTime` (a `Deviation:`, since 1.12.1 reads its clock live at `0x515ea0`), the cooldown anchor, aura expiries and `GetFramerate`. A stall reaches the UI whole. The pacer keeps every millisecond, drains its carry, and no longer jumps after a pause or splits a long hitch. Tests replay a stream measured on this machine.

Fixes #1846

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A held paperdoll turn re-arms a shuffle once per cycle. When it fell inside Stand's half-blend, the pane reused the one track it held for that clip, so the fading shuffle jumped back about 3 frames every 0.53 s, and the fade's end restarted the primary.

1.12.1's arm (`0x7121a0`) copies the outgoing primary into the blend slot with its own clock (`0x7125d6`). A refused arm (`0x7125c9`/`0x7125d4`) keeps that slot, the latched shortcut (`0x712549`) leaves it alone, and the slot ends on its own (`0x7147b1`), weighted by the smoothstep (`0x714880`).

Now each sequence has a second graph node, and the pane's blend slot runs on whichever node the primary is not on. A refused arm leaves it untouched, and the window's end stops only the secondary. Headless player tests cover the held turn, the fade end and the shortcut.

Fixes #1845

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GameTooltip re-sized itself on every resolve from its lines, at 10/40/260 units, counting a taller right text. So `GetWidth` after `Show` read stale, a script's `SetHeight` after `AddLine` was overwritten, and descriptions wrapped wide.

1.12.1 sizes only in the show (`0x530aad`), `AppendText` (`0x530615`) and the cache answers, never in `AddLine` (`0x531630`). `GameTooltipSize` (`0x530640`) takes the widest unwrapped line plus a 38.4 gap, wraps the rest at the running widest (capped at 230.4), sums shown left heights with 2.0 gaps, pads 10.24 a side, then re-anchors without clearing.

Now the sizer is that function, called from those sites, and the engine no longer anchors line cells; the stock templates declare them. A measure memo keeps a steady hover from measuring again. Tests pin each loop, constant and caller.

Found in Thorsten42/manila@50452d3fa5.

Fixes #1206
Fixes #1182
Fixes #1540

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The XML loader merged a template chain into one element, losing each attribute's level: `setAllPoints` lost to a template's `<Anchors>`, an empty `text=` wiped a template's text, the template's `<TexCoords>` won.

1.12.1 never merges: each loader re-enters on `inherits` (`0x76fe36`, `0x770f9e`, `0x76985c`), then reads its own node's attributes and first children (`0x6f2c50`).

Now regions load per level in `0x770f40`'s order; frames read geometry, `frameLevel`, StatusBar attributes and button text per level. A failed texture fills green (`0x878d24`). Level tests cover each rule; the stock load is unchanged but for the AddOns window's crops.

Fixes #1534
Fixes #1548
Fixes #1547
Fixes #1866
Fixes #1423
Fixes #1858
Fixes #1424
Fixes #1472
Fixes #1861
Fixes #1859
Fixes #1171
Fixes #1860
Fixes #1817

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Four Button behaviours differed from 1.12.1: `Click()` on a disabled check box did not toggle it, a checked texture made from a path drew with BLEND, `Click(name)` passed its argument raw, and a pressed button's label never shifted.

1.12.1's CheckButton click (`0x785550`) toggles before the base click's enabled gate (`0x779552`); the create leg of the checked setters stores ADD (`0x7852f4`); `Click` maps its argument case-blind to a button name or `UNKNOWN` (`0x7826c0`); and the label relink (`0x779810`) offsets the text by `PushedTextOffset` while PUSHED.

Now each follows the reference: a disabled box also ignores a press, `Set/GetPushedTextOffset` and `<PushedTextOffset>` are built, and held stock buttons shift their label about 1.3 units down-right. `Set*FontObject` raises on an unknown font name (`0x87c5fc`). Each has a test.

Fixes #1175
Fixes #1176
Fixes #1179
Fixes #1516
Fixes #1854

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The CVar set path differed from 1.12.1's: values compared case-sensitively, text on a numeric CVar was refused, a read-only CVar still took console and host writes, `ConsoleExec` ran commands a frame late, and console `set` kept the whole line.

1.12.1's InternalSet (`0x63e0b0`) returns for a read-only record (`0x63e0ba`), compares with `SStrCmpI` (`0x63e0ec`) and stores any text, with the record's int and float by `SStrToInt`/`SStrToFloat` (`0x63e127`). `ConsoleExec` runs the line inside the call (`0x515f43`), and `set` tokenizes with `SStrTokenize` (`0x64ae50`).

Now there is one set path: the registered default strings, one `SStrToInt`, read-only refusing every writer, CVar commands run inside `ConsoleExec`, and the host copies are gone. Saved values match 1.12.1: a slider at its default writes "1" over "1.0", and hand-edited text reads 0.

Fixes #1478
Fixes #1853
Fixes #1278
Fixes #1319
Fixes #1274
Fixes #1640
Fixes #1851
Fixes #1475

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fonts were copied at load: a later change to a font object never reached the fonts and strings built from it, and a Button's state fonts were names overlaid at draw, so a label measured in one font and drew in another.

In 1.12.1 a font follows one parent (`0x770c60`) under a five-bit inherit mask; each setter clears its own bit and pushes only what changed to its followers (`0x784180`, `0x770910`). A Button holds three embedded fonts and relinks its label on each real state change, after the script (`0x779810`).

Now fonts link live, case-blind by name (`0x64b3f0`), `Font:SetFontObject` links and refuses loops, a Button's fonts are real linked fonts with their getters, and `<NormalColor>`-style elements load (a `Deviation:` for two). Tests cover the push, the masks and each button state; the stock UI draws as before.

Fixes #1204
Fixes #1517
Fixes #1403
Fixes #1855
Fixes #1856

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
It was a nil stub, so an assistant got no raid target icons in the unit
popup (UnitPopup.lua offers RAID_TARGET_ICON to the leader or to
IsRaidOfficer) and the raid frame's add-member button stayed off for
them. 0x4bb910 walks the raid roster (0xb712a8, count [0xb713e0]) for the
row whose guid is the local player's and pushes 1 when that row's rank
([row+0xc], GetRaidRosterInfo's return 2) is above 0, so for an
assistant and for the leader; outside a raid, without a player or
without our row it answers nil.

Verified: cargo test -p benilla-ui --lib
(is_raid_officer_reads_our_own_roster_rank), clippy -D warnings.
Built on macOS only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Texture paint answered and drew unlike 1.12.1: `SetTexture("")` and non-string names answered nil, a missing file cleared desaturation, `SetTexCoord()` and an unknown `SetBlendMode` passed quietly, a failing bar path still installed a bar, and DISABLE, ALPHAKEY and MOD drew as BLEND.

1.12.1's load (`0x770200`) writes the shader slot only for a file that loads (`0x7702c0`); the binding answers 1 for a null name (`0x79bdae`). The submit draws a coloured DISABLE or ALPHAKEY texture as BLEND (`0x76fb82`); ALPHAKEY alpha-tests at 224/255 and MOD multiplies the destination (DESTCOLOR, ZERO, `0x80a1fc`).

Now one texture load serves every setter, the usage lines raise, the shader draws each mode with a MOD pipeline of its own, and a new test validates the UI shader with naga. The stock UI draws as before.

Fixes #1425
Fixes #1864
Fixes #1863
Fixes #1521
Fixes #1556
Fixes #1550
Fixes #1559
Fixes #1555

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A unit the player cannot select shows no name
- Nameplates: the own name's first-person fade is a Deviation, not ShouldShowName's leg

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Sam <samuv.eth@gmail.com>
feed_party and feed_units share FieldEventPushes with no order between
them. When the party feed ran first, TargetFrame's RAID_TARGET_UPDATE
handler read GetRaidTargetIndex("target") off the previous snapshot, so
a new mark showed over the mob but not on the target frame until the
target changed. A UnitSnapshots set holds feed_units, and the party feed
runs after it. the_party_feed_runs_after_the_unit_snapshots reads the
order off the declared graph; it fails on the tree before this change.

Verified: cargo fmt --check, clippy -D warnings, cargo test -p
benilla-app --lib (5093 passed). Live on a vmangos server, a build with
this change shows raid marks set in a raid on the target frame. Built
on macOS only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The addon probe fires events, clicks and runs on the bare interface
- A probe click and hover answer what the hover raised
- The probe outcome's doc names the bare interface and every answer form

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Sam <samuv.eth@gmail.com>
Colours and alphas were kept as floats where 1.12.1 packs them into bytes, and several frame verbs answered bad input unlike the client.

1.12.1's colour setters clamp, round half up into a byte, and read back times `0x81c6b4`; a frame's alpha is a truncated byte (`0x774f32`) whose walk stops at an unchanged byte (`0x76a690`); region and Font `SetAlpha` wrap. `SetScale`, the draw-layer verbs, the scroll range (`0x786e30`) and the binding verbs gate their arguments as the bytes do.

Now one packed `Rgba8` serves every colour, frame alpha is a byte, and each verb raises the reference's usage line. Read-backs shift by at most 1/255; nothing visible changes.

Fixes #1523
Fixes #1536
Fixes #1868
Fixes #1867
Fixes #1247
Fixes #1248
Fixes #1546
Fixes #1557
Fixes #1562
Fixes #1189
Fixes #1509

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The addon harness seats its target at world entry, so TargetFrame loads
- The harness's world-entry target cites the selection and load order

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Sam <samuv.eth@gmail.com>
The console and CVar store differed from 1.12.1 in seven places.

- `cvar_reset` restores the session-start value (`0x63dff0`), `cvar_default` the default; a latched row moves its value, never the device (RestartGx reads only the stage, `0x639ec0`).
- `cvarlist` prints `  "%s" is "%s"` with the default and reset suffixes, cut at 0x100 bytes.
- A staged latched value is saved (SaveConfig takes stage, value, default, `0x63da27`).
- The setting callbacks refuse what 1.12.1's refuse (MaxLights `0x688bd0`, horizonfarclip `0x689250`, ErrorFilter `0x403200` and nine more), printing their lines.
- Console replies go to the log in 1.12.1's words, never to chat.
- The saved account name is the `accountName` CVar (`0x46cdd0`); the old `benilla-config/account` file moves into config.toml once.

Tests pin each against the bytes; a byte audit re-derived every address.

Fixes #1267
Fixes #1268
Fixes #1269
Fixes #1340
Fixes #1643
Fixes #1650
Fixes #1852

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Three differences from 1.12.1 on the model and glue lanes.

- `SetCharSelectBackground`/`SetCharCustomizeBackground` stood only the stock seven scenes. Any pane file now stands as itself, opened loose-aware as the scene model is (`0x76ccf5` flags 5, `0x707365`), and a scene with no camera draws on the orthographic leg (`0x76ce80`, `0x7ad7f0`) instead of keeping the last aim.
- Every model lane takes one name rule (`0x706a50`): the last dot, `.mdl`/`.mdx` to `.m2`, `.m2` as is, anything else the ErrorCube (`0x70737f`); the name is cut at 0x103 bytes first.
- `LaunchURL` refuses a digit after `http://` (`0x73fb91`, isalpha).

A missing file that passes the rule still stands nothing; that is #330's fallback. Tests pin each fact; a byte audit re-derived every address and lane.

Fixes #1836
Fixes #1873
Fixes #1108
Refs #330

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The macro icon list and its verbs differed from 1.12.1.

- The list is built on first use per in-game interface (`GetNumMacroIcons`, and `CreateMacro`/`EditMacro` past their gates), never at VM creation; `GetMacroIconInfo` never builds and answers `""` off the list or for an empty entry (`0x4f1ac5`, `0x4f1a88`).
- Its sources are 1.12.1's three (`0x4f0090`): the listfiles of the patches, speech2 and interface.MPQ only, then the two loose `Interface\Icons\` scans (`0x4f0350`, `0x4f04f0`), sorted case-blind and deduplicated.
- `EditMacro`/`CreateMacro` read the icon as a number (a numeric string counts) and keep the old icon on a miss (`0x4f1945`, `0x4f12f0`).
- `GetMacroIconInfo` with a non-number raises its usage line.

The listing takes about 7 ms on the stock chain, down from 150. Tests pin each fact; a byte audit re-derived every address.

Fixes #1512
Fixes #1833
Fixes #1874
Fixes #1875

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Argument checks and usage lines across the UI API now raise 1.12.1's text in its order.

- One name helper reads the object's own name or `<unnamed>` (`0x84c7f0`), never Lua `GetName`.
- CreateFrame checks type, parent, template, then frame type, with the reference's five messages (`0x7060d5`-`0x7062a0`).
- Justify, scroll, max-letters, orientation, value, insert-mode, hyperlink-format, CreateFont and IsFrameType usage lines; not-movable errors say `<unnamed>`.
- Numbers become text by Lua's `%.14g` at the tooltip and eight other bindings.
- AppendText and the Slider XML loader follow theirs.

Every string was read from the client; a byte audit re-derived each.

Fixes #1882
Fixes #1529
Fixes #1535
Fixes #1549
Fixes #1885
Fixes #1904
Fixes #1886
Fixes #1168
Fixes #1909
Fixes #1910
Fixes #1911
Fixes #1879
Fixes #1912
Fixes #1913
Fixes #1914
Fixes #1916
Fixes #1927

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The XML font legs and the font-object getters differed from 1.12.1.

- A font file that fails to load writes nothing and logs `Unable to load font file` (`0x878dc8`, `0x87a4c8`); a font already held skips the load (`0x771502`).
- A FontString's `inherits`/`font=` look in the one font table `CreateFont` fills (`0x783870`) before templates; a miss logs `Couldn't find inherited node`.
- Regions read no `alpha=`; the options window's icon sets it in its OnLoad.
- Justify tokens follow `0x6f1990`: a miss is skipped, the other axis's token zeroes the axis.
- A Font object's default shadow is transparent.
- `GetFont` lists every flag, comma-joined (`0x6f1ad0`); only `THICKOUTLINE` is thick.

No stock screen changes. A byte audit re-derived every address and string.

Fixes #1888
Fixes #1891
Fixes #1921
Fixes #1922
Fixes #1887
Fixes #1919
Fixes #1928
Fixes #1899
Fixes #1905
Fixes #1816
Fixes #1929

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The minimap's methods, state and arrows differed from 1.12.1.

- Zoom, indoor flag, mask and ping are process-wide (#1876); SetZoom caps unsigned and writes only a change (`0x6da8e0`); every world entry reseeds the zoom from its CVars (`0x4ed9d0`).
- PingLocation pings in the call and signals MINIMAP_PING at once (`0x4eeca0`); raid and party pingers get their tokens.
- SetMaskTexture loads in the call, a miss stores green and raises; the tiles take the mask's colour (`0x4eae74`).
- The nine Model children are built (`0x4edbc0`); the rim and player arrows draw through them, above the dots, with SetArrowModel, SetPlayerModel, SetIconTexture and SetBlipTexture. The corpse is rim slot 2.

On screen: the player arrow is a 3D model; the corpse's in-range icon is about 4% larger. A byte audit re-derived every address.

Fixes #1522
Fixes #1645
Fixes #1652
Fixes #1653
Fixes #1871
Fixes #1872
Fixes #1876
Fixes #1877
Fixes #364

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A tooltip line wrapped past 20 rows sized narrower than in 1.12.1. The sizer splits a long line into at most 20 pieces, the last running to the end of the text (`0x53082f`, `0x53088e`), and sizes the plate to the widest. Each piece runs from one row's start to the next (`0x530884`-`0x5308a8`), so it carries the whitespace the wrap skipped (`0x5c24fe`, past `0x5c7710`), and a space measures as a glyph. Pieces are now measured that way, so most wrapped tooltips are about a space wider. Tests pin the 20-piece cap, the kept space and a piece crossing a line break.

Fixes #1880

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Sam <samuv.eth@gmail.com>
Frame sizes and positions skipped 1.12.1's screen-unit law. The client keeps layout units: a setter stores `S(v) = RN24(K1·RN24(RN24(v)/1024A))` and a getter answers `RN24(1024A·l)/K1`, from the cells `0x41ad10` sets from the aspect (benilla-re `systems/screencoord.md`, two readings).

- The layout resolves in layout units with the reference's four float roundings (`0x767414`, `0x76741f`, `0x767428`, `0x767a30`); the pixel seam converts.
- Size, edge, centre and point getters and setters, the screen-size and cursor globals (glue and game), scroll ranges and scroll events follow the law; OnSizeChanged passes the layout size (`0x76b660`).
- Scroll bars hide on `floor(range) == 0`, as the stock template does.
- The minimap and world-map arrows re-seat when the cells change.

At 4:3, `GetScreenHeight()` is 767.9999427795418. Nothing moves on screen beyond 2e-4 px. A byte audit re-derived the law and the resolver.

Fixes #1918
Fixes #1321

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`GetTrainerServiceInfo` answered nil for a rankless service's subText. In 1.12.1 the subText is the Spell record's NameSubtext (`0x4d8b50`, read at `0x4d8b84`), the empty string for a spell with no rank; only a service with no Spell record answers nil (`0x4d8b58`). The resolver now keeps an empty rank as `""` and the binding pushes None as nil. Tests cover both rows and the install-backed Apprentice Blacksmith trainer.

Supersedes #1935.

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>
Widget methods 1.12.1 has were missing or differed.

- EditBox IsAutoFocus, IsMultiLine, IsNumeric and IsPassword; ColorSelect GetColorHSV and SetColorHSV (`0x7bbc80`, `0x7bbd60`).
- AddMessage with no colour takes the frame font's colour.
- ScrollingMessageFrame: the write slot and the four line-position methods; the cached lines-shown, at-top and at-bottom state its readers use (`0x788440`); a relayout on resize (`0x788310`); OnMessageScrollChanged (`0x788a9a`); SetTimeVisible and SetFadeDuration usage, unclamped; each line fading on its own countdowns (`0x7884c1`).
- GetFramerate averages the last 30 frames (`0x672700`).
- SetCursor takes the eight named cursors in any case, with its usage line; custom cursor files are still to do.
- SimpleHTML links get buttons, so OnHyperlinkClick fires.

A byte audit re-derived every address.

Fixes #1518
Fixes #1520
Fixes #1506
Fixes #1519
Fixes #1883
Fixes #1682
Fixes #1538
Refs #1186

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An auction row whose item template had not landed answered a half-filled row: a nil quality beside the real count, prices and owner. In 1.12.1 `GetAuctionItemInfo` (`0x4cee40`) looks the row's item up in the item cache (`0x55ba30`); a miss jumps at `0x4cefa0` to the one exit `0x4cf1ec`, which answers the twelve-value miss shape with `count = 1` and `quality = -1`. Such a row now takes that exit. Tests cover an uncached row and a cached one.

Supersedes #1939.

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: Claude Sonnet 5.5 <noreply@anthropic.com>
- Anchors take a point from every child; a bad point or relativeTo is logged and skipped before its Offset is read (`0x767992`).
- Numbers parse with SStrToFloat (`0x64aaa0`) at every site; length readers take the node, then its first child.
- A bare `<Shadow/>` is 0.001 layout units, drawn rounded to whole pixels: the zone and world-map text shadows draw 2 px at 1080p.
- EditBox HighlightColor; the text frames' `font=`, a `<FontString>` child loading into the frame's own font, and message-frame TextInsets.
- Insets, resize bounds, title region and fog colour apply every child, the instance's last; a Backdrop dispatches its children in order and logs unknown ones.

A byte audit re-derived every address.

Fixes #1889
Fixes #1926
Fixes #1273
Fixes #1920
Fixes #1903
Fixes #1906
Fixes #1925
Fixes #1936
Fixes #1937
Fixes #1943
Fixes #1944
Fixes #1945

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SimpleHTML's element fonts, a plain frame's direct FontString, and a model pane's camera differed from 1.12.1.

- SimpleHTML's four element fonts are font nodes (`0x789e7c`) its blocks link to, P's when an element holds no face (`0x78ae29`), so a later font change reaches pages already shown and MONOCHROME reaches the text; the 16 font methods are the Font object's own (`0x79ef10`-`0x79fd70`). The loader links `font=` and reads `file=` before `hyperlinkFormat` (`0x78a2b2`).
- A FontString directly under a plain frame builds nothing; the base loader has no arm for it (`0x769b27`).
- A model pane keeps the camera it resolved at SetCamera (`0x76cec0`); after SetModel the old camera holds as last painted, in the glue scene and in-world panes.

No stock screen changes. A byte audit re-derived every address.

Fixes #1890
Fixes #1923
Fixes #1924
Fixes #1948
Fixes #1930

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ten differences from 1.12.1 in the UI core.

- StatusBar range and value follow `0x783270`/`0x7832d0` (a reversed pair collapses, a value before a range is dropped); the health watchers set both each update; the fill draws once both are set, its coordinates built from scratch (`0x7833c0`).
- A full collection runs before PLAYER_LOGIN (`0x490941`); the addon harness runs the client's world-entry cascade.
- Every index argument raises its own usage line (`0x6f34d0`, then the binding's literal).
- Strata, layer and frame-type names match whole (`0x6ee32c`); stack lines name unnamed functions by global (`0x6fbf90`).
- SetCursor takes a custom file (`0x523c40`); EditBox accepts OnInputLanguageChanged (`0x77a40d`).
- debugprofilestop answers milliseconds (a Deviation: multi-core 1.12.1 returns cycles).

A byte audit re-derived every address.

Fixes #1178
Fixes #1915
Fixes #1917
Fixes #1908
Fixes #1839
Fixes #1869
Fixes #1170
Fixes #1105
Fixes #1186
Fixes #1952

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Since #1932 every Model frame drew the error cube: the VM queued a pane's file by its match key, `model_key`, which drops the extension, and the host loaded that key as the path, which the cache's extension rule (`0x706a50`) refuses (`0x70737f`).

The queue now carries the pane's own path beside the key, and the host opens that path through the rule: `.mdx`, `.mdl` and `.m2` load the `.m2`, and a refused name stands the cube. `model_key` is now the file the cache opens, so a refused name keys as the cube and never shares facts with its valid twin.

Tests: the three spellings of the minimap arrow load `interface/minimap/minimaparrow.m2`; `Creature\Wolf\Wolf` and `Foo.blp` load and key as the cube; the minimap's children queue their stock models.

Found in Thorsten42/manila@103bbe4.

Fixes #1958

🤖 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>
Beside the frame size and point methods (#1951), 1.12.1 puts every other script length through the screen-unit law: a setter stores `S(v)` and a getter answers `G(l)` through `K1` (`0x41ad70`, `0x41ae40`, `0x41ae60`). benilla stored and answered these as given.

- Resize bounds, hit-rect and text insets, backdrop lengths, pushed text offset, text extents, font heights (Button:GetFont through `0x79f3b0` too), shadows and spacing follow the law.
- A `<Rel…>` XML length is stored as parsed, in layout units (`0x6f2037`-`0x6f2388`).
- The tooltip sizer keeps its pad, line gap and wrap cap at 53 bits, as `0x41ae60` leaves them in `st0`.
- User-placed frames re-seat from the layout cache when the cells change, over what the scripts last built.

`SetMinResize(100, 100)` now reads back `G(S(100))`, not exactly 100, as in 1.12.1. Tests pin each kind against the law at 4:3 and 16:9; a byte audit re-derived each site.

Fixes #1942

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Method setters, bindings and the nameplate solver now gate their arguments as 1.12.1 does.

- Setters given a non-number raise their own usage line; SetTextHeight, SetFrameLevel, SetMaxLines and SetHistoryLines raise on the values 1.12.1 refuses (`0x87c750`, `0x879628`). XML `historyLines="0"` still loads, through the ring setter (`0x77cd90`).
- Bindings gated by `lua_isnumber` take 1.12.1's miss path: nil, a default, or no values.
- SetLootMethod follows `0x4e92a0`: group and leader gates, threshold argument 2 except for master, 2 on a miss, master names resolved by prefix (`0x493aa0`), errors for an unknown method or looter. SetLootThreshold takes the same gates.
- Quest watches fall back to the last selected quest; MessageFrame times store unclamped and expire to 0 (`0x786323`).
- The placement bound is `0x509dd0`'s 32-bit product, so a zero-width rect no longer panics.

Credit to kalistoking/benilla-twow@5b5374bc for #1956. A byte audit re-derived every site.

Fixes #1953
Fixes #1955
Fixes #1956
Fixes #408
Fixes #415
Fixes #416

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

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
1.12.1 resolves every name through one function, `0x493aa0`: party and raid passes for members with no object held, then every held object its mode's filter passes, feeding one matcher (`0x493c60`). benilla had partial copies: the app's scan ran the object pass only, SetLootMethod the roster passes only, and Promote, assistant, duel and emote lookups matched exact names.

`benilla_ui::script::by_name::resolve` now runs the passes with each caller's typemask, mode and exact flag: TargetByName, /target, /assist, /follow, DoEmote, SetLootMethod, PromoteByName, PromoteToAssistant, DemoteAssistant and StartDuel. It answers nothing with no active player (`0x493ae0`). Mode 2's filter is `CanAssist` and alive (`0x493eca`).

The roster passes stay off for /target, /assist and /follow until the selection can hold a guid (#1965).

Tests per caller: prefix, a held stranger, a dead groupmate, a miss, no player. A byte audit re-derived the call sites and the matcher.

Fixes #1964

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

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Both verbs answered whatever the container snapshot held, so the keyring, the bank and the backpack answered as ordinary bags.

1.12.1: `GetContainerNumSlots` (`0x4f9560`) answers 16 for the backpack, 24 for the bank, the bag's size for 1-10 and 0 for anything else, the keyring included (`0x4f95fb`). `GetBagName` (`0x4fa670`) answers `BACKPACK_TOOLTIP` for the backpack (`0x4fa6f0`), the bag item's name for 1-10, and nil for the bank, the keyring and the rest (`0x4fa712`).

Now both apply those ranges, the backpack is named by its global, and the snapshot no longer carries English names for the backpack, bank and keyring. A test pins every range. The keyring stays readable through `GetKeyRingSize` and the item verbs.

Found in Thorsten42/manila@a9fffcb5df.

Fixes #1967
Fixes #372

🤖 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>
Since #1959 a bar texture cuts its drawn rect only when its install set the texcoord-modifies-rect flag. The nameplate attached its fill by hand, never through that install, so the fill stayed full whatever the health.

1.12.1 builds the fill with the bar's own path verb: the plate setup calls `0x783140` with the fill's path on layer 2 (`0x7cb31a`-`0x7cb329`), which pins it to the bar and installs it through `0x7831f0`, the step that sets the flag (`0x783220`).

The plate now builds its fill through that same path leg, lifted into a model-level function, so it is installed, flagged and pinned like any bar texture. A test drives a plate at 40, 20 and 10 of 40 health and checks the drawn fill at full, half and a quarter width; it fails without the fix.

Fixes #1969

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Unticking Character Specific Key Bindings, or pressing Defaults, raised `StaticPopup.lua:1713: attempt to compare number with nil` every frame the confirm stayed up.

1.12.1: `StaticPopup_Show` copies an entry's `timeout` into `dialog.timeleft`, and `StaticPopup_OnUpdate` compares it with 0 each frame; every stock entry declares one, the stock key-binding confirm included (`Blizzard_BindingUI.lua:10-24`, `timeout = 0`, `whileDead = 1`, `showAlert = 1`).

Our key-bindings page's two confirms declared none. Both now do, the character-set one with the stock entry's three fields. The checkbox test now runs a frame with the confirm up, and a new test fails on any dialog the loaded interface defines without a numeric timeout; both fail without the fix.

Fixes #1971

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After a key binding moved between action buttons, the buttons it left kept showing its label, so several buttons read the same key.

1.12.1 does the same: when a button has no key, `ActionButton_UpdateHotkeys` (`ActionButton.lua:129-140`) clears the label only for an empty slot or a ranged action, and writes nothing for an action with no range (a stance, a self buff), so the old label stays.

This is a deviation, in the layer's `FrameXMLFixes.xml`: the stock function runs unchanged, then a button with no key and a no-range action has its label cleared. A test moves Shift-2 across three stance buttons and checks each one it leaves goes blank; without the fix the first still reads `s-2`.

Fixes #1842

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A level-60 character entered the world with the XP bar shown instead of the max-level bar.

The stock watch bar sets the XP bar from `UnitLevel("player")` on `UPDATE_FACTION` (`ReputationFrame.lua:161-244`). The server sends the faction list before the player's create, and benilla's only login signals came from that first push, before `"player"` was seated, so the bar read level 0 and stayed shown.

1.12.1 rebuilds the list in the world-enter cascade (`0x4909d3` calls `0x4d5430`), after `PLAYER_ENTERING_WORLD`, and that rebuild's sort signals `UPDATE_FACTION` (`0x4d55de` → `0x4d5db0`); the rebuild does nothing without a player (`0x4d5453`). benilla now runs that rebuild in the cascade, and the first push into a VM is silent. A test drives the server's order: level 60 hides the XP bar and shows the max-level art, level 59 the reverse.

Fixes #1973

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
UnitDebuff (0x5198f0) reads the class's record at [0xc0d83c], gates on
[+0x28] and returns [+0x2c], column 11: Magic, Curse, Disease, Poison
on every install (0x519a89). FrameXML keys DebuffTypeColor on those
words in every locale. benilla returned the Name block at the locale
slot instead, Magie on a German install, so DebuffTypeColor[debuffType]
was nil and TargetFrame.lua:303 raised "attempt to index local `color'"
on every frame a debuff with a dispel class sat on the target. The aura
tooltip (0x52f880) keeps the locale's word: SpellDispelTypes now holds
both, name (column 11) and label (the Name block), and the tooltip reads
dispel_label. locale_routing probes the label, the localized string.

Verified: on a German install real_dispel_types_name_only_what_the_gate_allows
failed before (Magie for Magic) and passes now, and the new
the_key_is_english_on_every_locale_and_the_label_is_the_locales passes
on German and English data; benilla-formats lib tests 487/487 on
English data; on German data the 55 failures left are the existing
English-name expectations, none new. cargo test -p benilla-app --lib,
fmt, clippy -D warnings green. Built on macOS only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The reference answers IsEquippedAction for the slot's action at call
time (0x4e74e0, the worn-slot walker 0x622270), so the in-place
ACTIONBAR_SLOT_CHANGED of a pickup reads nil for the emptied slot and a
place reads the moved item's own answer. The VM answers from the app's
per-slot states instead, which the cursor verbs left where they were:
the pickup's handler read the lifted item's equipped flag off the empty
slot and drew the green border there, and the app's re-feed fires no
event ActionButton_Update answers, so it stayed. A spell placed over a
worn item read the item's border the same way.

The pickup now drops the slot's state with the action; a lifted bar
item carries its state on the cursor (CursorAction.state, beside count
and consumable) and the place writes it with the slot; any other placed
action waits for the re-feed instead of reading the displaced occupant's.

Verified: cargo fmt --check, clippy -D warnings for benilla-ui and
benilla-app, cargo test -p benilla-ui --tests (2669 lib tests, the new
an_equipped_items_green_border_moves_with_it among them),
cargo test -p benilla-app --lib (5088). Built on macOS only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sets the workspace version to 0.3.0 and refreshes the lock, so the client's startup line reads `benilla build 0.3.0`. The toolchain pin stays at 1.98.1, the compiler the release checks ran on.

Checked on this tree: the Linux and Windows compile, the Vulkan run, and a live login and logout against a local server, whose log reads `benilla build 0.3.0`.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The 0.3.0 tag was withdrawn within the hour: the milestone missed open Lua API defects its own definition covers, so 0.3 is not done.

With the tag gone and the version left at 0.3.0, every build from main would stamp a bare `0.3.0`, the same line the real release will print. Back at 0.2.0, the stamp reads `0.2.0+N` again, N commits past v0.2.0, until the 0.3.0 bump lands with its tag. The toolchain pin stays.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
v0.3's definition asks for the full test suite green on Windows (#798), and nothing runs it there: the gates run on macOS, the Windows check before a release only compiles the app, and CI runs on Linux.

A `windows` job now runs `cargo test --workspace` on `windows-latest`, with no install, for a fork's pull request and on a dispatch, beside the Linux gates. Its checkout uses the runner's own line-ending setting, as a Windows contributor's does, which is where #798's failures came from.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A Lua cast was queued for a later frame, so on the next line of the same script `SpellIsTargeting()` read the old state and `SpellTargetUnit` found nothing to bind, as FuBar_CorkFu and SmartRes do.

In 1.12.1 `CastSpellByName` reaches `ArmCastTargets 0x6e5250` through direct calls (`0x4b4ab0` → `0x4b3300` → `0x6e5a90` → `0x6e4b60`), and `SpellIsTargeting` (`0x6e6cd0`) reads the word the arm stores.

The input pass and key bindings now lend the engine's call host to the VM around each script. The 20 verbs that queued apply inside the call, each under its own input events, and `CURRENT_SPELL_CAST_CHANGED` fires there. Calls an unhosted entry leaves apply at the next lend's start. The one unsafe lend is in `script/host.rs`.

Tests: CorkFu's and SmartRes's lines arm and bind in the call; a refused cast does not arm; every routed verb runs with a host lent.

Fixes #447

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The first Windows run (#1984) died inside `benilla-app`'s lib tests with an access violation, which names no test, and `cargo test` stopped there, so no crate after it ran on Windows.

The Windows job now runs `--no-fail-fast`, so every crate reports, and on a failure reruns single-threaded, where each test's name prints before it runs, so the one a crash takes down is the last name in the log.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The combat log fills deDE's numbered holes, so a German client prints its fights
- The fill's comment cites the client's ordered printf

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Sam <samuv.eth@gmail.com>
- A frame in a saved variable writes as its name, as 1.12.1 writes it
- A saved frame's name is read off the model, not a Lua GetName

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Sam <samuv.eth@gmail.com>
The party and raid roster as Lua and its events see it did not follow 1.12.1: our raid row came first, a kick or disband kept the old members, status bits and chain ends read the wrong source, and events fired on edges, merged per frame.

Per `SMSG_GROUP_LIST 0x5e6a40`, `RebuildRaidRoster 0x4ba5f0` and the name callbacks `0x4ba1b0`/`0x4bad90`: our row is last and held only with members; endings clear at once (`0x4e84a0`); views read the descriptor or the record; and every signal is logged where the reference raises it, then fired once, in packet order.

Tests: wire-order signal sequences, solo, party, a raid with us in a later group, kick, disband, worldport, and names that arrive late.

Fixes #263
Fixes #265
Fixes #395
Fixes #670
Fixes #684
Fixes #701
Fixes #704
Fixes #705
Fixes #712
Fixes #715
Fixes #717
Fixes #2002

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On a Windows checkout every text file but Rust took CRLF, since `.gitattributes` pinned LF for `*.rs` alone. A test that matches a shader's source on `\n` (`a_fade_twin_blends_by_the_stage_zero_alpha_op`) failed there, and any `include_str!` of a shader, Lua, XML or TOML file read different bytes than on macOS and Linux.

`* text=auto eol=lf` now pins LF for every text file, keeping the two byte-for-byte exceptions (the vendored benilla-re tables and kira). Every file in the index is LF already, so a renormalize changes nothing stored; only what a Windows checkout writes changes.

Fixes #1998

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Unit relations answered from scattered stand-ins: `UnitCanAttack` ignored which unit attacks, `UnitReaction` its second unit, `UnitCanAssist` any pair without the player, and duels, FFA, contested guards and charm read wrong.

One model now follows the reference's functions rung by rung, two units in, argument order kept: `0x606170`, `0x6061e0`, `0x606530`, `0x606980`, `0x6066f0`, `0x605ff0`, `0x606ba0` and `0x6067f0`. Every caller asks it (ring, cursor, TAB, plates, tooltip, cast binder, usable, inspect, follow), and the six Lua verbs answer from it.

Tests: duels both ways and the opponent's pet, FFA, contested guards, a charmed player, cooperation, shipped-DBC reputation rows, and the follow and inspect gates.

Fixes #50
Fixes #132
Fixes #308
Fixes #309
Fixes #317
Fixes #826
Fixes #1323
Fixes #1545
Fixes #1588
Fixes #1602
Fixes #2008

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On Windows, `benilla-app`'s tests died with an access violation (`0xc0000005`) at the second test that opens the audio device. cpal 0.17.3 keeps one device enumerator for the process, made in the first caller's single-threaded COM apartment; when that thread exits, its `CoUninitialize` unloads the DLLs behind it, and the next call goes through a dead vtable (RustAudio/cpal#1302, open, the same `windows-latest` fault).

Every way into cpal now goes through one `host()`, which on Windows first anchors COM's shared multithreaded apartment for the process (`CoIncrementMTAUsage`, the pattern SDL uses) and joins it to build the enumerator, so no thread's exit can tear it down.

Test: two joined threads each read the device and agree.

Fixes #1984

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On Windows, `vm_parity`'s cos sweep failed on one item, `cos(1e300)`, whose last digit was the host libm's. 1.12.1 never computes it. `math_sin`, `math_cos` and `math_tan` are a bare `fsin`, `fcos` and `fptan` (`0x7fae1d`, `0x7fae4d`, `0x7fae7d`), and a finite operand of 2^63 or more is outside the x87's range and is left unchanged:
- `math.sin(1e300)` and `math.cos(1e300)` answer `1e300`;
- `math.tan` answers `-1.#IND`: `fptan` pushes nothing, so the pop at `0x7fae84` takes the operand and `0x7fae86` stores from an empty stack.

benilla-lua now answers the same. The sweep's recorded answers for that argument are the reference's, the same on every platform.

Test: the parity sweep's `1e300` items for `math.sin`, `math.cos`, `math.tan` and their degree forms.

Fixes #1999

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A cast's refusal line, `SPELLCAST_FAILED`, `CURRENT_SPELL_CAST_CHANGED` and `IsCurrentAction` answered after the verb returned, and in-flight state was cleared from many places, each by its own rule.

1.12.1 keeps one in-flight slot and one saved strike (`0xceca88`, `0xcecaa8`). The attempt pushes it, the cancel (`0x6e4940`) and the cast result (`0x6e741a`) pop it, and the report runs inside the call (`0x6e1a00`).

benilla now has that slot, and every cancel goes through it:
- auto-repeat sends its cast before ATTACKSTOP;
- a right-click skin casts through the attempt;
- a click the cursor cannot bind deselects;
- a second Esc re-sends a strike's cancel, the old deviation removed;
- a channel's button unchecks as it begins.

Tests: each behaviour, failing without it; a live trace shows the cast, its cancel and a refusal's cancel in the reference's order.

Fixes #1985, #1990, #1989, #295, #1325, #1986, #1585, #2012, #2015, #1695, #2014, #2021

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The test gates ran `cargo test`, which runs the workspace's test binaries one after another. On a warm tree the workspace's tests take 103 s that way, and 59 s under cargo-nextest, which runs every binary's tests in one pool; all 10,411 pass both ways.

`check.sh` and `gates.sh` now run tests through `scripts/unit-tests.sh`: cargo-nextest where it is installed, else `cargo test` as before. nextest runs no doctests, so `scripts/doc-tests.sh` runs those of the library crates in scope. Each gate keeps its environment (`BENILLA_REQUIRE_DATA`, `WOW_DATA=`, the skip log), and a failing run's summary now catches nextest's `FAIL` lines. CI is unchanged.

Test: this land's own gates run under it.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On Windows, `the_ui_quad_shader_composes_and_translates` panicked with `ui_quad.wgsl is embedded`. The test finds the shader by comparing each embedded path's string with `benilla_app/shaders/ui_quad.wgsl`, and Bevy's in-memory reader builds a child path with `PathBuf::join` (bevy_asset 0.18.1, `io/memory.rs:239`), so on Windows the string carries `\` and never matches. The same string became each shader's `embedded://` label.

The test now rebuilds each path from its components with `/`, as an asset path is written on every platform.

Test: the test itself, on the Windows CI job.

Fixes #2020

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The combat log, combat text and UNIT_COMBAT follow the reference's handlers:
- An unresolved unit is named at once as the unit getter's miss, not held and dropped.
- Each spell formatter's `Attributes & 0x180`, unknown-spell and empty-name gates apply.
- SPELL_GO's miss list and a missile's arrival write their miss lines, words and UNIT_COMBAT.
- Melee words follow the victim-state table.
- Hostile, party and pet are classed by the reference's tests.
- Honor and XP lines take their side, gender and gates from the bytes.
- UNIT_COMBAT queues at its place against its line in each handler: a swing's flash goes ahead of its line, a spell hit after it.

Tests pin each order and gate.

Fixes #45
Fixes #83
Fixes #84
Fixes #208
Fixes #210
Fixes #211
Fixes #353
Fixes #357
Fixes #367
Fixes #382
Fixes #383
Fixes #384
Fixes #398
Fixes #399
Fixes #1172
Fixes #1240

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The item tooltip setters (bag, inventory, link, merchant, trade, mail, bank, petition) draw the lines 1.12.1's builder draws:
- spell lines for every trigger, and "Already known" by the taught spell;
- PvP rank and medal, zone and map, the gendered reputation label;
- a recipe's product and reagents, and a charter's signatures;
- a link's enchant, and a trade item's proposed enchant;
- the creator, and a second click on the same link hiding the tooltip;
- the merchant price and repair cost.

Charges and durability come from the object, the block or the template, as `0x52d9bd` and `0x52cca8` choose, so a link, a loot row or a merchant item shows no durability line. Tests pin each line and its gate.

Fixes #153
Fixes #154
Fixes #283
Fixes #1183
Fixes #1497
Fixes #1498
Fixes #1501
Fixes #1551
Fixes #1552
Fixes #1564
Fixes #1565
Fixes #1569
Fixes #1571
Fixes #1572
Fixes #1838

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The update-object handler now runs 1.12.1's two passes:
- It writes every block of a packet first, then runs the field watches at the packet's end against each object's last write. A packet with two values blocks for one object fires once.
- A re-create notifies every field.
- An object that leaves range is released with its fields and reactivated without its create hook, not destroyed.
- A first create runs no watch, so the flag tutorials fire only on a change, from their watchers.

The guild tag rides the same core:
- `TABARD_CANSAVE_CHANGED` comes only from RefreshGuildTag.
- A `GetGuildInfo` miss waits and signals `PLAYER_GUILD_UPDATE`.
- A new `PLAYER_GUILD_TIMESTAMP` re-asks the guild.

Tests pin each pass, the released table and the guild waiters.

Fixes #1391
Fixes #1392
Fixes #1499
Fixes #1418
Fixes #1420
Fixes #1416
Fixes #1417

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The logon and world connections and the session clocks follow the reference:
- A world packet above 0x7fff takes a 3-byte size header, read and sent (`0x5b674d`, `0x5b56da`).
- A realm address with no port dials 9090, cut at its first colon, its port read by `_atol`.
- The logon dials one of the realmlist's addresses at random.
- GetNetStats reports the world link's KB/s.
- A transfer abort shows its TRANSFER_ABORT line.
- The game-time speed is held to 1/60 to 60.
- GetGameTime answers -1, -1 until the server sets the clock.
- The ping sequence carries across reconnects.

The handshake timeout, the 23-bit refusal and a port on a named logon server carry `Deviation:` lines. Byte-exact frame tests cover both header forms.

Fixes #1107
Fixes #1657
Fixes #1662
Fixes #1669
Fixes #1193
Fixes #1656
Fixes #1672
Fixes #1677

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Unit, spell and aura tooltips draw what 1.12.1's builders draw:
- Unit: ?? for a hostile player ten up, the drunk level, the owner, Beast Lore, a group member's Offline or zone line, Resurrectable, contested PvP, Skinnable by skill, GlobalStrings wording.
- Spell: rank on pet and stance bars, cast speed, per-school cost terms, skill-based level terms, and the totem, use-all and reputation lines.
- Aura: stacks multiply, the caster's level scales, raidFilter applies, and the player's slots are read.

Tests pin each line.

Fixes #220
Fixes #221
Fixes #588
Fixes #590
Fixes #591
Fixes #594
Fixes #599
Fixes #605
Fixes #662
Fixes #663
Fixes #664
Fixes #1184
Fixes #1185
Fixes #1541
Fixes #1542
Fixes #1553
Fixes #1554
Fixes #1558
Fixes #1560
Fixes #1561
Fixes #1566
Fixes #1567
Fixes #1570
Fixes #1573
Fixes #1574
Fixes #1579
Fixes #273
Fixes #682

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`scripts/check.sh` ran the scoped tests with the install only, so a test that reads the install without `wow_data_or_skip!` passed every round and failed first in the gates' `test-no-install` leg, at the land.

`check.sh` now runs that leg on its own scope, right after its tests: `WOW_DATA=` set and empty, `BENILLA_REQUIRE_DATA` unset, the same `scripts/unit-tests.sh` and package flags. It builds nothing new, so it reuses the binaries the test step just built; on the three app and UI crates it ran 8434 tests in under a minute.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Combat text missed most of the words 1.12.1 raises. A damage shield, an interrupt, extra attacks, a resisted proc, an immunity, a dispel, an aura, environmental damage, a drain and a reputation change about you wrote their log line but no `COMBAT_TEXT_UPDATE`. `SPELL_ACTIVE` never fired, and a swing's `UNIT_COMBAT` skipped the victim-state table.

Reference: each formatter raises its word at its own unit test after the line (`0x62cb5d`, `0x626b53`, `0x62db7d`, `0x628b8a`, `0x62d3ab`, `0x628daa`, `0x62d5ce`, `0x62b74f`, `0x62ac74`, `0x627b94`, `0x62c661`). The usable refresh walks the reactive list `[0xb71150]` (`0x4b31c0`). A swing's action comes from `0x83de28`, environmental damage sends a WOUND (`0x624f83`), and the float resolves its global (`0x703bf0`).

Each word rides the existing line path. The reactive list lives from arrival to arrival. Tests pin each word and gate.

Fixes #2027
Fixes #2028
Fixes #2029
Fixes #2030
Fixes #81
Fixes #82
Fixes #370
Fixes #2032
Fixes #2040

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Four glue-screen behaviours were wrong.

- **Realm names.** The list showed realm names raw.
- **Agreement flags.** The terms-agreement flags were reseeded from the CVars on every glue VM.
- **New characters.** A new character's record kept level 0 and no new-character flag.
- **PreviewRider.** Its comment misstated the sheathe.

Reference: the realm list fill turns each of `"*/:<>?\|` into a space and cuts trailing spaces and dots (`0x5b2450`, `0x5b24b0`). The agreement flags are seeded once per process. A level-0 record stores level 1 and sets `+0x10a` (tested at `0x46c203`), and that character enters at its race's intro eye with no loading tip. The select build holds each weapon in hand (`0x472c8a`, `0x47a15d`).

Changed: names are cleaned at the fill, the flags live in one shared set, and the roster stores the flag and looks up the intro eye at the pick. Tests pin each.

Fixes #1215
Fixes #1317
Fixes #1318
Fixes #1217

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The action bar answered and signalled where 1.12.1 does not:

- `IsActionInRange` measured distance before the target gates.
- `GetActionCooldown` answered enable 1 for slots that reach no cooldown query.
- `IsCurrentAction` held item slots against nothing.
- The cooldown and usable events fired off a button's own state.

Reference: `0x4e56f0` runs `0x6e4440`'s relation and implicit-target gates first. Enable starts 0 (`0x4e6f0d`) and only the query sets it (`0x6e1406`). The cast keeps its item's guid (`0xcecab0`). `BAG_UPDATE_COOLDOWN` comes from `0x4f93d0`'s callers, `ACTIONBAR_UPDATE_USABLE` from `0x4e5c00`'s four, and `SPELL_UPDATE_USABLE` from `0x4b31c0`'s fourteen. Placement signals only 213.

Changed: a usable cache refreshes at those producers. The push half runs before the unit snapshots and the events after them, so handlers read this frame's state, after the control edge. Tests pin each answer and the order.

Fixes #1528
Fixes #1532
Fixes #1537
Fixes #1596
Fixes #1597

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Four group behaviours were wrong:

- `IsRaidLeader` and `IsPartyLeader` misread a raid leader outside your subgroup.
- A loot method, master or threshold change printed no line.
- The uninvite menus always kicked by name.
- `SetRaidSubgroup` outside 1-8 did nothing.

Reference:

- Both leader tests compare the leader cell with your guid; only `IsPartyLeader` guards a zero cell (`0x4e9141`).
- The group list stores the loot cells and prints each change (`0x4e86f0`, jump table `0x4e880c`). The master line needs a cached name; the threshold reads `ITEM_QUALITY<n>_DESC`.
- `0x5e9510` kicks a held player by name and anything else with `CMSG_GROUP_UNINVITE_GUID`.
- `SetRaidSubgroup` raises its usage line first (`0x4bb9fe`).

Changed: the leader tests read the guids, the list stores and prints the loot cells, the kicks follow `0x5e9510`, and the subgroup is checked first. Tests pin each.

Fixes #141
Fixes #70
Fixes #267
Fixes #266

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Combat-log lines read wrong in nine ways:

- Your split damage and extra attacks were damage-typed.
- The XP line ignored the group float.
- An item-name wait dropped its line after 120 frames.
- Game objects and items printed "Unknown".
- An XP kill also printed a death line.
- Templates with unused or positional arguments were refused.
- Open-lock effects wrote nothing.

Reference: split form 2 (`0x62df92`) and the extra-attack line (`0x62b190`) type through the buff table unless the spell is hostile. The XP line compares `ftol(xp / float)` with `xp` (`0x637cd0`). The wait list never expires (`0xc4e208`). `0x6264e0` names by type mask. The XP packet marks the victim (`0x60a0e6`). `vsnoprintf` binds `%N$` and skips unused arguments (`0x669888`). `0x62dc10` writes the open-lock line.

Changed: each follows its formatter; one `fill_exact` serves both fills. Tests pin each.

Fixes #2067
Fixes #2073
Fixes #2068
Fixes #2071
Fixes #2039
Fixes #2041
Fixes #2044
Fixes #2043
Fixes #2046

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The game-speed, game-time update and game-time set messages were dropped. The login time packet also applied a malformed body and kept unset fields as 31 and 63.

Reference: each handler reads its body, refuses one not exactly its length with the "Malformed message" line, and decodes an all-ones field as -1 (`0x642b30`):

- **Set** steps back a minute and advances (`0x6434d0`), so an unset field lands at 00:01.
- **Sync** steps forward or banks a skip count (`0x6436c0`).
- **Frame tick** pays the skip (`0x6435a0`). It runs from world entry to logout (`0x401684`, `0x401f04`).
- **Rate** is clamped to [1/60, 60], NaN kept (`0x643820`).

Changed: the four bodies parse with exact-length goldens, and the clock is a port of the reference's, ticking only in the world. The day number uses UTC civil days, a written deviation from `mktime`. Tests pin each step.

Fixes #2056
Fixes #2082

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Seven console commands were not registered: `port`, `worldport`, `setrawpos`, `whois`, `togglehelm`, `togglecloak` and `cameraClip`. Three CVar rows also left the reference: `SoundBufferSize` shipped 100, `WorldDetail` was marked as a 1.12 CVar, and `gamma` shipped `1.000000`.

Reference:
- `0x4049e0` registers the movement and toggle commands, `port` and `setrawpos` splitting with the comma (`0x82ef90`) and `worldport` without it (`0x82ecc8`).
- `whois` sends its name (`0x5adee0`).
- `cameraClip` flips or sets its word (`0x50ddc0`).
- `0x457520` registers `SoundBufferSize` 50 above 1500 MHz, which current x86 parts read, and `0x402d70` registers `gamma` as `1.0`.

Changed: each command is registered with its category and its sends, which have byte-exact goldens. `SoundBufferSize` and the mix-ahead ring run at 50, `WorldDetail` is benilla's own row, and `gamma` registers `1.0`. Tests pin each.

Fixes #1639
Fixes #1663
Fixes #1749
Fixes #1270
Fixes #1279
Fixes #1281

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The readers differed from 1.12.1:

- A TGA's descriptor bit 4 mirrored it.
- A 32-bit TGA with no alpha bits drew with byte 3 as alpha.
- Spell-text slot digits 0 and 4-9 read a wrong slot and `$g`/`$l` parsed their own way.
- MCNR normals were named `x, z, y`.
- A DBC record of the wrong size was read.

Reference:

- No TGA read path tests bit 4.
- `0x5a37e0` admits truecolour only at 24 bits besides alpha.
- A refused texture draws opaque white (`0x44a260`) at sizes `0x4484ae` admits.
- Only '1'-'9' is a slot digit (`0x507827`), and `0x508180`/`0x5082a0` parse the selectors.
- MCNR is X, Y, Z (`0x6aff10`).
- The loader refuses any other record size (`0x545e36`).

Changed: each reader follows these; three TGA edge cases carry `Deviation:` lines. Three BigWigs ready-check icons now draw white, as in 1.12.1.

Fixes #1679
Fixes #1693
Fixes #1684
Fixes #710
Fixes #799
Fixes #15
Fixes #17
Fixes #2102

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Clicks and attack starts passed gates 1.12.1 refuses at:

- A right press with a cursor payload went on to the world.
- StartAttack ran without control or while pacified.
- A charmed player's right-click selected and interacted.
- Talk ignored shapeshift forms.
- A plate right-click while targeting skipped the interact.
- A `/target` miss said nothing.
- `/follow` read the cast slot.

Reference: `0x7662c0` runs the press pre-hook `0x492b50` before any hit-test, which drops a payload and consumes the press. StartAttack returns on `0x20000` or lost control (`0x5ecbaa`, `0x5ecbc0`) for every caller. The router tests `CHARMEDBY` first (`0x481f98`), which plates skip (`0x4949f0`). Talk tests form flag `0x8` (`0x5f0150`). `0x489dbd` picks the miss message, and `/follow` reads `UNIT_CHANNEL_SPELL` (`0x60ffb9`).

Changed: each gate follows these, and tests pin each.

Fixes #321
Fixes #381
Fixes #785
Fixes #1305
Fixes #1397
Fixes #52
Fixes #1326

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Macros were held by index and kept in creation order:

- A sort or delete moved what a bar slot held.
- `EditMacro` trimmed and capped differently and returned nothing.
- `CastSpellByName` took the wrong quoted span.
- A macro casting a pet spell never bound the pet's book.

Reference: each scope sorts by the CRT `qsort` with `_strnicmp` (`0x73f727`, `0x4f0e10`). Slots hold the macro id, with bit 24 set for a character macro, and `DeleteMacro` clears every slot holding it (`0x4e5d10`). `EditMacro` returns the index after the re-sort. The quoted name ends at the next quote (`0x4efef0`). Names bind the player's book, then the pet's (`0x4b3bc0`).

Changed: the table, the slots, the cursor and the saved files follow these, and tests pin each. Bar slots an earlier build saved for character macros are rewritten once to the new ids.

Fixes #1502
Fixes #1505
Fixes #1187
Fixes #196
Fixes #197

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Three Lua surfaces answered unlike 1.12.1:
- A Font object's `[0]` was a number, so saved variables and `type(v[0])` guards treated fonts unlike every other object.
- `CopyFontObject` pushed the source's values and held them.
- Guild and charter names went to the server with no client name check.

Reference: the saved-variable writer reads `[0]` as a light userdata (`0x704623`). `0x7841d0` merges what the source holds, then holds nothing (`0x7841f4`) and follows the source's font. `0x6c9b70` runs the name validator with a space as its one extra character, the profanity lists on, and a 24-unit cap. `0x4f51ec` maps its codes to the refusal lines.

Changed: Fonts take the object id counter, and the loader's `parent=` tests the Frame type. A font node keeps its held word apart from its values. The guild check runs before the send. Tests pin each.

Fixes #1997
Fixes #1983
Fixes #1399

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
NVIDIA's Vulkan driver (610.88 seen) faults inside vkQueuePresentKHR on the first present after a
swapchain resize while the present mode is Fifo, FifoRelaxed or AutoVsync; a maximize is a resize.
Mailbox, AutoNoVsync and DX12 survive. present_choice picks the mode from vsync, the render
adapter's vendor and backend, the OS and $WOW_PRESENT; the pairing above gets AutoNoVsync with the
loop paced to the monitor's refresh rate, everything else keeps Fifo. $WOW_PRESENT=fifo restores
it, $WOW_NOVSYNC is unchanged. The boot window still starts on Fifo; apply_present_mode re-runs
when the adapter first appears and swaps the boot mode alone.

Built and unit-tested on Windows only; the cfg!(windows) arm is a value, not a cfg, so the other
platforms compile the same code.
Four TradeSkill and Craft answers differed from 1.12.1:
- Recipes of one colour listed the lowest item level first.
- `SelectTradeSkill` on a header kept the old selection.
- The sub-class folds ignored a non-header row.
- `GetCraftButtonToken` always answered `CREATE`.

Reference: the recipe comparator returns +1 when the first row's ItemLevel is smaller, so the higher level sorts first (`0x4fd4e6`). `0x4fc9b0` stores 0 for a header or an out-of-range index. The folds raise unless `0x4fd650` finds a header, and below 1 they fold every group (`0x5000c0`, `0x500140`). `0x4f6c90` answers the word at the open craft type in the table at `0x84d0f4`: USE, TRAIN, DISGUISE, ENSCRIBE.

Changed: each follows these, and tests pin each.

Fixes #155
Fixes #1260
Fixes #1261
Fixes #876

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The update ingest did not do what 1.12.1 does with the same bytes:
- It dropped the header flag.
- It released and destroyed held objects at once.
- It re-asked records on every create.
- It replayed enchant times at an item's write.
- It read character names uncapped.
- It ignored `SMSG_PLAYER_SKINNED`.

Reference: the header byte runs a first pass over guids with top nibble 6 or 8 (`0x4651c1`, `0x4660d8`). A held object is only marked release-pending (`0x464920`) until its hold drops (`0x468410`). Asks come from the create hooks alone (`0x5fb880`, `0x5f7d81`). Enchant times replay when the record applies (`0x5d85b7`). Names read with a 0x30 cap, and an overrun empties the list (`0x5b4523`). `PLAYER_SKINNED` fires with the byte's truth (`0x5e7d52`).

Changed: each follows these, and tests pin each. The faults the passes have on malformed packets are recorded, not copied.

Fixes #2053
Fixes #2052
Fixes #2054
Fixes #2055
Fixes #2083
Fixes #87

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Five comments stated a wrong reference fact or named code that does not exist:

- `shown_pet_power_type` cited `UnitPowerType`'s member-record read (`0x5179d2`) as its pet leg, which is `0x5179ff`.
- `race_creature_type.rs` named a path that does not exist.
- `drag.rs` cited the unreached copies at `0x425666`/`0x425766`, where the live converter is `0x766f70`, called from `0x766103` and `0x7662f3`.
- `handler_prof` claimed an app-data refusal benilla-mlua does not have.
- `MacroView` cited the save format by file offset, where its address is `0x84cb60`, pushed at `0x4f0b61`.

The NVIDIA present `Deviation:` also gains where the fault is: the driver's own DXGI present, with NVIDIA's present layer disabled too.

Comments only, so no test changes.

Fixes #671
Fixes #676
Fixes #1106
Fixes #1991
Fixes #1245

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In 1.12.1 a verb's effect applies inside the call, whether from a click, `OnUpdate` or an event handler. Here, calls from `OnUpdate` and handlers queued until the next lend, so `TargetByName(name)` then `UnitName("target")` read the old target.

- Every system that runs Lua lends the call host around its body, in one ordered VM pass; the cross-entry queue is gone.
- Seams that changed units through `Commands` queue unit edits, flushed ahead of their readers, so sync points stay at 54 (main: 53).
- Late drains apply before the paint; a CVar or follow ask lands the same frame.
- The actionable ambiguity ceiling falls from 3079 to 1750.

Tests: an `OnUpdate` cast and a handler's cast arm inside the call, three same-frame drains, and a census that each schedule flushes after every edit user.

Lua run inside an engine turn still waits for the turn's end.

Part of #1993

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The cast-failure reporter skipped arms of 1.12.1's errorId chain and its pet text. A move also cancelled a cast from the wire word, not from the press.

- The player's chain now runs whole, in the bytes' order (`0x6e1aab`-`0x6e1c5f`). Every id, 0x2c included, formats through its key's live global (`0x4967d7`), and an empty line is still logged.
- Spells aimed at the pet show their `_PET` text (`0x6e1b1b`). An item-target refusal reads `ERR_INVALID_ITEM_TARGET`.
- The full-power line names the player's own power (`0x6e211f`). The pet's handler stays silent when it has no record or finds no name.
- A movement command press cancels the cast whoever drives (`0x515090`). Only a channel needs the player as the mover (`0x515180`).

Tests pin each arm, the pet lines and the cancel through the controller.

Fixes #2135
Fixes #1582
Fixes #1584
Fixes #1637
Fixes #1201
Fixes #2141
Fixes #2142

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The taxi map's routes, prices and node list differed from 1.12.1's.

- The search (`0x4dbce0`) weighs each hop by its flight path's points. It runs depth first over the map's slots, and keeps the first route on a tie. Gadgetzan to Sun Rock now flies through Freewind and Crossroads for 10s70c.
- A known node with no route is listed as NONE, with the start fare.
- The other faction's flight points are skipped by race (`0x4dbd2d`).
- `TakeTaxiNode` refuses a mounted player (0xb2), the current node (0xaa) and a node with no path (0xab).
- A closed map keeps its slots (`0x4dc0f0`).
- Out-of-range slots raise or answer INVALID, and the coordinate getters read bare numbers.

Tests replay the issue's routes over the shipped tables and pin each verb's refusal.

Fixes #204
Fixes #205
Fixes #1236
Fixes #1576
Fixes #1229

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Several of 1.12.1's client-side cast gates were missing, so the server refused casts the client refuses itself:

- The caster validator (`0x6094f0`) now runs its rungs in the bytes' order. Indoors and outdoors are judged by the entity outdoors test (`0x6706f0`).
- The crowd-control exemption checks every aura type (`0x6e9ca0`).
- The binder stops at the first arm that holds, with that arm's guards (`0x6e5b40`).
- TryCast refuses three more cases:
  - ranged without ammo, which also greys the button;
  - ranged without a ranged weapon (`0x6e4e13`);
  - the while-looting effects (`0x6e4ca2`).
- A cast on cooldown with no target says not ready.

Tests pin each rung and the greying through the bar's own site.

The learn and tame rungs remain.

Part of #1202
Fixes #1581
Fixes #1586
Fixes #675
Fixes #1199
Fixes #1294
Fixes #2132

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1.12's options declare Show Target of Target as a bare uvar
(UIOptionsFrame.lua:65) and the mode is read on save
(UIOptionsFrame.lua:365); neither calls TargetofTarget_Update. The frame
follows from TargetFrame_OnUpdate and TargetofTarget_OnUpdate on the next
frame (TargetFrame.lua:492-501). Our rows called it as their applyFunc,
with the row as `this`; the function refreshes `this`'s debuffs
(RefreshBuffs(this, 0, "targettarget"), TargetFrame.lua:538), so with a
target's target up the switch raised "BuffFrame.lua:281: attempt to index
local 'debuffBorder' (a nil value)" (seen on a Linux client).

Verified: the_target_of_target_switch_leaves_the_frame_to_its_own_updates
(with a target's target up: no error, the frame shows after one tick on
and hides after one tick off) fails with the applyFunc put back, with
exactly that error, and passes without it; the options and tot_frame
tests pass on a deDE install; cargo test -p benilla-app --lib 5145,
clippy -D warnings, fmt. Built on macOS only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sitting and Z skipped 1.12.1's refusals, and the cast-busy state they test was modelled as a ranged hold.

- The precast (`+0xc8c`, bit `0x400`) is a unit state of its own: set at the send for every spell with a visual (`0x60d020`), ended at its GO, a failure or a move (`0x60d040`), releasing loot as `0x60d0ee` does.
- Sitting (`0x5ed430`) and Z (`0x5eb480`) refuse in the reference's order, mounted through precasting; a sit closes loot and stops the attack (`0x5f0790`).
- A relic class's Z never reaches the ranged state (`0x611cf0`), and a morph keeps the committed sheath.
- Starting an attack aborts a ranged precast, and stops before the swing for Slam (`0x5ecb70`).
- Emotes wait out the busy bit.

Tests pin each refusal, end and tail.

Fixes #1249
Fixes #1590
Fixes #1591
Fixes #1603
Fixes #2084
Fixes #2137
Fixes #770
Fixes #2152

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Cooldown events fired on each record insert. 1.12.1 fires them at its flush sites (`0x4b31b0`). Several inserts also took values the reference doesn't.

- The insert (`0x6e12c0`) and StartCooldown (`0x6e2c60`) now fire nothing. Each packet and cast site flushes once.
- `SMSG_SPELL_COOLDOWN` timers go through op 11, the ranged pad and op 21 (`0x6e9460`).
- An item's game-object cooldown uses its block's category, else the spell's. It waits for the item record and inserts nothing for entry 0 (`0x6e8425`).
- Initial-spells cooldowns are held and back-dated through StartCooldown.
- A NOT_READY with argument 0, and every failed cast, clear the spell's records and flush.
- Bag, worn and tooltip queries ask for a missing item record. Its landing flushes (`0x6e2f70`).

Tests pin each flush site and insert value.

Fixes #2060
Fixes #679
Fixes #2061
Fixes #2063
Fixes #2064
Fixes #2066
Fixes #2076
Fixes #2077

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A possessed unit's spell and a pet spell with AttributesEx4 0x20 went out as CMSG_PET_ACTION. `CastSpellByName` never found pet spells and misparsed names. `SendChatMessage` sent `%t` and `%n` unexpanded and passed any `|` escape. `RandomRoll` sent any arguments.

The reference:
- `CastSpellByName` cuts the name and rank at the first `(` and `)` and falls back to the pet's book (0x4b3950).
- A unit caster runs the cast ladder, picking its own target (0x6e4ef4) and ordering Attack after the send (0x6131a0).
- `SendChatMessage` expands the tokens and raises on a bad escape inside its call (0x49f1e0).
- `RandomRoll` checks its arguments first (0x48c7b0).

What changed:
- A `CMSG_PET_CAST_SPELL` writer with byte-exact goldens.
- The unit caster's ladder covers its pick, autoSelfCast and the cursor's binds.
- Both chat verbs now apply inside their calls.
- The channel slots follow every step.

Each fact is tested against its address.

Fixes #639
Fixes #168
Fixes #406
Fixes #1331
Fixes #1332
Fixes #1531

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The unit getters answered for the player alone:
- Stat functions gave zeros past player and pet.
- `GetGuildInfo` failed on chained tokens.
- A group member out of view had no race, class or sex.
- `UnitRangedDamage` ignored the weapon's school.
- `UnitIsUnit` resolved equal strings first.
- `UNIT_INVENTORY_CHANGED` fired only for the player.
- Drunk thresholds printed nothing and left `UnitLevel` alone.
- `CanInspect` answered nil.

The reference:
- `UnitIsUnit` compares the strings first.
- The getters read the named unit's fields and the name cache.
- The visible-item watch (`0x51bc3c`) signals for every player holding a token.
- The drunk watcher (`0x5e2a90`) prints and lowers the level.

What changed: each getter reads the unit its token names. The item watches, the ammo callback (`0x4c84f0`) and the inspect view signal where the reference does.

Each fact is tested against its address.

Fixes #1544
Fixes #718
Fixes #1239
Fixes #1232
Fixes #2005
Fixes #169
Fixes #1408
Fixes #1157
Fixes #1409

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The party and raid roster answered and sent where 1.12.1 does not:
- Join and leave lines printed for names the cache did not hold.
- A raid joined with no roster never asked for its icons.
- The loot getters needed a group.
- `GetPartyLeaderIndex` answered 0 after a list with no leader.
- The subgroup verbs sent for a held subgroup or an uncached name.
- `UninviteFromParty` and `PromoteToPartyLeader` took only party1-4.

The reference:
- The party leave line asks the cache with no callback (`0x5e6d15`).
- Raid lines wait on theirs (`0x4bacd0`, `0x4bad30`).
- A first raid list sends `MSG_RAID_TARGET_UPDATE` 0xff (`0x4babf4`).
- The getters read the stored cells.
- The leader cell is zeroed by every list (`0x4e85b5`).
- Both verbs resolve any token (`0x515970`).

What changed: the roster follows each of those. Each is tested against its address.

Fixes #2000
Fixes #2001
Fixes #2003
Fixes #2086
Fixes #2087
Fixes #2088
Fixes #2089

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A verb called from Lua, or a packet's event, applied a frame later, after the call or handler had returned. 1.12.1 applies each verb inside its call and fires each event at its packet.

What changed:
- Verbs apply inside the call: targeting, item picks, refused casts' log lines.
- A packet's chat items and field notices fire in wire order.
- Combat, unghost and death-tutorial events fire at their packets.
- Signals raised while something waits are replayed with the lock and cursor state they had.
- Loot picks and bind confirms are one call-ordered queue. `LOOT_BIND_CONFIRM` fires before its slot is stored (`0x4c290f`, `0x4c2917`).
- A forked script no longer panics on a lend, and two comments now match the reference.

Each order is tested against its address.

Fixes #1993
Fixes #2045
Fixes #2051
Fixes #1419
Fixes #2016
Fixes #1987
Fixes #1237
Fixes #2139
Fixes #797
Fixes #2094

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Spell packets and cast state went wrong:
- A start or go with no record, or a non-unit's start, drew a cast.
- An enemy casting at you never became your target.
- Cooldown packets for other guids were dropped.
- Esc and move cancels reaped the wrong spell.
- Timers freed the cast slot.
- Spell state outlived a relog.
- Openers were scanned by id.

The reference:
- Both handlers exit first (`0x6e7876`, `0x6e7cd7`).
- A start aimed at you selects its caster (`0x6e799c`).
- Only the pet's guid routes to the pet (`0x6e9696`).
- The cancel reaps the record's spell (`0x6e4a8e`).
- No timer frees the slot; an inventory refusal cancels its item's cast (`0x5e3ab3`).
- World entry zeroes the spell state (`0x6e7150`).
- Openers keep the server's order (`0x5f83d0`).

Hold stops are now keyed by spell (`0x614167`). Each fact is tested against its address.

Fixes #2162
Fixes #1302
Fixes #1292
Fixes #1301
Fixes #2163
Fixes #2025
Fixes #1327
Fixes #293

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Auction lists went wrong:
- They kept the server's order until a header click, and re-sorted every frame.
- Time Left, High Bidder, Lvl and Seller sorted on the wrong keys, and names matched case.
- Time left froze at arrival, and expired rows stayed.
- The sell slot took items 1.12.1 refuses.

The reference:
- World entry seeds eight-key sort stacks (`0x4cc236`). A page sorts once, when its last item record lands (`0x4cc9e8`).
- The comparator `0x4cd940` and the CRT `qsort` (`0x73f727`) order the rows, ties included.
- Time left counts down from the deadline, and rows 10 s past it leave the lists (`0x4cdfe0`).
- The sell slot refuses in `0x4ce362`'s order.

benilla now has one CRT `qsort`, which the frame batches, macros, guild roster and addon list share. Each fact is tested against its address.

Fixes #260
Fixes #428
Fixes #253
Fixes #254
Fixes #255
Fixes #256
Fixes #1539
Fixes #258
Fixes #1524
Fixes #2191

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Targeting went wrong for units the client does not hold:
- A groupmate out of range could not be targeted.
- Assist with nothing to assist was silent.
- `StartDuelUnit`, `SetRaidTarget` and `InviteToParty` ignored most tokens.

The reference:
- `SetSelection` (`0x493540`) stores a guid whose object is not held (`0x4935c8`), and `TargetUnit` selects a group member by guid (`0x489a64`).
- The roster tears that selection down for an offline seat (`0x4e845a`), a departed member (`0x5e6df7`) and a lost pet (`0x5e2719`).
- A guid-only select plays `igCharacterSelect` (`0x4936f7`). A long-range spell at it sends untargeted (`0x6e545b`).
- Assist shows `ERR_GENERIC_NO_TARGET` (`0x489c0e`), and the token verbs resolve any token (`0x515970`).

Every reader of the selection now reads the guid: the target frame, the cast binder, the swing, the plates and the portrait. Each fact is tested against its address.

Fixes #1965
Fixes #1234
Fixes #322
Fixes #174
Fixes #199
Fixes #2178
Fixes #2179

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Chat channels went wrong:
- An eleventh join printed nothing, and only `YOU_LEFT` freed a slot.
- Lookups by number found suspended and pending channels.
- Admin verbs sent a number as the name.
- Defense broadcasts reached unconfirmed channels.
- Trade outside a city took "City", and zone channels answered full names.
- `/chatlist` printed a line of its own.

The reference:
- A join takes its slot when sent (`0x49edf3`), and refusals free it (`0x49c446`). A freed slot keeps its row id, so the walk skips it (`0x49bef0`).
- Number lookups take confirmed slots only. Admin verbs resolve the slot (`0x49e880`).
- The city word needs flag 0x20 and a city (`0x49a2d0`).

Channel verbs, `DoEmote`, `UninviteByName`, `SendAddonMessage` and `ListChannels` now act inside their call. Each fact is tested against its address.

Part of #2202

Fixes #276
Fixes #2169
Fixes #229
Fixes #2159
Fixes #225
Fixes #223
Fixes #230
Fixes #231

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Loot went wrong:
- Takes, closes and master gives queued, then ran in a fixed order.
- The last row closed a frame late.
- Auto-loot opened before it swept, read Shift when the loot arrived, and looted over an open window.
- `GetNumLootItems` counted taken rows, and a response for an object the client lacked opened a window.

The reference:
- `LootSlot` is the confirm form (`0x4c2e70`). A row click takes after its `<OnClick>` (`0x4c1867`). `CloseLoot` zeroes only the source (`0x4c1da0`).
- `SMSG_LOOT_REMOVED` closes an empty window in the packet (`0x4c1f8d`).
- The sweep runs before `LOOT_OPENED` (`0x4c1ed3`, `0x4c2240`). Each interaction stores the byte (`0x5df460`), and `CanLootNow` refuses an open session (`0x5ec1e1`).
- A missing object shows `ERR_LOOT_DIDNT_KILL` (`0x5ebb3f`).

Each fact is tested against its address, with the stock `LootFrame` for the replacing window.

Part of #2202

Fixes #2167
Fixes #2181
Fixes #2180
Fixes #2190
Fixes #1228
Fixes #784
Fixes #2189
Fixes #72

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After a death or a stop:
- A cast at a unit that died ran on, and Auto Shot kept firing after its target's death.
- Stopping a repeat left its kit running and fired its events a frame late.
- An attack restarted within a round trip of a stop sent no swing.
- A possessed unit's area melee ordered no attack, and its ground cursor measured from you.
- A game object's go drew a caster's kit.

The reference:
- `OnDied` (`0x605860`) cancels a cast whose target dies (`0x6058e0`), then deselects and stops the repeat (`0x605901`, `0x605906`).
- `StopAutoRepeatSpell` (`0x6ea080`) reaps, pops and signals where the key clears.
- The stop word `+0xc54` lets a start swing again (`0x5eccda`).
- A go's caster legs need a unit (`0x6e7f8b`).

Each fact is tested against its address.

Fixes #2177
Fixes #44
Fixes #2187
Fixes #2013
Fixes #2140
Fixes #2171
Fixes #2172
Fixes #2188

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Auction commands went wrong:
- A bid or cancel was sent with no local check.
- Results showed the wrong line or none.
- Notices re-queried instead of editing the lists, and the list verbs sent unarmed.
- Outbid auctions never reached the Bids tab.

The reference:
- `PlaceAuctionBid` and `CancelAuction` refuse in the call (`0x4cf762`-`0x4cf8a8`, `0x4d0338`).
- The result table `0x4cc7b4` maps 1 through `0x622630`, 4 to `0x17` and 13 to `0x1be`, and 5 always shows `0x175`.
- Notices remove through `0x4cdfe0`, and patches signal one event each, in the handler.
- The one-shot flags `0xb7263c`/`0xb72640` gate the list verbs.

Every auction verb but the close now acts inside its call. Each fact is tested against its address.

Part of #2202

Fixes #47
Fixes #48
Fixes #252
Fixes #1525
Fixes #1526
Fixes #1527
Fixes #1533
Fixes #2195
Fixes #2196
Fixes #2197
Fixes #2198

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The friends and ignore lists went wrong:
- Names showed blank, online friends came unsorted, and the list announced itself early.
- Several status results printed the wrong line or kept the wrong rows.
- The ignore list had no 25-slot cap, and an ignored player's emote still printed.

The reference:
- The list handler (`0x5ae350`) names a slot only from the cache, and signals at a pending count of 0.
- The CRT `qsort` with comparators `0x5ae870` and `0x5ae940` sorts the lists.
- Statuses follow the table at `0x5ace1c`, and refused names send their delete (`0x5acec8`, `0x5acf8c`).
- `SMSG_TEXT_EMOTE` drops an ignored sender (`0x49dc3d`).

The friend and ignore verbs act inside their call, and listeners see the list as signalled. Each fact is tested against its address.

Part of #2202

Fixes #133
Fixes #137
Fixes #138
Fixes #1109
Fixes #1112
Fixes #1113
Fixes #1668
Fixes #1769
Fixes #1328
Fixes #765

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The party and raid verbs now act inside their call, as 1.12.1's do (Part of #2202). Their gates and lines now match the reference:
- `ConfirmReadyCheck` sends nothing once the deadline has passed (`0x4bb2f0`), and sends its answer as one byte (`0x4bc0bf`).
- `DoReadyCheck` works for the leader only, refuses while a check runs, and shows its start line in place.
- A ready check survives a `/reload`. Only the leader's roster leg closes it (`0x4bac5a`-`0x4bacb4`).
- `UninviteByName` uses its usage line (`0x48a610`), and so do `InviteByName` and the promote verbs.
- Name lookups answer their waits in the order they were asked (`0x55f426`).
- A groupmate's death shows `ERR_PLAYER_DIED_S` (`0x4e7f70`).
- A `/reload` seeds the group without firing group events.
- An invite fires its event, then shows its line (`0x5e6747`, `0x5e6752`).

Tests pin each address. Built on macOS.

Fixes #2122
Fixes #1436
Fixes #2200
Fixes #2183
Fixes #2208
Fixes #2022

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The mailbox now follows 1.12.1's inbox and send code, and the mail verbs act inside their call (Part of #2202).
- Expired mail stays listed.
- Mail from an ignored player is dropped on type 0 only.
- Delete and return use the reference's tests, and the postage sum matches.
- Reply follows `0x4aefa5`.
- `GetInboxText` reads the mail's template id, and `CMSG_MAIL_CREATE_TEXT_ITEM` now carries it, pinned byte for byte.
- A row with no paper takes the first usable one, in `0x4ada90`'s order.
- The send slot refuses as `0x4adf38` does.
- The profanity filter masks subjects and bodies.
- The header icons and non-player sender names come from the bytes.
- Keeping the wire id where the reference crashes on a null paper is a written `Deviation:`.

Tests pin each address. Built on macOS.

Fixes #1241
Fixes #1235
Fixes #247
Fixes #240
Fixes #55
Fixes #140

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The /who search, its list and its chat lines now follow 1.12.1, and the who and LFG verbs act inside their call (Part of #2202).
- The filter is built token by token, as `0x5aebb0` does. Tags match a case-insensitive substring of the localized zone, race and class names, the walk stops at the fourth term, and an unclosed quote drops its token.
- The sort order resets at each world entry (`0x5adc50`).
- An id with no row reads `UNKNOWN`, in the list and in chat.
- The answer keeps at most 50 entries (`0x5adf87`), and the count line gets the raw count.
- `GetWhoInfo`'s bounds and miss tuple match `0x5ad71a` and `0x5ad836`.

Three `Deviation:` lines name the memory and platform quirks not copied. Built on macOS.

Fixes #39
Fixes #1329
Fixes #1114
Fixes #181
Fixes #200

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The trade window now follows 1.12.1, and the trade verbs act inside their call (Part of #2202).
- The open leg folds held coins only for a trade started by a drop (`0x4bf5f1`), with one `PLAYER_TRADE_MONEY`.
- Bag, equipped and trade-slot links go through one path (`0x52ae00`), with the enchant and suffix.
- `ClickTargetTradeButton(7)` with an enchant asks before replacing one (`TRADE_REPLACE_ENCHANT`), then casts at the partner's slot.
- Dropping or clicking an item onto a player starts or fills a trade (`0x48d960`, `0x4925d0`), and a refused placement changes nothing.
- The accept packet carries the partner's status word.
- The accept and un-accept signals fire before their packets.
- The money events fire where `0x4bf2b0` fires them.

Wire goldens pin the new bodies. Built on macOS.

Fixes #277
Fixes #279
Fixes #280
Fixes #281
Fixes #88
Fixes #292

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In 1.12.1 each packet's handler runs whole, and its events fire inside it in arrival order (`0x5384d0`, `0x537aa0`). benilla fired each producer's queue in system order instead. This lays the foundation:
- One stamp clock orders every queued event.
- A single drain fires them by stamp, with nested items placed straight after their cause.
- Chat lines and field notices are its first two lanes.
- A line waiting on a player name, creature record or item fires at its answer's place (`0x49ccc0`, `0x6294b0`).
- A notice's own chat line fires `CHAT_MSG_SYSTEM` in place, as `DisplayError` does (`0x496720` → `0x49a870`).

A guard test lists every handler-filled queue not yet on a lane, so the rest of #2168 can follow. A depth cap on nesting is a written `Deviation:` until #2202 finishes. Goldens pin the order. Built on macOS.

Part of #2168
Fixes #2173

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
More producers now fire their events in packet order, from the stamped drain of #2225 (Part of #2168).
- **Cast reports** fire at their packet's place.
- **Form and combo-point changes** come from their field watchers (`0x5dda18`, `0x5dd9d0`), so `UPDATE_BONUS_ACTIONBAR` fires on every form change (`0x4e4fc0`).
- **The group list** fires its events and lines in the handler's order (`0x5e6c19`-`0x5e6e52`).
- **The auction handlers'** lines and list steps fire at their packets. A page waiting on its items sorts at the answer.
- **Each aura-applications byte** has its own watcher (`0x60423b`).
- **The trade offer** is trimmed inside the coinage watcher, at its packet (`0x5ddf30`).

The guard lists every producer still left, with its reason. Goldens pin each interleaving against another packet's line. Built on macOS.

Part of #2168

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The quest windows, the quest log and gossip now follow 1.12.1, and their verbs act inside their call (Part of #2202).
- The quest log is ordered by the client's two `qsort` passes (`0x4de751`, `0x4dea63`), and collapse is keyed by group id.
- `GetAbandonQuestItems` lists the quest items an abandon destroys.
- `GetQuestLogRewardMoney` adds the level-60 money.
- `GetQuestReward` latches, so a double click sends one choice.
- An elite quest shows its tutorial.
- Rewards the player can't use read unusable, and an item of inventory type 0 reads quality -1.
- A coded gossip option asks for its code (`GOSSIP_ENTER_CODE`).
- A close drops the NPC inside its call (`0x501201`, `0x4e1fcc`).
- The gossip list reads at most 15 options (`0x4e2722`).

Tests pin each address, and a golden pins the code's bytes. Built on macOS.

Fixes #2194
Fixes #1508
Fixes #1507
Fixes #386
Fixes #385
Fixes #29
Fixes #1513

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The trainer, flight-master and merchant windows now follow 1.12.1, and their verbs act inside their call (Part of #2202).
- **`IsTalentTrainer`** answers 1 with no trainer open (`0x4d8ed0`).
- **The trade-skill and learn-spell queries** read each service spell's effects (`0x4d9f70`).
- **`GetTrainerServiceAbilityReq`** counts a known higher rank as met (`0x4d96e0`).
- **Trainer rows** sort as the client's one persistent list, with a case-sensitive compare. The order is pinned against the client's own sort, run in benilla-re's emulator.
- **`CloseTaxiMap` and `TakeTaxiNode`** act in the call. A map with fewer than two known nodes shows `ERR_TAXINOPATHS` and opens nothing (`0x5ecf39`).
- **Buying an item a quest needs** shows its progress line (`0x5dced0`).
- **The merchant's sale and drop verbs** send as `0x4fb760` does.

Tests pin each address. Built on macOS.

Fixes #1272
Fixes #213
Fixes #212
Fixes #166
Fixes #2148
Fixes #2147
Fixes #1804

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Since #2209, gathering a herb, mining a vein or opening a chest opened the loot window and closed it in the same frame. The gather cast's `SMSG_SPELL_GO` owes the loot close `0x48f200` from its precast tail (`0x60d0ee`-`0x60d10b`), and the reference runs it inside the GO. That close returns at once when no window is open (`0x48f23f`), so it never takes the window the following `SMSG_LOOT_RESPONSE` opens. benilla applied the owed close a packet late, in the drain, and shut the new window. A loot response now settles a close owed ahead of it first, at rest or on move start, before it opens its window. Tests pin both variants, a close owed after the response, and two responses in one drain. Built on macOS.

Fixes #2230

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The guild roster, charters and tabard designer now follow 1.12.1, and their verbs act inside their call (Part of #2202).
- `SetGuildRosterShowOffline` compares the integer it is given (`0x4d0f70`), and re-sorts only on a change with members held. The flag and the selection outlive a `/reload`.
- The away, busy and GM tags, overhead and in `GetGuildRosterInfo`, read the `CHAT_FLAG_*` globals (`0x5eca17`, `0x4d13ed`).
- `BuyGuildCharter`, `RenamePetition` and `SortGuildRoster` raise their own usage lines.
- `RenamePetition` with no charter open does nothing (`0x4f495e`).
- `ClosePetition` fires `PETITION_CLOSED` while the record is still readable (`0x4f402c`).

Tests pin each address. Built on macOS.

Fixes #1437
Fixes #371
Fixes #2124
Fixes #2123

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Item, item-push and loot events now fire in packet order, on the stamped drain of #2225 (Part of #2168).
- Each item watcher's callback fires whole at its packet, in its handler's order (`0x5d94e1`-`0x5d9519`, `0x5d85ce`-`0x5d8606`, `0x5d8798`-`0x5d87c1`).
- One record answer's callbacks run in the order they were asked (`0x5d8321`).
- The loot window's steps and the item push share one lane: a waiting copier ends at its record's answer (`0x4c2afc`), and a waiting push shows there (`0x491ee0`).
- Two responses in one frame give each window its own LOOT_OPENED and LOOT_CLOSED.
- The owed close of #2230 still settles before the window opens.

`BAG_UPDATE` and `ITEM_LOCK_CHANGED` remain in the guard, with their reason. Goldens pin each interleaving against another packet's update. Built on macOS.

Part of #2168

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The skill and reputation lists keep their folds and selection, and the talent, skill and reputation verbs act inside their call (Part of #2202).
- A standing change keeps the reputation folds: the mask `0x84a0a4` is keyed by header (`0x4d5b16`, `0x4d5bdc`).
- `SetSelectedFaction` and `GetFactionInfo` treat a header as a row with its own values (`0x4d6bec`, `0x4d654f`).
- `CollapseSkillHeader` and `ExpandSkillHeader` fold the row's group and fire `SKILL_LINES_CHANGED` (`0x4d3210`, `0x4d3270`, `0x4d2ed0`).
- The skill watchers rebuild the list on a max or bonus change and speak the skill-up line (`0x5dda6c`, `0x5de36a`).
- `GetTradeSkillLine` adds the permanent bonus to rank and max (`0x4fddf7`).
- `FactionToggleAtWar` writes the at-war bit into the reputation store, not only the pane's copy (`0x4d5fd0`).

The fold-by-position quirk is a recorded Deviation. Tests pin each address. Built on macOS.

Fixes #1238
Fixes #1231
Fixes #1230
Fixes #274
Fixes #272
Fixes #271
Fixes #288

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The bag, cursor and paper-doll verbs follow 1.12.1 and send inside their call (Part of #2202).
- `EquipCursorItem` acts only on a held item (`0x489660`).
- The enchant pick exits silently on a miss and tests the charter flag first (`0x495d60`, `0x495e05`, `0x5da2e3`).
- `PutItemInBag` and `PutItemInBackpack` follow the bag worker: a vendor row buys into the bag, and a backpack item dropped on the backpack cancels (`0x4c7c00`).
- A world click drops a held vendor row or bar item on every leg (`0x492c90`).
- Bank bags are no bags to the container verbs until the bank opens (`0x4f93e0`).
- A bag item with template page text reads as readable (`0x5d9e10`).

A vendor row the list no longer holds, and the backpack's 0x71 bound (`0x4f990f`), are recorded Deviations. Tests pin each address. Built on macOS.

Fixes #2155
Fixes #2154
Fixes #2153
Fixes #152
Fixes #151
Fixes #149
Fixes #146

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The pet bar, pet unit, portrait, cooldowns and stable follow 1.12.1, and the pet and stable verbs send inside their call (Part of #2202).
- Pet cooldowns from `SMSG_PET_SPELLS` start back-dated through `0x6e2c60`, with only bit 31 masked (`0x4bdaab`).
- Feeding casts first and binds the food only on success (`0x6ea266`); `DropItemOnUnit` takes any token naming the pet (`0x48da6c`).
- `CastPetAction` presses empty slots, drops a held action and plays its sound (`0x4be330`).
- A pet with no object exists, unseen, with an empty portrait (`0x4e80cb`, `0x51a0ce`).
- `UNIT_COMBAT` fires once per token naming the unit (`0x494600`).
- A pet rename evicts the name cache by pet number (`0x604ac6`).
- `UnstablePet` and the stable drops send to the stored master, as `0x4caf20` does.

Tests pin each address. Built on macOS.

Fixes #2161
Fixes #1598
Fixes #298
Fixes #180
Fixes #2006
Fixes #340
Fixes #187
Fixes #1414

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The console commands and CVar behaviours 1.12.1 has, and Lua's local time.
- `date` and `time` use the local zone, `time{}` by `_mktime`'s sum (`0x740811`, `0x740836`); screenshots and the logon challenge's bias follow.
- In fullscreen `GetCVar("gxResolution")` answers the screen's own size; the windowed size is what is saved.
- The twelve world commands (`0x66f6c0`), with `showDetailDoodads` gating the clutter and `waterRipples` the wakes.
- `quit`, `ver`, `setmap`, `showplayer` (`0x6400e0`, `0x4049e0`) and `SkySunGlare` (`0x6d1dc0`).
- `ffx`, `ffxGlow`, `ffxDeath` and `ffxRectangle` print enabled or disabled (`0x6cde30`).
- `widescreen` 0 gives the four 4:3 modes and a 4:3 portrait aspect.

Tests pin each address. Built on macOS; Linux and Windows compiled.

Fixes #2231
Fixes #2232
Fixes #1628
Fixes #1641
Fixes #1667
Fixes #2098
Fixes #1687
Fixes #1665
Part of #1577

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Piece 4 of the event order: the bag, lock, bank-slot and money events fire inside their watchers' runs, on the item lane, at their packet's stamp (Part of #2168).
- `BAG_UPDATE`, `BAG_CLOSED` and `PLAYERBANKSLOTS_CHANGED` come from the watchers (`0x4f9480`'s five callers, `0x4f8db0`, `0x4f90d0`, `0x4f91a0`, `0x5ddcf0`), not a diff.
- `PLAYERBANKSLOTS_CHANGED` has its two producers (`0x5ddcf0`, `0x4c7180`).
- The unlocks open each run, for the item now in the seat (`0x5ddd2f`).
- `PLAYER_MONEY` is a coinage notice (`0x5ddf4b`).
- Events stamped before the frame's first bag write read the bags as they stood.
- The guard now fails on a host-held queue a packet fills with no row.

Goldens pin each event against another packet's notice and line.

Fixes #2165

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The trainer window, its filters and the NPC service windows' lifecycle follow 1.12.1.
- `SetTrainerServiceTypeFilter` takes `"all"`, an exclusive third argument and its own errors; `GetTrainerServiceTypeFilter` matches case-blind (`0x4da260`, `0x4da510`).
- A mount or pet trainer always builds its known group first (`0x4d7609`); rows of state 3 and up show under no filter (`0x4da390`).
- An item record a getter waited on re-derives, re-sorts and fires, once per callback (`0x4d9140`, `0x4d7d40`).
- An NPC that loses a service flag closes that window (`0x60b420`).
- No window fires its event when the NPC's name arrives; `QUEST_ITEM_UPDATE` comes only from the item callback (`0x5018c0`).

Tests pin each address. Built on macOS.

Fixes #2241
Fixes #2242
Fixes #2243
Fixes #2244
Fixes #1494
Fixes #203

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
One live reputation store: the wire, the pane's verbs and the reaction read and write the same slots, as the client's one array does (`0xb73290`).
- `FactionToggleAtWar` and the inactive verbs write the store inside their call, so `UnitReaction` and `UnitCanAttack` on the next line answer the new reaction (`0x4d5fd0`, `0x60640c`).
- `SMSG_SET_FACTION_ATWAR` copies the at-war bit (`0x4d56b0`). A standing message sets it below Unfriendly and clears it only on a rise (`0x4d5833`, `0x4d583f`).
- A rank change refreshes the player's reactions and sweeps the units, once per message (`0x4d5905`, `0x606f20`).
- `UPDATE_FACTION` fires per message, as the four handlers do.
- Logout zeroes all 64 slots (`0x4d541f`).

Tests pin each address. Built on macOS.

Fixes #2240
Fixes #77
Fixes #1410

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The chat verbs follow 1.12.1, in its order (Part of #2202).
- `SendChatMessage` shows its away lines and sends the tutorial flag inside the call, ahead of the chat packet (`0x49a870`, `0x4b54c0`).
- A drunk player's line is slurred by `0x508680`'s rules.
- `SendAddonMessage` cuts prefix and message at their first NUL (`0x49f958`).
- The chat cache saves the confirmed custom slots at teardown (`0x499b90`).
- `/chatlist <channel>` prints the members by name once every name is known (`0x49c7e0`).
- `/played` prints once, through the stock frame (`0x401850`).
- `DoEmote` sends the stow, the stand change with its loot release and attack stop, then the emote, all in the call (`0x5ef560`, `0x6127b0`).

Tests pin each address. Built on macOS.

Fixes #2160
Fixes #2158
Fixes #2157
Fixes #2206
Fixes #226
Fixes #194
Fixes #2205

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Targeting and the player's own unit follow 1.12.1.
- `TargetUnit` raises its usage error for a non-string and "Unknown unit name" for an unknown token (`0x4899d0`, `0x515970`).
- The selection call refreshes the usable state, then fires `PLAYER_TARGET_CHANGED`, inside the call (`0x493892`, `0x49389c`).
- A deselect stops the attack, then the pet's attack behind its gate, then sends (`0x493a08`-`0x493a2a`); `PET_BAR_UPDATE` fires in place.
- Your own death clears the target, closes loot, cancels the cast and auto-releases when flagged (`0x5e99b0`).
- Sitting and Z act on the active player under possession (`0x5ed430`, `0x5eb480`).
- `UnitIsPVPFreeForAll` reads the controlling player's flag (`0x516540`, `0x606170`).
- A race's side comes from ChrRaces and FactionTemplate (`0x5efe00`).

Tests pin each address. Built on macOS.

Fixes #2192
Fixes #1233
Fixes #2212
Fixes #2136
Fixes #2004
Fixes #2259
Part of #2207

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Macros and the action bar's queries follow 1.12.1.
- Create, edit and delete fire `UPDATE_MACROS` and the edited slots' `ACTIONBAR_SLOT_CHANGED` in the reference's order, inside the call, and derive the bound spell there (`0x4f1322`, `0x4f1329`, `0x4f1313`).
- A macro's spell is re-derived only at load, arrival, create and edit (`0x4efe00`'s four callers).
- A rank supersede rewrites bar macros, and clears slots still on the old rank, items included (`0x4e5e60`, `0x5e6392`); an unlearn clears every slot on the spell (`0x4e5e20`).
- The macro file reads and writes as `0x4f0600` and its writer do: final newlines, `END`, quote-aware names, signed ids, 255-byte lines.
- Item slots answer range by their on-use spell; `ActionHasRange` tests "not below" 2^-22 (`0x4e5a50`, `0x8029d4`).

A line cut that would split a UTF-8 character is a recorded Deviation. Tests pin each address. Built on macOS.

Fixes #2115
Fixes #2111
Fixes #2107
Fixes #2065
Fixes #2062

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The item Lua API answers links, suffixes, makers and repair costs as 1.12.1 does.
- `GetInventoryItemLink`, `GetContainerItemLink` and `GetTradePlayerItemLink` answer nothing for an empty slot, and a white, nameless link for an uncached item (`0x4c8c56`, `0x52ae00`).
- Inspected crafted gear shows its maker line, read from the slot's creator (`0x5333cd`, `0x52e1d5`).
- `GetItemInfo` names an item with its link's random suffix through `ITEM_SUFFIX_TEMPLATE`, and keeps signed link numbers (`0x5d8b00`, `0x842d4c`); every item name uses that one format.
- A gift-wrapped item hides the roll of the item inside and costs nothing to repair (`0x5d8bc9`, `0x5da387`).

Tests pin each address. Built on macOS.

Fixes #2174
Fixes #2175
Fixes #1543
Fixes #282
Fixes #1160

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Combat log, monster chat and loot lines now follow 1.12.1's rules.

- An aura update writes every loss before any gain, with flags masked `& 0xe`. Stack lines print only for a `StackAmount` above 1.
- Dispel lines check only the bearer, which must be held and in range.
- A line whose item the server refuses is dropped.
- Melee `UNIT_COMBAT` carries the school of the swing's first sub-damage block.
- Monster lines expand against the addressee alone (`0x49daab`). `$R` and `$C` name a creature. A held unit whose name is missing reads `UNKNOWNOBJECT` (`0x609210`). At most 64 lines wait for a name, oldest dropped first (`Deviation:`).
- Loot-roll and item-received lines read their GlobalStrings templates, with the roll's property word, a signed count, and another member's push (`0x491d9f`).

Each rule has a test that failed before the fix.

Fixes #2075
Fixes #2074
Fixes #2092
Fixes #1587
Fixes #206
Fixes #356
Fixes #352
Fixes #369

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A verb's events used to fire only after its call returned. So a `PLAYER_TARGET_CHANGED` listener ran after the swing had gone out and the cast had resumed. In 1.12.1, `SignalEvent` (`0x703e50`) runs every listener before it returns.

`UiScript::signal_point` now lends the host back at the reference's signal points inside a call. Held items replay, and a listener's own calls nest. The selection verbs signal `PET_BAR_UPDATE` (`0x493a18`) before the send, and `PLAYER_TARGET_CHANGED` (`0x49389c`) before the re-swing.

StartAttack now selects its target (`0x5eccaa`) and stops again when its target test refuses (`0x5ecc37`). The switch latch tests the old selection (`0x49365f`-`0x4936a3`). A clear sends the selection as it stands (`0x493a49`). Nesting stops at 8 levels (`Deviation:`; the reference allows 200, `0x6f65ae`).

Tests pin the wire order, nesting, a panic, the stack and the census.

Part of #2271
Fixes #2276
Fixes #2277

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
UI texture loading and widget method calls now follow 1.12.1.

- A UI texture's decoder is chosen by its extension (`0x449e16`), not by the file's content. A refused BLP draws green (`0x44a50e`). A TGA side below 8 fails (`0x58ab40`). A refused TGA minimap mask draws white.
- Portraits, `SetCursor` and drag icons open only `.blp` (`0x525e0f`, `0x449610`), and so do the tabard emblem cells (`0x50338a`).
- `SetPortraitToTexture` takes a Texture object and ignores a bad path (`0x48d780`).
- Every widget method checks its receiver first: a table, then an object, then the class. A bad receiver raises the reference's own lines. The 8 of 481 methods that skip it at the bytes are exempt. Texture arguments to the portrait and taxi globals, `AddFontStrings`' FontStrings and `SetBuybackItem`'s slot are checked the same way.

A test asserts that every reference method is wrapped.

Fixes #1685
Fixes #2100
Fixes #2099
Fixes #2203
Fixes #2125

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Three fixes for timing and unusual input.

- **NPC windows.** Seven NPC windows could fire their show before the `npc` token was set: gossip, quest, bank, taxi, stable, tabard and registrar. In 1.12.1 the packet handler sets `[0xb4e2d0]` before the show (`0x4e1fa0` → `0x4930d0`, then `0x4e27df`), so a show never draws the book icon with an empty name. All eleven latched window feeds now run after the unit feed, and a schedule test names any that do not.
- **Faction slot.** The faction slot pick shifts a race or class byte as `dec ecx; shl` does (`0x4d5512`): 0 tests bit 31, 33 tests bit 0, and nothing panics.
- **Pet clock.** The pet bar and pet book read cooldowns at the frame clock, so their expiry tests step time instead of sleeping.

Three wrong comments are fixed.

Fixes #2278
Fixes #2048
Fixes #2275
Fixes #1479
Fixes #1227
Fixes #2144

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Seven window verbs read, keep and fire what 1.12.1 does:
- `QueryAuctionItems` takes its text through `lua_tostring`, `usable` as a number's `_ftol` sent as a byte, and quality -1 for a non-number (`0x4ce9cd`-`0x4cec32`).
- `GetInboxInvoiceInfo` needs the item entry (`0x4af3e8`), names an uncached item "" (`0x5d8b25`), and reads its body as `sscanf` with zeroed outputs and no return test (`0x4af440`-`0x4af471`).
- The who list reads names and guilds at the `0x30`/`0x60` caps and keeps a short answer (`0x5ae013`-`0x5ae0c1`).
- The gossip greeting expands once per message (`0x4e229a`).
- A trainer's headers are built per list (`0xb73a14`).
- The repair flag alone fires no `MERCHANT_UPDATE`.
- A staged guild rank survives a roster reload (`0x4d0a37`).

Each has a test that fails without its fix.

Fixes #2214
Fixes #2220
Fixes #2222
Fixes #2262
Fixes #2263
Fixes #2264
Fixes #2245

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A unit event's listener read the unit as the frame ended: two `UNIT_HEALTH` listeners for a 100→70→40 target both read 40.

In 1.12.1 a packet's handler writes every block (`0x4651a0`, `0x46520e`, `0x465222`), then calls its watchers synchronously (`0x465570`). `UnitHealth` and `UnitMana` read the live object (`0x517553`, `0x5176f7`). So a listener sees its own packet's state.

Each update packet now logs the dwords it moved, along with its destroys and teardowns. A "unit views" lane rewinds the player, target, targettarget and npc stores to frame start, then replays the log in stamp order between the other lanes' items. A debug check compares the rewind with a frame-start snapshot. `READS_FRAME_FINAL` lists every view push still reading frame-final state.

Tests: per-packet `UnitHealth`/`UnitMana`, `UNIT_COMBAT` ahead of its update, coalescing, an unlogged write, a worldport, and a stalled drain.

Part of #2217

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
More of 1.12.1's signal points fire their events in place, before the packet they precede:
- TryCast's pick (`0x6e4efb`, before `0x6e585a`), and the attack validator's pick, split from its re-read (`0x61305d`-`0x6130c1`), for Attack, the pet bar's Attack (`0x4bd40d`-`0x4bd44c`, possessed units included) and a unit caster;
- the world click, the plate click, a death and a despawn's selection change;
- a deselect outside a call fires `PET_BAR_UPDATE` from the stop (`0x4bd6b4`) before `CMSG_SET_SELECTION`;
- the engine's target writers test the old selection's latches and send ATTACKSTOP as `0x493a08` does;
- chat, channel-join, emote and party refusals show inside their calls (`0x49b9c5`, `0x48a485`);
- the dev capture and probe Lua runs after its turn with the host lent, and the host refuses Lua in place behind a held signal.

Each has a test that fails without it.

Part of #2271
Fixes #2272
Fixes #2282
Fixes #2266
Fixes #2185

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In 1.12.1 an event's listener reads state as its own packet left it: each packet's handler writes and then signals in place. benilla read party, raid and pet units, the by-guid chain ends, the group records and auras as the whole frame left them. So an earlier listener could see a later packet's member, pet or aura.

The unit views lane now rewinds every unit a view can read, and every unit those name during the frame, to each entry's own packet, then replays. `PLAYER_AURAS_CHANGED` fires from the aura watcher's callback (`0x4e4140` → `0x4e4170`), not a frame diff. `PARTY_MEMBER_ENABLE`/`DISABLE` fire at the member object's create (`0x4e86b6`) and release (`0x5e9b78`), and no longer at a worldport, which frees each member without a release (`0x467800`). A frame-final read in a lane now fails a guard test.

Tests go through the dispatch and fail on main.

Part of #2217
Fixes #2280

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The trade skill, craft and reputation verbs answered from benilla's own view of the lists, not 1.12.1's.

- Beast Training keeps a spell with no ability row, and its rows read none or used (`0x60c740`).
- `GetCraftInfo` answers the training-point cost and level, `GetAdjustedSkillPoints` the free profession count, and `GetCraftDisplaySkillLine` answers only for Enchanting (`0x4f6ce3`).
- Trade skill and reputation indices cover the full list in learn order or adder order, sorted as the CRT qsort sorts it (`0x73f727`).
- `TRADE_SKILL_UPDATE` and `CRAFT_UPDATE` fire per learn, unlearn, pet-spell packet and waited record, at the packet's place (`0x5e9e12`, `0x5ea240`, `0x4bdafa`). A pending product holds the sort and the update (`0x4fcf34`).
- Reputation messages add and sort at their handlers' places.

Tests pin each order and trigger, and fail on main.

Fixes #1264
Fixes #1265
Fixes #1266
Fixes #1276
Fixes #2128
Fixes #2129
Fixes #2255
Fixes #2237

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The cursor, equip, container and vendor verbs gated, waited and sent differently from 1.12.1.

- A swap that asks the bind-on-equip question keeps the item held, and asks inside the call (`0x5e0c40`). An uncached record parks the equip and replays it when the record arrives (`0x5e0dbe`, `0x5e17a0`, `0x5e1020`). `USE_BIND_CONFIRM` fires inside the use (`0x5d9222`).
- A split stack is equipped or swapped whole, since `0x5e1480` and `0x4c7300` never read the split count.
- An enchant on an uncached item gates on zeroed fields (`0x495d60`).
- Bank slots read empty to the container and inventory verbs while no banker is open (`0x6228a0`).
- A vendor row dropped on a bank bag buys into it (`0x4f9f32`), and `CMSG_BUY_ITEM` ends with 1 (`0x4fb933`).

Tests go through the verbs and fail on main. The vendor golden is byte-exact.

Fixes #2249
Fixes #2251
Fixes #2250
Fixes #2246
Fixes #2248
Fixes #2247
Fixes #2227

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In 1.12.1, attack, selection and death run their steps and signal their events in place. benilla queued several of them for later in the frame.

- The right-click select, StartAttack's own select (`0x5eccaa`) and the engine's selection writers now signal in place. A teardown reports its StopAttack before `PET_BAR_UPDATE` (`0x493a08`).
- StartAttack sets the attack target locally at its send (`0x5ecd51` → `0x47bf70`). A seated player stands before the swing (`0x5ecc9b`).
- A right-click that falls out of the attack arm greets (`0x60c257` → `0x60c270`).
- Your own death runs `0x5e99b0`'s steps in order, inside its update packet, then signals `PLAYER_DEAD` (`0x6058ae`).
- Every Lua verb's refusal shows inside its call.

Tests go through the packets and the calls, and fail on main.

Part of #2271
Fixes #2287
Fixes #2268
Fixes #2114
Fixes #2009
Fixes #2207
Fixes #2289

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Several unit, aura and world-entry answers came from the wrong source or at the wrong moment.

- `"pet"` is the player's charm, else summon (`0x515a5e`), not the pet bar. The pet's buffs go through `UnitBuff`'s unit gate (`0x5195aa`), and other units' auras through `RequiredAuraVision` (`0x519860`).
- `SetPetGuid` signals in place, in its order (`0x4bc84f`-`0x4bc90c`). World entry clears the pet guid and asks for pet info (`0x4bc6f0`, opcode 0x279).
- World entry always signals `UPDATE_BONUS_ACTIONBAR` (`0x4e5018`). `PLAYER_LEVEL_UP` and the level tutorials fire from `SMSG_LEVELUP_INFO` (`0x5e413e`).
- `GetZonePVPInfo` answers at the call (`0x48d540`), and the zone commit waits for our player (`0x5db95d`). After a live worldport into Stormwind, main answered nil inside `ZONE_CHANGED_NEW_AREA`; this answers friendly.

Tests fail on main. The 0x279 golden is byte-exact.

Fixes #2252
Fixes #261
Fixes #262
Fixes #2228
Fixes #1158
Fixes #2293

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The profession, craft and loot windows, and two death timers, disagreed with 1.12.1 across a `/reload`, a group header and a busy frame.

- A `/reload` keeps the profession book open and unshown (`0x4fc750`), and its record waits outlive the VM. Clicking an open profession closes it, as the toggle `0x4fc910` does.
- The world-enter cascade, and so a reload or a worldport, closes the craft window silently (`0x4f5ee0`).
- The craft list heads each group once there are two (`0x4f60c0`). It sorts them with the comparators' header skip over the CRT qsort, answers `"header"` and folds.
- A replaced loot session's steps run on their own latch, stash and copier wait. An inventory failure re-signals `LOOT_OPENED` under the auto-loot byte (`0x5e3aff`).
- The release and corpse timers answer whole seconds (`0x48b5ae`, `0x48b5f0`).

Tests fail on main.

Fixes #2295
Fixes #2296
Fixes #2291
Fixes #2210
Fixes #2256
Fixes #1530

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In 1.12.1 a Lua verb sends, changes state and signals before it returns. Here, the session, tutorial and window-close verbs queued their work for a drain later in the frame.

- `Logout`, `Quit`, `CancelLogout`, `ForceLogout` and `ReloadUI` act in the call (`0x5ab000`), as do the tutorial writers (`0x4b59dc`) and the registrar and tabard closes (`0x4f5010`, `0x4f58a0`). Each system's calls go through one host variant and applier.
- `AddFriend` flags its tutorial before `CMSG_ADD_FRIEND` (`0x5ae677`).
- `LOGOUT_CANCEL` fires only while a logout is pending: on the ack, a world load, a completed logout and a refused login (`0x5aae70`).
- A logout runs the leave-world sweep, which had needed the avatar it had just despawned (`0x5dd543`). The logout clears follow `PLAYER_LOGOUT` (`0x490c2a`).
- Two lanes with equal heads fire in lane order instead of stalling the drain.

Tests fail on main.

Part of #2202
Fixes #2267
Fixes #2138
Fixes #2297
Fixes #287
Fixes #297

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Camera, swim-pitch, sound and name-check verbs were inert or answered differently from 1.12.1.

- `MoveView*` drive the camera's keyboard axes, and `ToggleMouseMove` toggles mouselook (`0x50fd40`, `0x50f910`).
- `CameraOrSelectOrMoveStop(1)` keeps the camera where it was left (`0x50f940`), and a customised view arms no auto-return (`0x50fc30`).
- The pitch keys pitch a swimmer and send the pitch moves (`0x515010`), and mouselook sends `MSG_MOVE_SET_PITCH` and `MSG_MOVE_SET_FACING` under the 0.1 rad throttle (`0x60e1ea`).
- `PlaySound` finds type-2 rows only (`0x45c306`). `PlaySoundFile` plays on bus 3, two at a time.
- Server-pushed music has its own slot (`0x460520`), and a repeat of a duplicate-flagged track is refused at its open (`0x7a66a0`). Zone silences use `0x460100`'s fixed point.
- Every VM checks names against the client's word lists (`0x402b7f`).

Tests fail on main, and the pitch body has a golden.

Fixes #1190
Fixes #1642
Fixes #1651
Fixes #1615
Fixes #1617
Fixes #49
Fixes #2116
Fixes #1694
Fixes #1689
Fixes #1139

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Spell text, per-level terms, cast gates, ranges and travelling missiles answered or drew differently from 1.12.1.

- `$c` and `$p` expand (`0x6e31b0`, `0x6e3300`). A weapon spell's level reads the wielded weapon's skill (`0x5ea5d0`), and skill lines are indexed by race and class (`0x6de040`).
- The validator refuses a known learn, a pet's unlearnable spell and a second pet locally (`0x609854`, `0x609fd7`).
- A ranged spell's max range takes the ranged-slot item's scale (`0x6e36aa`).
- The launcher flies a ground cast as one missile at its point, and spawns none without a missile visual or ammo (`0x6e8a50`, `0x6e8a7f`). A reflected missile turns back at its caster (`0x61e17b`). Launch timing and the victim clips follow `0x61cfda` and `0x61d784`.

Tests fail on main, and the GO decode has a golden.

Fixes #2042
Fixes #2049
Fixes #1202
Fixes #683
Fixes #2069
Fixes #2070
Fixes #62

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- `Undress` empties both hands (`0x504490`). A tried-on one-hander takes the off hand only with Dual Wield (`0x504930`, `0x5eab70`). A try-on with no display does nothing, and an unequippable one raises `ERR_NOT_EQUIPPABLE` (`0x50458b`, `0x504b2d`).
- The coin row puts each denomination on its own line (`0x4c2550`).
- The talent tooltip reads all three prerequisite slots, then the tier, then the spell, with the words read at the call (`0x52b390`).
- An empty ticket shows `ERR_TICKET_NO_TEXT` (`0x5ef802`).
- A meeting stone names its area and level range (`0x5f6b10`).
- A reload keeps the login's combat text font (`0x401620`).
- A duration that arrives before its aura joins it (`0x4e4450`).

Each fix has a test that fails without it.

Fixes #53
Fixes #198
Fixes #54
Fixes #57
Fixes #142
Fixes #1495
Fixes #190
Fixes #757

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
About 35 Lua verbs queued their sends and events for a drain after the chunk; 1.12.1 acts inside the call.

- The NPC window closes (bank, trainer, auction, gossip, mail, merchant, stable, trade, taxi, trade skill, craft, quest) run their cores in the call, and fire CLOSED twice while the window held the latch (`0x493310`).
- Battleground, death, duel, loot roll, binder, summon, instance and PvP verbs send and signal in the call, with their gates: `RepopMe` only dead or a ghost (`0x605f30`), `AcceptXPLoss` in the healer's range (`0x5df7c1`), `PurchaseSlot`'s money and count (`0x4f86b0`).
- `AcceptAreaSpiritHeal` and the time packet rescan before acting (`0x4923b0`).
- The positions getters read the last packet with no slot gate (`0x4abf09`, `0x4ac1c6`).
- A unit is dead at health 0 or below (`0x605f3b`).

Each system has a test mixing its verb with `RandomRoll`, which fails without the change.

Part of #2202
Fixes #2321

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Over open ground the classifier's no-cursor exits clear to the base mode (`0x523d30`), so the repair and wrap cursors stay.
- While targeting, hover and click skip `IsSelectable` (`0x4828f4`, `0x4935d5`). An object is picked only for a word in `0x4800` that the lock test passes (`0x6e62e0`, `0x5f8260`), keys (effect 59) included.
- Object use refuses dead, locked out, mounted or shapeshifted, in combat and immune in `0x5f3130`'s order, with columns read by the object's own type. An auto-closing door at rest opposite its start says the object is in motion (`0x5f42f0`).
- `SMSG_GAMEOBJECT_PAGETEXT` opens the reader on the object's page (`0x5f88c0`), with a byte-exact golden.
- The interior trace keeps the nearest hit across every building (`0x6a8840`), with the portal tie as a fraction of the ray (`0x6be421`).

Each fix has a test that fails without it.

Fixes #436
Fixes #680
Fixes #677
Fixes #1710
Fixes #1711
Fixes #91
Fixes #2146

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A full `benilla-app --lib` run cost about 1,050 CPU-seconds, most of it building a headless client per test, and `check.sh` ran the scoped suite twice.

- `order_the_pass` sums each system's access with the host's in one reused buffer instead of cloning the host's access per system; `PassOrder` is unchanged.
- Fixtures that never run the app use `headless_client_unrun()`: every plugin and schedule but `Main` and `Update`, so the pass is never ordered. Running the app or reading the pass from it panics.
- The headless client skips the tonemapping tables, and the handler and lane access lists are measured only when a test asks.
- `check.sh` runs each scoped suite once (`test-no-install` stays in the gates) and adds the player build's schedule tests when `benilla-app` changes.

Measured: 602 CPU-seconds for the same run, 43% less.

Fixes #2324
Fixes #2325

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- `TryOn` looks its item up when the verb is applied, in call order with `Undress` and `Dress`: a cached record lands there, as `0x504540` sets the components inside the call (`0x5045dd`-`0x5045ee`), and only a missing one waits for the callback `0x504ba0`. So `TryOn` then `Undress` in one chunk shows nothing.
- `Dress` keeps a try-on still waiting for its record: the rebuild (`0x504cd0` → `0x505b50`) cancels no callback.
- `GetGMTicket` and `DeleteGMTicket` send nothing without an active player (`0x48c9bd`, `0x48cb8d`).
- More test fixtures build the client without an `Update` pass: about 25% less CPU for the `benilla-app` suite.

Each fix has a test that fails without it.

Fixes #2329
Fixes #2330
Fixes #2328

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The remaining Lua verbs that send a packet queued it for a drain; 1.12.1 sends inside the call.

- The call host is one flat set (`script_calls/disjoint.rs`): each applier's access is gathered once. Host setup fell from about 0.67 ms to 0.19 ms per system.
- Worn display, action slots (`0x4e58e0`), GM tickets, `Stuck()` (`0x489370`, the learned spell `[0xb70098]`), inspect, item text paging and close, `ConfirmPetUnlearn`, `DoCraft` and `SitOrStand` act in their calls.
- An aura cancel ends the player's precast and stops its effects, honours the far-sight gate, then signals and sends (`0x6e7040`).
- A minimap click with a ground spell armed casts at the terrain under it and does not ping (`0x4eee67`-`0x4eef14`).
- Movement verbs stay on the mover tick, as `0x616800` sends them.

Each system has a test that mixes its verb with `RandomRoll`, and fails without the change.

Part of #2202
Fixes #2322
Fixes #1644

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Nameplates, the minimap and fourteen tutorial prompts now follow 1.12.1's own code paths.

- **Plates:**
  - an unanswered name reads `UNKNOWNOBJECT` (`0x609324`);
  - friendly totems get no plate (the `0x605570` resolver);
  - plates centre on the camera's unit and skip it, and there are none without one (`0x60f68c`, `0x60f77c`).
- **Minimap:**
  - the pet or charmed unit gets its party dot (slot 4, `0x6dad8c`);
  - the interior flood snaps its window, runs in the model frame and takes the seed group's ceiling (`0x6d95f8`, `0x6dc540`, `0x6a524b`).
- **Tutorials:**
  - Food and Drink fire strictly below half (`0x5ecaf7`);
  - Welcome and Questgivers fire on every world entry;
  - fourteen more prompts fire from their engine sites.

Each fix has a test that failed before it.

Fixes #878
Fixes #1142
Fixes #1592
Fixes #363
Fixes #1271
Fixes #2038
Fixes #73
Fixes #804
Fixes #1568

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Eight events fired at the wrong time or with the wrong state.

- `SMSG_TRAINER_LIST` opens only for the asked trainer or the gossip NPC (`0x5dcc73`-`0x5dcca9`).
- `SMSG_PET_SPELLS` signals `SPELLS_CHANGED` and `SMSG_PET_MODE` signals `PET_BAR_UPDATE` on every packet, at the packet's place (`0x4b302a`, `0x4bdb4f`).
- The area is polled every tenth tick (`0x5db900`).
- World entry fires `ACTIONBAR_UPDATE_USABLE` inside its cascade (`0x4e4f90`), and the pet reset fires `UNIT_PET` with no pet UI (`0x4bc822`).
- The spirit healer's time packet sends in its handler and signals at its place (`0x4921c0`, `0x4922d1`).
- A new item-text lane opens a game object's page at its packet, once the page is cached (`0x4e3465`-`0x4e348b`, re-entered at `0x4e32a5`).
- Exclusive lane firings declare their measured reads to the lane guard.

Each fix has a test that interleaves its event with another packet's event, and fails without the change.

Fixes #2302
Fixes #2303
Fixes #2304
Fixes #2305
Fixes #2306
Fixes #2307
Fixes #2327
Fixes #2333

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ships and zeppelins follow 1.12.1's clock, seating and crossing.

- **The clock:** only a ship's create sets it, anchored at the packet's arrival (`0x5f7080`). The worldport reseek is gone, and a re-create keeps the anchor (`0x466350`).
- **Worldports:** an off-map ship stays frozen in view (`0x5f5274`), and a worldport keeps only the ridden ship (`0x401bc0`).
- **Crossings:** a crossing draws its route (`0x5f54c2` → `0x407de0`).
- **Seating:** a login seats us in the deck's frame, on a game object only (`0x630a80`). A rider stays aboard while inside the deck's volume (`0x630ba0`), and a gone transport ends the seat (`0x5f4fb0`).
- **Party pet portraits:** an out-of-view party pet's portrait probes the display cache (`0x525cb0`).

Each change has a test that failed before it.

Part of #1220

Fixes #1218
Fixes #1219
Fixes #1222
Fixes #645

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Eight defects, each re-derived at the client's bytes:

- **Action slots:** `PlaceAction` and its kin store the slot inside the call (`0x4e590a`), so the next verb in the same chunk reads it.
- **Far sight:** a buff cancel runs `UpdateFarSight` (`0x5ee290`) and toggles the view. Nothing is sent before the object arrives.
- **Pet trainer rows** read the summoned unit at its own level (`0x4d7f2d`).
- **Sound:** a kit's no-duplicates gate picks its file first and matches files, not kits, `PlaySoundFile` channels included (`0x45cf52`).
- **Game object animation:** a finished Despawn arms nothing (`0x5f4134`).
- **Zones:** the indoor zone-name override compares case-blind (`0x67e6e4`).
- **Comments and CVars:** a stale Auto Self Cast comment is gone, and `Sound_EnableSoundWhenGameIsInBG` is our own CVar.

Each change has a test that failed before it.

Fixes #2337
Fixes #2339
Fixes #2320
Fixes #2310
Fixes #2118
Fixes #1633
Fixes #596
Fixes #545

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Item, object, quest and text records are kept between sessions, as 1.12.1 keeps its template caches.

- **Which stores persist:** each store's constructor sets a persist byte at `+0x39`. The creature, game object, item name, item, NPC text, quest, page text and item text stores are saved. The name, guild, pet-name and petition stores are session-only and cleared at world entry (`0x555740`).
- **The file:** one per store per realm under `benilla-config/cache/`, in the reference's layout, header-checked (build, locale, record size), with a hit sending no query (`0x5586ca`). The records are the server's answer bytes, decoded on load by the live decoder.
- **The item-name store** answers `CMSG_ITEM_NAME_QUERY` (`0x559fe0`), and item-set member names read it.
- **Cleanup:** the old creature-only file is removed on load, and three comments that called the name caches persistent are corrected.

Tests cover every store across a session, header mismatches, eviction and the live packet path.

Fixes #85

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
More Lua verbs do their work inside the call, as 1.12.1's do. The chunk's next line sees the effect, and packets go out in call order.

- **`ConsoleExec`** runs its line in the call (`0x515f43` → `0x63ce00`): cvarlist, help, ver, the port commands, togglehelm, togglecloak, whois and reloadUI.
- **`FollowUnit` and `FollowByName`** run in the call (`0x60fed0`). A refusal shows its line, a re-follow fires its end (`0x60fb84`), and the begin fires at `0x610071`.
- **Cache misses ask in the call:** `UnitName` (`0x5171eb`), the trade getters (`0x4c017f`) and `GetGuildInfo` (`0x4c93d2`) send their query on a miss. A getter never raises.
- **The nameplate verbs** set their bits in the call (`0x489450`), and **`OpeningCinematic`** sends in the call (`0x48c8f6`).

Each converted verb has a test that mixes it with `RandomRoll` and checks the order.

Part of #2202

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
What the scene drew and what the camera looks at now decide plates, the mouse pick and the minimap, as in 1.12.1.

- **Mouse pick:** static doodads occlude it with their drawn opaque meshes, not their hulls (`0x69cdb0`, mode 2 of `0x7089c0`).
- **Plates:** a plate needs its unit drawn last frame (`0x6704c0`). Under far sight, plates centre on the camera's object, and there are none when it isn't a unit (`0x60f77c`).
- **Far sight:** `SMSG_CLEAR_FAR_SIGHT_IMMEDIATE` ends it (`0x5e43f8` → `0x5ee590`).
- **Minimap:**
  - the interior window keeps its z until x/y leave it or the group changes (`0x6d952c`);
  - party and pet dots and the group rim arrows have tooltips (`0x4eb33c`, `0x4eb42d`).
- **Tutorials:** Equippable Items skips ammo and quivers (`0x809200`).

Each fix has a test that failed before it.

Fixes #756
Fixes #2341
Fixes #2342
Fixes #2343
Fixes #2344
Fixes #2345
Fixes #2347

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Unit portraits follow 1.12.1's portrait drain.

- **The events:** `UNIT_PORTRAIT_UPDATE` and `UNIT_MODEL_CHANGED` fire from the drain (`0x524cd0`), newest mark first, once per unit and for each token naming it (`0x515e50`). Marks come from a body built or redisplayed (`0x607e0f`), equipment, helm and cloak, tabard, enchants and sheath glow, durability (`0x5d97c0`), a name answer (`0x51a10c`) and the party pet's display. A mount marks nothing.
- **A loading portrait** draws nothing (`""` stores a null image, `0x770218`), unless the cache already holds the face: by guid for a player, by display id otherwise (`0x525057`, `0x5250dd`).
- **A shapeshift** keeps its last bake until the drain's event re-binds it.

Each change has a test that failed before it.

Fixes #1411
Fixes #341

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Nine resources moved their change ticks on every update packet without changing, so the feeds that read them re-ran each frame for nothing.

- `Items`, `GuildState`, `AuctionOpen`, `TradeSession`, `LootState`, `DeathNet`, `BagView`, `BagCache` and `HouseLane` are now written only on a real change.
- A creature's health packet leaves all nine untouched.
- A raid update-storm bench times the whole client's frame: 40 units, 120 packets.

This prepares per-packet event state: a listener will refresh only what its packet really changed. The tests check the change ticks over the handlers alone and over a full frame.

Part of #2217

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Three fixes from 1.12.1's frame pass and minimap.

- **The loading screen hides the interface,** so the UI's `OnUpdate` walk and the model panes don't run under it, and the time spent there is dropped (`0x442431`, `0x4424b9`). `GetTime` and queued events go on. Our arrival ends it, aboard a transport or on foot (`0x5debb6`, `0x5debbd`), unless a cinematic is pending.
- **The minimap tooltip** lists every element under the cursor, one line each, in the reference's order: rim arrows, group arrows, icons, dots (`0x4eb58c`-`0x4eb840`). Grey lines replace the colour, as `|c` does (`0x5ccea6`).
- **The hover probe's** occlusion trace reads the same frame's aim as its object pick.

Each fix has a test that failed before it.

Fixes #2350
Fixes #2353
Fixes #2356

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The unit getters read the live object, as 1.12.1 does (`UnitHealth` reads `+0x40`, `0x5174d0`), through a new call-host member, `UnitLive`.

- **What reads live:** 21 getters read their field off the unit's object as of the listener's own packet. These include health and power, level, dead and ghost, tapped, charmed, combat, PvP and sex. Getters that read no object field skip the lookup. With no host lent, the snapshot answers.
- **One shared reader:** a single trait reads both paths, so they can't drift. A test pins 39 getters × 6 tokens equal.
- **A visible fix:** `TargetUnit("party1")` then `UnitHealth("target")` in one chunk now reads the new target's health, where it read 0.
- **Cost:** about 80 ns per call. The raid update storm is within 1% of main. `GuidIndex` hashes with foldhash.

Part of #2217

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Lua getters' record asks go out inside their call, on a miss, as 1.12.1's do. The query reaches the wire in call order, with the reference's owner guid.

- **The getters:** summon confirm, battlefield position, ammo texture and quality, item cooldown, `SetBagItem`/`SetInventoryItem`, trainer, quest item, craft, trade skill and the quest log lookups. Each uses its own callback and dedupe (`0x48b6e1`, `0x4ac118`, `0x4c83bc`, `0x6e2efb`, `0x4d907e`, `0x5017ce`, `0x4f75dd`, `0x4fdb90`, `0x4e05d1`).
- **The owner guid:** the player for ammo, the active player for cooldown, `0xb0000000:spell` (the taught spell for trainers) for craft, trainer and trade skill, the partner for trade, 0 for the quest log.
- **Window show** asks nothing; only the getters do (`0x4f60c0`, `0x4d7d40`).

Byte-exact goldens pin the query bodies, and each getter has an in-call order test.

Part of #2202

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PIN-protected accounts can log in, as in 1.12.1.

- **The challenge:** a non-zero security byte (`0x5bab5a`) carries the grid seed and salt. The logon reports the PIN state, and the stock keypad opens on `PLAYER_ENTER_PIN` with the grid built as `0x5b1e70` does.
- **`PINEntered`** takes 4 to 10 keys (`0x46d1c0`). It builds the proof, `SHA1(client salt ‖ SHA1(server salt ‖ digits))` (`0x5b31e0`), and sends it after the security byte (`0x5bbc80`).
- **A refused PIN** shows the PIN texts 8, 8, then 9 (`0x5b2d00`); a success resets the count.
- **The realm-list re-dial** forgets the PIN after a refusal rather than retrying into the server's lockout. The PIN is never logged.

Byte-exact goldens cover the proof and the grid. A fake realmd checks the proof as vmangos does (`AuthSocket.cpp:1204`).

Fixes #1110

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The login dialog jumped from Connecting straight to Authenticated, because the logon ran as one blocking step and reported nothing in between.

1.12.1's logon object reports its states as it reaches them, and the dialog shows each one's text (`0x836b40`):
- 3 as the challenge goes out (`0x5b2196`);
- 9 on a reply that asks for no PIN, or 7 for one that does (`0x5b1cd7`);
- 2 once the proof is sent (`0x5b1f93`).

The logon now reports each stage through its callback as it goes, and the glue sets the state from it.

Tests: the engine's stage order with and without a PIN, and the dialog's walk on both paths.

Fixes #2362

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Item getters answered from a copy the VM took at the frame's start. So a script reading an item right after a packet changed it saw the old count, link or durability, and an auction name the cache already held answered nil.

1.12.1 reads the item object in the call (`0x4c055d`, `0x4ae68e`), and the auction getter asks the name cache, waiting only on a miss.

The bag, bank, inspect, trade, mail, auction and `GetItemInfo` getters now read the live item and the name cache through the call host. The copy remains only as the fallback. Auction name waits outlive a `/reload` and signal the new VM, since no reload cancels them.

Tests: an agreement test over every bag, the bank, inspect, trade and mail, including a rolled and enchanted item; live counts; a cached name; a wait across a new VM.

Part of #2202
Fixes #2364

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The UI's `OnUpdate` walk ran before the frame's input pass. So an `OnUpdate` reading `IsShiftKeyDown()` or a button's pushed state saw the last frame's input, and a click's `OnMouseDown` ran after that frame's `OnUpdate`.

1.12.1's tick runs the window input (`0x420d23`), then the packets (`0x420d55`), then the frame time, and only then the frame pass (`0x420d6a`), whose interface layer runs the `OnUpdate` walk.

The input pass and its bindings now run ahead of the packet feeds, and those ahead of a new tick set. Clicks hit-test the last frame's rects, as the reference's records do. The `vm_pass` doc now names the walk's category.

Tests run the client's own systems in schedule order: Shift seen in `OnUpdate`, `OnMouseDown` before `OnUpdate`, and a packet event between them.

Fixes #2351

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A long same-map teleport showed the destination's buildings and ground arriving over several frames.

1.12.1's snap (`0x617e90`) reloads the world in place when it moves the followed mover more than 30 yards (`0x630bd0` → `0x694920`). The reads are synchronous and every one is waited for, ahead of the frame pass. So no frame presents until the destination is in,, with no loading screen.

Now, past 30 yards, from the ack or our own `MSG_MOVE_TELEPORT`:
- the window keeps its last frame;
- the pointer the UI reads holds still;
- raw input is delivered in order on the first frame shown.

A repeat teleport keeps the hold, and a worldport ends it. A failed tile read counts as loaded, as the reference's wait-all treats it. A hard ceiling bounds the hold.

Tests: the gate, the repeat, the worldport, the ceiling, the pointer, and input order with focus loss.

Fixes #2363

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An unattended login (`WOW_USER` plus `WOW_PASS`) could not answer a PIN-protected account's keypad, so it waited forever.

Two variables now answer it. They go through the same path as the keypad's `PINEntered`, with the same prompt check and the same state report:
- `WOW_PIN`: the digits, as typed.
- `WOW_PIN_TOTP`: a base32 secret. Its RFC 6238 code (HMAC-SHA1, 30 s step, 6 digits) is the PIN, the scheme vmangos verifies (`AuthSocket.cpp:1274`). The code is derived at each prompt, re-dials included.

Setting both is refused, and neither value is ever logged. The variables are documented under "Running it unattended".

Tests: the RFC 6238 vectors, a leading-zero code, each variable answering the prompt, both set refused, and fresh codes on a mocked clock. A live fixed-PIN login against vmangos passed.

Fixes #2371

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every headless test app built `GamePlugins`, whose `SoundPlugin` opened the default output device unless `$WOW_NOSOUND` or `$WOW_CAPTURE` was set, and no test run sets either. Under nextest that is one CoreAudio client and speaker stream per test process. With a few runs going at once, `coreaudiod` sat near 300% CPU, every sound on the machine crackled, and each test blocked on the audio server's connect.

A test build now takes the existing silent path, so its mixer is `None`, as with no device.

Test: `a_test_client_opens_no_audio_device`. With the change undone it fails on a machine with a speaker, taking 96 s; with it, the test passes in 0.06 s.

Fixes #2380

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The stat and player-field getters answered from a copy pushed at the frame's end. So a `UNIT_STATS`, `PLAYER_XP_UPDATE` or `PLAYER_MONEY` listener read the frame's last packet, not its own.

1.12.1's getters read the object inside the call: for example `UnitStat` at `0x518600`, `GetMoney` at `+0xf70` (`0x518160`), and dodge, block and parry at `+0xe5c`/`+0xe58`/`+0xe60`.

Twenty-five getters now read live through the lent call host, among them `UnitStat`, resistances, armor, damage, attack power, defense, the avoidance chances, money, XP and rest. The snapshot and the live read share one derivation. Worn weapons resolve through the per-packet views. With no player held, the getters answer 0 or nil, as the reference does.

Tests: live against snapshot for every getter across four tokens, gear and buffs included; each listener reading its own packet's value. Cost: `UnitStat('player')` takes 344 ns live, against 261 ns from the snapshot.

Part of #2217

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Dismounting while the mount model was still pending despawned that child, but the same attach pass could still visit it afterwards. Its plain inserts then landed on a despawned entity, and Bevy's default command handler panicked.

The attach pass now records the children it drops and skips them for the rest of the pass, so the outcome no longer depends on visit order. The despawns there became `try_despawn`.

The player build also installs a command error handler. A failed command now logs a warning naming the command and the entity, instead of ending the session. Every other error still panics, and the tests keep the panicking default, so our own bugs still fail there.

Tests: a dropped pending child is skipped whatever the visit order, for both the dismount and the stale display; the handler logs a failed command and still panics on other errors.

Fixes #2377

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
benilla could not decode 1.12.1's intro movies, the loose AVIs in `Interface/Cinematics`.

1.12.1 reads the AVI's `hdrl` and `idx1` (`0x78c360`). Index entries whose id's third letter is `d` are video, and `w` entries are audio. It hands the video to a DivX decoder (`0x78c7a0`), which refuses widths other than 1024 and 800.

This adds `benilla-movie`: that AVI reader, plus an MPEG-4 Part 2 decoder written from the published standard. It covers I and P frames, half-pel motion, AC/DC prediction, H.263 quantization and an IEEE 1180 IDCT. It decodes frames one at a time to YUV or RGBA, and returns the MP3 audio bytes. It has no codec dependency.

Tests: the tables, IDCT accuracy, the AVI reader and garbage input. Against the install, all four movies decode every frame. Compared with ffmpeg, the worst frame is 55 dB luma PSNR, and the intro decodes at about 24× realtime.

Part of #968

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A cinematic shot that moved the camera far faded in as soon as its path started, so its terrain and models popped in on screen.

1.12.1's shot arm (`0x48edf0`, shared by start and advance) reloads the world in place when the camera moves more than 30 yards (`0x48eed3` → `0x66fc80` → `0x694920`). That happens at full black. It sends `CMSG_NEXT_CINEMATIC_CAMERA` only afterwards (`0x48ef11`).

Now a far shot holds its black, with its clock at 0, until the spot is resident or the ceiling is reached. Then it announces, starts its narration and fades in. The gate, the residency release and the ceiling are the teleport hold's, shared. The shot, the teleport and the end's far reload bump one reload mark that re-arms the stream's load.

Tests: far and near shots, the ceiling, a skip, the announce and narration timing, and the re-arm.

Fixes #2370

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On Vulkan, the Windows default, a presentation stall longer than a second crashed the client. wgpu-hal 27.0.4's `acquire_texture` maps a `VK_TIMEOUT` from its post-acquire fence wait to `DeviceError::Unexpected`. wgpu-core treats that as a lost device, and Bevy then panics on "Couldn't get swap chain texture". Upstream tracks this as gfx-rs/wgpu#9029, still open in wgpu 30.

wgpu-hal 27.0.4 is now vendored under `third_party/wgpu-hal` through `[patch.crates-io]`, as `kira` is. Only `src/vulkan/instance.rs` differs. A timeout of the previous submission's wait, of the acquire, or of the acquired image's fence now waits again within a 30 s budget, then fails as upstream does. The Vulkan spec leaves the semaphore and fence unaffected by a timed-out call, so the retry is legal. Each retry logs, with the raw result. `BENILLA.md` says how to check the patch and when to drop it.

Tests: the lock resolves to the patch. Built for Linux and Windows.

Fixes #2373

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After a battleground ended and the player left, the VM kept the match's winner and scores. The board was reset only on a disconnect, so `WorldStateFrame`'s end-of-match panel could come back outside the battleground.

1.12.1's world-enter cascade calls `0x4a9db0` (`0x4909fb`), which zeroes the score caches: the request stamp `0xb6ebc8`, the ended flag and the winner (`0xb6ebcc`, `0xb6ebd0`), and the row and filtered counts (`0xb6ebc0`, `0xb6ebc4`).

Every world enter now clears the board and its request stamp, and the feed pushes the empty board to the VM. As in the reference, no event fires.

Test: after a finished match, a world enter leaves `GetBattlefieldWinner()` nil and zero scores, and the next score request is not throttled.

Fixes #2376

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`GetAuctionItemClasses`, `GetAuctionItemSubClasses` and `GetAuctionInvTypes` answered nothing while addons loaded. A per-frame feed filled them after the files had run, and the same happened after each `/reload`. So an addon that built its filters at file scope indexed nil. Bagshui is one.

1.12.1 reads `ItemClass.dbc` and `ItemSubClass.dbc` inside the call (`0x4cf970`, `0x4cf9c0`), so the names are there at any time.

The VM's prepare now seeds the class, subclass and inventory-type tables beside the world map catalog, for every load seat. The per-frame push is gone.

Test: an addon's file scope sees the ten classes in reference order, the weapon subclasses and the inventory types, on the first load, after `/reload` and after a relog.

Fixes #2374

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`UnitCharacterPoints` answered our own unspent points for every unit. `UnitOnTaxi` answered only for `"player"`, from our own taxi state, so a party or raid frame never saw a member on a flight path.

1.12.1 resolves the token in both:
- `UnitCharacterPoints` (`0x519420`) returns the counters at `+0xe48`/`+0xe4c` for the active player, and 0, 0 for anyone else.
- `UnitOnTaxi` (`0x517a40`) returns 1 when bit 20 of the resolved unit's flags is set (`0x517a86`).

Both now resolve the token. `UnitOnTaxi` reads the unit's flags live through the call host, falling back to the snapshot. `UnitCharacterPoints` raises its reference usage text for a bad argument.

Tests: points for the player and for another unit, the usage text, a party member on and off a taxi, and live against snapshot.

Fixes #2381
Fixes #2382

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`UnitBuff`, `UnitDebuff` and the tooltip's `SetUnitBuff`/`SetUnitDebuff` answered from a list pushed per frame. So a chunk that changed its target and then read `UnitDebuff("target")` saw the old target's auras. A listener whose later packet charmed us also saw a stale gate.

1.12.1 reads the unit object inside the call (`0x519500`, `0x5198f0`). That covers the aura slots, the flags nibble, applications and level; the gate is unit flags bit 27, `CanAssist` and our charmer (`0x5195f1`). A member out of view answers from the roster record, read by guid at the call.

These getters now read live through the call host, through one derivation shared with the snapshot. `GetPlayerBuff*` stays as it is: it reads client state rebuilt per packet.

Tests: live against snapshot across seven tokens with stacked, Magic, Curse and hidden auras, and the three cases that fail on main. Cost: about 50 ns per call.

Part of #2217

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The opening movie never played. `StartMovie` returned nil, so the stock `MovieFrame` hid at once.

1.12.1's movie frame (`0x78ce40`) reads the AVI, decodes, and streams the MP3. Its clock is the audio's play time, or the wall clock without audio. It shows clock × rate frames, decoding skipped ones unseen (`0x78cf30`). It reads `.sbt` captions (`0x78cb00`), and draws 2 or 6 tiles with the picture 24 or 6 rows up (`0x81d400`). On first boot the `movie` CVar flips to "0" and opens the movie screen (`0x402b3b`).

benilla now does all of that on the decoder crate, along with `StopMovie`, `EnableSubtitles`, `OnMovieFinished` (including on teardown), the captions' show and hide handlers, and screenshots waiting while a movie plays. Unattended runs skip the movie.

Tests cover each of these. Live, the logo and the intro played through to login.

Fixes #968

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Unit tokens resolved through a target map pushed per frame. So a listener of an earlier packet resolved `targettarget` as the frame ended. A pet's events fired under the frame's final pet tokens. A held unit that no token covered answered "Unknown Being".

1.12.1 resolves a token inside the call (`0x515970`): player, pet (our charm, else summon), every `target` hop from the held unit's `UNIT_FIELD_TARGET` (`0x515a1c`), and party and raid pets from the member's descriptor. `SignalUnitEvent` lists the tokens when it signals (`0x515e50`, `0x515c50`). `UnitName` names any held unit (`0x609210`).

These now read live through the call host, through one derivation shared with the snapshot. The target, mouseover, npc and roster globals stay client state. The event fan-out uses the same derivation.

Tests: 29 token forms agree live against snapshot, plus the three cases that fail on main.

Part of #2217

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The frame applied its packets before its input. An `OnClick` reading a unit saw what this frame's packets wrote, and a world click on a unit despawned in the same frame missed it.

1.12.1's tick runs every window input record (`0x423920` at `0x420d23`) before the packets (`0x420d55`). The mouse turn (`0x514400`), right-click interact and cast (`0x514b70` → `0x481f60`) and the press handoff run inside the record. Frame-time work, like the controller's movement and the leash (`0x4818c0`), comes after the packets.

The UI input pass, its bindings, the world mouse and the look sampling, and the world clicks now run ahead of the net drain. Then a new resume point, the feeds, the `OnUpdate` tick and the controller's step run after it.

Tests: a click reads the unit before its frame's packet, a click selects a unit its frame's packet removes, and the pointer edge.

Fixes #2372

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`IsUsableAction` answered from a list pushed per frame. So a `UNIT_MANA` or `ACTIONBAR_UPDATE_USABLE` listener read the frame's last power, and after drinking the last potion, the slot's change listener still saw it usable.

1.12.1 answers `IsUsableAction` from a usable cache (`0x4e5230`). The slot write `0x4e58e0` fills it, and eight field watchers refresh it inside each packet (`0x4b31c0` → `0x4e5c00`). `IsCurrentAction`'s form arm reads the live form byte (`0x4e558b`), and `IsEquippedAction` walks the worn slots (`0x622270`).

benilla now keeps that cache, refreshed at the same eight watchers and stored by the recount. The form and equipped reads go live through the call host, and items resolve through the per-packet views.

Tests: 14 getters agree live against snapshot across six slots, plus the usable, form, equipped and last-potion cases that fail on main. A refresh costs about 1% of a raid storm frame.

Part of #2217

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The WMO portal audit's camera-pin variables (`WOW_PIN_EYE`, `WOW_PIN_MAP`, `WOW_PIN_SITE` and nine more) shared the `WOW_PIN_` prefix with the unattended login's `WOW_PIN` and `WOW_PIN_TOTP`. The map listed an instrument's aim next to the login secret.

The audit's variables are now `WOW_WMO_PIN_*`, in the audit's two files. The login variables keep their documented names. Nothing else set the old ones.

Test: the crate's lints and the audit build; the instrument tests are run by hand, as before.

Fixes #2400

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An unattended login on a PIN account answered the keypad from `WOW_PIN` by calling `PINEntered`'s tail directly, which skipped the stock Lua. The stock `VirtualKeypadOkayButton_OnClick` (GlueXML `AccountLogin.lua`) calls `PINEntered` and then hides `VirtualKeypadFrame`, so the keypad stayed on the login screen.

The environment's PIN now presses the stock keypad: `Click` on the button for each digit's position (button `p + 1` carries id `p`), then on Okay. The stock handlers run as they do for a mouse press, including the hide. A login with no keypad on screen still submits directly.

Test: `wow_pin_presses_the_stock_keypad_and_it_hides` runs the real GlueXML with `WOW_PIN=4821`. It checks the positions reach the logon, the keypad is hidden and its PIN cleared. It fails before the fix.

Fixes #2405

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

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
In 1.12.1 a movement key press runs the controller's tick inside the press, before the frame's packets. `SetCommands` (`0x515090`) sets the bit, sends any cast cancel, then runs the tick at its tail (`0x5151a0` → `0x514640`), which reads root and stun at that moment. benilla ran its movement after the packet drain, so a root arriving in the same frame reached the wire before the start.

A new press pass in the input stage now ends follow and click-to-move (`0x60e990`), stands the player up, sends the cast cancel, then emits the start or stop. `control` keeps the frame-time integration.

Tests pin each wire order (start before a same-frame root ack, stand then start, cancel then start, the follow edges); each fails with its piece undone. A live trace showed the press ahead of the drain in all 80 presses.

Fixes #2399

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In 1.12.1 the cooldown and cast handlers write state, then fire their events, at each packet's own place. `SPELL_COOLDOWN` (`0x6e9460`) adds its records and then fires the cooldown pair. The cast result's pop (`0x6e741a` → `0x6e4940`) runs before `SPELLCAST_STOP`. The getters (`0x6e2ea0`, `0x6e2ed0`) read the live list. benilla raised cooldown events once a frame, from counter edges, and its cast getters read a frame-end view.

Now the player's cooldown list logs each write and signal per packet, and a lane replays them in order, with the call host lent. The four cooldown getters read the list live. Cast views are logged per packet, and a listener's own cast stays visible through later packets' views.

The tests cover two cooldown packets in one frame, cast start and stop in one frame, an early-stopped drain, a worn item's cooldown and a failed cast. Each fails without the change.

Part of #2217

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The reference snaps a string's anchor with `y = floor(H · y)` in y-up device pixels (`0x5cdf70`). In screen rows that is a ceiling. benilla rounded the block top to the nearest row on the logical grid, and rounded the ascender in logical pixels. So a row with a fraction in (0, 0.5] landed one row high, and the error moved with the display scale. The login screen's "Account Name" sat 1 px above 1.12.1's.

UI text now seats with the device-pixel ceiling, with the ascender from the font's pixel size and the seat nudge read as device pixels. World text is unchanged.

Test: `the_login_text_sits_on_the_reference_rows` checks the login labels, the version line and the copyright at the D3D shot's rows. It fails before the fix. Captures at 1× and 2× match the reference rows on every login-screen label.

Fixes #2407

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

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Every capture run panicked at startup. The fixture seed `seed_ui_fixture` took `ResMut<TrainerOpen>` beside the VM turn, whose call host has lent the trainer window since #2331, and Bevy refuses a system that borrows one resource mutably twice.

The seed now reaches the trainer state through the turn's parts. A sweep found no other capture system that overlaps the host.

Test: `every_capture_system_initializes_in_the_client` builds the client with the capture plugin and initialises every schedule's systems. It panicked with the access conflict before the fix. The existing schedule census left the capture plugin out, which is why the gates stayed green.

Fixes #2413

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

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A door lit its interact cursor out to 5.5556 yd, where 1.12.1 stops at 5.0. benilla used the NPC-service reach for every game object type.

In 1.12.1, `0x5f3130` tests `(dx² + dz²) + dy² <= r²` against the object's position. `r` is the type handler's `+0xc`: 5.0 by default (`0x5f2f67`), 5.5555553 for types 2, 9, 24, 26 and 27, 10 for type 4, 100 for chairs and fishing nodes, and 0 for type 12. When a known spell opens the lock, its maximum range is used instead (`0x5f3328`). Without one, state 2 refuses with error 0xe5. A chair then seats strictly within 3 yd.

Now the cursor, the click and the walk all use those ranges. The walk stops at 0.9 × the handler range.

Tests cover the door at 5.2 and 5.0 yd, a spell opener, a destroyed object, the chair and click-to-move; each fails without its piece.

Fixes #2410

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

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
In 1.12.1 the pet's cooldown list (list 1) flushes at each packet's own place. `PET_BAR_UPDATE_COOLDOWN` fires from five sites: `0x6e2e8e`, `0x6e3080`, `0x6e8601`, `0x6e95b9` and `0x6e971c`. The pet getters query that list live (`GetPetActionCooldown` at `0x4be03c`, plus the pet book and bar at `0x4b40dd` and `0x4e6cf7`). benilla flushed the pet's list once a frame and read it from the frame's final state.

Now the pet's list logs and replays like the player's (#2414), on its own lane in packet order, and the three getters read it live. A failed `CAST_RESULT` fires its GCD-clear pair (`0x6e167d`) before the pop (`0x6e2218`), so the cast slot and the targeting word change only at the pop.

The tests cover two pet packets in one frame, a player packet then a pet packet, and the GCD-clear pair seeing the cast still held. Each fails without its piece.

Part of #2217

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In 1.12.1 the press tick runs the pitch arm after turn (`0x514777`), so a swim pitch key starts inside its press, before the frame's packets. benilla latched the pitch keys in the frame-time pass, after the packet drain. A pitch press in a frame whose packet rooted the mover sent no pitch start at all.

`press_tick` now runs the pitch emitter after the turn arm, and its start or stop goes out after the turn's message. `stop_pitch_keys` and the mouse-look hand-off are unchanged. The latch facts already match: the stop is sent only while swimming (`0x5147e9`), and a stun clears the latch either way (`0x5147fa`).

Test: a swimming pitch press in a rooting frame sends the pitch start, the stop, then the root ack. It fails with the arm in frame time.

Fixes #2412

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#2311 added a second `let moving` in `control` for the pitch keys' integration, true through any arc. It shadowed the direction-key `moving` that the mover step, the mounted Space flourish and the body heading read. So a jump taken standing counted as moving with a zero direction: its first airborne frame spent the one air nudge with a zero vector. A direction key pressed later in the arc then steered nothing, where in 1.12.1 the first direction key pressed mid-arc gets the nudge (`0x7c6afc`).

The pitch integration's binding is now `pitch_integrating`, and the other three readers get the direction-key `moving` again.

Test: `a_standing_jump_keeps_its_air_nudge_for_a_key_pressed_mid_arc` jumps standing, waits five frames, then presses forward and sees the nudge. It fails without the fix.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
A BLP UI texture was made at any size. In 1.12.1, `0x448450` refuses a side that is not a power of two, or is 1024 or more (`0x4484ae`–`0x4484d0`), and `GxTexCreate` refuses a side below 8 (`0x58ab43`). After a refusal, the BLP callback leaves the solid 8×8 green (`0x44a500`, colour from `0x447c00`) and tries no next candidate.

A new `makes_device_texture` holds that gate, and the TGA header check now uses it too. A UI texture load (`TextureCreate`) whose decoded BLP fails the gate becomes the solid green. A TGA refusal still moves to the next candidate or fills white, as before. World, model, portrait and cursor loads are unchanged.

Test: `a_blp_texture_size_the_device_path_refuses_is_the_solid_green` covers sides 7, 8, 16, 512 and 1024, plus 3×4 and 64×32. It fails at 7×7 without the gate.

Fixes #2416

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

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
On a started or bid-placed result, 1.12.1 shows its line first (`0x4cc4c7`, `0x4cc507`). Only then does it remove the outbid pair (`0x4cdf30`), arm the one-shot flag (`0x4cc4da`, `0x4cc522`) and send the list request (`0x4cd680`, `0x4cd720`). benilla changed all three in the handler's body and showed the line later, so the line's `CHAT_MSG_SYSTEM` listeners saw the state after the request.

Each line now carries the house's request state (flags, offsets, outbid list) as it stood when the line was shown. Its listeners run against that copy. Their own changes carry forward, and later lines in the drain see them. The handlers still run whole, so the wire order is unchanged.

Tests: a listener on a bid-placed line and one on a started line each request at the pre-result offset, and a second result in the same frame sees the first's changes. Both fail without the change.

Fixes #2215

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On one Windows machine the client panicked while creating its window: winit's `OleInitialize` for drag and drop returned `RPC_E_CHANGED_MODE`, because the main thread had joined the multithreaded apartment first. Nothing in benilla joins it there. Our COM work runs on the audio thread, gilrs is off, and `rfd` only runs on the no-install error path. That leaves the system hook in `CreateWindowExW` that the reporter's trace shows. Bevy 0.18 builds the window attributes itself and offers no way to turn drag and drop off.

The main thread now joins a single-threaded apartment at launch, before any window. That is the apartment winit needs, so `OleInitialize` succeeds, and a later MTA join on that thread fails harmlessly instead. The audio thread keeps its own MTA.

Built for Windows and Linux with the cross-compile check. Nothing here can run it on Windows.

Fixes #2422

🤖 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>
Groundwork for firing each packet's events at its place (#2168). That needs every getter a listener can call to read live state through the call host, and about twenty families still have to get there in parallel.

**The seam.** The host gains one generic hook, `read_live`. A getter asks `live::ask(lua, &query)` and falls back to its snapshot when no host is lent. Every live read sits in one set, `LiveReads` (`script_calls/live.rs`). A family adds its query and its answer in its own files, plus one registration line. The cooldown and worn-item reads move onto it.

**The guard.** `lanes/host_read_tests.rs` caps `READS_FRAME_FINAL` (93 feeds) and lists the 23 systems that may write a live-read input between the dispatch and the lane drain. The flip needs both empty.

No event moves. Test: a getter reads a value written earlier in the same turn.

Part of #2168

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1.12.1 dispatches each window input record whole and in arrival order (`DrainWindowInput 0x423920` → `IEvtQueueDispatch 0x4245b0`). The record's handler sets the modifiers first (`0x423d06`), and a button record carries its own cursor (`0x42dd00`). Each record runs CSimpleTop's handlers down to WorldFrame's bindings (`0x483c40`, `0x483b70`). benilla read each frame's button and key state, so a key and a click in one frame ran in a fixed order, and a press landed wherever the cursor ended the frame.

Now the UI pass walks ordered records from the window's event stream, each with its own modifiers and cursor. The world mouse and the button binding agree on who owns each press.

Tests cover key-then-click order, a click where the cursor was, and a Shift chord in one frame. The probes now type and click through the window's events, and live runs typed chat and the login account.

Part of #1988

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The guild family, read at each packet's place, for #2168. Every guild getter now reads live through the call host seam, with the reference's nil cases (`0x4d1190`, `0x4d1200`, `0x4d14a0`, `0x4d1890`, `0x4d1e60`, `0x4d1e90`, `0x4d1fe0`, `0x516de0`, `0x4c9330`). Guild events go on a "guild" lane at their handler, and `feed_guild` is gone.

On the way, it matches the bytes: the stale roster signal fires for every guild event but 12, 13 and a failed 10 (`0x5e74dc`). GE_MOTD no longer stores the MOTD (only `0x4d0b6d` writes it), and leaving no longer clears the roster. One roster update fires per missed lookup. Rank names and the MOTD go through the masker (`0x4a1a60`). Two roster updates the reference never raises are dropped.

Tests: two roster packets in one frame each read their own roster, and each event arm and write is pinned. They fail on main.

Part of #2217

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

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Two profanity-mask sites the reference has and benilla lacked.

- **Guild notes:** the note accessors `0x4d09b0` and `0x4d09c0` call `MaskGuildMemberNotes` (`0x4d09d0`), which masks both notes with the cached form (`dl = 1`) once and clears the member's mark. The note sort, `GetGuildRosterInfo` and the note setters' compare all read through it. benilla now masks both notes once per roster list, so the sort, the getters and the setters see the masked text.
- **Letter text:** `ShowItemTextPage` masks a single item text, a letter body, with `dl = 0` (`0x4e366b`, `0x4e3672`) after its `$` expansion. Its page-chain arm never masks, so a book's pages stay as written.

Tests: a guild member's notes are masked once by their first read, a letter's text is masked with the filter on, and a book's page is not. The first two fail without the change.

Fixes #2431
Fixes #2432

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

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The party and raid family, read at each packet's place, for #2168. The group getters read live through the call host seam: count, leader, loot method, the `GetRaidRosterInfo` family, the `UnitIn*` tests and the `partyN`/`raidN`/pet token rows (`0x4e86d0`, `0x4bb560`, `0x4bb8c0`, `0x4e9130`, `0x4bb910`). The group tokens are rebuilt over the roster as of each packet. `READY_CHECK`, its answers and `UPDATE_INSTANCE_INFO` go on the party lane at their handlers.

On the way, it matches the bytes. The party slots are compact: a zero GUID leaves no gap (`0x4e82eb`), and our own entry is skipped (`0x5e6b88`). `UnitPlayerOrPetInParty`/`Raid` follow `0x4e8fa0` and `0x4bafd0`, matching members and their pets, never the player.

Tests: two group lists in one frame each read their own roster; each event reads the cells its reference store left; the zero-GUID and pet cases. They fail on main.

Part of #2217

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

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The reputation family, read at each packet's place, for #2168. The faction getters and the slot reads of the three faction verbs ask the call host live (`0x4d65ac`, `0x4d65ea`, `0x4d6613`, `0x4d6660`). A reputation lane applies each message's list edit at its stamp, and `feed_reputation` is gone.

The producers follow the handlers' order. A standing message stores the standing (`0x4d634a`) before its "increased by" line, so `CHAT_MSG_COMBAT_FACTION_CHANGE` listeners read the new standing. Then come the adder (`0x4d5826`), the flag write, the rank line (`0x4d5896`) and the sort. Flag writes replay as set/clear masks, so a listener's `SetFactionInactive` survives a later at-war packet. A reload seats the list and standings at the head of the world-enter cascade.

The tests cover per-packet standings, at-war, visibility, watched, `UnitReaction`, the chat line, the verb bit and the reload. They fail on main.

Part of #2217

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

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The name cache family, read live through the call host seam, for #2168. A new `names/live.rs` answers two queries:
- the player-name read (`0x55f080`), which replaces `CallHost::player_name`, for the auction seller and the drunk watcher (`0x609210`);
- the creature-record read, for `UnitCreatureType` (`0x605570`), `UnitClassification` (`0x605620`), `UnitLevel`'s hidden -1 and `UnitIsCivilian` (`0x612550`).

`UnitPVPName` (`0x5172b0`) takes its name and civilian leg live. Each read falls back to its snapshot with no host lent. The name events already sat on their lanes at their handlers.

On the way, `UnitPVPName` for a unit with no cached name returns the `UNKNOWNOBJECT` string, as `0x609210` does on a miss (`0x609324`), where benilla returned nil.

Tests: the type word, the rank and the world boss's -1, the cached name, the civilian flag and the unknown name, each read live. They fail on main.

Part of #2217

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

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Buff timers read live through the call host seam, for #2168. `GetPlayerBuffTimeLeft` (`0x4e4930`) and the buff tooltip's time line (`0x532be3`) read the expiry stamps (`0xbc5f68`, written by `0x4e4390`, read by `0x4e4450`). They answer the expiry minus now, 0 once lapsed or unstamped, in seconds. A new `PlayerBuffExpiry` query answers from the world's `AuraDurations`, and the feed no longer joins durations into the pushed list. With no host, the pushed expiry stands.

A buff with no catalog row keeps its flag unresolved, as `0x4e44b0` skips the write on a miss. The call resolves it from the expiry, so a server-sent timer still shows.

Tests: stamps written in the world read through a lent host, with no push between, and so does the tooltip's time line. They fail on main.

Part of #2217

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

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
During the in-game UI load, `GetScreenResolutions()` and `GetMultisampleFormats()` answered nothing. Their lists were filled only by per-frame systems that first run after the load, so an addon reading them at file scope, like Turtle WoW's options panel, got empty menus. In 1.12.1 the resolution list is built on the first call to any of the resolution verbs, from the display's modes (`0x48bc90`, guarded by `0xb4b3d4`). The multisample list is built the same way on the first multisample verb (`0x48c3e0`, guarded by `0xb4b440`).

Now the resolution and multisample verbs read both lists live through the call host, which is lent during the load. They fall back to the model with no host, and the per-frame feeds still keep the model current.

Test: an addon reads both lists at file scope during the world-entry load in the headless client, and gets the full lists. It fails on main.

Fixes #2439

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

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
At a 1.5 display scale, outlined text drew two dilation passes where 1.12.1 draws one, so letter edges came out thinner and partly covered. The outline's pass count was `r × round(dpi)`, and round(1.5) is 2. 1.12.1 rings an outlined glyph one texel per pass: NORMAL dilates once and THICK twice (`0x5cea30`, `0x5ce440`).

Now the count is `r × floor(dpi)`. A fractional scale gets the reference's one texel a pass, and a whole-number high-DPI raster keeps its logical ring weight. The recorded deviation now says floor, with its reason.

Test: at dpi 1.5 a NORMAL cell grows one texel on each side, both plain and mono. It fails with `round`.

Fixes #2408

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The group verbs' gates read the frame-final party snapshot. The raid rows were derived from the party slots, so the two could never disagree. In 1.12.1 a group list seats the party slots, zeroing the leader first (`0x4e84a0`), then fires `PARTY_MEMBERS_CHANGED` (`0x5e6c66`). Only after that does it store the leader (`0x5e6c7e`) and the loot method, and rebuild the raid rows (`0x4ba5f0`).

Now the gates read the live group through the call host: membership, loot method, convert to raid, ready check, the by-name verbs, and the chat and addon-message group checks. The raid rows are their own state, written at the rebuild. Loot placement, the map blips, battlefield counts, the meeting stone and targeting now read those rows.

Tests: a gated verb decides on the world's group at the call, and the raid rows stay as stored until the rebuild. Both fail on main.

Fixes #2437
Fixes #2436

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reloading or reconnecting while dead and unreleased shows the DEATH popup, which 1.12.1 does not. Every UI start in the reference zeroes the release timer (`0x490131`), so `GetReleaseTimeRemaining` answers 0 (`0x4962a0`). A `/reload` also signals no `PLAYER_DEAD` (`0x490168`), so `UIParent.lua` shows no popup and the player is left with no Release button.

benilla keeps the popup on purpose. This change writes that down: two `Deviation:` lines name the reference facts and the reason. It also corrects two comments that said the reference keeps the timer across a reload. Behaviour is unchanged.

Fixes #2300

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Key bindings, macros, the window layout and the chat setup stayed on the machine and never synced with the server. 1.12.1 keeps eight sections both on disk and on the server, in step by digest. At each world entry the server's digests (`SMSG_ACCOUNT_DATA_MD5`) meet each section's own and the cached one (`0x5afa10`). A section changed here goes up 30 seconds after the interface shutdown that wrote it (`0x5aff40`), and one changed there is asked for and stored.

Now benilla does the same, with the four wire bodies byte-exact. Two recorded deviations keep data the reference would lose: no truncated upload (`0x5afb42`), and no write on a bad download (`0x5afcbc`). `SaveBindings` now writes the set it stored, not the live table at drain.

Tests: wire goldens, the section check and transfer, and the timers. A live round trip against the local server came back byte-equal.

Fixes #1337
Fixes #2368

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The quest log's getters read copies pushed into the VM at frame end, and its events fired from a frame-end feed. A listener therefore saw the log as the frame's last packet left it, not as the reference's slot reads (`0x4dfa41`, `0x4e01df`) find it at the call.

Now the slots, held counts (`0x622130`), standing (`0x4d6370`), coinage and the timers' clock read live through the call host. Template landings and refusals, toasts with `QUEST_WATCH_UPDATE`, rank moves and time syncs fire on a quest events lane at their handlers' stamps. The LFG quest list reads the live slots too. The frame-end clock feed and most of the log feed are deleted.

Tests: five full-client and five VM tests cover the live reads and the event order at the packets, including a refused template. They fail with the live reads off or the events stamped late.

Part of #2217

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The honor line's rank title took the active player's gender from `PLAYER_BYTES_3` byte 0, while every other gendered text read the real gender. 1.12.1's `CGPlayer_C::GetGender` (`0x5ed5b0`) does read that byte whole for the active player. But the byte also carries the drunk bits (`gender | drunk & 0xFFFE` on vmangos), so a heavily drunk female reads as male.

Now the honor line reads the real gender (`UNIT_FIELD_BYTES_0` byte 2) like the rest. One `Deviation:` line on `unit_gender` names the reference's read and why benilla differs. The byte-0 getter is gone.

Test: a female with byte 0 full of drunk bits gets the female rank title. It fails on main.

Fixes #2103

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With `widescreen` at 0, the in-world interface kept the window's aspect. In 1.12.1 the game's aspect answers exactly 4/3 then (`0x4901e0`). It is stored at `0x48fe1e` for the screen basis `0x41ad10`, re-read on a resize (`0x492f13`) and for the automatic UI scale (`0x492fb4`). The interface lays out 4:3 and is drawn stretched across the window.

Now a `UiScreen` built from the window and the CVar carries the interface's size, its exact aspect and its stretch. Every window-to-UI conversion goes through it: input records, hover, the UI pass, tooltips, the minimap, the world map, nameplates, bubbles, combat text, the cursor and the probe click. With `widescreen` on the stretch is 1, so nothing changes.

Tests: a click round trip at 1920×1080, the cells' aspect at exactly 4/3, and the automatic scale at 700 and 766 tall. All fail on main.

Fixes #1577

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Two party tests read the wire's whole member list, which holds every raid subgroup and is not emptied when the party ends. In 1.12.1 both read the four party slots at `0xbc6f48`. The selection colour asks `CGPartyInfo::IsMember` (`0x4e7f70`): us, or a guid in one of the slots. The meeting stone tests slot 1 (`0x5f6a05`), then counts the slots (`0x4e86d0`, `0x5f6a45`).

Now the target ring and nameplates use the relations' party test, and the meeting stone gates on the party slots. A raid member of another subgroup no longer shows the party colour. A raid of four or more gets NO_RAID_GROUP rather than GROUP_FULL, and a player whose party ended can join.

Test: the stone gate with a raid whose other members sit in another subgroup, and with an ended party. Both fail on main.

Fixes #2450
Fixes #2451

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The `target` and `npc` tokens read guids pushed into the VM at frame end. A target whose object the server released was torn down by a frame-end system, after later packets' listeners. In 1.12.1 the tokens read the target and npc globals at the call (`0x515a86`, `0x515c00`). The release (`CGUnit_C::Release 0x5fbb60`) ends a target in place (`0x5fbbe5`), except a group member's, whose guid stays (`0x5fbbcd`).

Now both tokens read live through the call host, and the npc is the raw interaction guid. For a game object, `UnitName("npc")` names it (`0x5f8100`) and `UnitExists` is nil. The teardown runs inside the release on a new lane. A reconnect clears the old selection.

Tests: a live target and npc, a game-object quest giver's name, the release firing `PLAYER_TARGET_CHANGED` between two packets' events, and a reconnect. All fail on main.

Part of #2217

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The skill lines, talents and spellbook answered from VM copies pushed at frame end, and their events fired from frame-end feeds. A listener therefore saw them as the frame's last packet left them.

Now their getters read live through the call host: skills through `0x5ea3f0`, talents through `0x4f3200` with character points at `+0xe48`, the book through `0x4b3fe0`. `SPELLS_CHANGED` and `LEARNED_SPELL_IN_TAB` fire at the learn, unlearn and supersede handlers, after `UPDATE_SHAPESHIFT_FORMS` (`0x4b2904`), and also at arrival and level-up (`0x5edb7b`). `CHARACTER_POINTS_CHANGED` fires from its field watch (`0x4c7098`), its one source; the page-turn source was spurious. A talent counts a known higher rank (`0x60c8d0`). The respec question drops with no trainer in range (`0x5df9be`).

Tests: eight full-client tests cover the live reads and each event's place. Five fail on main.

Part of #2217

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The battlefield's status, list, scoreboard and positions answered from VM copies pushed at frame end, and their events fired from frame-end feeds. In 1.12.1 the getters read the battlefield state at the call (`0x4ab4a0`–`0x4ab820`, `0x4ab9d0`, `0x4abf90`). The status handler fires `UPDATE_BATTLEFIELD_SCORE` before `UPDATE_BATTLEFIELD_STATUS` (`0x4aaa5a`, `0x4aab05`).

Now every getter reads live through the call host, and the events fire on a battlefield lane at their handlers. The board sorts with the reference comparator through the CRT qsort (`0x4aa350`, `0x73f727`). A row with no name record counts as team 0, and a world entry clears the stat columns (`0x4a9e67`).

Tests: live getters against a written world, a frame of mixed packets firing in order, name callbacks at the name packet, and a 40-row sort. All fail on main.

Part of #2217

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The friends, ignore and who lists answered from VM copies pushed at frame end, and their events fired from a frame-end feed. In 1.12.1 the getters read the lists at the call (`GetFriendInfo 0x5ad060`, `GetIgnoreName 0x5ad460`, `GetWhoInfo 0x5ad6e0`). The handlers raise `FRIENDLIST_UPDATE`, `IGNORELIST_UPDATE` and the who answer in place, which goes to the frame or to chat by the flag at that moment (`0x5adf9c`).

Now the getters read live through the call host, and the events fire on a social lane at their handlers. Each item seats the lists as its packet left them, so a listener's `SetSelectedFriend(1)` or `RemoveFriend(1)` acts on that packet's list.

Tests: getters read lists the handlers wrote with no frame between, the events interleave in packet order, a listener's verbs act on its signal's list, and the who route follows the flag at its stamp. All fail on main.

Part of #2217

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The stance bar answered from a VM copy pushed at frame end, and the mirror timers' events fired from a frame-end feed. In 1.12.1 the stance getters read the form list at the call: `GetShapeshiftFormInfo` (`0x4b45c0`) gives four nils past the list or with no player, and castable comes from the usability walk (`0x6e3d60`). The mirror handler (`0x5e7990`) signals in place, then the tutorial. Releasing the active player stops all three timers (`0x5e9b90`).

Now the stance getters, `CastShapeshiftForm` and `GameTooltip:SetShapeshift` read live through the call host. The mirror events fire on the notice lane at their handler, and the player's release sends `MIRROR_TIMER_STOP` for each type. Both frame-end feeds are deleted.

Tests: live list, active, castable, cooldown and hover; four nils with no player; mirror events in packet order; the release's stops; `SetShapeshift(0)` past the list. All fail on main.

Part of #2217

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Inspect and honor answered from VM copies pushed at frame end. In 1.12.1 another player's slot getters read its visible-item block at the call (`0x4c8328` → `0x5f0d60`). The PvP getters read the active player's honor block (`0x51a4b0`–`0x51aa90`). The inspect reply handler stores a reply only for the target (`0x4c6e58`), then signals `INSPECT_HONOR_UPDATE` (`0x4c6f56`).

Now these getters read live through the call host, and the reply's event fires on the notice lane at its packet. The inspect honor slot is built as the reference's: target, has-data and pending, with one ask per new target. The recorded deviation that clears it on a re-target stays, with its reason restated: a server that never answers would leave the last player's figures shown.

Tests: an inspected link at the call, the honor event between two packets' events, the tooltip's maker line, and a re-target clearing the slot. All fail on main.

Part of #2217

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The merchant and the flight map answered from VM copies pushed at frame end, and their events fired from frame-end feeds. In 1.12.1 the merchant getters read the vendor's rows at the call (`0x4fb130`, `0x4fb580`), and a close leaves no rows (`0x4fae22`). The flight map's slots are built inside `SMSG_SHOWTAXINODES` (`0x4dc380`), with each fare read at the call.

Now both read live through the call host. `MERCHANT_SHOW`, `MERCHANT_UPDATE`, the buy and sell lines, `TAXIMAP_OPENED` with its tutorial, and the taxi replies fire on lanes at their handlers. A vendor row's item query names the vendor and a buy-back row's names 0 (`0x4faf05`, `0x4fb3f0`). A new flight path closes the gossip (`0x5e3e68`). Buy-back names carry their suffix (`0x5d8bc0`).

Tests: live rows, fares and repair costs; each event's place; byte-exact query goldens. All fail on main.

Part of #2217

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The dialog getters read frame-final copies, and their events fired from frame-final feeds after the drain, so a listener saw them out of packet order.

Now CheckBinderDist (`0x5dff50`), CheckPetUntrainerDist (`0x5dfd30`), the boot, spirit healer, meeting stone, summon and instance getters read live through the call host. Their events fire on a dialogs lane stamped at each handler. The duel, binder, dialog verb, meeting stone, summon and instance feeds are gone.

Fixes on the way:
- The binder and pet unlearn questions pass the range gate first (`0x5dfe3c`, `0x5dfc2d`), the innkeeper click included.
- The boot timer's delay is signed (`0x496340`).
- The duel checks the player's own challenge before ignore, and its timer runs before packets (`0x420d0c`).
- A deleted ticket signals UPDATE_TICKET (`0x5e461d`).

Tests in `lanes/dialog_live_tests.rs` fail on main. READS_FRAME_FINAL 75 → 67.

Part of #2217

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The trainer, stable, tabard and charter getters read frame-final copies, and their events fired from feeds after the drain.

Now they read live through the call host, matching their reference reads: the trainer's rows (`0x4d9096`, `0x4d9d07`, `0x4d97c8`), the stable's records (`0xb72058`) and display (`0x4cb99e`), and the charter's five getters (`0x4f43d0`…`0x4f5230`). Each family's events fire on its own lane at its handlers. TRAINER_UPDATE fires at each `0x4d7d40` caller: twice for a superseded rank (`0x5e63a4`, `0x5e64f1`), and between the skill line and the profession rebuild (`0x5de42d`). A close in the same frame no longer swallows a queued SHOW. That also fixes the merchant, whose CLOSED could fire before its SHOW. A /reload keeps the trainer's rows.

The four feeds are gone. Each family's tests fail on main. READS_FRAME_FINAL 67 → 63.

Part of #2217

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`benilla-blp`'s `parse_header` refused any BLP shorter than the 256-entry palette window, whatever its compression. So small DXT or raw textures failed to load, such as AtlasLoot's 516-byte `gold.blp` highlight.

1.12.1's parse (`0x5a83b0`) copies the 1172-byte header with no length check (`0x5a83da`). It fails only on the magic and the type, and only the paletted path reads the palette.

Now the palette window is required for compression 1 only. Test: `dxt_without_a_palette_decodes`, a 4x4 DXT5 with no palette.

From chairmanpow's benilla-octo `660cf760`, carried on Thorsten42/benilla@78f7362.

Fixes #2464

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The pet bar, menu, stats and book getters read frame-final copies. A reaction or Stay/Follow press raised PET_BAR_UPDATE from the VM's copy, even when the press was refused.

Now the pet's getters read live through the call host: PetHasActionBar (`0x4bdc20`), GetPetActionsUsable (`0x4bcf70`), GetPetActionInfo (`0x4bdc50`), HasPetUI (`0x4be670`), PetCanBeAbandoned/Renamed (`0x4be500`/`0x4be580`), HasPetSpells (`0x4b4410`) and the rest. A press writes the state word and signals inside the call, after the gate (`0x4bd1f2`) and before the send (`0x4bd444`). A stunned pet's press stays silent. The pet-teach "already known" line and CreateMacro/EditMacro read the pet's book live. `GameTooltip:SetPetAction(0)` answers nil (`0x532812`).

The bar, menu and book feeds are gone. Tests in `lanes/pet_live_tests.rs` fail on main. READS_FRAME_FINAL 63 → 60.

Part of #2217

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Walking into some stairs stalled the player until they jumped, such as the Stormwind auction house's street door. The movement code classified the floor by parry's contact normal. At a stair nosing that normal blends the faces meeting there (ny 0.38), so the tread read as steep.

1.12.1 classifies by the hit face record's own normal against cos 50° (`0x80dffc`). It does so in the step probe (`0x636592`), the ground classify (`0x636ecf`) and the step trigger (`0x6334e6`). The casts now return the hit triangle's face normal (`collision::BodyHit`), and all four sites read it. A mover with no direction or fall bit runs no resolver (`0x634100`), so a body stopped on a nosing stands there and sends no FallLand.

Tests on the real Stormwind WMO cover the climb, short taps, and a stop at every frame of it.

Fixes #755

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The item requirement state, lock openers and set blocks were read from frame-final copies pushed by feeds, so a listener saw the frame's last level, skills and bags, not its own packet's.

Now the requirement checks (`0x5eaae0`, `0x5ea930`), the known-spell test (`0x60c740`), the lock openers (`0x5da5e0`) and the set blocks (`0x52d8a0`) read the player and bags live, as of each packet. With no player held, the getters answer the reference's fixed values: GetQuestGreenRange 0 (`0x4e17f8`), GetComboPoints 0 (`0x51a261`), and the item tooltip hidden (`0x52b699`). The tooltip build asks for its creator, charter and set-member names inside the build (`0x52e204`, `0x52b93b`, `0x52df4b`). The merchant's buyback and repair reads are as of the packet too.

Four feeds are gone, including feed_player_req. Tests in `lanes/requirement_live_tests.rs` fail on main. READS_FRAME_FINAL 60 → 56.

Part of #2217

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The bank, gossip, purse and repair-cost getters read frame-final copies, and their events fired from feeds after the drain.

Now they read live as of each packet:
- the purse, as GetMoney does (`0x518160`)
- the repair costs per slot (`0x4faf30`)
- GetNumBankSlots and GetBankSlotCost, from the player's own field with no banker test (`0x4f85b0`, `0x4f8630`)
- gossip's slots and text (`0x4e28d0`, `0x4e28c0`)

A bank lane carries OPENED (`0x4f8522`) and the refusal lines. PLAYERBANKBAGSLOTS_CHANGED fires at its field (`0x4f8460`). A gossip lane pushes SHOW at the draw (`0x4e22b5`), and asks the NPC's name there. A gossip draw with no player or no NPC held writes nothing and fires nothing (`0x4e20bb`), so a late greeting no longer reopens a closed window.

Four feeds are gone. Tests in `bank_live_tests.rs` and `gossip_live_tests.rs` fail on main. READS_FRAME_FINAL 56 → 52.

Part of #2217

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Four cursor and button defects, each with a test that fails on main:

- RegisterForClicks stops at the first non-string (`0x78268f`).
- The pickup verbs (`0x4f9b87`, `0x4c7345`, `0x4f9fdb`) and every cursor setter gate on player control as the reference does (the item setter also allows a taxi flight). While feared, a player can no longer drag, split or buy into bags.
- Both destroy paths read the item record's flags at the call. Bit 0x20 refuses with ERR_ITEM_CANT_BE_DESTROYED, and an uncached record raises no popup (`0x495370`, `0x4953ac`).
- The ammo slot takes a drop. Projectiles load by entry, other items auto-equip, and an empty hand lifts the loaded ammo and sends SET_AMMO 0 (`0x4c73a6`–`0x4c7646`).

Also, the merchant toggle-off matches a held preview by entry (`0x4fb811`), and previews play the item display's grab and put-down sounds (`0x495154`, `0x49526a`).

Fixes #2468
Fixes #2469
Fixes #2470
Fixes #2471

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The quest giver's getters read the panel's cells live, as 1.12.1 reads its globals (`0x501a20`-`0x501a70`), and its events fire on a lane at their packets instead of from a frame-final copy:
- each of the six texts has its own buffer; only a greeting clears them (`0x500c3b`)
- titles beside levels, nil past the pool (`0x501b05`)
- item info, link and counts read off the item cache at the call; IsQuestCompletable also checks the held counts (`0x501230`)
- QUEST_ITEM_UPDATE fires at the item answer (`0x5018c0`); a GameObject objective's name comes from its template
- a world entry and a /reload reset the cells (`0x500af0`)
- QuestLogPushQuest reads the first party slot at the call (`0x4e13a6`)

`feed_quest` is gone; the frame-final ceilings drop to 51 rows and 268 inputs. Tests cover each getter, the reset and the lane order.

Fixes #2474
Fixes #2498
Fixes #2505
Fixes #2506
Fixes #2518
Fixes #2526

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Lua verbs that raised benilla's own text, or nothing, now raise the client's usage line or message, in the client's gate order (each address read in the commits):
- chat, channel and emote verbs take their argument through the usage gate, and send "" and padded names as given
- `SetChatWindowName` stores an empty or padded name as given, and "" for a non-string
- `GuildInviteByName` shows ERR_COMMAND_NEEDS_TARGET for nil or ""; `SetGuildInfoText` raises its usage line
- `NotifyInspect`: usage, then the player, then the token
- GM ticket verbs gate type, text, then the player; -1 goes out as u32::MAX
- `PickupPetAction` truncates through `_ftol`, so 4294967297 is slot 1

Each fix has a test that fails without it.

Fixes #2500
Fixes #2503
Fixes #2504
Fixes #2512
Fixes #2517
Fixes #2520
Fixes #2521
Fixes #2522
Fixes #2531

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Lua arguments are read the way the client reads them:
- numeric strings go through the VM's own number coercion instead of Rust's parse, truncating through `_ftol` where the bytes do (SetGuildRecruitmentMode `0x4a008f`, SetWorldDetail `0x488e0a`, the compare-item and model verbs)
- `debugstack` takes numeric-string levels, ports the level window's wrapping arithmetic, prints `(tail call): ?` below level 0, and joins leftover arguments top-down (`0x703760`)
- SendAddonMessage returns first with no player object (`0x49f94b`)
- the four sell-cursor verbs gate on targeting and repair mode before their arguments (`0x4fbab7`, `0x4fbac9`)
- SetPortraitTexture checks the token after blanking; 13 unit predicates take any value, and three more check the token

Each fix has a test that fails without it.

Fixes #2501
Fixes #2502
Fixes #2527
Fixes #2529
Fixes #2530
Fixes #2532

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comments that cited the wrong client address, or stated a fact the bytes contradict. Each one was re-read at the bytes and corrected. Comments only: no code changes.

Citations: the MCLQ cell read is `0x68b21f`, not the WMO group reader. The portrait bake has one caller, `0x713540`. Camera mode 0 keeps facing the target. The survey id call is `0x474064`, the macro setter `0x494f80`, and the empty party list jump `0x49e08e`. The clock constructor sets six fields to -1 and one to 0. A statusbar reads a numeric string as its number.

Deviation comments: each now gives the reference fact its ruling rests on, with every ruling kept. Examples: SavedVariables are kept as .bak (`0x51f794`), only flag-0x10 emitters sort (`0x7b3a1b`), the buff expiry cache clears at game entry (`0x4e40c0`), and SetTexture keeps the previous image (`0x7702ac`).

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Logging in or reloading dead and unreleased left the body on the ground with a Release button. 1.12.1 releases it instead. The world-enter cascade's last test reads health (`0x490a2d`-`0x490a38`) and sends CMSG_REPOP_REQUEST (`0x490a3c` → `0x5e0ae0`). A /reload runs the same cascade (`0x490168`). The server makes the player a ghost at the graveyard (vmangos `MiscHandler.cpp:48-67`).

Changes:
- benilla sends the repop at the end of every cascade with health ≤ 0, and drops both deviations.
- UI_Init zeroes the release timer (`0x490131`), so a reload reads 0.
- The arrival fires PLAYER_DEAD or PLAYER_ALIVE once, after the cascade (`0x5deb65` → `0x495850`). A reload fires neither.

Tests cover the send order, a dead and a live body, the reload's zero timer and its silence.

Fixes #2620

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GameTooltip setters accepted links, slots and indices that 1.12.1 refuses, and showed what it would not. Each one now checks and answers as its handler does.

- SetHyperlink takes only `item:` or `enchant:` (case-folded, `0x5320b1`), checks for an already-held link before the id, and raises `Unknown link type` on anything else.
- SetAction clears first. An Attack slot shows one white ATTACK line (`0x5323ad`).
- The trade skill, craft and trainer setters raise their lines on a bad index; a craft header clears and hides.
- Merchant and trade subtract 1.0 before truncating (`0x534159`).
- Any region can own the tooltip. A Font owner raises, written as a `Deviation:` (the client's `0x5313c9` is undefined there).
- Every setter checks `this` before its index.

Each fix has a test that fails without it.

Fixes #2544 Fixes #2545 Fixes #2546 Fixes #2547 Fixes #2548 Fixes #2549

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Widget methods called with a wrong argument returned quietly or did the wrong thing. In 1.12.1 they raise the client's usage line, or clear and read back, as the bytes show. This batch covers EditBox, FontString, Button, Slider, StatusBar, ScrollFrame, Model, PlayerModel and the Frame verbs.

Every method gates and reads back as its handler does at the address its commit cites. Examples: a missing frame name prints `(null)` (`0x6f5bc6`), and colours read back through f32 (`0x79ab3a`). Slider:GetMinMaxValues answers min plus the stored span.

Each fix has a test that fails without it. The tests that asserted the old behaviour now assert the reference.

Fixes #2533 Fixes #2534 Fixes #2535 Fixes #2537 Fixes #2538 Fixes #2539 Fixes #2540 Fixes #2541 Fixes #2550 Fixes #2551 Fixes #2552 Fixes #2554

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Lua 5.0 behaviours of the client that an addon can observe, which benilla-lua's 5.1 base got differently:
- string.sub and string.find keep a position's low 32 bits (`0x7fbd55`).
- An escaped byte at or above 0x80 matches nothing (`0x7fcbc6`).
- `error()` checks its argument (`0x702993`) and adds the position at any level but 0 (`0x7029a8`).
- `__unm` gets the operand and nil (`0x6f8edc`).
- `__index` chains reach 102 tables and `__newindex` 101.
- math.deg multiplies by 180/pi (`0x7fb14d`).

The bare aliases run as their own `compat.lua` chunk (`0x703b21`), each on the client's line. Its closures keep a local `math`, so their error lines match and replacing the global `math` leaves them working.

`seterrorhandler` keeps the top value, and nil clears it (`0x702930`).

Each fix has a test.

Fixes #2632 Fixes #2633 Fixes #2634 Fixes #2635 Fixes #2636 Fixes #2637 Fixes #2638 Fixes #2639 Fixes #2640 Fixes #2654

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Lua getters that answered what 1.12.1 never does now read what their handlers read:
- **Bind area.** GetBindLocation and the `$z` spell token fall back to HOME_INN (`0x507f3a`).
- **Craft.** GetCraftName answers the trade-skill slot by craft type (`0x4f601c`).
- **Trainer.** The skill line and requirement come from the service's spell (`0x4d9261`, `0x4d94d6`).
- **Map.** GetPlayerMapPosition takes any unit token; GetMapInfo answers its sizes.
- **Mail.** GetInboxItem gates by quality and misses uncached items (`0x4af721`).
- **Pets.** Stabled pets pack in list order, the summoned one by its byte's bit 1 (`0x4cabe3`). UnitCreatureFamily reads any unit's record.
- **Gates.** HasFullControl and UseAction gate as the client's do; ItemTextGetItem answers nil with no text open.

Each fix has a test.

Fixes #2475 Fixes #2497 Fixes #2499 Fixes #2508 Fixes #2509 Fixes #2510 Fixes #2511 Fixes #2513 Fixes #2514 Fixes #2515 Fixes #2516 Fixes #2519 Fixes #2523 Fixes #2524 Fixes #2525 Fixes #2528

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The next piece of the event core (#2168): six of the unit feeds that pushed frame-end copies into the VM. Their getters now read the world live through the call host:
- the relation verbs, UnitIsPVP and the tooltip gates (`0x5167e0`-`0x516c50`);
- the reach behind CheckInteractDistance, CanInspect and the slot getters, with each position computed at the call;
- the item-use control exemption;
- the languages (`0x5ec720`, `0x5ec890`).

PLAYER_FARSIGHT_FOCUS_CHANGED fires from its field watch (`0x5dda45`). LANGUAGE_LIST_CHANGED fires at the learn handlers (`0x4b2641`) and once per language spell at arrival. The client interleaves those with the form events in wire order, the same gap #2217 records for forms.

GetNumLaguages returns nothing with no player (`0x49fb5e`).

The frame-final feeds go from 51 to 45. Each change has a test that fails without it.

Fixes #2664

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
What differed from 1.12.1:
- **Channel notices.** Moderator and voice changes print their notices (`0x49e8a0`).
- **Copy.** Copying from an edit box strips link, colour and `||` codes (`0x5c2590`, mask 0x500).
- **Links.** A `|H` token ends at the first `|`, which must be `|h` with a byte after it.
- **SimpleHTML.** A resize rebuilds the links (`0x78a320`).
- **Secure add-ons.** Their `.url` file is read at registration.
- **Font size.** A FontString refuses any scaled size of 0 or below, 1 or above, or NaN (`0x5c1afe`).
- **Message frames.** Justify lives on the frame's font, CENTER by default (`0x783a87`).
- **Script names.** Each widget type takes only its own scripts: SetScript and GetScript raise `doesn't have a "%s" script`, and HasScript answers nil.

Each fix has a test.

Fixes #2483 Fixes #2488 Fixes #2489 Fixes #2490 Fixes #2491 Fixes #2494 Fixes #2543 Fixes #2553

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Frame layout mechanics that differed from 1.12.1:
- **Drag raise.** A dragged toplevel window rises once it overlaps another. A pass after the update recomputes each toplevel's overlap bit (`0x7657d0`). A rect change during a move raises unforced (`0x764a4c`).
- **EnableMouse.** Re-enabling a visible frame enters it behind its equals in the mouse list (`0x764b2d`).
- **Anchors.** SetPoint and SetAllPoints refuse a target that depends on the receiver (`0x768010`) and check the relative point last.
- **Scale.** An effective scale of 0 is refused before it is stored (`0x768366`), so the layout stands. GetEffectiveScale answers the stored value.
- **Draw place.** A region takes the last place in its layer each time it registers: Show after Hide, a new layer, or a frame show (`0x77fcb0`).

A scan of stock FrameXML finds nothing that visibly flips. Each fix has a test.

Fixes #2657 Fixes #2658 Fixes #2659 Fixes #2661 Fixes #2662 Fixes #2668

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A script error reached the `seterrorhandler` function once per frame, from a queue, after every other listener had run. In 1.12.1 the handler is the error function of the pcall each script runner makes (`0x704e4f`-`0x704e79`; the error function `0x703b40`). So it runs at the raise, inside the failing script: `this`, `event` and `arg1` are still the failing handler's, `debugstack()` shows the failing call, and the event's next listener has not run yet.

Every benilla runner now passes that error function: handlers, OnLoad, file chunks, RunScript, key bindings and slash commands. It returns the handler's result, as `0x703b40` does. The queue is gone.

A handler body that fails to compile reaches no handler (`0x704c70`). benilla's host log keeps each original fault, and `/errors` replays the ones raised before benilla's layer loaded.

Tests cover the raise-time globals, the stack, the listener order, OnUpdate and chunks.

Fixes #2656

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Lua numerals and the sscanf ports used Rust's correctly rounded parse. 1.12.1's C runtime rounds its own way: `__RoundMan` rounds up only when the round bit and some bit two past it are set (`0x40fb43`), so ties truncate and `6e23` reads one ulp low. It flushes results below 2^-1022 to +0 (`0x741ed3`) and keeps 24 digits (`0x41157f`). benilla-re holds these at proof two.

One port of that conversion now lives in benilla-lua (`strtod.rs`, `ld12.rs`). It serves `str2d` and `%f`, which rounds once to float (`0x40feef`) and consumes a digitless exponent marker (`0x7451a5`). `%d` wraps in 32 bits (`0x7456c9`). The time zone is read once per process (`0x414568`), and two client faults are written as Deviations.

A table of 3487 cases generated from benilla-re's transcription, and the exe's fixed cases, pin it.

Fixes #2647 Fixes #2648 Fixes #2649 Fixes #2650 Fixes #2651 Fixes #2652

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
What differed from 1.12.1:
- **Ticket results** show their lines: create and update codes 1, 3 and 5, a fetch status of 0 and a failed delete (`0x5e4432`, `0x5e44ff`). Update text is cut at 1999 bytes (`0x5efacd`).
- **UPDATE_TICKET** clamps a negative age to 0 and passes the client's NaN and denormal bits for the no-ticket answer. Arguments the client fills from stack residue are a `Deviation:`.
- **Ticket status** 2 sends the no-ticket answer; status 3 fires GMSURVEY_DISPLAY (`0x5e78f0`).
- **Casting** reads the cursor first: a held spell goes through pickup, and anything else is cleared (`0x4b3310`).
- **Hover:** Disable fires OnLeave first, Enable fires OnEnter after (`0x779160`).
- **Gossip:** leaving the world resets the gossip lists (`0x4e1f60`).
- **Nameplates:** a name flashes red for 5 seconds on damage (`0x7cba77`).

Each fix has a test.

Fixes #2462 Fixes #2472 Fixes #2476 Fixes #2478 Fixes #2481 Fixes #2484 Fixes #2536

🤖 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>
The next piece of the event core (#2168): two more unit feeds that pushed frame-end copies into the VM are gone.

- The held-unit chain and the by-name candidates are now read live through the call host.
- The by-name searches and the emote lookup take the player's and each unit's position at the call, so a unit that moved this frame is measured where it stands.
- The object, name and record getters ask a thin held-unit query instead of building a full unit state. With the host lent, `UnitHealth("target")` drops from 3.26 µs to 1.20 µs.
- A guid that is not held as a unit is no longer a name candidate.

The frame-final feeds go from 45 to 43. `ui_unit::feed_units` stays for the next piece. Each change has a test that fails without it.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Seven issues from the UI core pass.

- Sorted particle emitters, with file flag 0x2 (`0x70fd1d`, `0x7b5d00`), now draw back to front. They use the reference's view-depth heap, so ties pop in its order (`0x7b3a5b`). Every other emitter keeps list order. (#2676)
- A party member leaving view snapshots the committed zone, `0xb4e314`, and their position at the call, as `0x5f0880` does. (#2677)
- GetDefaultLanguage answers nothing during a login's load (`0x49fcf0`). (#2665)
- GetGamma, the video getters and flat SavedVariables numbers read the CVar record's float (`0x63e135`). (#2673, #2672)
- The engine's counted and gendered text calls the Lua GetText and GetPluralIndex (`0x703bf0`, `0x703cf0`). Part of #2666.
- The pet cooldown test no longer depends on wall-clock time. (#2670)

Each change has a test that fails without it.

Fixes #2676
Fixes #2677
Fixes #2665
Fixes #2673
Fixes #2672
Fixes #2670

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The XML loader and the SavedVariables writer follow 1.12.1.

- **Broken handlers.** One that fails to compile replaces the template's, reports nothing and keeps input on (`0x704c9f`).
- **Script files.** A `<Script file>` with a body runs both (`0x6ee0d4`).
- **FrameXML.log.** Load faults reach it in the client's wording. A failing script file logs at severity 2 (`0x704ae0`), and inline bodies log nothing.
- **Template levels.** Flags are read one inherited level at a time, and handlers come before a Slider's or StatusBar's values.
- **Fonts, gradients, names.** Unnamed fonts are logged, heights of a screen or more are refused, gradients take their last colours, and empty names stay unnamed.
- **Size changes.** A NaN size change fires OnSizeChanged.
- **SavedVariables.** They write bool and fractional keys, and the client's comments for skipped entries.

Fixes #2555
Fixes #2556
Fixes #2560
Fixes #2561
Fixes #2565
Fixes #2566
Fixes #2570
Fixes #2572
Fixes #2678
Fixes #2683
Fixes #2684

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The first two pieces of the event-core move (#2168, #2217).

- The lane guard now states what firing each packet's events inside the dispatch needs. Its old in-between list splits into the obstacles and the rows that are right after the packets in 1.12.1. Frame time runs after the packets (`0x420d63` after `0x420d55`), but a unit's arrival snap is written inside the packet (`0x7c6420`). A new list holds every placed-queue reader that pushes a lane, and `flip_readiness` prints what is left.
- The login's cache clears run inside the login handler, before the world's first packet, as `DBCacheClearUnsaved` does at `0x48f586`. Before, a name asked by a later packet in the login frame was asked twice.

Each list's test fails on a new or stale row, and the login test fails on main.

Part of #2168
Part of #2217

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Thirteen fixes to the cursor, world arrival, loot and chat.

- Losing control (`0x495901`) or a world port drops the cursor and the cast, inside the packet.
- Every arrival clears the target (`0x490927`), just before the world-entry events.
- A resolved roll frees its loot row, and a group coin loot prints its share line.
- A boss whisper becomes a whisper, and channel chat carries its instance number.
- The delete confirm names an uncached item.
- Bag books send CMSG_READ_ITEM and handle both replies.
- The spellbook pickup and drop sounds play.
- The quest log's gold guide arrow shows, cleared first on each select (`0x4def39`).
- A cleared loot row records its `Deviation:`.

Each change has a test that fails without it.

Fixes #2573
Fixes #2576
Fixes #2446
Fixes #2578
Fixes #2584
Fixes #2577
Fixes #2558
Fixes #2564
Fixes #2562
Fixes #2563
Fixes #2627
Fixes #2679
Fixes #2680

🤖 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 builds a frame one inherited level at a time, template first (`0x769820`). Each level runs:
- its geometry (`0x767800`);
- its flags;
- one child loop in document order;
- its type's own part.

An XML anchor to a frame not yet built is logged, "Couldn't find relative frame", and dropped. benilla instead merged the levels and retried missed anchors later. That hid order bugs, such as Button labels built before their state textures.

- The loader now builds level by level, every frame type's part included, and the retry queue is gone.
- `<Frames>` and `<ScrollChild>` take their first element only.
- Each TitleRegion and ButtonText makes a fresh object.
- A tooltip finds its lines after OnLoad, and Minimap models read the instance's own node.

All 4,140 anchors in FrameXML and the 12 Blizzard addons resolve in this order. A test holds that.

Fixes #2574

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fifteen UI API fixes.

- **Text and names.** FontString text, buttons, tooltips, names and bubbles apply the `|1`/`|2` grammar escapes (`0x7716f0`). Rank names count code points.
- **Raid.** The raid roster selection stores the member's guid, survives a /reload, and clears at world login.
- **World states.** Each world-state packet fires WORLD_MAP_UPDATE then UPDATE_WORLD_STATES at its place.
- **Events.** DISPLAY_SIZE_CHANGED fires below scale 0.64. UPDATE_CHAT_COLOR carries the caller's name.
- **CVars.** Four refuse what 1.12.1 refuses, and anisotropic and ErrorLevel print its console lines.
- **Calls.** GuildSetLeaderByName("target") sends no name. Repair costs read by row position. Quest reward slots count to the first empty one. OfferPetition checks its eight guards.
- **Glue and errors.** The glue VM has the file calls. Widget methods share one wrong-receiver text.


Fixes #2507
Fixes #2557
Fixes #2559
Fixes #2567
Fixes #2568
Fixes #2581
Fixes #2588
Fixes #2593
Fixes #2599
Fixes #2606
Fixes #2615
Fixes #2623
Fixes #2630
Fixes #2687
Fixes #2688

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The next piece of the event-core move (#2168, #2217). The update handler's field callbacks now run inside the packet's dispatch, as the reference's notify pass does.

- A new end-of-update handler runs each changed field's callbacks in the order the pass wrote them, inside the dispatch:
  - the aura, stack and loyalty lines;
  - the death;
  - the form release and service closes;
  - the pet rename.
- The form release comes right after its own block's signals (`0x5de010`, then `0x5de07f`), so a window closes between UPDATE_BONUS_ACTIONBAR and the next packet's events.
- The stack line reads the live descriptor at notify (`0x62b822`).
- An XP award hides a death line only when it arrived in an earlier packet (`0x62c232`).

Placed-queue readers outside the dispatch go from 5 to 0. A two-packet test fails on main's order.

Part of #2168
Part of #2217

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ten fixes, each with a test that fails without it.

- Status lines are cut at 255 bytes (`0x41a049`), banners too (`0x419f19`), and an empty line is dropped without raising the severity (`0x41a05f`).
- A message frame's `bytes=N` holds N−1 bytes. A negative N empties its lines, since `0x771692` tests only zero.
- The reward panel after a progress panel plays the completion chime (`0xbe081c`, `0x500d36`).
- A panel opens only for a held unit, game object or item (`0x500c16`-`0x500c35`).
- Readable pages are rewritten into their language for the reader (`0x49b560`).
- A page of 1024 bytes or more reads empty, as `0x4191b0` does at capacity 0x400.
- World entry wipes only the four unsaved caches (`0x555740`)., keeping saved asks.
- Two comments cite the right addresses.

Fixes #2580
Fixes #2690
Fixes #2692
Fixes #2694
Fixes #2696
Fixes #2700
Fixes #2701
Fixes #2710
Fixes #2711
Fixes #2712

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

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Every write to the player's selection now signals where 1.12.1 does, inside its packet or call, not from a per-frame diff.

- The attacked and cast-at acquires select inside their packets (`0x6259fe`, `0x6e799c`). SetSelection keeps its order: the usable refresh (`0x493892`), then `PLAYER_TARGET_CHANGED` (`0x49389c`).
- Deselects (`0x493910`) run at their packet's end, on their lane, or inside the call. `CloseLoot` deselects a dead source after the loot session ends (`0x48f33d`, `0x48f369`).
- The server's swing stop and the refusal latch apply inside their packet (`0x625ab8`).
- The control flag is stored in its handler, then LOST or GAINED, then the refresh (`0x4958e0`).
- The feeds' own raises are gone, and a world-less clear fires nothing (`0x4939df`).

Each change has a test that fails without it. flip_readiness falls to 98 rows.

Refs #2168
Refs #2217

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Three fixes, each with a test that fails without it.

- ShowHelm and ShowCloak send their toggle only while the live PLAYER_FLAGS bit says the opposite (`0x5ee6c4`, `0x5ee783`), and ShowingHelm/ShowingCloak read that bit (`0x48dd8b`). On the native display the call re-dresses the model at once. A display rebuild, a head or back visible-item change, or an item record landing dresses by the flag again (`0x604990`, `0x5e2541`, `0x5ed880`). The mannequin clones what the model shows (`0x476cb0`).
- Account-data readers register through `0x5afe50`. Each section is read when it settles, and its saves are skipped while it is pending. Bindings read set 1 and set 2 apart (`0x4b6240`, `0x4b64b0`), and macros merge by id (`0x4f0600`).
- The login burst is held until the entry UI has loaded, Pong excepted (`0x537b56`), as 1.12.1 loads before it logs in (`0x46c272`).

Fixes #2602
Fixes #2448
Fixes #2715

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`civil` reads the local zone once per process, as the CRT's `localtime` does (`0x414568`). Its test reread was `#[cfg(test)]`, so benilla-app's tests set `TZ` against a zone some earlier test had already cached. The screenshot stamp test then failed in a full `cargo test` run.

- `ZoneGuard` and `reread_zone` are now open to other crates' tests through benilla-ui's `test-support` feature, which only dev-dependencies enable. The guard re-reads the zone at once on set and on drop.
- The screenshot and chat-log tests hold their zone through it.
- A new test cycles three zones in one process, and fails with the reread stubbed out.

The player build carries none of it.

Fixes #2713

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Mouse moves and wheel turns are input records, run in arrival order among the keys and clicks, as 1.12.1 queues them. Kinds 0xa and 0xb dispatch 0xc and 0x10 (`0x424031`, `0x42417e`).

- Each move runs the hover walk (`0x7660d0`), so a frame crossed and left within one frame gets OnEnter and OnLeave.
- A wheel turn sweeps at the saved cursor (`0x766524`), and the world frame's binding runs inside it (`0x483d20`). One message is one step, from its sign alone (`0x76c1a9`). A trackpad's pixel carry is kept as a `Deviation:`, at most one step per event.
- During a long teleport's frozen frame the window stream waits. Released records take the live cursor, as each takes `GetCursorPos` when handled (`0x42dd00`).
- The pick order is reused until a frame changes: 2 µs a walk, 0.034 ms a frame at 1000 Hz polling.

Each change has a test that fails without it.

Fixes #2430

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Our player's create now does its own writes inside the update packet, at the end of its create step (`0x5dec7b`), as 1.12.1's PostInit does:

- SelfPlayer is tagged in the create's own commands, so a later packet in the same frame finds us tagged and in control.
- The control flag and its two signals (`0x5deb56`), gated on `+0xc8` (`0x5deb49`).
- `CMSG_SET_ACTIVE_MOVER`, sent only when the mover changes (`0x6006ee`-`0x6006fb`). The mover clears at world entry, at the unit's destructor, at a release and at a control stop.
- The cascade message (`0x5deb60`), then the class family (`0x5debcc`).

The movement lock moves to its own resource. Only the arrival writes it, so a `/reload` while feared or possessing leaves it alone, since 1.12.1's reload runs only `0x4908c0`.

Each change has a test that fails without it. flip_readiness falls to 96 rows.

Refs #2168
Refs #2217

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Transports' props animate and cull with their ship, as in 1.12.1.

- A carried prop's world sphere comes from its host's pose every frame. The tick re-poses a transport's doodad defs before the cull (`0x6709a0` → `0x698f50` → `0x6a2070` → `0x6a81d0`), so a ship's parts stay drawn wherever it is.
- A transport's props play the ship's phase (`0x5f52f9` → `0x6709f0` → `0x6a24b0` → `0x6a20f0`), with window ends chained through `0x5f49b0`. A carried play cross-fades over its sequence's blend time (`0x6a2128`). The zeppelin's engine `$DSL` keys sit in its moving sequences (0xa2, 0xa3), so its engine is heard now. Before, its props looped Stand.
- The transport and lift loaders guard the reference's own faults as `Deviation:` lines: unwritten leg lengths, an unbounded append, a zero divide (`0x5f62a4`), an endless seek.
- A whole-period test of the Booty Bay–Ratchet path.

Each change has a test that fails without it.

Fixes #1210
Fixes #1221
Refs #1220

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The census that finds Lua verbs whose work outruns their signals now sweeps the whole suite. It also counts state a lend writes while a handler is held, which a late listener would read and no send shows.

- Over 12,964 tests, one verb sends ahead of its signals: `UseAction`, V2's.
- An accepted item swap is in order: its send (`0x5e0fdb`) comes before the cursor clear (`0x5e0fe7`).
- One write after a held signal: a target clear's strike-cancel report waits behind the action bar's usable refresh (`0x493a08`, `0x493a18`, `0x493a69`, `0x493a73`). It is now listed, with its test.

All of it is test-only; the player build is unchanged.

Refs #2271
Refs #2202

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

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Bag and item reads now answer as of the listener's own packet, not the frame's end, and the logout reads what 1.12.1 has there.

- The bag getters read the player's bags, the bank and the keyring live. Item and container writes go into the view log, so a listener sees its packet's slots, stacks and bags.
- The loot close's two unlocks fire at its packet (`0x48f2bd`, `0x4c1d36`). A wrong-bag refusal names the bag its packet found (`0x5e3a1e` → `0x5ede00`).
- At logout the object manager goes before PLAYER_LOGOUT fires (`0x46771e`, `0x490c2a`). The inventory getters then answer nothing with no active player (`0x4944a0` → `0x4c8c56`). `UnitName("player")` keeps the character's name.
- The item tooltip feed follows record changes by cursor. The suffix template is copied after the frame's Lua.

Each change has a test that fails without it. flip_readiness falls to 92 rows.

Fixes #2725
Refs #2168
Refs #2217

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The world click was resolved in the look session, frames after the mouse command that made it. In 1.12.1, `CameraOrSelectOrMoveStart` and `TurnOrActionStart` arm it inside their call: `ArmClick 0x514810` picks under the press (`PickAtCursor 0x481190`), and the last primary's release dispatches it (`0x514b70` → `0x481f60`). The VM now keeps the command bits, and the click applies at the record whose binding made it.

Changes:
- The world frame's right-press hook `0x492c20` now runs whole, ahead of the binding (`0x483c62`, `0x483c93`): it cancels targeting, clears a pending wrap, and ends repair mode. It does not run on a press `0x492b50` eats.
- The loading cover, a bindingless right release, and a reload's cascade (`0x49093c` → `0x5144c0`) free the bits.
- The release counts its own frame's travel.

Tests cover each. The click's legs still run after the walk, not inside the call: part of #1988.

Fixes #2729

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Aura and pet state reached the interface from per-frame feeds, so a listener at one packet read the frame's final state. In 1.12.1 each is written where its packet or call makes it.

- **Pet GCD.** A pet cast's global cooldown is armed in the pet bar at the send (`0x6e58fb`), inside the cast.
- **Happiness.** `UNIT_HAPPINESS` fires from the field watch (`0x515e7a`) inside its packet.
- **Unit auras.** The aura getters read the unit's store live.
- **Dispel list.** It is kept from the learn (`0x4b26a2`-`0x4b26ee`), so an unlearn leaves it.
- **World entry.** The cascade rebuilds the buff cache at `0x490983`, so `PLAYER_AURAS_CHANGED` follows `PLAYER_ENTERING_WORLD` and comes ahead of the bag updates.

Each fix has a test that fails without it. Part of #2168 and #2217.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`scripts/smoke.sh` and `scripts/summon-live.sh` run under `set -o pipefail` and checked their log markers as `printf '%s' "$log" | grep -q marker`. `grep -q` exits at its first match, so on a long log `printf` died of SIGPIPE, the pipeline returned 141, and a clean run failed: "no roster came on the same link after the logout", with the line right there in the log. Replaying the check on one 73 KB run log failed 15 times in 50.

Each check now reads a here-string (`grep -q marker <<<"$log"`), which no early exit can break. The same replay passes 200 of 200, and a missing marker still fails the check.

Fixes #2733

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The trade window reached the interface through a per-frame feed, so its events fired ahead of the frame's other packets and its getters read a VM copy. In 1.12.1 the trade status handler (`0x4bfa08`'s cases) signals inside the packet, and the getters read the session at the call.

- The getters read the session through the host. Every trade signal goes on a trade lane, stamped in its handler.
- A request's checks run inside BEGIN_TRADE, with the ignore list ahead of the object lookup (`0x4bf750`).
- Our slots 1-6 raise no TRADE_PLAYER_ITEM_CHANGED from the status (`0x4bf2b0`).
- A close waits behind the open it closes, cancel included (`0x4bf527`, `0x493365`).
- Status 12 prints its line before the close (`0x4bf88e`).
- A chat-kind line raised in a Lua call fires inside it (`0x496842` → `0x49a870`).

Each change has a test that fails without it. Part of #2168 and #2217.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A window's close that came in the same frame as its open still did part of its work ahead of the open's queued event. The tabard close ended the preview at the handler (`0x4f58ce` → `0x5e0830`). The open's handler then started it again, and the body kept wearing the preview tabard with no designer open. The taxi close held behind nothing, so `TAXIMAP_CLOSED` (`0x4dc15f`) fired before `TAXIMAP_OPENED` and the map stayed open with no flight master.

Each close core now closes its session at once, as the trade window's does. Its interface half queues on the window's lane behind the open it follows: the preview's end and `CLOSE_TABARD_FRAME` for the tabard, and `TAXIMAP_CLOSED` for the taxi. Each has a one-frame open-then-close test that fails on main.

Fixes #2730
Fixes #2731

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
`OffhandHasWeapon` answered from the VM's kept inventory snapshot with no player check. So a `PLAYER_LOGOUT` handler saw the last session's off hand. In 1.12.1 it looks up the active player first (`0x4c9578`, `0x4c9589` → `0x468460`) and pushes nil when there is none (`je 0x4c95c4` at `0x4c9590`). At logout there is none, because the object manager is already gone (`0x467700`).

It now takes the same active-player gate the inventory getters gained in #2728. A `PLAYER_LOGOUT` listener reading it with a sword in the off hand gets nil; on main it got 1.

Fixes #2727

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The mail window reached the interface through a per-frame feed. Its events fired ahead of the frame's other packets, and its getters read a pushed copy. In 1.12.1 the mail handlers signal inside their packet, and the getters read the session at the call.

- The getters read the session through the host (`MailLive`). Every mail event goes on a mail lane, stamped in the handler that raises it.
- Each answered record lookup signals one `MAIL_INBOX_UPDATE`, only when the record is found (`0x4af080`, `0x4af0a0`, `0x4af340`, `0x4af5b0`, `0x4ad010`).
- The pending step runs after the frame's packets and after the walk-away check (`0x4818c0`, `0x4818d0`).
- A close in its open's frame no longer fires `MAIL_CLOSED` without `MAIL_SHOW`, or sends its query ahead of earlier events.

Each change has a test that fails without it. Part of #2168 and #2217.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A path ends with the unit turned to the packet's final facing, as 1.12.1 leaves it.

- A monster move that installs a path stores its final facing on the spline (`0x618840`-`0x6188a0`). Only a move with no path turns the unit on arrival.
- At the path's end, `0x61a882` writes the facing in this order:
  - Angle, as sent;
  - Target, the heading to where that unit stands (`0x61a8c1`), from the deck-local end on a transport;
  - Spot, through `0x47f220`.
- The turn runs through the facing smoother (`0x600cd0`), never a snap. The display facing resets only at create and reactivate.
- `heading_between` ports `0x47f220`'s fixed headings for a point under the unit.

Each change has a test that fails without it.

First diagnosed and drafted by @kalistoking in #2695.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In 1.12.1, `GetContainerItemInfo`'s slot mapping bounds the backpack, the bank (base `+0x27`, `0x4f98ae`) and the keyring (base `+0x51`, `0x4f98c0`) only by the player list's count (`0x4f990f`). An index past a container's own size reads the next container's slots: `GetContainerItemInfo(-1, 25)` reads the first bank bag's slot.

benilla bounds all three by their own slots, but the recorded deviation named only the backpack. The maintainer ruled that the bank and the keyring take the same deviation for the same reason, a reference bug. The `Deviation:` line now covers all three. No behaviour changes.

Fixes #2617

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The world click's legs ran after the record's walk returned, so Lua after `CameraOrSelectOrMoveStop` in the same binding body, or a hook around it, read the world as the click found it. In 1.12.1 the release dispatches inside its call (`0x514b70` → `0x481f60`), and the router calls each leg directly, deferring nothing.

- The release's click now runs in the call host through a click applier, in the reference's leg order. On a unit that is trade, pet feed, greeting, then select (`0x49278e`, `0x4927e8`, `0x4927ff`, `0x49280c`). On a right click, the select comes before the interact dispatch (`0x492870`, `0x492881`).
- The press pick is traced ahead of each world press's binding (`0x481f00`).
- A charmed player's click acts on nothing (`0x481f98`), for every leg.

Each change has a test that fails without it.

Fixes #1988

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An event handler that unregistered the next listener ended the dispatch, as a `Deviation:` on the grounds that 1.12.1 then reads a freed node. That read is settled at the bytes. `SignalEvent` saves the successor before each handler (`0x703ee8`). The unlink zeroes the node's links (`0x70559d`, `0x7055a3`), and the free writes only the block header (`0x645eb0`), so the object survives. The unregistered listener runs once more, then the zero link ends the walk, and the listeners after it miss the event.

The dispatch now does the same, and the `Deviation:` is gone. A self-unregister still reaches its successor. A listener unregistered further on is skipped. One registered during the signal is reached unless the running listener is last. Each case has a test; the two changed ones fail on main.

Fixes #2743

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After a disconnect, a game object's name, an item name, a page of text or an NPC's text whose query was in flight at the drop stayed blank until restart. Only some saved caches cleared their lost asks, from four scattered places, and no `Deviation:` line recorded the rule.

In 1.12.1 a saved cache's ask stays "asked" for the rest of the process. `DBCacheClearUnsaved` (`0x555740`) wipes only the unsaved caches. The maintainer ruled that benilla asks again after a reconnect.

One disconnect handler now clears the in-flight asks of all eight saved caches (creatures, game objects, item names, items, NPC text, quests, page text and mail bodies). It keeps their records and refusals, under one `Deviation:` line, and the scattered clears are gone. Test: an ask in flight in each store at a disconnect is asked again after the re-login.

Fixes #2744

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
`UseAction` on an item slot that equips sent its equip before the cursor's signals ran. In 1.12.1 `0x5e1840` sets the cursor first (`SetCursorItem 0x494b60` at `0x5e18c9`), whose clear, `ACTIONBAR_SHOWGRID` and `CURSOR_UPDATE` fire in place (`0x494bb6`, `0x494c54`). Only then does `AutoEquipCursorItem` (`0x5e18d5`) send (`0x5e1757`) and clear (`0x5e1763`).

The bar's equip now runs in two halves around a signal point: the set and its listeners, then the equip of whatever the cursor holds by then, and the send. The census lists `UseAction` as sending in order, and `SENDS_AHEAD` is gone. Test: a `CURSOR_UPDATE` listener that rolls makes the wire read roll, roll, equip, roll; main sent equip first.

The cursor verbs that equip have the same misorder, filed as #2752. Part of #2271.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The auction window reached the interface through a per-frame feed. Its events fired ahead of the frame's other packets, its getters read a pushed copy, and a result's list request went out before its line's listeners. In 1.12.1 the auction handlers signal inside their packet, and the getters read the house at the call.

- **Live reads.** The getters read the house through the host: rows, the browse throttle (`0x4cfbb0`) and the deposit rate (`0x4ce170`).
- **The house lane.** `AUCTION_HOUSE_SHOW` fires in the hello (`0x4cd5e6`). A notice waiting on an item record shows when the record arrives (`0x4cd190`). A nested close acts on the session it closes.
- **Line first.** A started, bid-placed or outbid result shows its line first and does the rest after its listeners (`0x4cc4cc`, `0x4ccfd5`).
- `LatchCloses` is removed; nothing read it.

Each change has a test that fails without it.

Fixes #2426

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Three mail getters differed from 1.12.1.

- **#2739.** `GetSendMailItem` asked nothing on a missing record. Now, with the attachment's object present (`0x4ae5e1`), it asks with the callback `0x4ae710`, which signals `MAIL_SEND_INFO_UPDATE` once per call on arrival.
- **#2740.** The usable stationery list was rebuilt every frame. Now it is rebuilt (`0x4ad970`) only at the world-enter init (`0x4acbd7`), at the last missing paper's answer (`0x4acbf8`), and at `GetNumStationeries` with nothing awaited (`0x4ae202`). The getters read it as last rebuilt. With no player a rebuild keeps the list (`0x4ad9aa`), and leaving the world cancels the paper waits (`0x4acc00`).
- **#2741.** `GetSelectedStationeryTexture` reads the Stationery store by id (`0x4ae3f0`), not the usable list.

Each fix has a test that fails without it.

Fixes #2739
Fixes #2740
Fixes #2741

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A mouse command run from a key binding or a handler picked and clicked only after the record's walk returned. So a body `CameraOrSelectOrMoveStart(); CameraOrSelectOrMoveStop(); print(UnitName("target"))` printed the old target. In 1.12.1 `ArmClick` picks inside the Start's call (`0x5141ae` → `0x514810` → `0x481f00`), at the cursor stored from the last mouse event (`0x481f1d`), and the Stop dispatches inside its own.

The records' walk now lends a tracer beside the call host, so an arm picks inside its call at its record's cursor: one trace per arm, and no new schedule ambiguity. The pick traces the world alone (`0x481190`), never a nameplate. The GameObject pick set is now one resource, kept every frame ahead of the input.

Tests cover a key body, an `OnClick` body, the target event in the handler, a key-armed pick under a plate, and a chest clicked by mouse and by key.

Fixes #2749

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every item action queued its packet for after its Lua call, so the cursor clear's and the lock's listeners ran before the packet went out. In 1.12.1 each sends first, then clears or locks. Examples: `AutoEquipCursorItem` (send at `0x5e1757`, clear at `0x5e1763`), the split (`0x4f9e43`, then `0x4f9e4f`), `DeleteCursorItem` (`0x4898a3`, `0x4898af`), the bank and sale arms (`0x4fa305`, then the lock at `0x4fa312`), and the merchant sell (`0x4fb7a7`, `0x4fb7b3`).

Every item send now goes to the host in place, ahead of its clear's or lock's signals, and `Model::item_calls` is gone. The projectile unlock is decided once, from the item's record (`0x5d9f30`). A set refused with no control plays no pickup sound (`0x494b8d`).

Tests cover 16 paths, in the order each sends and signals. Each fails on main.

Fixes #2752

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An item put in the auction sell slot before its record was cached showed a price and deposit of 0 until something else redrew the frame. In 1.12.1, `CalculateAuctionDeposit` and `GetAuctionSellItemInfo` ask for the record with the callback `0x4ce2f0` (`0x4ce25e`, `0x4ce5e3`). It signals `NEW_AUCTION_UPDATE` when the record is found (`0x4ce2ff`).

Both getters now ask, gated on the item's object (`0x468460`), with the item's own guid (`0x4ce263`, `0x4ce5e8`). Each asking call keeps its callback, and the event goes on the house lane. On a miss they return the reference's empty shape: a deposit of 0, and `nil, nil, 1, -1, nil, 0` (`0x4ce726`). The deposit does no lookup with no house (`0x4ce1f7`). With a host, the name, icon, quality and price read live.

Tests: a record that lands later fires the event once per call and then answers; a gone object asks nothing.

Fixes #2747

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A ghost queued for an area spirit healer's wave, resurrected another way, cleared the healer a frame late, from the per-frame poll. So `AREA_SPIRIT_HEALER_OUT_OF_RANGE` and the aura cancel came after `PLAYER_UNGHOST`. In 1.12.1 the ghost flag's clear (`0x5dea20`) calls `SetAreaSpiritHealer 0x4921c0(0, 0)` ahead of `PLAYER_UNGHOST` (`0x5dea2a`). With a wave pending, it cancels aura 2584 (`0x6e7040`), sending `CMSG_CANCEL_AURA` and signalling the event, then zeroes the clock.

The unghost edge now runs that clear at its packet: the cancel goes out there, and the event goes on the lane ahead of `PLAYER_UNGHOST`. Tests: a pending wave gives the event before the unghost plus the cancel; none gives neither.

Fixes #2756

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
World entry was spread over the frame's systems, so `PLAYER_ENTERING_WORLD` fired before every lane item of its frame, and its cascade's resets landed out of order. In 1.12.1 the arrival `0x5dea50` runs inside the create (`PostInit 0x5debe0`) and calls the cascade `0x4908c0` in order.

- The arrival is one item on an `arrival` lane, stamped at the create. It runs its legs in order: the book replay, then each form's UPDATE_SHAPESHIFT_FORMS against the stored list, the slot pass, the cascade with its resets and corpse step, the death state and `LOGOUT_CANCEL`. A /reload pushes the cascade alone.
- System chat is held behind the display latch `0x8435fc` until the cascade releases it (`0x490974`). The leave sweep sets it, and the name and text are composed at the packet.
- The party catch-up fires after PEW.

Each change has a test that fails without it. Part of #2168 and #2217.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
benilla never signalled UPDATE_LFG_TYPES after a level change, a moved quest slot or world entry, so nothing listening for it refreshed.

Reference: the type recount (`0x4e89c0`) and the quest list's rebuild (`0x4e8be0`) each signal it (`0x4e8b0e`, `0x4e8c70`). A level change and each moved quest slot reach them. The world-enter cascade calls both through `0x4e7f00` (`0x4909e7`), after the reputation rebuild and before the skill list's.

Changed: field_watch signals it on a level change and on each moved quest slot. The cascade fires it twice between the reputation and skill rebuilds.

Tests: book_live_tests covers the level and quest-slot signals. `the_cascade_signals_the_lfg_types_twice_between_its_neighbours` checks the order at world entry and fails without the fires.

Fixes #2755

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

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The loot window's getters read a copy the feed pushed once a frame, and its events fired from that feed. An addon reading the window inside a loot event saw the last frame's rows.

Reference: the loot response handler fills the window and signals LOOT_OPENED at its packet. A removal signals LOOT_SLOT_CLEARED (`0x4c1f77`) and then closes an emptied window (`0x4c1f7f`). The master-loot list (`0x61c550`) names its candidates from the name cache alone.

Changed: the getters read the live window, and each event goes on the loot lane at the packet that raises it. A listener's close sends one release. A refused master-loot list frees its push (`0x491ee0`). A removal with no window open still signals its clear.

Tests: loot tests cover the order around a close, a lockbox, auto-loot and master loot.

Part of #2168.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Spell and aura tooltips, `SpellCanTargetUnit` and the `"mouseover"` getters read a copy pushed once a frame. A listener inside a packet saw the last frame's values, and `GetRaidTargetIndex("mouseover")` was always nil.

Reference: the setters build the view at the call (`0x52e610`, `0x52f880`). `SpellCanTargetUnit` reads the unit and range at the call (`0x6e6d00` → `0x6e6460`). A reagent's record answer rebuilds a shown spell tooltip (`0x52f820`).

Changed:
- the setters build through the call host;
- the frame feed and its re-push machinery are gone;
- targeting and the mouseover read live;
- a reagent's answer rebuilds the shown tooltip.

The hover's guard rows cite the cache callbacks that rebuild it inside packets.

Tests: a trainer listener's `SetSpell` reads its own packet's cost. Also covered: `SpellCanTargetUnit` out of range, the mouseover mark, the reagent rebuild, and the pet and aura views.

Part of #2168.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The quest log's text, its turn-in and accept lines, and the share verdicts came from feeds that ran once a frame. A listener inside a quest packet read the last frame's text, and an accept line was lost when a later packet in the frame emptied its slot.

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

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

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

Part of #2168.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The player's combo points, action-bar toggles and zone PvP masks, and the four unit tokens, read copies pushed once a frame by `feed_units`. A listener inside a packet saw the last frame's values.

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

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

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

Part of #2168.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Since #2767 the spell tooltips build at the call, so the spell-read recording that drove their re-push records nothing. `SpellReads` had two implementors: `Unrecorded`, all no-ops, and a test log. Every production call passed `Unrecorded`.

Changed: the trait, `UnitField`, `Unrecorded` and the `reads` parameter go from the spell cost, level, cast-time and equipped-item helpers, with the masks only the recording used. `spell/reads.rs` becomes `spell/level.rs`. No behaviour changes.

Tests: the existing suites. The cast-time test keeps its values and drops the log's assertions.

Part of #2168.

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

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A worldport tore the old world down inside its packet: the despawn, the world handle and LOGOUT_CANCEL there, then PLAYER_LEAVING_WORLD before the frame's earlier packets' events had fired.

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

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

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

Fixes #2763

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

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
/played, PLAYER_SKINNED, the game clock, members' minimap pings, world broadcasts and the chat channel list came from feeds that ran once a frame. A listener inside a packet read the last frame, and two /played answers in one frame kept only one.

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

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

Tests: per family, a neighbouring packet's listener reads the live value; each fails on main.

Part of #2168.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A cross-map worldport kept the old cooldowns and left open loot rolls up in the new world.

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

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

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

Fixes #2769
Fixes #2770

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

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The Windows player build died at the login screen with `thread 'main' has overflowed its stack`. The app runs on the main thread, where the windowing event loop lives. The MSVC linker reserves 1 MB for it, and mingw 2 MB, where Linux and macOS give 8 MB. Nothing in the workspace raised it.

Changed: `crates/benilla/build.rs` asks the linker for an 8 MB main-thread stack on Windows (`/STACK` on MSVC, `--stack` on gnu). A new step in the Windows CI job builds the player binary and reads `SizeOfStackReserve` back from its PE header.

Built: macOS, and x86_64-pc-windows-gnu with this build script on a probe binary (`SizeOfStackReserve` 0x800000, against 0x200000 without). The reporter's `editbin /STACK:16777216` on the MSVC binary confirmed the cause.

Fixes #2776

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The book, letter and plaque reader read a copy pushed once a frame, and opened on its lane. A bank or gossip open in the same frame lost to the book, and a listener read the last frame's page.

Reference: the open, the page and record answers and the turn each run in their handler. ITEM_TEXT_BEGIN (`0x4e349f`), READY and TRANSLATION are signalled there, and a waiting open finishes in its record's callback (`0x4e3280`, `0x5f59d0`).

Changed: the getters read live, each step runs in its handler, the events go on the lane. A nested close acts only on its read. Deviation: 1.12.1's reopen closes the book when an uncached page or a letter's author lands, and benilla keeps the book open.

Tests: a listener reads its packet's page, a same-frame bank open wins, and a book read before its template arrives opens when it lands.

Part of #2168.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A close that a packet handler ran fired at once when its own lane was empty, ahead of earlier packets' events on other lanes. benilla also closed the binder, pet-untrain and talent-wipe confirms itself when you walked away.

Reference: each close core releases, clears its cell and sends in the handler, then signals. The stock OnHide's close verb re-enters it: trade's release re-enters at `0x493365`, so a latched partner gets three CMSG_CANCEL_TRADE. The confirms' latches are never cleared (`0x5dfa21`); StaticPopup.lua hides them.

Changed:
- the engine half of every close runs at the handler, and the signals park behind the lane;
- one shared guard keeps a re-entered close verb off a newer session, replacing auction's own;
- the range-close systems go, and the polls test range at the call.

Tests: quest detail Accept sends its NPC, and the trade cancels and Begin go in order.

Part of #2168.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The guard's unread host-held queues were a list to read before the flip: a queue a handler fills and a feed later fires off would hide a packet's event from the flip's lanes.

Each of the 64 was read for what its feeds fire. Six are not quiet and become `HostHeld` rows, each with its feed and the reference's place:
- the item records' equip replays and quest-log refire (`ItemCacheReceive` `0x55bdb0`);
- the bar's unlearn and supersede queues;
- the profession openers (`0x6e4bd7`);
- the vendor and trainer tutorials (`0x4fad32`, `0x4d74ae`);
- the targeting cursor's parked picks (`0x4935d5` → `0x6e5b40`).

The other 58 each carry their quiet reason. Five reasons were widened where they missed a reader.

No behaviour changes. A deleted row fails the guard, naming its queue.

Part of #2168.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The loot-roll getters read a copy pushed once a frame, and START_LOOT_ROLL, CANCEL_LOOT_ROLL and the roll lines fired from that feed. A vote also deleted its roll, where the reference keeps it listed until the result.

Reference: START fires at the start packet with the item cached, or at the item answer (`0x61b44d`, `0x61b7a7`). A vote sets only the closed byte after its CANCEL (`0x61bf2a`, `0x61bf32`). A win's line waits for the winner's name (`0x61b96a`). Time left is an unclamped 32-bit difference (`0x61b404`).

Changed: the getters read live, the rolls get their own lane, and each signal fires at the handler whose lookup lands last. One Deviation: a refused record closes the roll, where 1.12.1 leaves a dead frame.

Tests: a listener reads the live roll; START falls between neighbouring packets' events; a start and a win in one frame leave nothing listed.

Part of #2168.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The guard counted only the live-read families' frame-final reads. A feed that rewrote, once a frame, state that a call or a handler reads inside a packet went unseen, as did a call that queued an event for the tick. After the flip, a listener would read such state a frame stale with no row saying so.

Changed:
- The BETWEEN/AFTER check now counts as read the whole call host's state and READ_ONLY_TO_HANDLERS, and it fails naming the writer.
- A new source scan lists every call that queues onto the tick's pending events, held against `QUEUED_FOR_THE_TICK`.
- Each hit is filed as a row naming the reference's place and its piece. Among them, WORLD_MAP_UPDATE comes from the explored-bits field watcher (`0x4a6430` → `0x4a6ce4`).

No behaviour changes. flip_readiness goes from 54 to 65 rows.

Tests: deleting a new row fails naming it, and a synthetic test covers the scanner.

Part of #2168.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The party, raid and pet tokens read copies pushed once a frame, and so did the roster copy, the event-token walk and the dungeon latch. A listener inside a member's update read the last frame's party.

Reference:
- the tokens look their member or pet record up by guid at the call (`0x496400`, `0x496420`);
- the event-token walk names player, pet, target, party row, raid row, npc, mouseover (`0x515c50`);
- the dungeon latch is set at the port's new object manager (`0x401c22` → `0x495d10`), and at login with the character list's map (`0x4015f3`);
- a ready check closes at the raid rebuild's end (`0x4bac5a`).

Changed: the tokens, the token walk, the roster, the group predicates and the relations read live, and the latch moves to those places. The frame-final party and pet pushes go.

Tests: a member listener reads party1 and raid3 from its own packet, through a same-frame roster swap.

Part of #2168.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An item record's answer ran its callbacks from hand-placed hooks. The equip replays ran in the drain, the quest log's refire at the frame's end, and the tooltip rebuilds at the tick, so a replay's send followed later packets' sends. A refused record also replayed every frame.

Reference: `ItemCacheReceive` (`0x55bdb0`) stores the record, then walks one callback list per record, oldest first, filled by each lookup's miss (`0x55ba30`). A refusal calls each callback with found 0 and frees the list (`0x55bea8`, `0x55bed2`).

Changed:
- `Items` keeps that list, registered at each miss and walked at the answer's handler;
- a deferred stretch's calls apply at once and its signals park.

The late callbacks stay named in the `Items` row.

Tests: an equip replay sends before a later packet's send; two parked swaps keep their order; a refused record sends nothing.

Part of #2168.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SetCVar ran its change callback and its effect in the frame's later sync, and SetView, SaveView and ResetView queued their camera writes for a drain. A script that read the effect on its next line saw the old value.

Reference: `CVar::Set` (`0x63df50`) runs the row's callback inside the call (`0x63df7d`), and a refusal drops the set. CVAR_UPDATE follows from `SetCVar` (`0x488cad`). SetView writes `cameraView` in its call (`0x512f17`), and SaveView writes its view there too (`0x50fa09`).

Changed:
- the 23 callbacks, once observers, are plain functions the host runs in the call;
- the knobs they write became host state, with their readers ordered around the VM pass;
- `drain_view_requests` is gone.

Tests: SetCVar("deselectOnClick","0"), then a click on empty world on the chunk's next line, keeps the target. A view verb's CVar reads on the next line.

Part of #2202.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
WORLD_MAP_UPDATE for a newly explored zone was queued for the VM's next tick from a frame feed, so it fired behind every packet's events. A listener read the map's overlays before the explored bits were written.

Reference: `0x4a6430` arms a field watcher on the player's explored bytes (section 4, `+0xe6c`, `0x100` bytes). Its callback `0x4a6460` re-selects the shown map: `0x4a67a0` reads the bytes, rebuilds the overlay and landmark lists, and signals WORLD_MAP_UPDATE once (`0x4a6ce4`), inside the update's notify pass.

Changed: a field notice for the explored bytes, fired on the field-notices lane at its packet. It writes the bits, rebuilds the landmarks, then signals. The tick queue's row goes.

Tests: the event falls between neighbouring packets' events; a listener reads the new overlay; an update that moves no explored byte fires nothing.

Part of #2168.

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

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The bar's slot clears, rank-ups and learned-ability latches were rewritten once a frame by feeds. A listener inside an unlearn or a supersede read the old bar, and ACTIONBAR_SLOT_CHANGED fired ahead of every packet event.

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

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

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

Part of #2168.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A profession's book opened from a frame feed, after the cast verb had returned, and its "Create All" repeat recast from a drain. A listener read the book shut inside the call that opened it, and a repeat on a recipe with a cooldown refused its own recast.

Reference: `TryCastSpell` opens the book inside the call (`0x6e4bce` → `ToggleTradeSkill` `0x4fc910` / `ToggleCraftWindow` `0x4f6000`). `HandleSpellGo` runs the repeat hook (`0x6e81e7`) before its cooldown inserts (`0x6e8425`). The repeat disarms only at a failure report, a failed cancel, or the close (`0x4fd790`'s four callers).

Changed:
- the toggles run in the call;
- the repeat runs in the go ahead of the cooldowns;
- `drain_trade_skill` and `open_trade_skill` go.

Tests:
- a listener's CastSpellByName shows the book between two packets;
- Create All recasts at the go, also with a cooldown.

Part of #2168.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Tutorials were judged once a frame by a feed. A tutorial a packet raised fired behind every packet's events. Looting fired at a corpse's first sight, even one already dead at login, and Backpack fired before its item's record arrived.

Reference: each trigger fires in the handler or input that raises it.
- the vendor and trainer lists (`0x4fad32`, `0x4d74ae`);
- Looting on the dynamic flags' bit 0 rising (`0x600482`-`0x60049b`);
- the item tutorials after the received line, once the record is known (`0x491cec`);
- Raid Groups at the roster rebuild (`0x4ba60d`).

Changed: tutorials are a stamped lane, and each site triggers where it happens. TutorialsEnabled reads live. `feed_tutorials`, `judge_selections`, `watch_windows` and `TutorialEvent` go.

Tests: a packet's tutorial falls between neighbouring packets' events, and a ClearTutorials in an earlier listener stops a later trigger.

Part of #2168.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Several verbs did their work in a later drain: try-ons, the chat input's /target, /assist and /follow, RestartGx, and the engine CVar verbs (SetGamma, the video pairs, SetMultisampleFormat, the minimap zoom, the glue writes). A script's next line read the old state. LoadBindings also switched the current set, so a Character Specific tick then Cancel left the character set live.

Reference: a try-on reads the cache and lands or refuses in its call (`0x504540`, `0x504b2d`). RestartGx runs gxRestart synchronously (`0x48dab0` → `0x63ce00`), and SetMultisampleFormat restarts in its call (`0x48c79b`). LoadBindings never writes the current set (`0x4b8249`-`0x4b8252`).

Changed: those verbs apply in the call, and the slash commands call the stock verbs. The drains left at the frame each name why; SaveBindings only marks its set until shutdown (`0x4b6310`, `0x4b65e0`).

Tests: per verb, a same-chunk read of its effect.

Part of #2202.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The action bar's state getters and cooldown queries answered from a copy the feed refreshed once a frame, so a call made between feeds read the last frame's state.

Reference: each getter reads its slot at the call. A cooldown query that misses the item cache registers a callback on the record (`0x6e2f70`) and answers when the record arrives. A pet spell list derives its macros where it loads (`0x4bdae0` → `0x4f0ce0`). The go handler sweeps both cooldown lists every 120 s (`0x6e1910`); the insert sweeps nothing.

Changed: the bar getters (IsUsableAction, GetActionCooldown and the rest) resolve and read live. The cooldown query uses the record's callback list. The pet list derives its macros in its place. The sweep moves to the go.

Tests: each getter read between two feeds, the callback answered at the record, the macros derived after UPDATE_MACROS, and the swept GCD.

Part of #2168.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Three windows still answered from a copy a feed refreshed once a frame, and fired their events there.

**Reference**
- The cursor's control gate reads our unit's taxi bit at the call (`0x494b97`-`0x494ba6`).
- PLAYER_CAMPING, PLAYER_QUITING, LOGOUT_CANCEL and the logout-failed line are raised by their packets' handlers (`0x5aaf17`, `0x5aaf72`, `0x5aaf26`).
- The death signals fire at their packets, and the corpse range is checked each frame (`0x492130`).

**Changed**
- The taxi gate reads the flag off our object.
- The logout replies go on the notice lane at their handlers.
- The death getters (GetCorpseRecoveryDelay and the rest) read live, and the death signals fire at their packets.
- `feed_on_taxi`, `feed_logout` and `feed_death` are gone.

The fake world server logs a session before answering its authentication, which a loaded test run read behind.

**Tests:** each getter read between two feeds, and each event between its neighbouring packets' events.

Part of #2168 and #2217.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The movement verbs and DropItemOnUnit queued their work for frame time, so a cast a movement press should cancel was still running on the script's next line.

**Reference**
- Every movement verb goes through `SetCommands` (`0x515090`). It sets the bit, and a new press in mask 0x10f0, or the press completing both mouse bits, cancels the cast inside the call (`0x51512a` → `0x6e4940`), then the channel, then runs the tick.
- ToggleAutoRun reads the bit at the call (`0x513de7`).
- DropItemOnUnit fills the trade or starts one in its call (`0x48da50`, `0x48da62`).

**Changed**
- With a host lent, the 23 movement verbs and Jump apply in the call.
- The autorun bit flips there.
- DropItemOnUnit applies in the call.
- The mouselook verbs still queue.

**Tests:** a press mid-cast cancels before the next line, two toggles in a frame cancel once, and a drop on a player trades in its call.

Part of #2202.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The error and info lines a packet raises showed from a per-frame feed, ahead of every lane's events, so they sat out of order against their own packets' signals.

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

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

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

Part of #2168.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The profession window answered from a copy a feed refreshed once a frame, and rebuilt and signalled there, so a getter or TRADE_SKILL_UPDATE between two packets read the frame's last state.

**Reference**
- An item answer runs the getter's or the build's record callback (`0x4fdc30`, `0x4fc8e0`), which rebuilds and signals at its packet.
- The build reads the book at its call (`0x4fca20`), and with no player it empties the list and signals nothing (`0x4fca52`, `0x4fcb1b` → `0x4fd170`).
- GetTradeSkillLine reads the open line at the call (`0x4fdd40`).

**Changed**
- The callbacks go on the item cache's list.
- The build and the getters read through the host.
- A build with no player empties the book silently.
- `feed_trade_skill` is gone.

**Tests:** the line getter between two packets, a reagent's answer between two falls, a learn banded at its packet, a product answer after `/reload`, and the silent empty build.

Part of #2168 and #2217.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With the targeting cursor up, a unit a packet selected was parked and bound to the cursor a frame later. A spell's deferred swing at its go also skipped the attack validator.

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

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

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

Fixes #2113. Part of #2168.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Craft window answered from a copy a feed refreshed once a frame, and rebuilt and signalled there.

**Reference**
- The build (`0x4f60c0`) zeroes its counts and, with no craft type open or no player, leaves before CRAFT_UPDATE (`0x4f661a`). The open, SMSG_PET_SPELLS, the record callback, learn and unlearn, and the item watchers rebuild it.
- The getters read at the call (`0x4f6c3b`, `0x4f6d52`, `0x4f6fe6`, `0x4f76b2`, `0x4f7a3c`).
- GetCraftDescription falls back to the taught spell's text (effect 36 or 57), with the pet flag (`0x4f7b53`).

**Changed**
- The build and the getters read through the host, and a build with no player empties silently.
- The description takes the fallback and the pet flag; the VM fills its templates in the call.
- `feed_craft` is gone; the trainer and the window share one token helper.

**Tests:** each getter read between two packets, CRAFT_UPDATE between its neighbours, the silent build, the description fallback.

Part of #2168 and #2217.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The bar's usable pairs were stored by a per-frame diff, and EditMacro's and a bag update's refresh fired at frame end, so ACTIONBAR_UPDATE_USABLE and a slot's IsUsableAction lagged their cause.

**Reference**
- Each slot write stores its pair (`0x4e58e0`, `0x4e590a`).
- The refresh `0x4e5c00` runs at world entry, inside EditMacro (`0x4f132e`) and in the item watcher's run before BAG_UPDATE (`0x4f9374`).
- Game entry zeroes the cache (`0x4e4f50`).
- The reactive list keeps learn order (`0x5dea86`, `0x4b27b3`).

**Changed**
- Every writer stores its pair, and the refreshes run in place.
- The cache zeroes at game entry.
- The reactive list moves to its own resource, in wire order.
- The per-frame diff and its event queue are gone.

**Tests:** EditMacro's update inside the call, a bag run's between its packets, PlaceAction's listener reading its pair, the reactive order.

Part of #2168.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A follow ended at frame time, in `steer_follow`, and AUTOFOLLOW_END fired there, out of order with the packets that ended it.

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

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

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

Part of #2168 and #2217.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The guards' source scan misses a raise reached through a helper in another module, so a frame-time feed could raise outside every lane unseen.

**Changed**
- In debug builds, every raise into the VM goes through one check: the immediate fire (`tick::fire_event_into`) and the queue (`Model::queue_event`, now its only push).
- A raise outside a script call, a lane or the VM's tick fails with the event and its caller chain, unless an allow entry names its guard row or reference place.
- The app's tests fail hard. A live run warns once per site.
- Its first run found `feed_char` raising UPDATE_INVENTORY_ALERTS at frame time, where the item field watchers signal it in the packet (`0x4c8085`). That is now a guard row.

**Tests:** a frame-time raise fails naming its caller, the same raise in a lane passes, and a live run reports each site once.

Part of #2168.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`vm_parity` failed on Linux x86_64 on the `fix-libs deg_rad` case. #2660 made `math.deg` multiply by 180/pi, as `math_deg` does (`0x7fb14d`), but the case's round-trip summary kept the old divide's answer, 98 misses and `deg(rad(3)) == 3` true. With the multiply, IEEE doubles give 166 and false on every platform. macOS never saw it, because a platform case's whole answer is compared only on the reference platform.

Changed: both lines' summaries now read 166,false. The per-value items already matched.

Found in jhinzuo2/benilla-twow@3b40b46f.

Fixes #2804.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Five engine calls queued their event for the VM's next tick, behind every packet, where the reference signals in place.

**Reference**
- A vendor sale clears the cursor and signals CURSOR_UPDATE inside the click (`0x5df5f0`, `0x494b60` → `0x495190`), unless the player lost control (`0x494b8d`).
- The landmarks rebuild and signal WORLD_MAP_UPDATE (`0x4a67a0`, `0x4a6ce4`) at the gossip marker's set, at its deadline, and at an arrival tested on a moved update (`0x6dac93`, `0x6d9411`, `0x6d9a4c`).
- World entry drops the marker but keeps its deadline (`0x6d8fb0`).
- The catalog's load signals nothing (`0x4a5d00`).

**Changed**
- The sale signals in its click.
- The landmarks signal on their own lane.
- World entry and `/reload` drop the marker.
- The seeds are silent.
- The world map's Deviation keeps only the world-enter sync.

**Tests:** the sale before the binding's next line, the gossip set between its packets, the deadline, a reload's marker.

Part of #2168.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The bar's icon, count and consumable getters, its aura notifies and GameTooltip:SetAction read a copy a feed resolved once a frame, so a call between two packets read the frame's last state.

**Reference**
- GetActionTexture, GetActionCount and IsConsumableAction read at the call (`0x4e6a50`, `0x4e6c70`, `0x4e5250`), with the counts at `0xbc6390` zeroed at game entry (`0x4e4f50`).
- An item answer runs the bar's callback (`0x4e5b80` → `0x4e5c70`), firing ACTIONBAR_SLOT_CHANGED per slot.
- An aura add or removal notifies its slot around its log line (`0x4e5de0`).
- SetAction runs the cooldown query and attack test at the call (`0x5323a6`, `0x5323b4`).

**Changed**
- The getters and SetAction read through the host.
- The callback rides the item cache's list.
- The aura handlers notify in place.
- `feed_actions` keeps only the engine's refusals and a new VM's seed.

**Tests:** an item answer's slot event between its packets, the seed's counts, the aura notifies, SetAction inside its packet.

Part of #2168.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A ready check's start line took the leader's name from the party roster, so it printed even when the client did not know the name, with an empty name if the roster lacked the leader.

**Reference:** the handler asks the name cache with no callback (`0x4ba50f` → `0x55f080`). A miss returns at once without a query (`0x55f120`-`0x55f125`, `0x55f221`), and the line is skipped (`0x4ba514`-`0x4ba516`). The deadline and READY_CHECK follow either way (`0x4ba535`, `0x4ba53a`).

**Changed:** the line reads the leader through the name cache, as the leave line does (#2000), and a miss prints nothing and sends nothing.

**Tests:** a cached leader prints the line before the popup, and an uncached one prints none and sends no query, while READY_CHECK still fires.

Fixes #2792.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
UPDATE_INVENTORY_ALERTS fired from the character feed at frame time, and GetInventoryAlertStatus read its copy, so an alert lagged the durability or ammo packet that changed it.

**Reference:** `0x4c7ee0` recomputes the 12 statuses, including the temporary-enchant arm (`0x4c8035`). A break's tutorial fires inside the loop (`0x4b5390`), and the signal at `0x4c8085` on every pass. It runs from the item field watchers inside the update packet (`0x5d952a`, `0x5d97aa`, `0x5d9920`, `0x5d9b2f`, `0x5dee7a`) and at world entry. Any player's visible item also rebuilds the profession and craft lists (`0x5dee30`).

**Changed**
- The recompute runs at each watcher's step and reads live, including the temporary enchant.
- Its tutorial fires in place.
- Another player's visible item rebuilds both lists.
- The feed only stores.

**Tests:** a durability change's signal between its packets, the getter between packets, the enchant statuses, the tutorial ahead of the signal, the rebuilds.

Part of #2168.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The bar's last per-frame feed is gone, along with four mismatches with 1.12.1 its audit found.

**Reference**
- A macro or item slot's texture runs the attack and ranged face tests on its spell first (`0x4e6ab2`, `0x4e6acb`, `0x4e5a50`).
- A `/reload` keeps the bar in the engine, so the new interface reads it from its first file.
- SMSG_ACTION_BUTTONS only stages its words (`0x5e6680`), and the arrival loads them, skipping unknown spells (`0x5deaf5`).
- The usable walk asks with the bar's callback, behind its player and count gates (`0x4e5b30`, `0x4e5080`, `0x4e50ab`).

**Changed**
- Each of those, and a worldport's book no longer reruns the rank pass over loaded words.
- `feed_action_state` is deleted, and its tests read live.

**Tests:** an Attack macro's weapon icon, the bar during a reload's load, an unknown spell's empty slot, the walk's ask, the worldport's book.

Fixes #2810. Fixes #2811. Fixes #2812. Fixes #2813. Part of #2168.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The cast state's getters read a copy a feed pushed once a frame, and a refused cast line that missed the item cache retried at frame time, so both lagged their packets.

**Reference**
- IsCurrentAction, IsAutoRepeatAction, SpellIsTargeting, SpellStopCasting and the targeting word's checks read the cast state at the call (`0x4e54c1`, `0x6e48e0`, `0xcecac0`).
- A reagent or totem refusal that misses the cache registers `ShowItemCastFailure` (`0x6e1e82`-`0x6e1ea4`, `0x6e29b0`). The item's answer shows it, with UNKNOWN on a refusal (`0x6e2a49`). With no item word no line ever shows (`0x55be5d`).

**Changed**
- The getters read through the host, and `feed_cast_reports` is gone.
- The refused line rides the item cache's callback list.
- The "Missing reagent" fallback that 1.12.1 never shows is removed.

**Tests:** the line between its neighbours for a record and for a refusal, the getters between packets, no line with no item word.

Part of #2168 and #2217.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The party feed pushed the roster to the interface once a frame, seeded a new interface late, and printed the ready check's summary a frame after its answer.

**Reference**
- Every party and raid getter reads the engine at the call (`0x4e86d0`, `0xbc6f48`, `[0xb713e0]`, `[0xb712a8]`).
- A `/reload`'s interface sees the group and lockouts from its first file (`0x490bd0`, `0x48fbf0`; `[0xb6e5e8]`).
- The ready check's not-ready and away lines print inside the answer's handler (`0x4bb310` → `0x49a870`).
- The friend list signals only from its own handlers (`0x5ae867`, `0x5ae92d`).

**Changed**
- The roster push and its catch-up are gone.
- The seed runs at the load, before the files.
- The ready-check lines print in place.
- The social feed's dead seed is gone.

**Tests:** a new interface's first lists signal at their packets, a reload's files read the lockouts, the ready-check lines between their packets, no stray list event.

Part of #2168 and #2217.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`cvar_app()` built a client from the window and capture variables without `ENV_LOCK`, while the capture, account-data and state-folder tests set them under it. Under `cargo test`, which runs a crate's tests on threads of one process, `a_locale_default_reaches_its_knob_at_load` saw another test's `WOW_WIN` and failed.

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

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

Fixes #2816.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Since #1466 the world entry parks the glue VM. `load_ingame_ui_seated` unparks it and then returns early when a capture runs without the UI. That left the glue VM in the world, so a world capture such as a vista painted the login screen over the world.

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

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

Fixes #2819.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The unit-token resolver's inputs were pushed into the VM once a frame. The verbs that read the push answered with the frame's last group, where 1.12.1 resolves at the call (`0x515970`).

**Changed**
- The feed is gone. The views' rewind and targeting walk the tokens' reach off app state (`ui_unit/reach.rs`).
- `InitiateTrade` resolves at the call (`0x48a120`).
- `UnitIsFriend` and `UnitCanCooperate` test membership through the host (`0x491900` → `0x4e80a0`, `0x4bafd0`).
- `GetPlayerMapPosition` reads at the call (`0x4a8610` → `0x4a7870`): a held Player's position, else the group record. The record is skipped when status bit 0x20 is set, and placed under its zone's own map, so a member in an instance reads (0,0).

**Tests:** trade, the friend test, and map position between two group lists, mid-frame joins, an instanced member, bit 0x20. Each fails on main.

Part of #2168.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Several small engine reads used copies a feed wrote once a frame, and the guard missed whole classes of such reads.

**Reference and changes**
- The "npc" token reads the interaction latch (`0x515c00`, `0x5f0251`).
- The camera pose loads at the interface load (`0x481b4e`).
- The display modes and the language skill are read at the call (`0x48bc90`, `0x4e3837`).
- Far sight:
  - starts or ends at its field change (`0x5de144`, `0x5de15d`) and at the object's create (`0x5fbb1a`, `0x5d552d`);
  - resets where our player is freed (`0x5dd33d`);
  - its bit, not the camera, tears input down (`0x514537`).
- The guard now scans every system that writes the VM. It matches by full name and counts read-filled queues and writes through `&self`. That surfaced 10 honest rows.

**Tests:** each moved read between two packets, far sight at create, at loss, and across sessions, and every guard gap shown to fail.

Part of #2168.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The movement, camera and mouselook verbs queued their work for the frame's input pass. 1.12.1 does it in the call: the tick, the stand-up and AFK clear (`0x514e23`), freelook's entry and leave, the sheath, the zoom and the loot close (`0x60e990`).

**Changed**
- Each verb acts inside its call.
- The mouse's primary bits live in one store, which IsMouselooking reads live (`0x514278`).
- Freelook entry moves the mouse focus to the world frame (`0x764dc0`). Its records walk no hover (`0x766250`).
- A press goes to the frame holding a button (`0x76637f`), and in freelook to the world (`0x492bd9`).
- The mouse turns the camera only while the command word holds a bit (`0x51440c`).
- `/follow` ends at the mouse hand-off (`0x5145fa`).

**Tests:** each verb's effect read on the next line of its chunk, the focus change's order, routing, and rebinding. Each fails on main.

Part of #2202.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The character doll's feed pushed the worn slots, weapon enchants and combat stats into the VM once a frame. A listener for an equip packet read the frame's last item, where 1.12.1 reads the item list at the call.

**Changed**
- Worn and bank-bag slots read live (`GetItemInSlot` `0x6228a0`, at `0x4c8cd3`), each slot built only when asked.
- `GetWeaponEnchantInfo` reads live (`0x4c97ca`), with six nils and no player (`0x4c9965`).
- Item countdowns read as of the listener's packet.
- The feed goes, along with its gate, the name cache's counter, and an ammo-hit `UNIT_INVENTORY_CHANGED` that 1.12.1 never raises: a cache hit signals nothing (`0x4c83ce`).
- New guards list the host's reads of handler-written state and its raw store reads.

**Tests:** an equip listener reads its own packet's item, and a line between two writes reads the first charges and enchant time. Each fails on main.

Part of #2168.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The world mouseover tooltip was rebuilt once a frame from the frame's final state. 1.12.1 picks and publishes it at frame time (`0x481790` → `0x482090` → `0x492890`), but rebuilds it inside the packets, at its callbacks:
- a unit's name and creature records and its skinnable flag (`0x4919d0` via `0x491a00`, `0x5ff7be`);
- a game object's lock key (`0x52ad10`);
- a corpse's owner name (`0x52afe0`).

**Changed**
- Each rebuild runs at its callback and reads through the host. Other field changes don't rebuild, and a rebuild neither re-anchors nor raises UPDATE_MOUSEOVER_UNIT, as in 1.12.1.
- A record-less locked object shows "Locked" (`0x52aae2`).
- A `UnitName` miss signals UNIT_NAME_UPDATE when the name lands, whether or not the unit is held (`0x517290`), and the wait ends with the world session.

**Tests:** each rebuild read between two packets, the locked line, an out-of-view member's name update. Each fails on main.

Part of #2168.

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

**Wrong.** `lanes::firing` told its drain the listeners had pushed whenever the process-wide clock moved during the call. Under `cargo test`'s threads another test's world stamps meanwhile, so `fire_item_text` stopped after `ITEM_TEXT_READY` and left `ITEM_TEXT_TRANSLATION` queued. The bag-book test fires the lane once, so it saw only the first.

**Changed.** A firing's pushes are already its own thread's: `stamp()` nests them under the thread-local item firing. `firing` now compares a thread-local count of the stamps taken, so another thread's stamp moves nothing.

**Test.** `another_threads_stamp_is_no_push_of_the_firing` fails on main. The bag-book test under `--test-threads=16` failed 3 of 12 runs on main and none of 12 here.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The guard listed `sync_cvars` and `sync_input_cvars` as frame-final reads: a handler's CVar write was said to reach the VM's mirror only at the next sync. No handler writes the registry but through the call host, whose appliers sync the mirror inside the call, as `CVar::Set` runs its callback inside its writer (`0x63df7d`) and `GetCVar` reads that record (`0x488bf9`). Every other writer runs before the input sync or after the dispatch.

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

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

Part of #2217.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fixes #2168

Listeners fired once a frame, at a drain after every packet, so one packet's events could fire after a later packet's writes. 1.12.1 runs each packet's handler whole, in wire order (`0x5384d0`, `0x537ae5`), and a signal runs every listener before it returns (`0x703e50`).

**Changed**
- The lanes flush after each packet's handlers, and before the first packet for what input and the tick stamped.
- An update packet's blocks, a speed packet's pose and a saved answer's bytes stay inside their packet.
- Log starts become span starts. Chat's age pass runs once a frame.
- The guard holds each lane's own reads.

**Tests:** a listener's send ahead of the next packet's query, per-log reads between packets, close shapes across lanes. Each fails with one drain. Twelve suites now run in the world.

Part of #2217.

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

A nameplate's click was queued and applied by frame systems after the click returned. 1.12.1's click slot `0x7cb910` runs `0x4949f0` at `0x7cb92a`, ahead of `RunClick` (`0x7cb936`): the left leg's trade, pet feed, greeting and `SelectTarget` (`0x4925d0`), or the right leg's select and interact (`0x492820`).

**Changed**
- Both legs run inside the click, for a press and a scripted `Click()`, on buttons 1 and 4 only, a disabled plate included.
- A select's re-swing is judged on the unit (`0x5ecb70`: alive, `CanAttack`), not the cursor, at every select.
- A retired plate keeps its guid (`0x608a10` never writes `+0x4e8`), so its click still selects that unit.

**Tests:** `Click()` then a same-chunk target read, the select's events ahead of `OnClick`, the trade, feed and bind order, the re-swing under any cursor, a retired plate's click. Each fails on main.

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

The bindings, layout and macro readers reran at the frame's end off the sections a download stored. 1.12.1 runs each inside the settle its digest or download handler makes (`0x5afaca`, `0x5afd19`): the bindings `0x4b6240`, the layout `0x490380`, the macros `0x4f0600`.

**Changed**
- Each reader runs at its section's settle, ahead of the next packet.
- The interface load registers the readers after VARIABLES_LOADED (`0x4900c2`-`0x4900d1`), so a `/reload` reads the bindings and signals UPDATE_BINDINGS there. File scope sees the fresh, empty binding table.
- A 0.2 macro file's button moves wait for the bar's arrival (`0x5deae2`). The file is rewritten only after them.
- A save queued before the set choice is dropped (`0x4b6506`).

**Tests:** a download between two packets for each reader, the `/reload` order, the macro moves with the bar before and after the settle, a save at file scope. Each fails on main.

Part of #2217.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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)
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>
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>
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)
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)
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>
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>
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>
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 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 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>
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>
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 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>
`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>
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>
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>
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>
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>
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>
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>
`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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
`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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
Since #2429 the UI's input records are read from the window's `WindowEvent` stream. The layout remap (#602) rewrote only the typed `KeyboardInput` messages, so a key a layout remaps, such as Caps Lock under X11's `caps:escape`, reached the bindings and text boxes as its physical key again.

1.12.1 reads a key as the virtual-key code the OS layout produced (`0x42d21e` → `0x42d800`), so Caps under `caps:escape` is Escape.

`remap` now rewrites the `KeyboardInput` a `WindowEvent` carries by the same rule, in `PreUpdate`, before the records are collected in `Update`.

Test: `both_streams_carry_the_layout_key`, both streams come out as Escape.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
A damaged file or a malformed list took the client down, or was shown as it came.

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

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

Each has a test.

Found in Thorsten42/benilla@af86636a54

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
"Complete" overclaimed, and "Addons, options" did not say what is left. The status now says the client is fully playable, what the long tail is, and that addon compatibility, the remaining options and performance are ongoing.

Fixes #2863

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 #2078
Fixes #2384
Fixes #2480
Fixes #2485
Fixes #2920
Fixes #2943
Fixes #2944

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 #1197
Fixes #1198
Fixes #2482
Fixes #2487
Fixes #2591
Fixes #2614
Fixes #2945
Fixes #2947

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The select screen looped one Stand clip and never showed a guild's emblem. Select models now replay Stand with a fresh pick at each window's end, and a guild tabard asks for its guild and wears the emblem.

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

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

Fixes #1314
Fixes #1316
Fixes #2946

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
"Any 1.12.1 server" overclaimed: a server that enforces Warden disconnects the session, as "Running it" says, and one that needs its own client changes won't work. The status line now says so.

Refs #2863

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 #1697
Fixes #1699
Fixes #1706
Fixes #2948

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A server whose changes are all in MPQ archives plays: benilla reads the install's patch chain (`crates/benilla-formats/src/chain.rs:41`), and a client change outside the archives is what it can't follow. The status line now says that.

Refs #2863

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>
The workspace version and its lock move to 0.3.0, and nothing else. The toolchain pin stays at 1.98.1, the compiler these checks ran on.

The Linux and Windows compile, the Vulkan run and the smoke run passed on this tree. The full gates passed on its parent (#2968) and were not rerun for the version change, at the maintainer's word.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The README named kira as the only vendored code under `third_party/`, but wgpu-hal is vendored there too, under the same two licenses with its notices beside it. `third_party/alsa-0.9` stays unnamed: it is benilla's own stand-in, with no upstream code (its `BENILLA.md`).

Fixes #2972

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>
Fixes #2978

A Wayland trackpad sends pixel travel. benilla asked 100 physical pixels for one wheel step and gave at most one step per event, so at scale 1.33 a list needed about 7.5 times GNOME's notch of finger travel.

10 axis units are one step on Wayland (SDL's `WAYLAND_WHEEL_AXIS_UNIT`, GLFW, Xwayland, mutter's `DISCRETE_SCROLL_STEP`). On Linux a notch is now 10 logical pixels at the window's base scale, and an event steps once per whole notch. macOS keeps Bevy's factor and its one step per event. Line events stay at one step each, like the reference's `WM_MOUSEWHEEL` (`0x76c1a9`), and winit reports only lines on Windows and X11. The `WheelNotches` deviation says so.

Tests: `pixel_travel_steps_by_the_platforms_notch` runs both platforms' arms on any host, and `a_pixel_notch_reads_the_base_scale` pins the scale.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Sam <samuv.eth@gmail.com>
Fixes #2980

A glue scene's seat read only a rotation and scale parked on global-sequence keys, once, when the scene was built. That lost a seat keyed in the scene's sequence: a custom `UI_CharacterSelect` turns its seat bone 180° and scales it 0.673 there, so the body stood backwards at about 1.5×. No stock scene keys its seat that way.

The reference seats a child at the bone's whole animated matrix times the attachment position, every frame (`0x7140d8`-`0x714149`), after it animates the scene (`0x714000`). The body and the pet now take that matrix from the scene rig's pose each frame. This runs in `PostUpdate` after the rig's model pass, now the named set `RigModelPass`, and before propagation, so the seat and the scene share a frame.

Test: `a_seat_takes_the_bones_whole_matrix_at_the_attachment`.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Sam <samuv.eth@gmail.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>
benilla found a tile's chunks by walking record sizes, so a Noggit export whose MCNK sizes count their header lost every chunk after the first. 1.12.1 reads the tile through MHDR's offsets (`0x6c2010`) and each chunk from its MCIN entry's file offset (`0x6c299d`), never an MCNK's size. benilla now does too.

Two more reads follow the reference. With MCNK flag 0x8000 an alpha map decodes whole at 64×64 (`0x6b0a6b`, `0x6b0ac0`), and without it at 63×63 with the edge copied. Doodads and WMOs are made only from the chunks' MCRF refs (`0x6b00f0`), so an entry no chunk references never appears. The static item and terrain shaders now sample in uniform control flow, which WGSL requires, and a lint holds them to it.

Every chunk of 2,380 install tiles parses identically before and after. Each fix has a test that fails without it.

Fixes #2981
Fixes #16
Fixes #21
Fixes #613

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

Co-Authored-By: Claude Sonnet 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 #1121
Fixes #2958

🤖 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 #2922
Fixes #2931
Fixes #1208
Fixes #327

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Dropping a bag item on the ground left the camera in mouse-look, with the pointer hidden, while the destroy confirmation stood.

The world drop marked its release as the interface's, so the world frame's binding never ran its up-half and `CameraOrSelectOrMoveStop` never ended the session. In 1.12.1 the drop happens inside that release: `0x514b70` ends mouse-look (`0x514c48`), then dispatches the click (`0x481f60(1)`), whose ground or nothing leg raises the confirmation. The release is the world frame's (`0x483cb0`).

The drop no longer claims the release. A press the drop takes still arms no select, so nothing else clicks.

Test: holding an item, a left click on the world runs the binding's up-half; it fails without the fix. The world-drop tests now expect the release to be the world's.

Diagnosed in #2993.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: hyrw <47274436+hyrw@users.noreply.github.com>
A localized client tagged its own race's speech with a language header, so Say showed `[Common]` on a zhCN client.

The stock chat frame draws the header when the line's language name differs from `GetDefaultLanguage()` (`ChatFrame.lua:1442`). Both names come from `Languages.dbc`, in the locale's column (benilla-re `systems/ui/chatlanguage.md`). We answered `GetDefaultLanguage()` from the DBC but passed each line's language from a hard-coded English table, so the two never matched outside enUS.

The line's language is now named from the same `Languages.dbc` rows, and the English table is gone.

Test: `a_line_names_its_language_in_the_locale` gives a monster line language 7 with a zhCN row and checks `arg3` is that name; without the fix it reads `Common`.

Fixes #2996

🤖 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>
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>
A release build warned that `SpanStart::epoch` is never read. Its only reader is the debug build's check that a span-start snapshot is of the entries it saw (`ui_unit/views.rs`, `fire_unit_views`), and its only writer, `mark_span_start`, is debug-only too. The field now carries the same `#[cfg(debug_assertions)]`. The play build compiles without the warning.

The second warning on the issue, in `third_party/wgpu-hal/src/gles/egl.rs`, is upstream's code as published. The fork keeps its diff to the marked hunks it needs (`third_party/wgpu-hal/BENILLA.md`), so it stays.

Fixes #2970

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After a cross-map loading screen, the world drew for about a round trip with no body for our player and an empty portrait. The screen cleared on readiness alone, a few frames after the ack, ahead of the create the ack brings.

In 1.12.1 the screen ends at our arrival: `CGPlayer_C::PostInit` (`0x465dbc` → `0x5debe0`) runs the arrival and `EndLoadingScreen` (`0x5debbd` → `0x407e80`).

A raise whose load brings our create (world entry, a transfer, a worldport) now also waits for that create. A ship's leg change brings none and does not wait. An abort or a logout drops the wait, and a pending cinematic's arrival still counts. The header no longer claims the old clear was the reference's.

Test: `a_cross_map_screen_waits_for_our_create` holds the screen over a resident destination until the create, then clears. Without the fix it clears early. A live login and a hop to Kalimdor both clear.

Fixes #2959

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
benilla 0.3.1: the workspace version and the lock. The Rust pin stays at 1.98.1, the compiler the release checks ran on.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The README's setup paragraph named `WOW_USER` and `WOW_PASS` and nothing for a PIN-protected account, so a player with 2FA on their account had no pointer to `WOW_PIN` or `WOW_PIN_TOTP`, which only `docs/CONTRIBUTING.md`, "Running it unattended", describes. The paragraph now names both variables in a sentence and points at that section.

Fixes #3007

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
On a server that sends no `SMSG_LOGIN_VERIFY_WORLD` for the character list's map (MaNGOS Zero since its `402c570`), the world-entry loading screen never cleared, and with the wait forced past our player stood on the previous map's terrain: the screen waited for that packet's snap, and `CurrentMap` was set by the worldport packets alone.

In 1.12.1 the glue's state 8 opens the world at the character record's map before the login (`0x46c236` → `0x401570`), `HandleLoginVerifyWorldPacket` (`0x401de0`) does nothing for the manager's own map (`0x468580`), and `CGPlayer_C::OnActiveArrival` ends the screen (`0x5dea50` → `0x5debbd` → `0x407e80`).

A glue entry now seats `CurrentMap` from the pick (the first half of #2824), and a world entry's wait for its snap and for our create ends once our player is there. A transfer still waits for `SMSG_NEW_WORLD`; a reconnect in the world leaves the map to the server.

Tests: `a_glue_entry_opens_the_world_on_the_picks_map`, `an_arrival_ends_a_world_entrys_wait_for_its_snap`.
Overhead unit names were laid out in Friz Quadrata whatever the install, so on a zhCN install, whose glyphs that face lacks, NPC names over heads drew nothing (#3005).

In 1.12.1 the name font is not a locale table: `PlayerNameInitialize` (`0x6c7470`) reads the Lua global `UNIT_NAME_FONT` at world entry through `FrameScript::GetVariable` (`0x704350`) and builds the name font from it (`0x5c1ae0`), so the install's own `Fonts.xml`, or an addon that reassigns the global, picks the face. `s_loadingFontPaths` (`0x82f018`) is the loading screen's tip font.

A `UnitNameFont` resource is read at the same world-entry seat as `DAMAGE_TEXT_FONT`, the name mesh lays out its baseline and glyphs in that face, and the mesh cache drops when the face changes.

Tests: `the_name_mesh_lays_out_in_the_unit_name_font`, `world_entry_reads_unit_name_font`, `a_new_name_face_drops_the_meshes_and_the_live_plates`.

Fixes #3005

Co-authored-by: hyrw <47274436+hyrw@users.noreply.github.com>

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Добавлены сцена лунного колодца, ветви и растительность из игровых архивов,
анимация листьев и ряби, отражение ветки, Wisp с локальным светом,
логотип и собственная музыка главного экрана. Автовход показывает сцену
минимум шесть секунд со спиннером; выбор реалма и ruRU сохранены.

Проверено на Windows: fmt, Clippy workspace, сборка play, проверка без dev,
116 тестов менеджера входа и 7 тестов ввода/ruRU с игровыми файлами,
тест Lua-интерфейса и полное декодирование MP3. Сцена проверена в 16:9/4:3.
Полный workspace-gate не завершён: расширенный прогон тестов был остановлен.
Реальная авторизация на сервере и воспроизведение через динамики не проверены.
Влиты 17 коммитов апстрима после d9c64230, версия workspace 0.3.1.
База трёхстороннего слияния — d9c64230: предыдущая синхронизация
779460d8 была включена в main через squash и не сохранила родителя апстрима.

Сохранены ruRU, переключаемый интерфейс, графика MoonWell, экран входа,
музыка и задержки автовхода. Для анимации предметов принята обновлённая
проверка posing_idle из апстрима. Карта и отклонения сгенерированы заново.

Проверено на Windows: cargo check -p moonwell --locked и fmt.
Полные проверки и сборка релиза выполняются перед слиянием в main.
sindoring merged commit 02dd646b78 into main 2026-11-08 18:55:51 +03:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sindoring/moonwell-client-core#15