Commit Graph

711 Commits

Author SHA1 Message Date
Sam Sung d1fa9bdedf Quest givers streamed in before our own tag are still asked their status (#1455)
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>
2026-10-16 15:33:02 +07:00
Sam Sung bc52e8a7f4 Item tooltips show the lock's key or skill line and a gift's giver (#1454)
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>
2026-10-16 14:26:56 +07:00
Sam Sung 9ff8f6aff4 The login engine moves into glue_session; native login shows its notices (#1451)
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>
2026-10-16 14:04:59 +07:00
Sam Sung ff3dbc9416 Worn items answer their cooldown; a spent charged item drops it (#1447)
`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>
2026-10-16 13:20:24 +07:00
Sam Sung 902e170a18 The roster, the pick and the session edges move into glue_session (#1446)
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>
2026-10-16 13:10:33 +07:00
Sam Sung 8a545a6682 Tooltips colour Locked by the lock check and show Cooldown remaining (#1444)
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>
2026-10-16 12:40:37 +07:00
Sam Sung 951f9fc465 Glue session state serves both screen paths; scripted runs pick a realm (#1443)
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>
2026-10-16 12:22:39 +07:00
Sam Sung 5d19a7be22 Stock glue: raw realm preferences, queue countdown, empty-roster smokes (#1438)
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>
2026-10-16 11:16:25 +07:00
Sam Sung c7b4898bd9 Buff anchors and the minimap mask follow 1.12.1's load order (#1432)
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>
2026-10-16 10:46:27 +07:00
Sam Sung 4a76d48fd9 Two UI tests read the files and frames they claim to check (#1431)
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>
2026-10-16 10:36:19 +07:00
Sam Sung 2027a7057e EditBox caret, Ctrl editing letters and word rule follow 1.12.1 (#1430)
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>
2026-10-16 10:03:50 +07:00
Sam Sung 03e4ba5e0c Record the unstreamed-creature guid read as a deviation (#1428)
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>
2026-10-16 09:51:49 +07:00
Sam Sung 106e7280d9 A texture's wrap is fixed when its file loads, not read from its UVs (#1427)
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>
2026-10-16 09:42:00 +07:00
Sam Sung caccf1be64 Stock glue: live scene after the world, reloadUI rebuilds, header width (#1426)
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>
2026-10-16 09:26:11 +07:00
Sam Sung 740bd27a0a The wheel walks the wheel list once, and the world takes what is left (#1422)
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>
2026-10-16 08:47:07 +07:00
Sam Sung 83bbf55db3 Objects are read by their descriptor's entry, and three fork-found fixes (#1421)
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>
2026-10-16 08:12:37 +07:00
Sam Sung eb6bb9d950 Unit events fire from field watches and packets, not a per-frame diff (#1415)
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>
2026-10-16 07:38:50 +07:00
Sam Sung 330b297606 The loose-file index lists only the folders a lookup reaches (#1413)
**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>
2026-10-16 07:22:06 +07:00
hashed-person 5c19844682 README: any 1.12.1 client locale reads its own text (#1387)
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>
2026-10-16 06:38:26 +07:00
migwynkriid 7923d67abb A new nameplate draws its first WORLD batch in the frame it's built (#1341)
**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>
2026-10-16 06:36:26 +07:00
Sam Sung 4e84da4322 Names, words, fonts and defaults follow the client's locale (#1407)
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>
2026-10-16 06:22:51 +07:00
Sam Sung 4332bcb9dc Every target select runs SelectTarget's charmed and spell-cursor front (#1400)
`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>
2026-10-16 05:47:18 +07:00
Sam Sung 7b5cfef242 Field-watch events fire per values block, never on create or reload (#1396)
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>
2026-10-16 04:52:20 +07:00
Sam Sung 407aeed544 Item counts walk mask 0; trade state ends and signals as 1.12.1's (#1395)
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>
2026-10-16 04:13:43 +07:00
Chris 8c35874790 The client dials the first IPv4 address alone, as 1.12.1 does (#1385)
**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>
2026-10-16 03:43:28 +07:00
Sam Sung 99458b32a8 Protected calls pass tainted code only inside an input event (#1388)
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>
2026-10-16 03:25:42 +07:00
Sam Sung 93e5042194 Reference at c854c4e; player-field events carry their watch's arguments (#1386)
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>
2026-10-16 02:29:22 +07:00
Sam Sung 7b608671a6 Item objects notify the action bar and trade as 1.12.1's handlers do (#1366)
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>
2026-10-16 01:54:27 +07:00
Sam Sung 831e4d3afc The client reads its locale from Wow.ini, as 1.12.1 does (#1360)
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>
2026-10-16 01:32:06 +07:00
Sam Sung b220d6a41c Debug globals, talent icons, European numbers, the protected-action gate (#1353)
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>
2026-10-16 00:37:06 +07:00
Sam Sung b727e30f31 Character model frames draw their own SetUnit subject (#1352)
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>
2026-10-15 23:55:17 +07:00
Sam Sung e6f9d93db2 The Warden deviation says why benilla cannot answer it (#1351)
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>
2026-10-15 23:43:55 +07:00
Sam Sung 20775b15f9 Tooltip lines, EditBox byte caps and slot textures as 1.12.1 has them (#1350)
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>
2026-10-15 22:59:34 +07:00
Sam Sung 14ae72269d Cooldown reads, GCD after an event, lost control as 1.12.1 has them (#1348)
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>
2026-10-15 21:47:41 +07:00
Sam Sung 623c149fde docs/DEVIATIONS.md: where benilla differs from 1.12.1, generated (#1345)
`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>
2026-10-15 20:51:34 +07:00
Sam Sung 4ab54ad3a2 Loose install files answer the opens 1.12.1 flags (#1342)
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>
2026-10-15 20:33:44 +07:00
Sam Sung e3eade8e0e Lua errors and chat gates where 1.12.1 has them (#1339)
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>
2026-10-15 20:10:50 +07:00
OmniTroid 9c6c03decc A held item's emitters ride its idle-posed bones, in the world and the booths (#1334)
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.
2026-10-15 19:57:58 +07:00
Sam Sung 78e8c27a82 Event gates read every fire site; five NPC events fire bare (#1336)
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>
2026-10-15 19:46:25 +07:00
Sam Sung 5b12d4f24b The in-flight cast slot is pushed and popped where 1.12.1 does (#1330)
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>
2026-10-15 18:40:24 +07:00
Sam Sung b816ff2f60 Update packets: self release, mover blocks, spline facing, placement (#1324)
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>
2026-10-15 18:28:45 +07:00
Sam Sung b21b49c084 The 44 Lua functions 1.12.1 registers that benilla lacked (#1322)
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>
2026-10-15 18:15:26 +07:00
Sam Sung cf1aa41e3d NPC greetings from the latch; death and shapeshift close the window (#1320)
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>
2026-10-15 17:46:23 +07:00
Sam Sung 358731f95a FontStrings wrap by 1.12.1's line kernel (#1306)
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>
2026-10-15 17:34:32 +07:00
Sam Sung f5dd4c79a0 The gates memo treats the issue and PR templates as docs (#1293)
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>
2026-10-15 16:45:29 +07:00
Sam Sung e5321beda0 NPC windows share 1.12.1's interaction latch, trade and books included (#1290)
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>
2026-10-15 16:40:07 +07:00
Sam Sung c49c201176 CVars refuse what 1.12.1's callbacks refuse; Lua arguments and numerals (#1286)
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>
2026-10-15 15:20:23 +07:00
Sam Sung 8f6162e5f1 UI core: hard line breaks, loose files first, tiling, chat link buttons (#1285)
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>
2026-10-15 15:06:19 +07:00
Sam Sung b7bc7a7e3f Contributor docs: the quick test loop, the reference pin, open gaps (#1283)
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>
2026-10-15 14:07:29 +07:00
Sam Sung 994f9f9610 reference/ vendors benilla-re's tables at a pinned commit (#1282)
`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>
2026-10-15 11:09:45 +07:00