Fifteen UI API fixes.
- **Text and names.** FontString text, buttons, tooltips, names and bubbles apply the `|1`/`|2` grammar escapes (`0x7716f0`). Rank names count code points.
- **Raid.** The raid roster selection stores the member's guid, survives a /reload, and clears at world login.
- **World states.** Each world-state packet fires WORLD_MAP_UPDATE then UPDATE_WORLD_STATES at its place.
- **Events.** DISPLAY_SIZE_CHANGED fires below scale 0.64. UPDATE_CHAT_COLOR carries the caller's name.
- **CVars.** Four refuse what 1.12.1 refuses, and anisotropic and ErrorLevel print its console lines.
- **Calls.** GuildSetLeaderByName("target") sends no name. Repair costs read by row position. Quest reward slots count to the first empty one. OfferPetition checks its eight guards.
- **Glue and errors.** The glue VM has the file calls. Widget methods share one wrong-receiver text.
Fixes#2507Fixes#2557Fixes#2559Fixes#2567Fixes#2568Fixes#2581Fixes#2588Fixes#2593Fixes#2599Fixes#2606Fixes#2615Fixes#2623Fixes#2630Fixes#2687Fixes#2688🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
What differed from 1.12.1:
- **Channel notices.** Moderator and voice changes print their notices (`0x49e8a0`).
- **Copy.** Copying from an edit box strips link, colour and `||` codes (`0x5c2590`, mask 0x500).
- **Links.** A `|H` token ends at the first `|`, which must be `|h` with a byte after it.
- **SimpleHTML.** A resize rebuilds the links (`0x78a320`).
- **Secure add-ons.** Their `.url` file is read at registration.
- **Font size.** A FontString refuses any scaled size of 0 or below, 1 or above, or NaN (`0x5c1afe`).
- **Message frames.** Justify lives on the frame's font, CENTER by default (`0x783a87`).
- **Script names.** Each widget type takes only its own scripts: SetScript and GetScript raise `doesn't have a "%s" script`, and HasScript answers nil.
Each fix has a test.
Fixes#2483Fixes#2488Fixes#2489Fixes#2490Fixes#2491Fixes#2494Fixes#2543Fixes#2553🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 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>
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>
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>
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>
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>
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>
An XML `font=` naming a file with no `<FontHeight>` still got that font at a default height.
`CSimpleFontString::LoadXML` (`0x770f40`) reads `FontHeight` and refuses a height that is not above 0 (`0x771146`–`0x771154`). It warns `FontString %s: Missing font height in %s element` (`0x878e10`) and returns at `0x771191`, setting no font. SimpleHTML's element fonts go through `CSimpleFont::LoadXML`, which has the same check (`0x783d6d`, `0x87a4f0`).
Both loaders now refuse a missing, zero or negative height with the reference's warning and set no font or outline, so `SetText` on such a string raises "Font not set". None of the 219 corpus addons trips it.
Tests: absent, 0 and negative heights, the ButtonText message, and a SimpleHTML element; all fail without the fix.
Fixes#1469🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The protected-action gate (`0x494a50`) now runs its input arms. Tags 2, 4 and 10 pass tainted code only under an input flag:
- the edit box's Enter (`[0xcf4dc4]`);
- a button's click or double-click (`[0xcf4da0]`);
- a running binding.
Without one they refuse silently. This covers casts through `TryCastSpell`, the pet actions, `SpellStop*`, `SpellTargetUnit`, item targeting, `TargetNearest*` and `AttackTarget`, and `ReloadUI`.
Branches the reference runs before the gate still run when the cast is refused: aura and form cancels, and the bind-on-use and soulstone legs. Queued calls replay the input events they ran under, around their own events too. So a TAB-driven `PLAYER_TARGET_CHANGED` handler casts as in 1.12.1.
`SetEuropeanNumbers` now also swaps separators in tooltip lines, both message frame classes (at show for scrolling ones), SimpleHTML, chat bubbles and nameplates.
Tests: a 30-verb census, and the stock buttons, bindings, `/cast`, spellbook and macros.
Fixes#1354Fixes#1355🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On the stock credits screen the fades drew as solid black boxes over the text, and the black bars covered the borders.
A texture's gradient now tints its four vertices over the texel (0x77f910, TL BL TR BR). A gradient alone draws nothing, and any vertex-colour write collapses it (0x77f750).
A layer batch sorts its quads by texture handle at End (0x7731a0), with an exact port of the client's qsort. Its tie order was checked against the client's own code. The client's handles are heap addresses, so different textures rank by a creation stamp that follows allocation order. That is a narrowed Deviation.
Extract costs what it did before. The options window's ground dim moves to BORDER to keep its look.
Tests cover the gradient, the sort and the stock credits frame.
Fixes#874Fixes#1095Fixes#1092🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Lua bindings now read their arguments the way the 1.12.1 client does:
- Numeric strings parse as Storm's `SStrToInt` (`0x64ac60`) and `SStrToFloat` (`0x64aaa0`). That covers channel keys, `GetNumber` and `item:` fields.
- Flag arguments go through `GetBoolOrDefault` (`0x6f1c10`) with each site's default. That is about 40 bindings, `SetChecked()` and `ShowHelm()` among them.
- Colour channels take numeric strings (`lua_tonumber`), and alphas are `isnumber`-gated: no raise, 1.0 by default. A three-argument `SetVertexColor` keeps the current alpha (`0x79ae0a`). So action-bar rings dimmed by a drag stay dimmed, as in 1.12.1.
- `SetTexture("1")` takes the colour form. MessageFrame channels follow `0x795590`.
- `LoggingChat`/`LoggingCombat` return the flag.
Binding-level tests use the reference's values.
Fixes#135Fixes#144Fixes#160Fixes#161🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The solver took any node's recomputed rect whenever it resolved, because about 55 layout writes named a node without invalidating it. So a freed region stayed in its dependents' anchors, `SetTexture` re-resolved a failed texture, and a scroll child kept its authored anchors behind a per-solve override.
In 1.12.1 a node re-resolves only when invalidated or a target stores a new rect (`0x768d20`, slot 0 `0x768cf0`). `SetTexture` only dirties the draw (`0x770200`). A destructor frees its dependents' anchors to it (`0x768e20` → `0x767e70`, no invalidate). A scroll child is re-anchored to one TOPLEFT (`0x787100`).
Every write now invalidates exactly where the reference's does, and the solver's guess, the hold and the art latch are gone. The tooltip invalidates only on its relayout (`0x530640`).
Tests bite on the parent; the verifier is green; the stock UI's quads are byte-identical.
Fixes#947Fixes#949Fixes#958🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`SetFont` raised on a `false` flags argument and wrote the height even when the load failed, and only SimpleHTML had `SetSpacing`/`GetSpacing`, so SpellAlert's message frame raised in its OnLoad.
In 1.12.1 `SetFont` (`0x79f210`, the FontString's `0x79d4f0`) reads flags only when `lua_isstring` (`0x79f287`), writes them on every successful load (`0x79f334`), and writes nothing on a failed load or a height ≤ 0 (`0x79f361`, `0x79f2f4`, `0x771571`). Every text table has the spacing pair (`0x79fb40`/`0x79fbe0`; the FontString's clamps at `0x772240`); the line step adds the gap rounded up to a device pixel (`0x5c2070`, `0x5cdc20`), as do the message frames (`0x78899e`, `0x785f06`) and EditBox rows (`0x77d133`).
All seven SetFont bindings share these gates and ask the host's font probe; spacing is stored, inherited, measured and drawn on all six tables.
Tests bite on the parent for each.
Fixes#873Fixes#692Fixes#945🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Anchors were a list: SetPoint re-applied an unchanged anchor, GetPoint counted in the order points were set and answered five nils past the last, and the cursor tooltip replaced every anchor each frame.
In 1.12.1 a frame holds one anchor per point. SetPoint (`0x767c70`) returns at `0x767d5e` with no store and no layout queue when that point's anchor has the same target and relative point and both offsets differ by under 2⁻²² layout units (`[0x8029d4]`). GetPoint (`0x7a2340`) walks the nine slots in point order and pushes nothing past the last (`0x7a240d`). The `ANCHOR_CURSOR` update sets BOTTOM alone (`0x530b20`).
Anchors are now nine slots, and SetPoint, GetPoint and the cursor update follow those rules.
Tests: an unchanged re-set and an in-threshold offset touch no layout, GetPoint order and indexes, a cursor tooltip keeps an addon's second point.
Fixes#926Fixes#131Fixes#879🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Any write that changed the layout graph's roster (a frame or region created, freed, re-owned or re-pointed) made the next resolve derive the whole graph: about 1.35 ms for one `CreateTexture` or `CreateFrame` on the stock UI, against 0.05 ms for a no-op.
A write now names the nodes it touched, and the resolve re-derives just those in the cached graph: a joining node is added with its edges, a leaving one is removed and its dependents seeded, a staying one has its edges and inputs re-synced. The full derivation stays the fallback. A region's owner and an art change are now layout inputs that name their region. Verify now also compares the graph itself.
A randomized test checks rects and graph against a scratch solve after every batch of creates, frees, re-owns and re-anchors. `CreateTexture` now costs 0.14 ms, `CreateFrame` 0.15 ms, a backdrop rebuild 0.22 ms.
Fixes#903
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Part of #41: the comments of `benilla-ui` (the Lua UI engine, its FrameXML loader and its widgets) are rewritten to the rule in `docs/METHOD.md`, which is what the code does and the reference fact behind it, in a line. 43,140 comment lines become 15,161.
- **The mechanical strip.** The citations that were only a private record number go first, and each edit is shown to be a pure removal.
- **Wrong facts corrected.** The rewrite corrects the comments that stated wrong facts. Among them:
- `0x64a480` is the case-sensitive compare (a byte `repz cmpsb`), and `0x64a4c0` the case-insensitive one.
- A frame's id sits at `+0xb0`, not `+0xb4`. `EnableKeyboard`/`IsKeyboardEnabled` are `0x776ec0`/`0x776f90`.
- A region's unpinned edge has no fallback to its owner's rect: the owner supplies only the scale.
- `$parent` expands to the nearest named ancestor, or "Top" when none is named (`0x76c5b0`), not simply to the parent.
- A StatusBar fills from its internal normalised fraction, not from `GetValue()`.
- The addresses of `StartDuel` and `StartDuelUnit` were swapped.
- Many "untraced" notes now give the traced reference fact: the drag threshold, the per-grip sizing edges, the empty-`autoFocus` skip, and `SetTexCoordModifiesRect`'s default.
- Stock FrameXML line citations are checked against the stock 1.12 files and corrected. `ref-` file names become the stock file names.
- **Deviation labels.** "Deviation:" now marks only a choice with a reason. A surface that exists only for the retired Era addon target says so plainly: it is not 1.12.
- **Verification.** Every file strips to identical code, and `cargo fmt --check` is clean. Nine second readers compared every file old against new, restored the facts the rewrite had dropped, and checked each new citation against the code, the stock files, vmangos and the reference.
Behaviour differences the rewrite surfaced are filed as their own issues (#131–#142), and more are being validated. The comments here state what the code does. String literals are untouched; they are #92.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Comments that pointed into the maintainer's private reverse-engineering notes (a note by name, a
path, a section or finding id, the analysis's own name for a client function, a process word) now
carry the fact itself, cited by the reference client's address, by vmangos file and line, or as
"the reference". Nobody outside could follow those pointers.
Comment text only, plus the test-assert and log strings that carried the same pointers. No claim
changes: a comment the notes have since corrected keeps its wording here, and correcting it is
separate work.
The two reference generators, `scripts/gen-reference-globals.py` and
`scripts/gen-reference-verb-events.py`, named the private notes' layout to find their inputs. They
now take the input files as arguments (`--capture`, `--framexml`, `--names`, `--extents`,
`--disasm`). Run on the same inputs, both write the committed tables row for row; only the two
header lines that named the old environment variable change.
Checked per file:
- the code is identical: each file with its comments stripped compares equal before and after; a
changed string literal is compared under a mask that proves only literal text moved and every
literal kept its line count;
- no client address the old text cited was dropped;
- a second reviewer checked every added address against its derivation;
- a detector for the notes' names, paths, ids, function names and process words finds none left;
- `scripts/gates.sh` is green.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- a slider with a value step snaps to it: a stepped drag fires once per row instead of once per pixel, a scrollbar cannot rest between two rows, and one wheel notch on the trainer list reaches the bottom (2133)
- setting a step on a live slider moves its value onto the new lattice and runs its handler, which an addon on the real client can count on (2143)
- a button pushed from script releases on the next mouse-up, so Tablet-2.0's rows stop staying pressed forever; a release off the button's rect un-presses it, and a locked state holds (2134)
- the mail body and the GM ticket box scroll as you type past their bottom, and the Use UI Scale checkbox reads a registered setting (2141)
- an engine warning, a call that was accepted and did nothing, reaches the /errors window in grey instead of one terminal line; a text row trimmed mid-character no longer kills the window's repaint (2135, 2138)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- a unit's pick geometry is every model chained to it — body, held items, mount — one candidate per root resolving to the same unit, which is how the reference registers its pick (it walks the attachment tree and registers each model); Naxxramas' floating weapons could only be selected by their name plate before (no body, no candidate), and every armed unit gains its gear as clickable surface (our own body 12 → 21 tested parts); the broad phase stays the body's sphere, so a very long polearm on a small body is the named residual
- the selection ring's writer has two branches and we had transcribed one: a model whose stand box is degenerate (zero extent in x and y) rings at the literal 1.2 × scale — 2.7 yd under a trigger creature — instead of `sqrt(0.5·sqrt(dx²+dy²))` = 0 clamped to an invented 0.05 floor; both invented fallbacks are gone
- an HTML book image with no width/height is its art's texel square (one texel = one FrameXML unit, bit-for-bit the `<AbsDimension>` converter), not the rest of the page — *A Treatise on Military Ranks* drew its crest twice too big and stretched to the page's aspect; a texture-size oracle answers off the same decoder the renderer draws with, and an unfloated height-less `<IMG>` reserves its art's height in the flow; 797 shipped textures swept, exactly two zero-size axes move and neither is visible
- the dev debug panel killed the client on open with "Attachments have differing sample counts": Bevy's 2-D depth texture is keyed on the render target alone, so two Camera2ds on one window at different MSAA counts cannot both be valid, and the egui overlay camera had silently kept the default 4× after the player-UI camera went to Off; it now says `Msaa::Off`, and a preflight check reports any two 2-D cameras that disagree on a shared target, every run
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- the four extra action bars become per-character options, off by default, on a new Action Bars options page — the toggles ride the server's own byte (`CMSG_SET_ACTIONBAR_TOGGLES` into `PLAYER_FIELD_BYTES`), so a fresh character's zero is the default for free; turning the bottom-left bar off gives the stance bar its previously unbuilt unraised look, end-caps and 64×64 rings
- bag windows stop drawing straight through a raised bottom bar: the bottom band has ONE owner — the manage pass — the bag stack joins it through a listener seam, and a guard test walks all eight raisable-bar combinations across 24 bar/tenant pairs and refuses overlap
- the item-push card takes the reference's real geometry, closing the gap 0887 measured
- resizable addon windows stop at their bounds instead of pinning and walking across the screen: four per-frame f32s with 0.0 the disable sentinel, the clamp on the interactive drag only with the residual rebated out of the drag delta, `<ResizeBounds>` in XML honoured — and `SetFontString`'s three raises land exactly as the bytes have them
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- a text write names its own label, so the per-frame measure sweep drains a short ledger instead of hashing every font string twice — ~0.3 ms a frame at the Stormwind gates, gone
- the idle interface tick stops re-writing settled state: buff durations and the weapon-enchant frame write only on change, and the fade and unit-popup drivers park until woken
- the interface extract skips a hidden region before cloning its row, not after
- WOW_UPLOAD_BUDGET=<MB> brackets the arrival burst's upload pacing — a lever for measuring world entry, not a fix yet
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- the interface's layout graph is maintained as anchors change instead of rediscovered from scratch: 1.48 ms per mutated frame becomes 34 µs, and end to end a running cast now costs +0.14 ms where it cost +4.33
- the autocast shine draws its authored population — 1200 particles through the star's own projection, spinning as the model says
- the shine is clipped to its own widget: the model frame installs its rect as the viewport, so nothing it draws leaves the button
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- a teleport between cities on one continent could release you before the destination had streamed in and drop you under the map — the residency publish now waits for the snap and names the tile its facts are about, so the loading screen provably holds until the ground is real
- nameplates stop jittering: plates of units you cannot see no longer shove the visible ones around, and the snap rides the correct pixel grid
- unit-name text takes its first fix — a string fits the width its own measure reported, and a re-bake reaches the screen
- books and plaques render through a real SimpleHTML widget inside a real ScrollFrame — strict parse, per-element fonts, block layout, and a scroll range measured from the whole subtree
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>