Commit Graph
17 Commits
Author SHA1 Message Date
gromandClaude Opus 5 bb2e40d1dd ui: GetDebugName, the name an unnamed frame still answers
`Cinematic/Frames.lua:3734` is `entry.name or entry.frame:GetDebugName()` — an addon that keeps a
list of frames reaches for this precisely because some of them have no name, which is the one case
`GetName` cannot serve. With the method absent, `/cine debug frame <name>` raised on the first
unnamed row in the list instead of printing the row the player asked for.

On all three method tables, since the era puts it on ScriptObject. A named frame answers its name,
so a feature test on it learns nothing a 1.12 client would answer differently. The unnamed leg
deviates in its fallback only: it walks the parent's own fields for the key the child is stored
under (frames here are plain tables, so `parent.icon = tex` is visible) and answers
`Parent.<unnamed>` when there is none, where the era answers an index among the parent's children —
an index here would be the order of a list this client does not keep, and a wrong number reads
worse than an honest absence.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
2026-11-02 14:17:44 -07:00
gromandClaude Opus 5 33d0868009 ui: SetEnabled, the one setter retail put in place of Enable/Disable
A later-era addon writes `SetEnabled` **unguarded on its own controls**, even
where it guards on someone else's. `Cinematic/Options.lua:4176` is a tick's own
`Refresh`, greying it when a rule owns the row; `:305` twelve pages up is the same
file writing `control.SetEnabled and …` about a control it did not make. The raise
at 4176 unwound `RefreshOptions` → `SettingsChanged` → `SetProfile` →
`Welcome.lua:43`, so picking a profile from the addon's welcome window applied the
profile and then abandoned the panel refresh, leaving the traceback in chat and
the window still reading "Nothing runs until you pick one." That is the chat error
grom recorded on 2026-11-02.

Composed from the stock `Enable`/`Disable` pair (`0x780160`, `0x7801b0`) on
Button's table, which CheckButton and LootButton reach down the same chain the
pair travels. EditBox takes it too, written out rather than composed because the
reference gives a box no enabled state at all: it writes the mouse and keyboard
the frame already owns and drops focus, so input is refused and the text is NOT
greyed, which 1.12 has no slot for — `Options.lua:1629` is the call that needs it.

A Slider still answers neither this nor the trio: addons duck-type on them to tell
a control from a bar, which `tests/slider.rs` pins.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
2026-11-02 10:00:30 -07:00
gromandClaude Opus 5 75ba592fba ui: the Era map canvas over 1.12's own world map, and the Line region
What 1.12 withholds here is a framework, not a verb. Its world map is a fixed
1002x668 frame showing one zone — twelve tiles, a hit button, POI buttons, two
dropdowns — with no canvas, no pan, no zoom, no pins and no notion of where one
map sits on another. Every map addon written after 4.0 drives the canvas
framework instead, and there is no stock file to migrate from because there is
no stock feature.

The constraint that shaped every decision: `WorldMapFrame` already has eight
1.12 consumers on this install, all anchoring icons to `WorldMapDetailFrame`
and `WorldMapButton`. So nothing is replaced. The canvas is added, and those
two frames are reparented INTO it — at rest the rect they had, at scale 1 with
no pan, so the stock window is pixel-identical, and the payoff is that the 1.12
art and every icon on it now pan and scale WITH the canvas for free.

Clipping comes from a real ScrollFrame: this client clips a scroll child and
every descendant of it from one source, in the draw pass and the hit-test, so
drawing and hitting agree. The base-art layer a canvas addon hides is a proxy
that switches draw layers rather than the frame, because hiding the frame would
take the POI buttons and every addon's icons with it.

Three regressions, each found by measurement, each pinned: the anchor cycle
(container to detail frame to canvas to container — a cycle resolves to
nothing, and the symptom was `GetCenter()` nil inside the stock hover pass); the
base scale (a minScale below 1 opens the map zoomed out of its own window); and
`dataProviders` being a set rather than an array, because callers iterate it
with `pairs` and bind the key — an array handed that loop the number 1.

