ui: WOW_UI_PHASES — the UI pass's phase split, windowed

`WOW_UI_TICK` measures `UiScript::tick`. `[sys-timing]` measures the Bevy system around it,
`ui_script::extract::tick_script`, which also runs the FontString measure round-trip and
`UiScript::resolve`. In a combat session the first read 38 ms/s — its lowest of the whole run —
while the second read 157 ms/s. Neither instrument was wrong, and 76% of the cost was in the space
between them, which is where the stutter players report lives.

`UiFrameCost` has carried the whole split since the hover recorder needed it, and `WOW_UI_COST=1`
prints it one line per frame: at this client's framerate, twenty thousand synchronous stderr writes
a minute from the thread being measured, which is the trap `sys_timing`'s header records costing
15 ms a frame. The split existed and was unusable in exactly the situation it was for.

So a window, and a worst column — a phase that is ruinous on one frame in a hundred has a mean near
nothing and is the only thing a player feels. `derives` is in the per-frame line because
`UiFrameCost::derives` is documented "expected zero", so a nonzero one names a whole-graph
re-derivation on sight.

`UiFrameCost::stamped` comes with it: the paint can return before any of its three arms wrote this
frame's phases, and a reader that cannot tell counts the previous frame twice.

Armed in ./measure.sh at 5 s, with the per-frame form left alone for the hover recorder.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
This commit is contained in:
2026-10-20 18:17:01 -06:00
co-authored by Claude Opus 5
parent 21c0befdca
commit e4851df7df
5 changed files with 365 additions and 2 deletions
@@ -483,6 +483,8 @@ pub(super) fn tick_script(
// Per frame: both halves' measure passes add to these, and only this zeroes them.
ui_cost.measured = 0;
ui_cost.measured_texts.clear();
// The phases below are last frame's until the paint writes them; see `UiFrameCost::stamped`.
ui_cost.stamped = false;
let mut t_mark = cost_on.then(std::time::Instant::now);
// μs since the previous mark, re-armed; nothing with the meter off.
let mut lap = move || -> u128 {
@@ -735,6 +737,7 @@ pub(super) fn paint_script(
skipped: true,
spliced: 0,
dropped: 0,
stamped: true,
};
}
if printing {
@@ -958,6 +961,7 @@ pub(super) fn paint_script(
skipped: false,
spliced: changed.len(),
dropped: align.dropped.len(),
stamped: true,
};
}
if printing {
@@ -1090,6 +1094,7 @@ pub(super) fn paint_script(
skipped: false,
spliced: 0,
dropped: 0,
stamped: true,
};
if printing {
eprintln!(