Several of 1.12.1's client-side cast gates were missing, so the server refused casts the client refuses itself:
- The caster validator (`0x6094f0`) now runs its rungs in the bytes' order. Indoors and outdoors are judged by the entity outdoors test (`0x6706f0`).
- The crowd-control exemption checks every aura type (`0x6e9ca0`).
- The binder stops at the first arm that holds, with that arm's guards (`0x6e5b40`).
- TryCast refuses three more cases:
- ranged without ammo, which also greys the button;
- ranged without a ranged weapon (`0x6e4e13`);
- the while-looting effects (`0x6e4ca2`).
- A cast on cooldown with no target says not ready.
Tests pin each rung and the greying through the bar's own site.
The learn and tame rungs remain.
Part of #1202Fixes#1581Fixes#1586Fixes#675Fixes#1199Fixes#1294Fixes#2132🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The taxi map's routes, prices and node list differed from 1.12.1's.
- The search (`0x4dbce0`) weighs each hop by its flight path's points. It runs depth first over the map's slots, and keeps the first route on a tie. Gadgetzan to Sun Rock now flies through Freewind and Crossroads for 10s70c.
- A known node with no route is listed as NONE, with the start fare.
- The other faction's flight points are skipped by race (`0x4dbd2d`).
- `TakeTaxiNode` refuses a mounted player (0xb2), the current node (0xaa) and a node with no path (0xab).
- A closed map keeps its slots (`0x4dc0f0`).
- Out-of-range slots raise or answer INVALID, and the coordinate getters read bare numbers.
Tests replay the issue's routes over the shipped tables and pin each verb's refusal.
Fixes#204Fixes#205Fixes#1236Fixes#1576Fixes#1229🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The cast-failure reporter skipped arms of 1.12.1's errorId chain and its pet text. A move also cancelled a cast from the wire word, not from the press.
- The player's chain now runs whole, in the bytes' order (`0x6e1aab`-`0x6e1c5f`). Every id, 0x2c included, formats through its key's live global (`0x4967d7`), and an empty line is still logged.
- Spells aimed at the pet show their `_PET` text (`0x6e1b1b`). An item-target refusal reads `ERR_INVALID_ITEM_TARGET`.
- The full-power line names the player's own power (`0x6e211f`). The pet's handler stays silent when it has no record or finds no name.
- A movement command press cancels the cast whoever drives (`0x515090`). Only a channel needs the player as the mover (`0x515180`).
Tests pin each arm, the pet lines and the cancel through the controller.
Fixes#2135Fixes#1582Fixes#1584Fixes#1637Fixes#1201Fixes#2141Fixes#2142🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In 1.12.1 a verb's effect applies inside the call, whether from a click, `OnUpdate` or an event handler. Here, calls from `OnUpdate` and handlers queued until the next lend, so `TargetByName(name)` then `UnitName("target")` read the old target.
- Every system that runs Lua lends the call host around its body, in one ordered VM pass; the cross-entry queue is gone.
- Seams that changed units through `Commands` queue unit edits, flushed ahead of their readers, so sync points stay at 54 (main: 53).
- Late drains apply before the paint; a CVar or follow ask lands the same frame.
- The actionable ambiguity ceiling falls from 3079 to 1750.
Tests: an `OnUpdate` cast and a handler's cast arm inside the call, three same-frame drains, and a census that each schedule flushes after every edit user.
Lua run inside an engine turn still waits for the turn's end.
Part of #1993🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Five comments stated a wrong reference fact or named code that does not exist:
- `shown_pet_power_type` cited `UnitPowerType`'s member-record read (`0x5179d2`) as its pet leg, which is `0x5179ff`.
- `race_creature_type.rs` named a path that does not exist.
- `drag.rs` cited the unreached copies at `0x425666`/`0x425766`, where the live converter is `0x766f70`, called from `0x766103` and `0x7662f3`.
- `handler_prof` claimed an app-data refusal benilla-mlua does not have.
- `MacroView` cited the save format by file offset, where its address is `0x84cb60`, pushed at `0x4f0b61`.
The NVIDIA present `Deviation:` also gains where the fault is: the driver's own DXGI present, with NVIDIA's present layer disabled too.
Comments only, so no test changes.
Fixes#671Fixes#676Fixes#1106Fixes#1991Fixes#1245🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The update ingest did not do what 1.12.1 does with the same bytes:
- It dropped the header flag.
- It released and destroyed held objects at once.
- It re-asked records on every create.
- It replayed enchant times at an item's write.
- It read character names uncapped.
- It ignored `SMSG_PLAYER_SKINNED`.
Reference: the header byte runs a first pass over guids with top nibble 6 or 8 (`0x4651c1`, `0x4660d8`). A held object is only marked release-pending (`0x464920`) until its hold drops (`0x468410`). Asks come from the create hooks alone (`0x5fb880`, `0x5f7d81`). Enchant times replay when the record applies (`0x5d85b7`). Names read with a 0x30 cap, and an overrun empties the list (`0x5b4523`). `PLAYER_SKINNED` fires with the byte's truth (`0x5e7d52`).
Changed: each follows these, and tests pin each. The faults the passes have on malformed packets are recorded, not copied.
Fixes#2053Fixes#2052Fixes#2054Fixes#2055Fixes#2083Fixes#87🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Four TradeSkill and Craft answers differed from 1.12.1:
- Recipes of one colour listed the lowest item level first.
- `SelectTradeSkill` on a header kept the old selection.
- The sub-class folds ignored a non-header row.
- `GetCraftButtonToken` always answered `CREATE`.
Reference: the recipe comparator returns +1 when the first row's ItemLevel is smaller, so the higher level sorts first (`0x4fd4e6`). `0x4fc9b0` stores 0 for a header or an out-of-range index. The folds raise unless `0x4fd650` finds a header, and below 1 they fold every group (`0x5000c0`, `0x500140`). `0x4f6c90` answers the word at the open craft type in the table at `0x84d0f4`: USE, TRAIN, DISGUISE, ENSCRIBE.
Changed: each follows these, and tests pin each.
Fixes#155Fixes#1260Fixes#1261Fixes#876🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Three Lua surfaces answered unlike 1.12.1:
- A Font object's `[0]` was a number, so saved variables and `type(v[0])` guards treated fonts unlike every other object.
- `CopyFontObject` pushed the source's values and held them.
- Guild and charter names went to the server with no client name check.
Reference: the saved-variable writer reads `[0]` as a light userdata (`0x704623`). `0x7841d0` merges what the source holds, then holds nothing (`0x7841f4`) and follows the source's font. `0x6c9b70` runs the name validator with a space as its one extra character, the profanity lists on, and a 24-unit cap. `0x4f51ec` maps its codes to the refusal lines.
Changed: Fonts take the object id counter, and the loader's `parent=` tests the Frame type. A font node keeps its held word apart from its values. The guild check runs before the send. Tests pin each.
Fixes#1997Fixes#1983Fixes#1399🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Macros were held by index and kept in creation order:
- A sort or delete moved what a bar slot held.
- `EditMacro` trimmed and capped differently and returned nothing.
- `CastSpellByName` took the wrong quoted span.
- A macro casting a pet spell never bound the pet's book.
Reference: each scope sorts by the CRT `qsort` with `_strnicmp` (`0x73f727`, `0x4f0e10`). Slots hold the macro id, with bit 24 set for a character macro, and `DeleteMacro` clears every slot holding it (`0x4e5d10`). `EditMacro` returns the index after the re-sort. The quoted name ends at the next quote (`0x4efef0`). Names bind the player's book, then the pet's (`0x4b3bc0`).
Changed: the table, the slots, the cursor and the saved files follow these, and tests pin each. Bar slots an earlier build saved for character macros are rewritten once to the new ids.
Fixes#1502Fixes#1505Fixes#1187Fixes#196Fixes#197🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Clicks and attack starts passed gates 1.12.1 refuses at:
- A right press with a cursor payload went on to the world.
- StartAttack ran without control or while pacified.
- A charmed player's right-click selected and interacted.
- Talk ignored shapeshift forms.
- A plate right-click while targeting skipped the interact.
- A `/target` miss said nothing.
- `/follow` read the cast slot.
Reference: `0x7662c0` runs the press pre-hook `0x492b50` before any hit-test, which drops a payload and consumes the press. StartAttack returns on `0x20000` or lost control (`0x5ecbaa`, `0x5ecbc0`) for every caller. The router tests `CHARMEDBY` first (`0x481f98`), which plates skip (`0x4949f0`). Talk tests form flag `0x8` (`0x5f0150`). `0x489dbd` picks the miss message, and `/follow` reads `UNIT_CHANNEL_SPELL` (`0x60ffb9`).
Changed: each gate follows these, and tests pin each.
Fixes#321Fixes#381Fixes#785Fixes#1305Fixes#1397Fixes#52Fixes#1326🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The readers differed from 1.12.1:
- A TGA's descriptor bit 4 mirrored it.
- A 32-bit TGA with no alpha bits drew with byte 3 as alpha.
- Spell-text slot digits 0 and 4-9 read a wrong slot and `$g`/`$l` parsed their own way.
- MCNR normals were named `x, z, y`.
- A DBC record of the wrong size was read.
Reference:
- No TGA read path tests bit 4.
- `0x5a37e0` admits truecolour only at 24 bits besides alpha.
- A refused texture draws opaque white (`0x44a260`) at sizes `0x4484ae` admits.
- Only '1'-'9' is a slot digit (`0x507827`), and `0x508180`/`0x5082a0` parse the selectors.
- MCNR is X, Y, Z (`0x6aff10`).
- The loader refuses any other record size (`0x545e36`).
Changed: each reader follows these; three TGA edge cases carry `Deviation:` lines. Three BigWigs ready-check icons now draw white, as in 1.12.1.
Fixes#1679Fixes#1693Fixes#1684Fixes#710Fixes#799Fixes#15Fixes#17Fixes#2102🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Seven console commands were not registered: `port`, `worldport`, `setrawpos`, `whois`, `togglehelm`, `togglecloak` and `cameraClip`. Three CVar rows also left the reference: `SoundBufferSize` shipped 100, `WorldDetail` was marked as a 1.12 CVar, and `gamma` shipped `1.000000`.
Reference:
- `0x4049e0` registers the movement and toggle commands, `port` and `setrawpos` splitting with the comma (`0x82ef90`) and `worldport` without it (`0x82ecc8`).
- `whois` sends its name (`0x5adee0`).
- `cameraClip` flips or sets its word (`0x50ddc0`).
- `0x457520` registers `SoundBufferSize` 50 above 1500 MHz, which current x86 parts read, and `0x402d70` registers `gamma` as `1.0`.
Changed: each command is registered with its category and its sends, which have byte-exact goldens. `SoundBufferSize` and the mix-ahead ring run at 50, `WorldDetail` is benilla's own row, and `gamma` registers `1.0`. Tests pin each.
Fixes#1639Fixes#1663Fixes#1749Fixes#1270Fixes#1279Fixes#1281🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The game-speed, game-time update and game-time set messages were dropped. The login time packet also applied a malformed body and kept unset fields as 31 and 63.
Reference: each handler reads its body, refuses one not exactly its length with the "Malformed message" line, and decodes an all-ones field as -1 (`0x642b30`):
- **Set** steps back a minute and advances (`0x6434d0`), so an unset field lands at 00:01.
- **Sync** steps forward or banks a skip count (`0x6436c0`).
- **Frame tick** pays the skip (`0x6435a0`). It runs from world entry to logout (`0x401684`, `0x401f04`).
- **Rate** is clamped to [1/60, 60], NaN kept (`0x643820`).
Changed: the four bodies parse with exact-length goldens, and the clock is a port of the reference's, ticking only in the world. The day number uses UTC civil days, a written deviation from `mktime`. Tests pin each step.
Fixes#2056Fixes#2082🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Combat-log lines read wrong in nine ways:
- Your split damage and extra attacks were damage-typed.
- The XP line ignored the group float.
- An item-name wait dropped its line after 120 frames.
- Game objects and items printed "Unknown".
- An XP kill also printed a death line.
- Templates with unused or positional arguments were refused.
- Open-lock effects wrote nothing.
Reference: split form 2 (`0x62df92`) and the extra-attack line (`0x62b190`) type through the buff table unless the spell is hostile. The XP line compares `ftol(xp / float)` with `xp` (`0x637cd0`). The wait list never expires (`0xc4e208`). `0x6264e0` names by type mask. The XP packet marks the victim (`0x60a0e6`). `vsnoprintf` binds `%N$` and skips unused arguments (`0x669888`). `0x62dc10` writes the open-lock line.
Changed: each follows its formatter; one `fill_exact` serves both fills. Tests pin each.
Fixes#2067Fixes#2073Fixes#2068Fixes#2071Fixes#2039Fixes#2041Fixes#2044Fixes#2043Fixes#2046🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Four group behaviours were wrong:
- `IsRaidLeader` and `IsPartyLeader` misread a raid leader outside your subgroup.
- A loot method, master or threshold change printed no line.
- The uninvite menus always kicked by name.
- `SetRaidSubgroup` outside 1-8 did nothing.
Reference:
- Both leader tests compare the leader cell with your guid; only `IsPartyLeader` guards a zero cell (`0x4e9141`).
- The group list stores the loot cells and prints each change (`0x4e86f0`, jump table `0x4e880c`). The master line needs a cached name; the threshold reads `ITEM_QUALITY<n>_DESC`.
- `0x5e9510` kicks a held player by name and anything else with `CMSG_GROUP_UNINVITE_GUID`.
- `SetRaidSubgroup` raises its usage line first (`0x4bb9fe`).
Changed: the leader tests read the guids, the list stores and prints the loot cells, the kicks follow `0x5e9510`, and the subgroup is checked first. Tests pin each.
Fixes#141Fixes#70Fixes#267Fixes#266🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The action bar answered and signalled where 1.12.1 does not:
- `IsActionInRange` measured distance before the target gates.
- `GetActionCooldown` answered enable 1 for slots that reach no cooldown query.
- `IsCurrentAction` held item slots against nothing.
- The cooldown and usable events fired off a button's own state.
Reference: `0x4e56f0` runs `0x6e4440`'s relation and implicit-target gates first. Enable starts 0 (`0x4e6f0d`) and only the query sets it (`0x6e1406`). The cast keeps its item's guid (`0xcecab0`). `BAG_UPDATE_COOLDOWN` comes from `0x4f93d0`'s callers, `ACTIONBAR_UPDATE_USABLE` from `0x4e5c00`'s four, and `SPELL_UPDATE_USABLE` from `0x4b31c0`'s fourteen. Placement signals only 213.
Changed: a usable cache refreshes at those producers. The push half runs before the unit snapshots and the events after them, so handlers read this frame's state, after the control edge. Tests pin each answer and the order.
Fixes#1528Fixes#1532Fixes#1537Fixes#1596Fixes#1597🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Four glue-screen behaviours were wrong.
- **Realm names.** The list showed realm names raw.
- **Agreement flags.** The terms-agreement flags were reseeded from the CVars on every glue VM.
- **New characters.** A new character's record kept level 0 and no new-character flag.
- **PreviewRider.** Its comment misstated the sheathe.
Reference: the realm list fill turns each of `"*/:<>?\|` into a space and cuts trailing spaces and dots (`0x5b2450`, `0x5b24b0`). The agreement flags are seeded once per process. A level-0 record stores level 1 and sets `+0x10a` (tested at `0x46c203`), and that character enters at its race's intro eye with no loading tip. The select build holds each weapon in hand (`0x472c8a`, `0x47a15d`).
Changed: names are cleaned at the fill, the flags live in one shared set, and the roster stores the flag and looks up the intro eye at the pick. Tests pin each.
Fixes#1215Fixes#1317Fixes#1318Fixes#1217🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Combat text missed most of the words 1.12.1 raises. A damage shield, an interrupt, extra attacks, a resisted proc, an immunity, a dispel, an aura, environmental damage, a drain and a reputation change about you wrote their log line but no `COMBAT_TEXT_UPDATE`. `SPELL_ACTIVE` never fired, and a swing's `UNIT_COMBAT` skipped the victim-state table.
Reference: each formatter raises its word at its own unit test after the line (`0x62cb5d`, `0x626b53`, `0x62db7d`, `0x628b8a`, `0x62d3ab`, `0x628daa`, `0x62d5ce`, `0x62b74f`, `0x62ac74`, `0x627b94`, `0x62c661`). The usable refresh walks the reactive list `[0xb71150]` (`0x4b31c0`). A swing's action comes from `0x83de28`, environmental damage sends a WOUND (`0x624f83`), and the float resolves its global (`0x703bf0`).
Each word rides the existing line path. The reactive list lives from arrival to arrival. Tests pin each word and gate.
Fixes#2027Fixes#2028Fixes#2029Fixes#2030Fixes#81Fixes#82Fixes#370Fixes#2032Fixes#2040🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Unit, spell and aura tooltips draw what 1.12.1's builders draw:
- Unit: ?? for a hostile player ten up, the drunk level, the owner, Beast Lore, a group member's Offline or zone line, Resurrectable, contested PvP, Skinnable by skill, GlobalStrings wording.
- Spell: rank on pet and stance bars, cast speed, per-school cost terms, skill-based level terms, and the totem, use-all and reputation lines.
- Aura: stacks multiply, the caster's level scales, raidFilter applies, and the player's slots are read.
Tests pin each line.
Fixes#220Fixes#221Fixes#588Fixes#590Fixes#591Fixes#594Fixes#599Fixes#605Fixes#662Fixes#663Fixes#664Fixes#1184Fixes#1185Fixes#1541Fixes#1542Fixes#1553Fixes#1554Fixes#1558Fixes#1560Fixes#1561Fixes#1566Fixes#1567Fixes#1570Fixes#1573Fixes#1574Fixes#1579Fixes#273Fixes#682🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The logon and world connections and the session clocks follow the reference:
- A world packet above 0x7fff takes a 3-byte size header, read and sent (`0x5b674d`, `0x5b56da`).
- A realm address with no port dials 9090, cut at its first colon, its port read by `_atol`.
- The logon dials one of the realmlist's addresses at random.
- GetNetStats reports the world link's KB/s.
- A transfer abort shows its TRANSFER_ABORT line.
- The game-time speed is held to 1/60 to 60.
- GetGameTime answers -1, -1 until the server sets the clock.
- The ping sequence carries across reconnects.
The handshake timeout, the 23-bit refusal and a port on a named logon server carry `Deviation:` lines. Byte-exact frame tests cover both header forms.
Fixes#1107Fixes#1657Fixes#1662Fixes#1669Fixes#1193Fixes#1656Fixes#1672Fixes#1677🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The update-object handler now runs 1.12.1's two passes:
- It writes every block of a packet first, then runs the field watches at the packet's end against each object's last write. A packet with two values blocks for one object fires once.
- A re-create notifies every field.
- An object that leaves range is released with its fields and reactivated without its create hook, not destroyed.
- A first create runs no watch, so the flag tutorials fire only on a change, from their watchers.
The guild tag rides the same core:
- `TABARD_CANSAVE_CHANGED` comes only from RefreshGuildTag.
- A `GetGuildInfo` miss waits and signals `PLAYER_GUILD_UPDATE`.
- A new `PLAYER_GUILD_TIMESTAMP` re-asks the guild.
Tests pin each pass, the released table and the guild waiters.
Fixes#1391Fixes#1392Fixes#1499Fixes#1418Fixes#1420Fixes#1416Fixes#1417🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The item tooltip setters (bag, inventory, link, merchant, trade, mail, bank, petition) draw the lines 1.12.1's builder draws:
- spell lines for every trigger, and "Already known" by the taught spell;
- PvP rank and medal, zone and map, the gendered reputation label;
- a recipe's product and reagents, and a charter's signatures;
- a link's enchant, and a trade item's proposed enchant;
- the creator, and a second click on the same link hiding the tooltip;
- the merchant price and repair cost.
Charges and durability come from the object, the block or the template, as `0x52d9bd` and `0x52cca8` choose, so a link, a loot row or a merchant item shows no durability line. Tests pin each line and its gate.
Fixes#153Fixes#154Fixes#283Fixes#1183Fixes#1497Fixes#1498Fixes#1501Fixes#1551Fixes#1552Fixes#1564Fixes#1565Fixes#1569Fixes#1571Fixes#1572Fixes#1838🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The combat log, combat text and UNIT_COMBAT follow the reference's handlers:
- An unresolved unit is named at once as the unit getter's miss, not held and dropped.
- Each spell formatter's `Attributes & 0x180`, unknown-spell and empty-name gates apply.
- SPELL_GO's miss list and a missile's arrival write their miss lines, words and UNIT_COMBAT.
- Melee words follow the victim-state table.
- Hostile, party and pet are classed by the reference's tests.
- Honor and XP lines take their side, gender and gates from the bytes.
- UNIT_COMBAT queues at its place against its line in each handler: a swing's flash goes ahead of its line, a spell hit after it.
Tests pin each order and gate.
Fixes#45Fixes#83Fixes#84Fixes#208Fixes#210Fixes#211Fixes#353Fixes#357Fixes#367Fixes#382Fixes#383Fixes#384Fixes#398Fixes#399Fixes#1172Fixes#1240🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The test gates ran `cargo test`, which runs the workspace's test binaries one after another. On a warm tree the workspace's tests take 103 s that way, and 59 s under cargo-nextest, which runs every binary's tests in one pool; all 10,411 pass both ways.
`check.sh` and `gates.sh` now run tests through `scripts/unit-tests.sh`: cargo-nextest where it is installed, else `cargo test` as before. nextest runs no doctests, so `scripts/doc-tests.sh` runs those of the library crates in scope. Each gate keeps its environment (`BENILLA_REQUIRE_DATA`, `WOW_DATA=`, the skip log), and a failing run's summary now catches nextest's `FAIL` lines. CI is unchanged.
Test: this land's own gates run under it.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A cast's refusal line, `SPELLCAST_FAILED`, `CURRENT_SPELL_CAST_CHANGED` and `IsCurrentAction` answered after the verb returned, and in-flight state was cleared from many places, each by its own rule.
1.12.1 keeps one in-flight slot and one saved strike (`0xceca88`, `0xcecaa8`). The attempt pushes it, the cancel (`0x6e4940`) and the cast result (`0x6e741a`) pop it, and the report runs inside the call (`0x6e1a00`).
benilla now has that slot, and every cancel goes through it:
- auto-repeat sends its cast before ATTACKSTOP;
- a right-click skin casts through the attempt;
- a click the cursor cannot bind deselects;
- a second Esc re-sends a strike's cancel, the old deviation removed;
- a channel's button unchecks as it begins.
Tests: each behaviour, failing without it; a live trace shows the cast, its cancel and a refusal's cancel in the reference's order.
Fixes#1985, #1990, #1989, #295, #1325, #1986, #1585, #2012, #2015, #1695, #2014, #2021🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On Windows, `vm_parity`'s cos sweep failed on one item, `cos(1e300)`, whose last digit was the host libm's. 1.12.1 never computes it. `math_sin`, `math_cos` and `math_tan` are a bare `fsin`, `fcos` and `fptan` (`0x7fae1d`, `0x7fae4d`, `0x7fae7d`), and a finite operand of 2^63 or more is outside the x87's range and is left unchanged:
- `math.sin(1e300)` and `math.cos(1e300)` answer `1e300`;
- `math.tan` answers `-1.#IND`: `fptan` pushes nothing, so the pop at `0x7fae84` takes the operand and `0x7fae86` stores from an empty stack.
benilla-lua now answers the same. The sweep's recorded answers for that argument are the reference's, the same on every platform.
Test: the parity sweep's `1e300` items for `math.sin`, `math.cos`, `math.tan` and their degree forms.
Fixes#1999🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On Windows, `benilla-app`'s tests died with an access violation (`0xc0000005`) at the second test that opens the audio device. cpal 0.17.3 keeps one device enumerator for the process, made in the first caller's single-threaded COM apartment; when that thread exits, its `CoUninitialize` unloads the DLLs behind it, and the next call goes through a dead vtable (RustAudio/cpal#1302, open, the same `windows-latest` fault).
Every way into cpal now goes through one `host()`, which on Windows first anchors COM's shared multithreaded apartment for the process (`CoIncrementMTAUsage`, the pattern SDL uses) and joins it to build the enumerator, so no thread's exit can tear it down.
Test: two joined threads each read the device and agree.
Fixes#1984🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Unit relations answered from scattered stand-ins: `UnitCanAttack` ignored which unit attacks, `UnitReaction` its second unit, `UnitCanAssist` any pair without the player, and duels, FFA, contested guards and charm read wrong.
One model now follows the reference's functions rung by rung, two units in, argument order kept: `0x606170`, `0x6061e0`, `0x606530`, `0x606980`, `0x6066f0`, `0x605ff0`, `0x606ba0` and `0x6067f0`. Every caller asks it (ring, cursor, TAB, plates, tooltip, cast binder, usable, inspect, follow), and the six Lua verbs answer from it.
Tests: duels both ways and the opponent's pet, FFA, contested guards, a charmed player, cooperation, shipped-DBC reputation rows, and the follow and inspect gates.
Fixes#50Fixes#132Fixes#308Fixes#309Fixes#317Fixes#826Fixes#1323Fixes#1545Fixes#1588Fixes#1602Fixes#2008🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The party and raid roster as Lua and its events see it did not follow 1.12.1: our raid row came first, a kick or disband kept the old members, status bits and chain ends read the wrong source, and events fired on edges, merged per frame.
Per `SMSG_GROUP_LIST 0x5e6a40`, `RebuildRaidRoster 0x4ba5f0` and the name callbacks `0x4ba1b0`/`0x4bad90`: our row is last and held only with members; endings clear at once (`0x4e84a0`); views read the descriptor or the record; and every signal is logged where the reference raises it, then fired once, in packet order.
Tests: wire-order signal sequences, solo, party, a raid with us in a later group, kick, disband, worldport, and names that arrive late.
Fixes#263Fixes#265Fixes#395Fixes#670Fixes#684Fixes#701Fixes#704Fixes#705Fixes#712Fixes#715Fixes#717Fixes#2002🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A Lua cast was queued for a later frame, so on the next line of the same script `SpellIsTargeting()` read the old state and `SpellTargetUnit` found nothing to bind, as FuBar_CorkFu and SmartRes do.
In 1.12.1 `CastSpellByName` reaches `ArmCastTargets 0x6e5250` through direct calls (`0x4b4ab0` → `0x4b3300` → `0x6e5a90` → `0x6e4b60`), and `SpellIsTargeting` (`0x6e6cd0`) reads the word the arm stores.
The input pass and key bindings now lend the engine's call host to the VM around each script. The 20 verbs that queued apply inside the call, each under its own input events, and `CURRENT_SPELL_CAST_CHANGED` fires there. Calls an unhosted entry leaves apply at the next lend's start. The one unsafe lend is in `script/host.rs`.
Tests: CorkFu's and SmartRes's lines arm and bind in the call; a refused cast does not arm; every routed verb runs with a host lent.
Fixes#447🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sets the workspace version to 0.3.0 and refreshes the lock, so the client's startup line reads `benilla build 0.3.0`. The toolchain pin stays at 1.98.1, the compiler the release checks ran on.
Checked on this tree: the Linux and Windows compile, the Vulkan run, and a live login and logout against a local server, whose log reads `benilla build 0.3.0`.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After a key binding moved between action buttons, the buttons it left kept showing its label, so several buttons read the same key.
1.12.1 does the same: when a button has no key, `ActionButton_UpdateHotkeys` (`ActionButton.lua:129-140`) clears the label only for an empty slot or a ranged action, and writes nothing for an action with no range (a stance, a self buff), so the old label stays.
This is a deviation, in the layer's `FrameXMLFixes.xml`: the stock function runs unchanged, then a button with no key and a no-range action has its label cleared. A test moves Shift-2 across three stance buttons and checks each one it leaves goes blank; without the fix the first still reads `s-2`.
Fixes#1842🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Since #1959 a bar texture cuts its drawn rect only when its install set the texcoord-modifies-rect flag. The nameplate attached its fill by hand, never through that install, so the fill stayed full whatever the health.
1.12.1 builds the fill with the bar's own path verb: the plate setup calls `0x783140` with the fill's path on layer 2 (`0x7cb31a`-`0x7cb329`), which pins it to the bar and installs it through `0x7831f0`, the step that sets the flag (`0x783220`).
The plate now builds its fill through that same path leg, lifted into a model-level function, so it is installed, flagged and pinned like any bar texture. A test drives a plate at 40, 20 and 10 of 40 health and checks the drawn fill at full, half and a quarter width; it fails without the fix.
Fixes#1969🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Both verbs answered whatever the container snapshot held, so the keyring, the bank and the backpack answered as ordinary bags.
1.12.1: `GetContainerNumSlots` (`0x4f9560`) answers 16 for the backpack, 24 for the bank, the bag's size for 1-10 and 0 for anything else, the keyring included (`0x4f95fb`). `GetBagName` (`0x4fa670`) answers `BACKPACK_TOOLTIP` for the backpack (`0x4fa6f0`), the bag item's name for 1-10, and nil for the bank, the keyring and the rest (`0x4fa712`).
Now both apply those ranges, the backpack is named by its global, and the snapshot no longer carries English names for the backpack, bank and keyring. A test pins every range. The keyring stays readable through `GetKeyRingSize` and the item verbs.
Found in Thorsten42/manila@a9fffcb5df.
Fixes#1967Fixes#372🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Thorsten42 <3400178+Thorsten42@users.noreply.github.com>
1.12.1 resolves every name through one function, `0x493aa0`: party and raid passes for members with no object held, then every held object its mode's filter passes, feeding one matcher (`0x493c60`). benilla had partial copies: the app's scan ran the object pass only, SetLootMethod the roster passes only, and Promote, assistant, duel and emote lookups matched exact names.
`benilla_ui::script::by_name::resolve` now runs the passes with each caller's typemask, mode and exact flag: TargetByName, /target, /assist, /follow, DoEmote, SetLootMethod, PromoteByName, PromoteToAssistant, DemoteAssistant and StartDuel. It answers nothing with no active player (`0x493ae0`). Mode 2's filter is `CanAssist` and alive (`0x493eca`).
The roster passes stay off for /target, /assist and /follow until the selection can hold a guid (#1965).
Tests per caller: prefix, a held stranger, a dead groupmate, a miss, no player. A byte audit re-derived the call sites and the matcher.
Fixes#1964🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Method setters, bindings and the nameplate solver now gate their arguments as 1.12.1 does.
- Setters given a non-number raise their own usage line; SetTextHeight, SetFrameLevel, SetMaxLines and SetHistoryLines raise on the values 1.12.1 refuses (`0x87c750`, `0x879628`). XML `historyLines="0"` still loads, through the ring setter (`0x77cd90`).
- Bindings gated by `lua_isnumber` take 1.12.1's miss path: nil, a default, or no values.
- SetLootMethod follows `0x4e92a0`: group and leader gates, threshold argument 2 except for master, 2 on a miss, master names resolved by prefix (`0x493aa0`), errors for an unknown method or looter. SetLootThreshold takes the same gates.
- Quest watches fall back to the last selected quest; MessageFrame times store unclamped and expire to 0 (`0x786323`).
- The placement bound is `0x509dd0`'s 32-bit product, so a zero-width rect no longer panics.
Credit to kalistoking/benilla-twow@5b5374bc for #1956. A byte audit re-derived every site.
Fixes#1953Fixes#1955Fixes#1956Fixes#408Fixes#415Fixes#416🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Beside the frame size and point methods (#1951), 1.12.1 puts every other script length through the screen-unit law: a setter stores `S(v)` and a getter answers `G(l)` through `K1` (`0x41ad70`, `0x41ae40`, `0x41ae60`). benilla stored and answered these as given.
- Resize bounds, hit-rect and text insets, backdrop lengths, pushed text offset, text extents, font heights (Button:GetFont through `0x79f3b0` too), shadows and spacing follow the law.
- A `<Rel…>` XML length is stored as parsed, in layout units (`0x6f2037`-`0x6f2388`).
- The tooltip sizer keeps its pad, line gap and wrap cap at 53 bits, as `0x41ae60` leaves them in `st0`.
- User-placed frames re-seat from the layout cache when the cells change, over what the scripts last built.
`SetMinResize(100, 100)` now reads back `G(S(100))`, not exactly 100, as in 1.12.1. Tests pin each kind against the law at 4:3 and 16:9; a byte audit re-derived each site.
Fixes#1942🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Since #1932 every Model frame drew the error cube: the VM queued a pane's file by its match key, `model_key`, which drops the extension, and the host loaded that key as the path, which the cache's extension rule (`0x706a50`) refuses (`0x70737f`).
The queue now carries the pane's own path beside the key, and the host opens that path through the rule: `.mdx`, `.mdl` and `.m2` load the `.m2`, and a refused name stands the cube. `model_key` is now the file the cache opens, so a refused name keys as the cube and never shares facts with its valid twin.
Tests: the three spellings of the minimap arrow load `interface/minimap/minimaparrow.m2`; `Creature\Wolf\Wolf` and `Foo.blp` load and key as the cube; the minimap's children queue their stock models.
Found in Thorsten42/manila@103bbe4.
Fixes#1958🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Ten differences from 1.12.1 in the UI core.
- StatusBar range and value follow `0x783270`/`0x7832d0` (a reversed pair collapses, a value before a range is dropped); the health watchers set both each update; the fill draws once both are set, its coordinates built from scratch (`0x7833c0`).
- A full collection runs before PLAYER_LOGIN (`0x490941`); the addon harness runs the client's world-entry cascade.
- Every index argument raises its own usage line (`0x6f34d0`, then the binding's literal).
- Strata, layer and frame-type names match whole (`0x6ee32c`); stack lines name unnamed functions by global (`0x6fbf90`).
- SetCursor takes a custom file (`0x523c40`); EditBox accepts OnInputLanguageChanged (`0x77a40d`).
- debugprofilestop answers milliseconds (a Deviation: multi-core 1.12.1 returns cycles).
A byte audit re-derived every address.
Fixes#1178Fixes#1915Fixes#1917Fixes#1908Fixes#1839Fixes#1869Fixes#1170Fixes#1105Fixes#1186Fixes#1952🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SimpleHTML's element fonts, a plain frame's direct FontString, and a model pane's camera differed from 1.12.1.
- SimpleHTML's four element fonts are font nodes (`0x789e7c`) its blocks link to, P's when an element holds no face (`0x78ae29`), so a later font change reaches pages already shown and MONOCHROME reaches the text; the 16 font methods are the Font object's own (`0x79ef10`-`0x79fd70`). The loader links `font=` and reads `file=` before `hyperlinkFormat` (`0x78a2b2`).
- A FontString directly under a plain frame builds nothing; the base loader has no arm for it (`0x769b27`).
- A model pane keeps the camera it resolved at SetCamera (`0x76cec0`); after SetModel the old camera holds as last painted, in the glue scene and in-world panes.
No stock screen changes. A byte audit re-derived every address.
Fixes#1890Fixes#1923Fixes#1924Fixes#1948Fixes#1930🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Anchors take a point from every child; a bad point or relativeTo is logged and skipped before its Offset is read (`0x767992`).
- Numbers parse with SStrToFloat (`0x64aaa0`) at every site; length readers take the node, then its first child.
- A bare `<Shadow/>` is 0.001 layout units, drawn rounded to whole pixels: the zone and world-map text shadows draw 2 px at 1080p.
- EditBox HighlightColor; the text frames' `font=`, a `<FontString>` child loading into the frame's own font, and message-frame TextInsets.
- Insets, resize bounds, title region and fog colour apply every child, the instance's last; a Backdrop dispatches its children in order and logs unknown ones.
A byte audit re-derived every address.
Fixes#1889Fixes#1926Fixes#1273Fixes#1920Fixes#1903Fixes#1906Fixes#1925Fixes#1936Fixes#1937Fixes#1943Fixes#1944Fixes#1945🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Widget methods 1.12.1 has were missing or differed.
- EditBox IsAutoFocus, IsMultiLine, IsNumeric and IsPassword; ColorSelect GetColorHSV and SetColorHSV (`0x7bbc80`, `0x7bbd60`).
- AddMessage with no colour takes the frame font's colour.
- ScrollingMessageFrame: the write slot and the four line-position methods; the cached lines-shown, at-top and at-bottom state its readers use (`0x788440`); a relayout on resize (`0x788310`); OnMessageScrollChanged (`0x788a9a`); SetTimeVisible and SetFadeDuration usage, unclamped; each line fading on its own countdowns (`0x7884c1`).
- GetFramerate averages the last 30 frames (`0x672700`).
- SetCursor takes the eight named cursors in any case, with its usage line; custom cursor files are still to do.
- SimpleHTML links get buttons, so OnHyperlinkClick fires.
A byte audit re-derived every address.
Fixes#1518Fixes#1520Fixes#1506Fixes#1519Fixes#1883Fixes#1682Fixes#1538
Refs #1186🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`GetTrainerServiceInfo` answered nil for a rankless service's subText. In 1.12.1 the subText is the Spell record's NameSubtext (`0x4d8b50`, read at `0x4d8b84`), the empty string for a spell with no rank; only a service with no Spell record answers nil (`0x4d8b58`). The resolver now keeps an empty rank as `""` and the binding pushes None as nil. Tests cover both rows and the install-backed Apprentice Blacksmith trainer.
Supersedes #1935.
Co-authored-by: kalistoking <148718830+kalistoking@users.noreply.github.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Frame sizes and positions skipped 1.12.1's screen-unit law. The client keeps layout units: a setter stores `S(v) = RN24(K1·RN24(RN24(v)/1024A))` and a getter answers `RN24(1024A·l)/K1`, from the cells `0x41ad10` sets from the aspect (benilla-re `systems/screencoord.md`, two readings).
- The layout resolves in layout units with the reference's four float roundings (`0x767414`, `0x76741f`, `0x767428`, `0x767a30`); the pixel seam converts.
- Size, edge, centre and point getters and setters, the screen-size and cursor globals (glue and game), scroll ranges and scroll events follow the law; OnSizeChanged passes the layout size (`0x76b660`).
- Scroll bars hide on `floor(range) == 0`, as the stock template does.
- The minimap and world-map arrows re-seat when the cells change.
At 4:3, `GetScreenHeight()` is 767.9999427795418. Nothing moves on screen beyond 2e-4 px. A byte audit re-derived the law and the resolver.
Fixes#1918Fixes#1321🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The minimap's methods, state and arrows differed from 1.12.1.
- Zoom, indoor flag, mask and ping are process-wide (#1876); SetZoom caps unsigned and writes only a change (`0x6da8e0`); every world entry reseeds the zoom from its CVars (`0x4ed9d0`).
- PingLocation pings in the call and signals MINIMAP_PING at once (`0x4eeca0`); raid and party pingers get their tokens.
- SetMaskTexture loads in the call, a miss stores green and raises; the tiles take the mask's colour (`0x4eae74`).
- The nine Model children are built (`0x4edbc0`); the rim and player arrows draw through them, above the dots, with SetArrowModel, SetPlayerModel, SetIconTexture and SetBlipTexture. The corpse is rim slot 2.
On screen: the player arrow is a 3D model; the corpse's in-range icon is about 4% larger. A byte audit re-derived every address.
Fixes#1522Fixes#1645Fixes#1652Fixes#1653Fixes#1871Fixes#1872Fixes#1876Fixes#1877Fixes#364🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The XML font legs and the font-object getters differed from 1.12.1.
- A font file that fails to load writes nothing and logs `Unable to load font file` (`0x878dc8`, `0x87a4c8`); a font already held skips the load (`0x771502`).
- A FontString's `inherits`/`font=` look in the one font table `CreateFont` fills (`0x783870`) before templates; a miss logs `Couldn't find inherited node`.
- Regions read no `alpha=`; the options window's icon sets it in its OnLoad.
- Justify tokens follow `0x6f1990`: a miss is skipped, the other axis's token zeroes the axis.
- A Font object's default shadow is transparent.
- `GetFont` lists every flag, comma-joined (`0x6f1ad0`); only `THICKOUTLINE` is thick.
No stock screen changes. A byte audit re-derived every address and string.
Fixes#1888Fixes#1891Fixes#1921Fixes#1922Fixes#1887Fixes#1919Fixes#1928Fixes#1899Fixes#1905Fixes#1816Fixes#1929🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Argument checks and usage lines across the UI API now raise 1.12.1's text in its order.
- One name helper reads the object's own name or `<unnamed>` (`0x84c7f0`), never Lua `GetName`.
- CreateFrame checks type, parent, template, then frame type, with the reference's five messages (`0x7060d5`-`0x7062a0`).
- Justify, scroll, max-letters, orientation, value, insert-mode, hyperlink-format, CreateFont and IsFrameType usage lines; not-movable errors say `<unnamed>`.
- Numbers become text by Lua's `%.14g` at the tooltip and eight other bindings.
- AppendText and the Slider XML loader follow theirs.
Every string was read from the client; a byte audit re-derived each.
Fixes#1882Fixes#1529Fixes#1535Fixes#1549Fixes#1885Fixes#1904Fixes#1886Fixes#1168Fixes#1909Fixes#1910Fixes#1911Fixes#1879Fixes#1912Fixes#1913Fixes#1914Fixes#1916Fixes#1927🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The macro icon list and its verbs differed from 1.12.1.
- The list is built on first use per in-game interface (`GetNumMacroIcons`, and `CreateMacro`/`EditMacro` past their gates), never at VM creation; `GetMacroIconInfo` never builds and answers `""` off the list or for an empty entry (`0x4f1ac5`, `0x4f1a88`).
- Its sources are 1.12.1's three (`0x4f0090`): the listfiles of the patches, speech2 and interface.MPQ only, then the two loose `Interface\Icons\` scans (`0x4f0350`, `0x4f04f0`), sorted case-blind and deduplicated.
- `EditMacro`/`CreateMacro` read the icon as a number (a numeric string counts) and keep the old icon on a miss (`0x4f1945`, `0x4f12f0`).
- `GetMacroIconInfo` with a non-number raises its usage line.
The listing takes about 7 ms on the stock chain, down from 150. Tests pin each fact; a byte audit re-derived every address.
Fixes#1512Fixes#1833Fixes#1874Fixes#1875🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Three differences from 1.12.1 on the model and glue lanes.
- `SetCharSelectBackground`/`SetCharCustomizeBackground` stood only the stock seven scenes. Any pane file now stands as itself, opened loose-aware as the scene model is (`0x76ccf5` flags 5, `0x707365`), and a scene with no camera draws on the orthographic leg (`0x76ce80`, `0x7ad7f0`) instead of keeping the last aim.
- Every model lane takes one name rule (`0x706a50`): the last dot, `.mdl`/`.mdx` to `.m2`, `.m2` as is, anything else the ErrorCube (`0x70737f`); the name is cut at 0x103 bytes first.
- `LaunchURL` refuses a digit after `http://` (`0x73fb91`, isalpha).
A missing file that passes the rule still stands nothing; that is #330's fallback. Tests pin each fact; a byte audit re-derived every address and lane.
Fixes#1836Fixes#1873Fixes#1108
Refs #330🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The console and CVar store differed from 1.12.1 in seven places.
- `cvar_reset` restores the session-start value (`0x63dff0`), `cvar_default` the default; a latched row moves its value, never the device (RestartGx reads only the stage, `0x639ec0`).
- `cvarlist` prints ` "%s" is "%s"` with the default and reset suffixes, cut at 0x100 bytes.
- A staged latched value is saved (SaveConfig takes stage, value, default, `0x63da27`).
- The setting callbacks refuse what 1.12.1's refuse (MaxLights `0x688bd0`, horizonfarclip `0x689250`, ErrorFilter `0x403200` and nine more), printing their lines.
- Console replies go to the log in 1.12.1's words, never to chat.
- The saved account name is the `accountName` CVar (`0x46cdd0`); the old `benilla-config/account` file moves into config.toml once.
Tests pin each against the bytes; a byte audit re-derived every address.
Fixes#1267Fixes#1268Fixes#1269Fixes#1340Fixes#1643Fixes#1650Fixes#1852🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>