`CreateLine` is a Texture carrying two endpoints, drawn through the
explicit-corner quad path the cooldown wedge already uses. Nothing stands in
for it: a line between two moving points is a rotated quad whose angle and
length both change. `C_Texture.GetAtlasInfo` answers nil for every name, and
the nil is the answer — but the getter's presence is what makes the common
atlas guard take its fallback branch instead of raising.

The harness grew a second surface, and the finding is worth keeping: the full
screen map hides `UIParent`, so opening it in the ordinary render probe dropped
the corpus's draw column from 37/86 to 18/86 — nineteen unrelated addons. As
its own pass: no addon lost a quad and nineteen gained, pfQuest 374 -> 911,
pfUI 1063 -> 2259.

OpenMapClassic now reads 1/1 on all five columns, stitching 74 of the realm's
own zones. Corpus roster unchanged at 85 ok / 1 fail; the 12 data-half reds
byte-identical to HEAD's.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
2026-11-01 22:21:35 -07:00
gromandClaude Opus 5 b49856c5cb ui: texture masks, the later-era texture dials, and the pools a canvas map needs
The mask pair is the one that matters and nothing can stand in for it. 1.12 has
no texture masking at all — its single masked thing is the minimap disc, cut
engine-side — and no idiom reaches the same end, because the point is that a
rectangular texture draws as a non-rectangular shape. A seamless world map is
that operation and nothing else: a zone's art is a rectangle of parchment and
the map wants it cut to the zone's coastline.

It was affordable because the render pass already had the slot. `UiQuad::mask`
is a screen-anchored alpha mask built for the minimap's fixed circle with tiles
panning under it, which is exactly `AddMaskTexture`'s semantics — the mask is
another region with its own anchors, and the subject keeps the fragments its
alpha covers at that place. So this is plumbing, not a new material.

One mask per quad, said rather than hidden: widening it is a second texture
binding and a second uniform on every UI quad in the client. The list is kept
whole, because the order decides the winner — first added, which for a stitched
map is the land mask. Two traps are pinned by tests: a mask region is skipped at
extract rather than marked hidden, since its rect is what it covers; and a mask
whose art is missing drops the mask, not the quad, because zero coverage would
blank the art it was meant to shape.

`Texture:SetRotation` was nearly free — the renderer has carried the angle since
the minimap arrow, so this is the Lua setter for it. `SetDesaturation` is the
fractional form of 1.12's own flag, rounded onto the one flag this renderer has,
with the getter answering the caller's own number. The tile pair is applied: the
host computes the repeat count once the art has loaded.

`SetPropagateKeyboardInput` is stored, not applied: 1.12 already always
propagates, so there is no swallow mode for false to select. `EditBox:HasFocus`
publishes a fact this client already tracks and never exposed.

Corpus: 85 ok / 1 fail, roster and digest unchanged. The 12 data-half reds are
byte-identical to HEAD's.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
2026-11-01 21:15:55 -07:00
gromandClaude Opus 5 0886952611 ui: a numeric texture argument is a file id, not a colour channel
With this and the font-object rename below, **OpenMapClassic loads clean, survives a session
start and survives a UI probe** — from dying on its first file, inside Ace3, before a line of
its own code ran.

Later clients gave every shipped file a numeric id and addons pass those instead of paths. Both
of our texture setters read a number as the **colour** arm, and they failed differently, which
is why this is worth a module of its own:

- `region/paint.rs`'s `SetTexture` needs *two* numbers for a colour, so a lone
  `SetTexture(130763)` fell through to the catch-all and drew **nothing**.
- `button.rs`'s slot setters take one number as a colour with green and blue defaulting to 0,
  so `SetNormalTexture(130763)` painted a **solid block** where the button art belongs. That is
  the arm Ace3 calls, and it failed silently: nothing raised, the art was simply wrong, which is
  the worst way for this to go.

