Files
benilla-octo/crates
gromandClaude Opus 5 0c862838da ui: a SetScale names its subtree instead of giving up on the graph
`[ui-phases]`'s new write-site tally answered the combat-fps question on its
first armed session: 463 whole-graph layout derivations across 34 windows, and
**232 of them** at `object/frame_state.rs:427` — the `touch_layout()` inside
`SetScale`. Splitting the per-window count by site and regressing against
resolve time over 43 in-world windows, resolve correlates with `SetScale`
derivations at r = +0.996 and with every other derivation at r = -0.041. The
four costliest windows of the run are 36-64 `SetScale` derivations each and
almost nothing else.

That also corrects the flat "~15.5 ms per derivation" of the round before: a
mint-triggered derivation costs ~1.1 ms, a `SetScale`-triggered one 12-13 ms.
The difference is that `SetScale` gave up *two* ledgers, not one.

The caller is pfUI's action-button press animation
(`pfUI/modules/actionbar.lua:182`, `:207`, `:233`, `:259`): four styles that all
run `this:SetScale(this:GetScale() + fade)` on `OnUpdate` for the ~60 frames a
button's fade lasts. Pressing an ability starts it, which is why a settled
interface never reproduced this and a fight always did.

Both give-ups had precise versions already, written for `SetParent`:

- `touch_layout()` -> `touch_layout_subtree_scale(h)`. A scale write moves the
  subtree's `effective_scale` and nothing else: anchor targets are ids fixed at
  `SetPoint`, so no edge moves and the roster is unchanged.
- `touch_measure_all()` -> the same walk now names the subtree's regions on the
  measure ledger. This was the bigger half. `measure_dirty = None` makes the
  next `fontstrings_needing_measure` walk all ~43,000 regions of a real
  interface to issue, typically, zero requests; `stale_measure_key` reads only a
  region's own data plus *its owner frame's* `effective_scale`, so the subtree's
  regions are exactly the ones that can go stale.

Measured on the player's own 92-addon interface, 43,195 regions, one `SetScale`
per frame for 240 frames (`tick_bench`'s new `[scale]` row):

    before   changing  resolve 0.601 ms/f · 0.996 derivations/f (239/240)
    after    changing  resolve 0.005 ms/f · 0.000 derivations/f (  0/240)
    control  same      resolve 0.005 ms/f · 0.000 derivations/f (  0/240)

`changing` is now indistinguishable from a control that writes the value it
already holds. The harness understates the live cost (its measure answers are a
6px-per-char stub), so the live saving is to be confirmed in a play-test, not
predicted from that row.

Correctness is left to `WOW_LAYOUT_VERIFY`, which `cfg!(test)` forces on for all
1975 benilla-ui tests: it re-derives the whole graph after every `resolve()` and
panics on any rect the incremental pass got wrong, and runs the full measure
roster beside the ledger drain and panics on any stale region the ledger missed.
So the two new tests assert only that the cheap path was taken, on a graph three
frames deep with a FontString on the leaf. The real-interface verify run fails
identically to its documented pre-existing red (`frames 13379 -> 13379, regions
29597 -> 29645`), so this change adds nothing to it.

Also repairs an assert message in `layout_gate.rs` whose line continuations had
been eaten by a heredoc, leaving runs of spaces mid-sentence.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
2026-10-20 20:57:15 -06:00
..