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>
Colours and alphas were kept as floats where 1.12.1 packs them into bytes, and several frame verbs answered bad input unlike the client.
1.12.1's colour setters clamp, round half up into a byte, and read back times `0x81c6b4`; a frame's alpha is a truncated byte (`0x774f32`) whose walk stops at an unchanged byte (`0x76a690`); region and Font `SetAlpha` wrap. `SetScale`, the draw-layer verbs, the scroll range (`0x786e30`) and the binding verbs gate their arguments as the bytes do.
Now one packed `Rgba8` serves every colour, frame alpha is a byte, and each verb raises the reference's usage line. Read-backs shift by at most 1/255; nothing visible changes.
Fixes#1523Fixes#1536Fixes#1868Fixes#1867Fixes#1247Fixes#1248Fixes#1546Fixes#1557Fixes#1562Fixes#1189Fixes#1509🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The addon probe fires events, clicks and runs on the bare interface
- A probe click and hover answer what the hover raised
- The probe outcome's doc names the bare interface and every answer form
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Sam <samuv.eth@gmail.com>
Texture paint answered and drew unlike 1.12.1: `SetTexture("")` and non-string names answered nil, a missing file cleared desaturation, `SetTexCoord()` and an unknown `SetBlendMode` passed quietly, a failing bar path still installed a bar, and DISABLE, ALPHAKEY and MOD drew as BLEND.
1.12.1's load (`0x770200`) writes the shader slot only for a file that loads (`0x7702c0`); the binding answers 1 for a null name (`0x79bdae`). The submit draws a coloured DISABLE or ALPHAKEY texture as BLEND (`0x76fb82`); ALPHAKEY alpha-tests at 224/255 and MOD multiplies the destination (DESTCOLOR, ZERO, `0x80a1fc`).
Now one texture load serves every setter, the usage lines raise, the shader draws each mode with a MOD pipeline of its own, and a new test validates the UI shader with naga. The stock UI draws as before.
Fixes#1425Fixes#1864Fixes#1863Fixes#1521Fixes#1556Fixes#1550Fixes#1559Fixes#1555🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fonts were copied at load: a later change to a font object never reached the fonts and strings built from it, and a Button's state fonts were names overlaid at draw, so a label measured in one font and drew in another.
In 1.12.1 a font follows one parent (`0x770c60`) under a five-bit inherit mask; each setter clears its own bit and pushes only what changed to its followers (`0x784180`, `0x770910`). A Button holds three embedded fonts and relinks its label on each real state change, after the script (`0x779810`).
Now fonts link live, case-blind by name (`0x64b3f0`), `Font:SetFontObject` links and refuses loops, a Button's fonts are real linked fonts with their getters, and `<NormalColor>`-style elements load (a `Deviation:` for two). Tests cover the push, the masks and each button state; the stock UI draws as before.
Fixes#1204Fixes#1517Fixes#1403Fixes#1855Fixes#1856🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The CVar set path differed from 1.12.1's: values compared case-sensitively, text on a numeric CVar was refused, a read-only CVar still took console and host writes, `ConsoleExec` ran commands a frame late, and console `set` kept the whole line.
1.12.1's InternalSet (`0x63e0b0`) returns for a read-only record (`0x63e0ba`), compares with `SStrCmpI` (`0x63e0ec`) and stores any text, with the record's int and float by `SStrToInt`/`SStrToFloat` (`0x63e127`). `ConsoleExec` runs the line inside the call (`0x515f43`), and `set` tokenizes with `SStrTokenize` (`0x64ae50`).
Now there is one set path: the registered default strings, one `SStrToInt`, read-only refusing every writer, CVar commands run inside `ConsoleExec`, and the host copies are gone. Saved values match 1.12.1: a slider at its default writes "1" over "1.0", and hand-edited text reads 0.
Fixes#1478Fixes#1853Fixes#1278Fixes#1319Fixes#1274Fixes#1640Fixes#1851Fixes#1475🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Four Button behaviours differed from 1.12.1: `Click()` on a disabled check box did not toggle it, a checked texture made from a path drew with BLEND, `Click(name)` passed its argument raw, and a pressed button's label never shifted.
1.12.1's CheckButton click (`0x785550`) toggles before the base click's enabled gate (`0x779552`); the create leg of the checked setters stores ADD (`0x7852f4`); `Click` maps its argument case-blind to a button name or `UNKNOWN` (`0x7826c0`); and the label relink (`0x779810`) offsets the text by `PushedTextOffset` while PUSHED.
Now each follows the reference: a disabled box also ignores a press, `Set/GetPushedTextOffset` and `<PushedTextOffset>` are built, and held stock buttons shift their label about 1.3 units down-right. `Set*FontObject` raises on an unknown font name (`0x87c5fc`). Each has a test.
Fixes#1175Fixes#1176Fixes#1179Fixes#1516Fixes#1854🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The XML loader merged a template chain into one element, losing each attribute's level: `setAllPoints` lost to a template's `<Anchors>`, an empty `text=` wiped a template's text, the template's `<TexCoords>` won.
1.12.1 never merges: each loader re-enters on `inherits` (`0x76fe36`, `0x770f9e`, `0x76985c`), then reads its own node's attributes and first children (`0x6f2c50`).
Now regions load per level in `0x770f40`'s order; frames read geometry, `frameLevel`, StatusBar attributes and button text per level. A failed texture fills green (`0x878d24`). Level tests cover each rule; the stock load is unchanged but for the AddOns window's crops.
Fixes#1534Fixes#1548Fixes#1547Fixes#1866Fixes#1423Fixes#1858Fixes#1424Fixes#1472Fixes#1861Fixes#1859Fixes#1171Fixes#1860Fixes#1817🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GameTooltip re-sized itself on every resolve from its lines, at 10/40/260 units, counting a taller right text. So `GetWidth` after `Show` read stale, a script's `SetHeight` after `AddLine` was overwritten, and descriptions wrapped wide.
1.12.1 sizes only in the show (`0x530aad`), `AppendText` (`0x530615`) and the cache answers, never in `AddLine` (`0x531630`). `GameTooltipSize` (`0x530640`) takes the widest unwrapped line plus a 38.4 gap, wraps the rest at the running widest (capped at 230.4), sums shown left heights with 2.0 gaps, pads 10.24 a side, then re-anchors without clearing.
Now the sizer is that function, called from those sites, and the engine no longer anchors line cells; the stock templates declare them. A measure memo keeps a steady hover from measuring again. Tests pin each loop, constant and caller.
Found in Thorsten42/manila@50452d3fa5.
Fixes#1206Fixes#1182Fixes#1540🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The UI ticked on raw `Time<Real>`, which under pipelined rendering mis-splits shown frames (26.4 and 8.0 ms for two 16.7 ms frames), so a held paperdoll arrow turned in uneven steps while the bones, on paced `Time<Virtual>`, did not.
1.12.1 hands `OnUpdate` the loop's own frame time, unclamped (`0x420f70` → `0x885608` → `0x76b2c0`).
Now the pacer publishes one step per frame (`FrameStep`) that `Time<Virtual>` and the UI both read: `OnUpdate`'s elapsed, `GetTime` (a `Deviation:`, since 1.12.1 reads its clock live at `0x515ea0`), the cooldown anchor, aura expiries and `GetFramerate`. A stall reaches the UI whole. The pacer keeps every millisecond, drains its carry, and no longer jumps after a pause or splits a long hitch. Tests replay a stream measured on this machine.
Fixes#1846🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Six places where benilla's Lua answered unlike 1.12.1's:
- The lexer took `[=[` long-string levels; 1.12.1's `luaX_lex` tests one byte after `[` (`0x6ff793`) and `--[=[` is a line comment (`0x6ff6c9`).
- `math.random` ran a glibc-style `rand` seeded with 1; now MSVC's (`0x7400e5`, `% 0x7fff` times 1/32767 at `0x7fb310`), clock-seeded as the client is (`0x5d1c8b`), fixed in captures and the addon harness.
- `getfenv`/`setfenv` honour a raw `__fenv` field (`0x702ba0`), and `setfenv` returns nothing.
- An object's text is `type: %08X` in the embedding API too (`0x74301d`).
- `time` converts a date table through MSVC's `_mktime` (`0x74066b`).
- `date` formats with MSVC's C-locale patterns (`0x8323a0`): `%c` is `09/09/01 01:46:40`, `#` drops zeros.
Each has a test that fails without it; the addon corpus parses as before.
Fixes#1257Fixes#1258Fixes#1510Fixes#1511Fixes#1503Fixes#1504🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The flat saved-variables file ran as Lua, and an add-on's saved file was skipped when its toc declared no scope.
1.12.1 reads `SavedVariables.lua` as lines (`0x4913b0`): a name, `=`, then a quoted string (raw, empty is nil) or a number read by `SStrToFloat` (`0x64aaa0`) and rounded to f32; anything else is skipped and no Lua runs. An add-on's account and character files run whenever they exist (`0x51f4a2`), and at save a scope that declares nothing removes its file (`0x51f859`). `RegisterForSave` is refused outside Blizzard code (`0x4884e0`).
Now the reader parses lines, unescaping the writer's escapes (a `Deviation:`). An old-format file with lines the reader drops is copied once to `.bak`. An undeclared scope's file read cleanly this session is renamed to `.bak` instead of deleted (a `Deviation:`), and `RegisterForSave` raises 1.12.1's two errors.
Fixes#1166Fixes#1167🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A `RegisterAllEvents` frame sat on a list of its own: it heard every event after all named listeners, even untabled names, and `UnregisterEvent` could not take one event off it.
In 1.12.1 there is no all-events flag. `RegisterAllEvents` (`0x774c20` → `0x7023e0`) appends the frame to each tabled event's list where it is absent, as one `RegisterEvent` per event would, so dispatch (`0x703e50`) runs in registration order and `UnregisterEvent` (`0x702280`) removes that one event. The walk sets `event` once and restores it after (`0x703ea4`, `0x703f04`); `this` and the args stay per listener (`0x704f10`).
Now registration goes through one per-event list and the second pass is gone. Tests cover the interleaving, the per-event unregister, a re-register going last, a mid-dispatch registration, a frame registered both ways, and the `event` global.
Fixes#1165🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`Model:SetCamera(n)` on a PlayerModel did nothing: the character pane always drew through its fixed pane rig.
1.12.1's PlayerModel registers no SetCamera of its own (`0x506230`), so the Model verb (`0x76e0e0` → `0x76cec0`) installs camera n of the copy; one past the model's count drops to the orthographic leg (`0x76ce80`). Every new copy gets camera 1 back at the ready hook (`0x505890` → `0x76cec0(1)` at `0x50589c`), and is armed with Stand (`0x505a2f`).
Now the pane draws through the installed camera, live or frozen as the reference does, with the ortho leg's particles at its unit (`0x7b2a50`). A camera or arm held for a pending copy dies when a new build is asked. The API-wall scanner now follows `#[path]` test modules. Tests cover each leg, the reset and both held-state sequences.
Fixes#1835Fixes#1847🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Widening the window on a glue screen stretched the login, select and create scenes past 16:9.
1.12.1 lays the glue out at `gxResolution`'s aspect (`0x41ad10` at `0x46a828`), and `GlueParent_OnLoad` (GlueParent.lua:74-84) boxes GlueParent to a centred 16:9 rect on a wider screen. The size callback `0x46abe0` only re-scales and re-lays GlueParent, and its window keeps gxResolution's shape while dragged. benilla's glue root is the window, but GlueParent's box was set only at build.
Now a resize of the glue's screen re-runs the chain's own `GlueParent_OnLoad` from its declared `setAllPoints`, so the box follows the window without a rebuild, which would reset the create screen and the typed login (a `Deviation:` line). A new test resizes a built glue through 21:9, 2:1, 16:10 and 4:3, and checks the box, the scenes and that the screen stands.
Fixes#1844🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A player could walk or swim past the edge of the world and keep going. 1.12.1 stops every movement step whose swept box touches terrain that isn't loaded. The query `0x6aa8b0` fails on a null tile or chunk (`0x6aadfa`, `0x6aae1f`), and the substep loop then runs no resolver and reports the time skipped (`0x634154` → `0x615ae0`).
benilla now builds the reference's walk, swim and fall boxes and holds the body when its box touches a tile that isn't resident. Position, fall clock and FALLING are kept, and `CMSG_MOVE_TIME_SKIPPED` goes out for the held time. A tile whose terrain file fails to load counts as resident, so it is never a wall.
Tests: a walk stops at the edge and walks back, and a jump hangs. Live, a teleport past the edge held in place.
Fixes#1830🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The quest log left out two kinds of objective line. A quest with an end text, such as an escort or event quest, showed no objective and couldn't be watched. A quest that requires a standing with a faction lacked its reputation line.
1.12.1 counts both (`0x4e0000`). `GetQuestLogLeaderBoard` (`0x4e0110`) answers line 1 with the raw end text, typed "event" and finished by the slot's complete bit (`0x4e02a2`), and the other lines move down one. It ends with a "reputation" line: `QUEST_FACTION_NEEDED` with the faction's name and the current and required standing labels (gendered), finished at the required standing (`0x4e0787`). Both are now built in that order.
Tests: an event-only quest, an event plus kill quest, and a reputation-gated quest's lines, text and finished state.
Fixes#1205Fixes#1837Fixes#1834
Co-authored-by: Thorsten42 <3400178+Thorsten42@users.noreply.github.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Two glyph-cell differences from 1.12.1.
Rows: the client keeps at most the font's pixel em of a glyph's rows. `0x5d1120` stores `min(em, rows)` (`0x5d1195`-`0x5d11a2`), and every bake reads that many rows from the top. benilla packed the full raster, so unhinted glyphs one row taller than the em (FRIZQT `ý þ` at 14 px, Arial Narrow `Ò Ó Ù Ü` at 14 px) drew a faint extra row. Cells are now clipped to their first `ppem` rows before the ring is built.
Alpha: the client's glyph texels are ARGB4444. The plain bake keeps coverage's top 4 bits (`0x5cf298`-`0x5cf2a1`), and the outlined fill's gray is `(cov·0xff) >> 12` (`0x5cf1b8`). benilla packed 8-bit coverage. Coverage cells and outlined fills now pack 4-bit values, so coverage under 16 draws nothing, as in 1.12.1.
Tests: an over-tall `Ò` keeps its top 14 rows, and every packed alpha is 4-bit exact.
Fixes#1827Fixes#1828🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Text glyphs were rasterized hinted and seated 1 to 3 px left of 1.12.1's. The client loads every glyph with `FT_LOAD_NO_HINTING` (`0x5d1324`, flags `0x208a`/`0x20c2`). It places each quad at the pen plus `ceil(horiBearingX)` (`0x5cd099` → `0x5cb080`), with the ink 0, 1 or 2 columns into the cell for plain, NORMAL and THICK (`0x5ce481`, `0x5ce48a`). benilla hinted the anti-aliased cells and seated them at `floor(xMin)` less the outline padding.
Anti-aliased cells now come from the face's unhinted 26.6 outline, the one the measure already reads, through `zeno`, swash's own scan converter. Every bake is seated at the rounded-up bearing with its ink column. The drawn ink now meets the measure.
Tests: Arial Narrow `e` at 18 px matches FreeType's unhinted raster byte for byte, and the five bakes seat at the bearing. Fitted to a 1.12.1 capture, a chat line lands on the client's columns.
Fixes#1813Fixes#1822🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Moving fast raised the loading screen whenever the body outran terrain streaming. 1.12.1 never shows a screen there. Its tile pass (`0x6985d0`) blocks on each missing inner-window tile (`0x698689` → `0x443bd0`), so the frame hitches.
benilla now does the same. It requests every missing inner-window tile at once, waits for them in the frame, and builds them and their colliders untimed, during play and cinematics. The loading-screen backstop is gone. A read still hung after 10 s is waited on once, then streams on. A second defect fed this: a settled stream re-walked its window only at a tile crossing, so tiles ahead were requested late. It now re-walks on every chunk step.
Tests: outrunning the stream raises no screen, and a hung read costs one capped wait. Live, at speed ×100 across Kalimdor, no screen came up.
Fixes#1028Fixes#1823🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Action-bar hotkey digits sat on black boxes. Their font inherits `monochrome="true"`, and benilla had no monochrome mode, so it baked them anti-aliased with a THICK outline that filled the whole small cell.
1.12.1 rasterizes a monochrome font 1-bit (`0x5d1300`, render mode 1). `0x5ce440` then draws it with a one-pixel black ring, plus a 7/15-alpha second ring under THICK. benilla now carries the flag from Font and FontString XML and from every `SetFont`, `Button:SetFont` included. It rasterizes such glyphs with FreeType's mono rules (drop-out mode 2, span jitter) and bakes the ring.
Also fixed: a FontString's XML `outline="NORMAL"` or `"THICK"` with a font file now outlines (`0x771214`). It had been parsed with the Lua spellings.
Tests: the hotkey numerals match a 1.12.1 capture texel for texel, a THICK mono cell is ringed rather than filled, and the flags round-trip.
Fixes#1811Fixes#1812🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A jump on a descending lift (Undercity, Gnomeregan) played the hard-landing grunt and dust. While airborne, benilla carried the rider with the deck but kept the jump's start height in world coordinates. So the lift's drop during the arc, about 24 yd on Undercity's 29 yd/s cars, counted as a fall, and FALLINGFAR latched early.
1.12.1 measures the fall height (`0x7c60c0`) in the mover's own frame: transport-local while aboard, re-mapped when the mover boards or leaves (`0x7c6620`). `Player::carry_to` now moves the start height with the deck. A remote mover's arc is measured in the deck's frame when it takes off and lands on the same transport.
Tests: a 60 fps jump on a deck dropping 28.95 yd/s lands with no fall height and no FALLINGFAR, for us (23.64 yd before) and through packets for a remote (23.88 yd before).
Fixes#1250🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The loading screen's "Tip:" could read "Tlp". benilla drew the tip with Bevy's own text raster at a fractional size, which hinted the i's dot onto its stem at some heights, 1080p among them.
1.12.1 draws the tip through its font engine (`0x5c1c30`, from `0x40679c`). The tip now goes through benilla's font engine, the one every FontString uses, at the reference's size, line spacing, bottom-left justification, trimmed line ends and pen step. The shadow takes the body's alpha (`0x5cd65b`), and the shadow offset and the anchor snap to device pixels (`0x5c7010`, `0x5cdf70`). The Bevy text reshape plugin, now unused, is removed.
Tests: the i keeps a gap row at six window heights, the layout matches the engine's for the DBC line, and placement holds at DPI 1 and 2.
Fixes#1089🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#1808's stand-in search ranked ALSA devices only when the host's name read "ALSA", but cpal names it "Alsa". So the ranking never ran, and the first device ALSA lists won. On the reported machine that was `null`, which discards every sample, so sound stayed silent behind a warning that it was playing.
The check now compares the host's `HostId::Alsa` variant, so the ranking applies: `pipewire`, then `pulse`, then each card's own outputs, and never `null`.
The Linux crosscheck compiles it. The ranking's tests are #1808's.
Fixes#1806🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On Linux, sound stayed silent for the whole session when ALSA's `default` would not open. On the reported machine, PipeWire holds the card and `default` is left as the stock `dmix`, which cannot open it.
benilla opened only the host default and gave up when that failed. Now, when the default will not open, the first other output that does stands in: `pipewire`, then `pulse`, then each card's `sysdefault` and `plughw`, with HDMI cards last. It never picks `null` or the rate and mix plugins. The device watch now compares against the default the device was chosen against, so a stand-in is not torn down every poll. A `Deviation:` line records that 1.12.1 runs silent when FMOD's driver 0 fails (`0x7a445f`).
Tests: the ranking over three `aplay -L` lists, including the reported one. The Linux crosscheck is green.
Fixes#1806🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Quest completion and timed-quest failure now follow 1.12.1.
- **isComplete runs the client's turn-in test (`0x4df580`).** The complete bit counts only when the template's flags ask for it, and a quest with nothing to check is never complete. Counters, held items, both standing bounds and money are read live. A pass fires the "Quest Completion" tutorial (`0x4df792`).
- **A timed quest that runs out latches failed.** The latch is set by the rebuild, by GetQuestTimers and by `SMSG_QUESTUPDATE_FAILEDTIMER` (`0x4df4f0`, `0x4df550`). GetQuestTimers prints the failure line and then fires QUEST_LOG_UPDATE inside the call (`0x4e1529`, `0x4e1554`).
- **Timers count by the local clock until the first sync,** and a zero-difference sync reads a second more (`0x4def00`).
- **Quest-giver failures wait for the quest record** and skip a latched quest (`0x5dc898`). A plain update failure no longer re-asks the givers.
Each change fails its test without it.
Fixes#1793Fixes#1794🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A ScrollingMessageFrame now fades, keeps its view and spaces its messages as the client's does.
- Only the shown messages fade (`0x7884a5`-`0x788582`). A relayout off the bottom re-arms what it walks, including the first older message, before the fit test (`0x788af0` at `0x7888be`). That covers a scroll, a resize, a font change, a show and UpdateColorByID.
- A scrolled-up view stays on its messages as they arrive. It moves down a line only when a full ring overwrites the topmost shown message (`0x78819e`-`0x7881bb`).
- The gap between messages is the snapped unscaled spacing times the scale (`0x788954`-`0x788975`). Rows within one message snap their scaled gap (`0x5c2143`-`0x5c215c`). Each message must fit whole.
- ScrollToTop and ScrollToBottom do nothing on an empty frame (`0x788690`, `0x7886d0`).
Each change fails its test without it.
Fixes#1796Fixes#1797Fixes#1798🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The quest log getters now read live state at the call, and QUEST_LOG_UPDATE fires where the client fires it. Before, every log quest's reward items were asked up front, and a per-frame snapshot rebuilt the log.
- World entry registers the log watcher and rebuilds (`0x4de430`). The rebuild holds while quest records are pending, and the last landing rebuilds it (`0x4de510`, `0x4de8b0`).
- The getters ask on a miss (`0x4e0461`, `0x4e05e0`, `0x4e0b9e`). One update fires per missed record that lands, a recorded deviation from the client's per-call callbacks.
- An item a listed quest needs fires one update (`0x4df7b0`). The time response and a faction rank change fire one each (`0x4def1b`, `0x4d58f3`).
- Cached names and templates seed each new VM.
On an idle frame with 20 quests: 46.5 µs before, 1.0 µs after. Each change fails its test without it.
Fixes#1209🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A ScrollingMessageFrame now lays out, pages and scrolls as the client's relayout does.
- A message that does not fit whole, past the anchored one, ends the layout (`0x7889b3`-`0x7889c5`). A half-fitting top line is hidden, not clipped, and the shown count that paging reads agrees.
- PageUp and PageDown take one line step per shown message, then one step back, and stop on a refusal (`0x7885b0`, `0x7885e0`). One shown message nets no move.
- ScrollUp is refused once the oldest message shows, and AtTop reads that flag (`0x78861c`, `0x7931b5`). It is nil on an empty frame (`0x787775`).
A character pane's camera takes its aimed pose with its spawn, so a pane is framed on the frame it opens.
Each change fails its test without it.
Fixes#1786Fixes#1787🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
benilla-lua's tables and globals now follow the client's Lua 5.0, not 5.1.
Tables:
- They size, fill and rehash as 5.0's ltable.c does: the hash part doubles (`0x6fab90`), and a log size of 0 is the shared dummy (`0x6fa601`).
- A hash part past 2^24 nodes raises `table overflow`.
- Constructors store every 32 items.
- A weak table's clearing removes the key, so `next` from it raises (`0x6f79d0`).
- A weak-keyed table keeps its string keys (`0x6f79f0`).
Globals and libraries:
- `ipairs` returns itself, and a second argument steps once.
- `setglobal` assigns the top of the stack.
- The state opens in the client's order, with no `_G`.
Each new test fails without its fix.
Fixes#1487Fixes#1789Fixes#1790Fixes#1169🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>