**The rule**: a lone numeric argument of 2 or more is a file id. A colour channel is 0..1 and
every id a client ever minted is six figures, so the two cannot collide and `SetTexture(0.5)`
keeps answering exactly as 1.12 does.

**The table is transcription, not invention.** Every numeric literal passed to a texture setter
anywhere in the installed corpus *and* in both `Addon_Ports` addons — 21, all from Ace3/AceGUI
— is annotated by its own author with the path it means:

    button:SetNormalTexture(130763) -- "Interface\Buttons\UI-DialogBox-Button-Up"

So the ids stay the real ones rather than something of ours, and the source is the call site. An
id with no row draws nothing and records a warning naming it, because a wrong texture is harder
to notice than a missing one — and that warning is what says which id to add. `GetFileIDFromPath`
answers only what the table knows and nil otherwise: minting a synthetic id would hand back a
number no other client agrees with.

**`SetNormalFontObject`** is the later-era spelling of 1.12's `SetTextFontObject` (`0x780400`)
— the one name this family left behind, since `SetHighlightFontObject` (`0x780700`) and
`SetDisabledFontObject` (`0x780580`) are 1.12's own. It hands out the same function value, so
the spellings cannot drift. Ace3 calls it on every button it builds
(`AceConfigDialog-3.0.lua:586`), one line above the `SetNormalTexture(130763)` above.

What openmap still cannot do is **draw**: it does not build a map, it re-skins Era's canvas
`WorldMapFrame` (`Map/WorldMap.lua:823`), and that canvas does not exist here. That is the map
stack, not this.

Corpus: 100 addons, 85 ok / 1 fail, roster identical. benilla-ui 2085 passed. Chain green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
2026-11-01 18:43:51 -07:00
gromandClaude Opus 5 af823dd708 compat: the secure-handler family and the animation surface
Both modules land together because `classic_api/mod.rs` and `widget_surface.rs` carry rows for
each: split, the first commit named a module the second added, and would not have compiled.

## The secure-handler family, because 1.12 has nothing to be secure from

