Commit Graph

669 Commits

Author SHA1 Message Date
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
Sam Sung 77b05e34be Docs: find the reference fact in benilla-re, and take its gaps there (#1280)
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>
2026-10-15 10:05:01 +07:00
Sam Sung 94eb6a8bea TargetByName, TargetUnit and ClearTarget show their pick in the chunk (#1244)
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>
2026-10-14 17:17:29 +07:00
Mathih13 b15fb9abc6 A MessageFrame's link is text; only chat lines take link buttons (#1149)
- 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>
2026-10-14 14:57:15 +07:00
Sam Sung 5338675fc8 EditBoxes keep the client's text window; paste types per character (#1143)
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>
2026-10-13 22:46:58 +07:00
Sam Sung 537defc7a0 Link benilla-re, the reverse engineering benilla is built against (#1141)
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>
2026-10-13 21:33:17 +07:00
Sam Sung dd3983a470 Lua API edges: tradeskill filters, rolls, autocast, bindings and mail (#1102)
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>
2026-10-13 10:39:37 +07:00
Sam Sung a96977e759 Gradients tint per vertex; a layer's textures sort by handle (#1101)
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>
2026-10-13 10:30:07 +07:00
Sam Sung c77386c42e Leaving the world closes every window in the client's sweep order (#1100)
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>
2026-10-13 10:00:07 +07:00
Sam Sung 5a93d1a432 Gate failures print the failing test before the log tail (#1098)
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>
2026-10-13 08:53:30 +07:00
Sam Sung d106c124c4 Glue booth keeps its model: create re-dresses, a hidden scene pauses (#1097)
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>
2026-10-13 08:49:22 +07:00
Sam Sung 8537ce7cc9 Forced reactions answer both reaction directions and refresh unit frames (#1096)
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>
2026-10-13 08:22:53 +07:00
Sam Sung 11f6fe97c0 Stock glue: credits music, cursor hide, status number, Model pane lights (#1091)
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>
2026-10-13 07:27:36 +07:00
Sam Sung 48a62ee835 A scroll child draws inside its ScrollFrame's highlight callback (#1090)
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>
2026-10-13 06:44:42 +07:00
Sam Sung 39d1753abe Glue verbs run inside their Lua call; Update arms signal before writing (#1088)
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>
2026-10-13 05:44:14 +07:00
Sam Sung a7f331efdf Item use follows the client: bag store, bar equip, BoE hold, control (#1086)
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>
2026-10-13 05:30:39 +07:00
Sam Sung d3ac9f75bf Verbs raise their events in place; feeds push only server changes (#1085)
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>
2026-10-13 03:54:00 +07:00
Sam Sung 13b7bb50f4 Stock character create: its 24 verbs, state 5 and the booth preview (#1079)
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>
2026-10-13 03:23:04 +07:00
Sam Sung 0b4b3ddc2c Item locks held per item at the client's own lock and unlock sites (#1074)
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>
2026-10-13 03:02:43 +07:00
Sam Sung e175209f28 Stock EnterWorld: its guards, the refused login and the glue reseat (#1067)
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>
2026-10-13 01:50:44 +07:00
Sam Sung 71d65a4883 Stock character delete and rename, with the client's name check (#1060)
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>
2026-10-13 00:28:58 +07:00
Sam Sung 0127b8690f Stock AddOns screen on the registry; SMSG_ADDON_INFO read in full (#1043)
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>
2026-10-12 23:14:01 +07:00
Sam Sung 79842423f2 Cursor verbs follow the client's arms: bar, bags, doll and trade (#1042)
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>
2026-10-12 22:24:24 +07:00
Sam Sung 2af917044d Stock login fetches the server alert; LaunchURL behind the gate (#1040)
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>
2026-10-12 21:42:29 +07:00
Sam Sung 5c7eb5fc75 Stock glue screens draw their 3-D scenes through the glue booth (#1039)
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>
2026-10-12 21:13:47 +07:00
Sam Sung eb63c8861b Cursor runs on ClearCursor and the setters' own event tails (#1038)
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>
2026-10-12 20:47:20 +07:00
Sam Sung bb82dd1ac8 Lua getters answer the reference's miss shapes; tooltip setters' flag (#1030)
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>
2026-10-12 20:28:35 +07:00
Sam Sung 781bf36ab3 Key input follows the client's walks; stock login takes typing (#1027)
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>
2026-10-12 18:07:34 +07:00
Mathih13 7547081a58 fix: a same-map teleport shows no loading screen, as 1.12.1 does (#1018)
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>
2026-10-12 17:46:50 +07:00
Sam Sung d759008172 Logout returns to character select on the same world connection (#1022)
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>
2026-10-12 17:38:05 +07:00
Sam Sung aba120cdcb Lua verbs fire their events before returning, in the reference's order (#1015)
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>
2026-10-12 16:14:10 +07:00
Sam Sung 24c7cab983 Stock character select roster; the screen asks for the list (#1014)
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>
2026-10-12 15:59:54 +07:00
Sam Sung 0f505a2ebf RegisterEvent ignores case; a frame's first resize precedes OnLoad (#1001)
- `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>
2026-10-12 15:08:07 +07:00
Sam Sung 720dcf8188 Lua bindings return 1/nil and the reference's value counts (#1000)
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>
2026-10-12 15:01:32 +07:00
Sam Sung 1b335717c3 Addon loading and .toc parsing follow AddOn_Load and Toc_Parse (#992)
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>
2026-10-12 14:28:48 +07:00
Sam Sung 1efd9a7163 Lua bindings coerce numbers, flags and colours as 1.12.1 does (#991)
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>
2026-10-12 13:41:47 +07:00
Sam Sung b2926edf53 Stock realm list and wizard; realm park cancels cleanly (#985)
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>
2026-10-12 12:46:31 +07:00
Sam Sung 0c58d85cf9 Layout file saves the rect; bubbles re-sort on change; resize Deviation (#981)
- **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>
2026-10-12 12:26:11 +07:00
Sam Sung 8ccaa7b411 An unattended run keeps its state in a fresh folder of its own (#980)
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>
2026-10-12 12:15:31 +07:00
Sam Sung a329f19e06 Reputation lines, NPC greeting gates, and packet bytes in the trace (#976)
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>
2026-10-12 11:54:21 +07:00
Sam Sung 7158c58575 Hidden frames defer their layout; GetWidth answers the set size (#973)
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>
2026-10-12 11:06:04 +07:00
Sam Sung 45b7914c9e The stock login screen logs in; realmd 0x0E is a success (#970)
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>
2026-10-12 10:28:08 +07:00
Sam Sung 133414f5c1 WOW_GLUE_STOCK=1 loads and draws the stock GlueXML (#969)
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>
2026-10-12 08:40:34 +07:00