`frame.rs` declined it with a stated reason — "modern restricted execution, which 1.12 has no
equivalent of" — and that is right about the *sandbox* and is the whole of why it is now built.
A secure handler exists so an untainted snippet may call **protected** functions; 1.12 has
neither taint nor protected functions, so the restriction has no counterpart here, while the
plumbing underneath (a named frame store, a snippet on a child's handler) is ordinary
behaviour the snippets need. The body runs as plain Lua with the names the secure environment
would bind.

**The per-header shared table is the part worth getting right.** `MenuWheel.lua:484` writes a
bare `held = false` inside a snippet, and in the real secure environment that lands in a table
per header, not `_G`. `setfenv` onto one table per frame, `__index` to `_G` so the rest of the
API resolves; the test asserts `held` is **not** a global afterwards. My first test asserted
through `_G` and failed — the implementation was right and the test measured the wrong thing.

**`SecureActionButtonTemplate` is not empty**: reading your own attributes on click is plain
dispatch. `type="click"` clicks the frame in `clickbutton` (how the wheels reach the game's
micro buttons), and the lookup follows upstream's order (`type-<button>`, `*type*`, `type`).

**That exposed a real bug of ours**: `SetAttribute` silently dropped a frame.
`attribute_value` kept bool/int/number/string and sent everything else to `ScriptValue::Nil`,
so `SetAttribute("clickbutton", SomeFrame)` stored nothing — and upstream's `clickbutton` is
routinely a frame, which `Cinematic/MenuWheel.lua` relies on. A widget table is now kept as
`ScriptValue::Frame(id)`, the variant `NAME_PLATE_CREATED`'s arg1 already uses, which
round-trips through `object::frame_wrapper`. A non-widget table is still nil, documented.

**Not built, named rather than left to be discovered**: the binding half (`ClearBindings`,
`SetBindingClick`, …) are no-ops — no override-binding layer here to write into — so a wheel
opened with the mouse works and hold-a-key-and-release does not; and the `_onshow`/`_onhide`
attribute snippets are not run, since `SetAttribute` is a plain store with nothing to drive
them.

Templates in `assets/ui/CompatTemplates.xml`, listed on that wall with the reason: no stock
file to migrate from, and XML rather than `compat/` only because `inherits=` is loader-resolved.
A trap caught before shipping: `BackdropTemplateMixin.GetBackdropColor` forwarding to
`self:GetBackdropColor()` calls **itself** once `Mixin()` copies it onto the frame — the stock
getters answer, so there are no getter rows, and a test mixes in and reads back.

## `CreateAnimationGroup`: the surface, with nothing animated

1.12 has no animation system and this UI pass has no interpolation to hang one on. The cost is
the **absent constructor**: `Cinematic/Options.lua:7106` builds a rotating spinner and the nil
method aborted the whole options panel. Measured first — that is the addon's only animation in
55,661 lines, so this is a surface for a decoration.

**A non-looping group finishes the moment it plays**, firing `OnFinished`, because nothing
moves and that is the reading that keeps an addon's flow right; one that chains its next step
off `OnFinished` would otherwise wait forever. A looping group plays until stopped. Stated in
the header, since the name implies motion: a spinner does not spin, a fade appears at its
destination, a sequence lands on its last frame. Setters and getters round-trip.

Corpus: all 100 of grom's addons surveyed before and after — 85 ok / 1 fail, rosters
identical. benilla-ui 2081 passed. Full chain green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
2026-11-01 17:12:13 -07:00
gromandClaude Opus 5 e731a77a42 compat: RegisterUnitEvent, the fixed-strata pair and SetWordWrap
The next three blockers of the `Addon_Ports` pair, each measured with `addon_harness` before
and after rather than picked off a list.

**`RegisterUnitEvent(event, unit1 [, unit2])`** — Cinematic's load blocker, and
`docs/ADDON_COMPAT.md` already had it on the "plausible and untraced" line. The registration
is the stock one: same `event_to_frames` list, same registration order, and
`Model::frame_unit_events` holds the units per frame per event. `tick.rs`'s walk skips a
frame whose row for this event does not name `arg1`; the map is empty on an install with no
such addon and the walk tests `is_empty()` first, so the common path costs one load.

All three unregister paths drop the row, including the one that is easy to miss: a plain
`RegisterEvent` after a unit register **widens** the registration back, as upstream's does —
the frame keeps its place and loses the filter. A row surviving an unregister/re-register
cycle would swallow that event forever, so the test walks the whole cycle.
`Cinematic/Frames.lua:2611` registers `UNIT_AURA` for `"player"` at file scope, so the nil
method stopped the file dead.

**`SetFixedFrameStrata`/`SetFixedFrameLevel`** are no-ops, and that is the honest answer
rather than a stub: 9.0 added them to tell the *frame manager* to stop re-deciding a frame's
strata and level, and this client has no such manager — both are whatever the setters last
wrote and nothing else rewrites them, so the guarantee the caller asks for already holds.
Ace3 calls both unguarded (`AceConfigDialog-3.0.lua:575-576`).

**`SetWordWrap`/`CanWordWrap`** is 3.x's whether-to-wrap switch; 1.12's `SetNonSpaceWrap` is
a different question (where a wrap may fall, not whether one happens). **Stored, not
applied**, on the footing `SetSpacing` and `non_space_wrap` already set in `types.rs`: the
text layout wraps to the declared width unconditionally and has no one-line-and-clip mode for
the flag to drive, so honouring it would mean inventing a layout mode. Kept because the pair
must round-trip and because the missing *setter* is what costs —
`Cinematic/Options.lua:4130` calls it on a label and aborted the options panel there.

All four are registered in `widget_surface.rs`'s `ALLOWED_BEYOND` with their call sites. The
first `RegisterUnitEvent` row said "Frame" and the wall rejected it: event registration lives
on the one shared frame method table, which is where 1.12 keeps `RegisterEvent` too, so the
two are registered alike on all twenty frame kinds or neither is.

Movement: Cinematic's load `Frames.lua:2611` → `CameraWheel.lua:243`, session
`Options.lua:4130` → clear; openmap `AceConfigDialog:573` → `:575`.

benilla-ui 2077 passed, 0 failed. Full chain green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
2026-11-01 14:45:36 -07:00
gromandClaude Opus 5 79e2494fc6 compat: math.huge and SetColorTexture, the two load blockers of the ported pair
Measured through `addon_harness` before anything was written, which is the only reason this
is two small changes rather than a guess at a list. Both `Addon_Ports` addons failed to load
at all; these are their first two stops.

**`math.huge` was nil, deliberately.** `stdlib_backport.rs` restores the 5.1 stdlib members
ClassicAPI backports, scoped to what the corpus calls, and `math.huge` was on the stated
"nothing calls them" list. `Cinematic/Core.lua:1103` is `local lastMovingOff = -math.huge`
at **file scope**, so the nil stopped the addon's main file dead ("attempt to perform
arithmetic on field `?`") and nothing after it ran — one site, but a load-time one, which is
the bar that table sets. The real `ClassicAPI.dll` carries `huge` in its own stdlib name
table beside `fmod`, `modf`, `gmatch`, `match`, `select`, `maxn`, `cosh` and `reverse`, so
this is what the client we already claim to be actually has.

Two tests pinned it absent and are updated rather than deleted: `lua50.rs`'s keeps asserting
this *layer* removes it (the registry stash is the proof), and `stdlib_backport.rs`'s keeps
the narrowness assertion for the four that stay gone. The new assertion is `-math.huge`,
because file-scope arithmetic is the shape that failed.

**`Texture:SetColorTexture`** is the modern spelling of `SetTexture`'s colour arm, which
1.12 already has (`0x770360`'s 8×8 solid surface, and `region/paint.rs` defaults the alpha
to 1 as it does). Retail split the names when `SetTexture` stopped taking a colour, so every
Era-era addon writes the new one. `Cinematic/Options.lua:701` paints its list border with it
and died at session start; OpenMapClassic its map chrome. Four lines of the same composition
chunk `SetShown` and the geometry pair use, and an `ALLOWED_BEYOND` row with both call sites
— `docs/ADDON_COMPAT.md` had it on the same "plausible and untraced" line as
`RegisterUnitEvent`, and this is that line's follow-through.

Movement, same harness both ways:
- Cinematic load: `Core.lua:1103` → `Frames.lua:2611` (~10 files further; wants
  `RegisterUnitEvent` next). Session: `Options.lua:701` → `Options.lua:4130` (3400 lines on,
  wants `SetWordWrap`).
- OpenMapClassic: unchanged at `DialogBorderOpaqueTemplate`, which neither fix touches.

benilla-ui 2076 passed, 0 failed. Full chain green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
2026-11-01 14:19:56 -07:00
gromandClaude Opus 5 c8c4e7f855 Four defects out of his log, and the 65 warnings that are not ours
His 19-minute session put 8 errors on screen from two causes, and 73 WARN lines
of which 8 are ours. The whole triage is written down in the play-test memory,
because most of the log is the addons' own and nothing should re-chase it: five
files their `.toc`s name and do not ship, a QuickHeal template that is never
defined, an ATSW global string that only exists in its deDE branch, inspect
frames from a load-on-demand addon with no `## Dependencies` line, `<Binding>`
elements the frame loader is *supposed* to log, and a world-map lag line that
reported its own recovery 5.2 s later. Four of the eight are fixed here.

`GameTooltip:SetSpellByID` was nil, and it is not a keypress: the traceback is
pfUI's `ButtonEnter` into `SuperCleveRoidMacros/Core.lua:4217`, so it raised on
every action-bar hover of a macro that resolves to a spell — 7 times, each
leaving the button with no tooltip at all. `compat/classic_api/tooltip.rs` had
declined the whole `Set*ByID` family pending "an argument-shape check against
the stock verb each aliases". Done, and the check is why this is four lines:
there is no aliasing to do, because `tooltip_spell::set_spell_by_id` is the body
all four of 1.12's spell verbs already funnel through, and it takes a bare id —
the book slot or list row is what *they* have to convert first. `show_rank` is
the action's rather than the spellbook's, because the book draws "Rank N"
itself as the button's `subSpellName` and an id-named caller has no such label.
The caller's own comment says it reaches this only for a spell in neither
spellbook, so the cold-view path is the common one here; that needs no special
case, since the app's feed builds a view for any id in the catalog and an action
button re-asks its tooltip every `TOOLTIP_UPDATE_TIME` while hovered.

`resolve_spell_fx` despawned an entity the world no longer had. `FxInstance`'s
root is a *stored handle*, and `attach_spell_fx` is the system that notices one
lost with the unit's whole visual and re-arms or drops the instance — so between
that loss and that pass, the same-slot replace, the spell-id reap and the
self-termination clock all hold handles to entities that are gone. `try_despawn`
at those three, as `lifecycle.rs` already does; `tend_world_plants` keeps its
plain `despawn` because its entity comes from a query, not from a record.

The first paste of every Wayland session could never have worked.
`smithay-clipboard` sets `latest_seat` only on a `wl_keyboard` Key/Modifiers
event or a pointer press (`state.rs:522`, `:386`) — `Enter` sets `has_focus` and
nothing else — and refuses to load a selection without it. `HostClipboard`
opened its backend on first *use*, so the backend that read the clipboard was
created by the very Ctrl+V asking for it and its keyboard object was younger
than that chord's own key events. It is now opened as soon as the window can
give the `wl_display`. Costs +2 on the `Update` ambiguity ceiling, both pairs
against the schedule's two exclusive `&mut World` systems, which pair with
everything they have no declared order against.

And the one `changed_windows` ERROR a login prints had no remedy behind it: no
client can warp a free pointer on Wayland, so the loading screen's cursor
hand-back now asks only where the answer can be yes — off Wayland, or with the
pointer locked, which is winit's own rule.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
2026-10-28 20:26:05 -06:00
gromandClaude Opus 5 75f04b4f28 compat: FontString:GetStringHeight, which AceGUI calls unguarded
1.12 registers no `GetStringHeight` (`script/button.rs`: `0x782390` reads the label's height
instead) and ClassicAPI backports it, so `compat/classic_api/frame.rs` is its home. That file had
it on its own not-built list under "needs per-line layout facts the host measurer is not asked for
today" — true of `GetNumLines` and `GetWrappedWidth`, and not of this one: the whole-string height
is the other half of the number `GetStringWidth` already reads. `measure::current_measure` now owns
the staleness rule for both, so the two cannot disagree about when a measure has gone stale, and a
changed string still reads 0 rather than the previous string's height.

It is not cosmetic. AceGUI-3.0 calls it **unguarded** — `AceGUIWidget-Label.lua:54` is
`height = label:GetStringHeight()` and `AceGUIWidget-CheckBox.lua:90` is
`self:SetHeight(28 + self.desc:GetStringHeight())` — so every Ace options panel on this install
drew its labels and checkbox descriptions at the library's own "avoid zero-height labels" fallback
of one pixel: a window with all of its prose invisible. `addon_harness --probe AI_VoiceOver` builds
that panel's full 32-widget tree with zero errors and every `Label` at `624x1`, which is what
"can't see the actual options in VoiceOver" was.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
2026-10-16 18:45:25 -06:00
gromandClaude Opus 5 81d96af35d Everwood: the HD/OctoWoW line of benilla, imported onto upstream
This checkout had no git history at all, so this one commit carries the whole
of the Everwood work as a single import on top of chairmanpow/benilla-octo's
`main` (706cd71). The two trees have genuinely diverged — each has files the
other does not — so this is a fork point, not a catch-up.

Today's work, which is the reason a baseline exists at all:

- Booth previews no longer cast world shadows (`NO_BOOTH_SHADOW`). Portrait
  characters were entering the sun's shadow cascades, so every skinned-rig
  shadow specialization compiled live mid-frame; the hitch detector logged
  "main thread blocked 1316 ms" beside a `compiled LIVE` warning. This was
  the Fashion/Tmog stutter.
- Per-pane dress-up: each `DressUpModel` pane assembles and stages its own
  tried-on look instead of every pane mirroring the live player.
- Item template cache is append-only JSONL written on the IO pool, replacing
  an 11.6 MB whole-map JSON rewrite on the main thread every 10 s.
- `WOW_SYSTEM_TIMING` rebuilt per-thread: it took 2 mutexes per system enter
  and 4 per exit, costing 15 ms a frame (590 fps -> 60) and corrupting the
  measurements it was taken to produce.
- The visibility-churn sweep moved to its own `WOW_FLASH_CHURN` switch, so a
  frame-cost reading no longer pays 2.7 ms a frame for it.

Local state (`benilla-config/`, `logs/`, `target/`) is excluded by the
existing .gitignore; the account credential it holds is not in this commit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
2026-10-15 15:01:14 -06:00
Sam SungandClaude Opus 5.5 75edfd7ca5 SetFont gates as 1.12.1 does; SetSpacing on every text table (#946)
`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 #873
Fixes #692
Fixes #945

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 05:13:09 +07:00
Sam SungandClaude Opus 5.5 78a983b00e The VM speaks 1.12 alone: the Era globals and the uncalled hooks are gone, the engine's tooltip seams are Rust calls, strata/draw-layer/state-texture/sound/OnClick/quest-item/pickup take their 1.12 shapes, and the surface gate covers the production load (#573)
The Lua VM keeps no surface the 1.12.1 client lacks.

- **Removed:** the nine Era-only globals: `UnitAura`, `CancelUnitBuff`, `GetCursorInfo`, `GetInventoryItemID`, `GetPlayerFacing`, `GetTradePartnerName`, `IsGossipOptionCoded`, `UnitIsAFK`, `UnitIsDND`.
- **Hooks off Lua:** `GameTooltip:BenillaSetItemById` and `:BenillaGetTooltipOwner` are replaced by Rust-side calls. The uncalled `Benilla*` hooks are deleted.
- **Frame strata:** `SetFrameStrata` knows the reference's eight strata and raises its message for any other (`0x774450`).
- **`SetDrawLayer`** reads the layer name alone (`0x79a780`).
- **`Set<State>Texture`** reads a number as a path (`0x781b23`); there is no colour form.
- **`PlaySound`** names a kit and returns nothing (`0x4586d0`). `PlaySoundFile` returns one value (`0x458780`); benilla answers 1, a recorded deviation, since it plays after the call.
- **`OnClick`** sets only `arg1` (`0x779540`).
- **Quest getters:** `GetQuestItemInfo` and the quest-log reward and choice getters answer the reference's five values and its miss behaviour (`0x501f80`, `0x4e0b00`, `0x4e0cf0`).
- **`PickupContainerItem`** returns nothing (`0x4f9b30`).
- **Surface gates:** the bare VM's globals and every widget class's methods are held to `reference/1.12-globals.tsv` with one commented allowlist. A new gate on the full production load (core plus layer) allows beyond that only the layer's `Benilla`-prefixed globals.

Verified:
- A test per removal and per corrected form; a scripted undo of each fails its test.
- Each gate fails on a re-added name.
- `scripts/gates.sh` and `scripts/smoke.sh` are green.
- A live run showed the same warnings and 0 script errors before and after.
- In the 219-addon corpus, AckisRecipeList's bundled AceGUI-3.0 and Roid-Macros' Grid extension now behave as they do on 1.12.1.

Built on macOS; no platform seam touched.

Fixes #171

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 01:44:52 +07:00
Sam SungandClaude Opus 5.5 9c32e5126e String literals in benilla-ui, -world and -formats carry no record numbers (#299)
Part of #92, for `benilla-ui`, `benilla-world` and `benilla-formats`.

- 88 string literals drop private record and bug numbers, the dates of private incidents, and the workspace's name for the maintainer. They are assert and panic texts, log and printout lines, the `--` comments of the Lua prelude and of test fixtures, and one Lua assert. Each keeps its fact in its own words.
- Two test names lose their numbers: `burning_steppes_lava_builds_a_magma_surface_at_the_reported_pin` and `join_channel_by_name_answers_the_row_custom_and_nil_legs`.
- A test-only event, `B271_PROBE`, is now `ERROR_PROBE` in the two crates that register and fire it. A fixture's error text in `benilla-app`'s `ui_script` tests drops its bug number, together with the assertion that reads it.
- Doc comments that named a client function by a disassembler's label (`FUN_0068a830`) now give its address, in `benilla-formats`, `benilla-assets` and one `benilla-app` file.
- Every literal change keeps each literal's line count, proven with comments stripped and every literal masked. The two renamed tests differ only in their names.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 17:01:02 +07:00
Sam SungandClaude Opus 5.5 fd386e75ae Comments in benilla-ui: one line of fact each, no history, no record numbers (#143)
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>
2026-09-25 01:58:49 +07:00
SamandClaude Fable 5.1 26fb54b402 fourteen settings go live, /leave holds, and /assist can swing
- mouse-look speed is cameraYawMoveSpeed and cameraPitchMoveSpeed, so pfUI's afkcam does what it asks and an addon driving the stock Interface Options window runs its load and Defaults walks to the end; the shipped feel does not move (2147)
- the eight combat-log range sliders are real settings, so BigWigs' Range plugin and DPSMate read and write them, and the Show Pet Melee Damage and periodic-spell settings genuinely silence what they gate (2147)
- the stock Sound Options window loads hidden beside ours for addons to find, and its three check-box settings are real: the listener sits at your character, emote voices can be muted, zone music can loop without its silence gap (2147)
- /assist with assistAttack set selects and starts swinging; /leave General survives a relog, a /join puts the channel back in its chat window's list, and SetGuildRecruitmentMode joins and leaves GuildRecruitment - City (2144)
- wipe, strsplit, strjoin, strtrim, strconcat and tostringall are gone: no 1.12 client has them, and the addons that call them are multi-client builds whose vanilla path raises on the real client too (2146)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-11 20:53:36 +07:00
SamandClaude Fable 5.1 31371641eb the mailbox takes typing again, and events carry their real arguments
- the mail window's send tab takes typing again: the subject, body and money boxes were taking no input while the recipient box swallowed every key, and a sent letter clears its subject and item icon instead of putting them straight back (2145)
- seven events fire the way the reference does: UNIT_DYNAMIC_FLAGS fires at all, swapping two identical stacks between bank slots fires PLAYERBANKSLOTS_CHANGED twice, the pet sheet's training points repaint, and ITEM_LOCK_CHANGED loses its invented arguments (2140)
- nine widget methods 1.12 addons probe for are in: ShaguTweaks stops recolouring textures the reference leaves alone, pfUI and _Nameplates stop raising on a tooltip's GetAnchorType, and 63 AceEvent addons get RegisterAllEvents (2142)
- six post-1.12 methods leave the widget surface (SetSize, SetShown, HasFocus, SetFormattedText and two tooltip verbs), so an addon reaching for a later client's API finds what a real 1.12 client gives it (2142, 2146)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-11 20:52:50 +07:00