From 96e95a51ec28dca1f474bd3cab2dacef0a7555c6 Mon Sep 17 00:00:00 2001 From: grom Date: Thu, 29 Oct 2026 09:55:07 -0600 Subject: [PATCH] Price the rest of the tooltip path, and name what a raise costs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit He said the tooltip lag is not only signs, so the two hover benches now tally layout derives and the live frame count per window, which is what turned the sign case from a hypothesis into a number. What they say. A held hover is clean: 1.7 ms/frame under forty plates, 0.76 in a city, and no per-frame derive. A rebuild costs 6.1 ms and gives up naming the graph exactly once, always at frame_id — the mint of a frame the model has not seen, which is the one write that genuinely cannot be named, since a new node has to join the cached graph. The frame is not ours. OctoPawn hooks GameTooltip's OnShow and creates an anonymous frame on every show, purely to own a one-shot OnUpdate delay — twice over, from ScoringCore.lua:540 and Pdmo.lua:101, which is the ordinary 1.12 way to write C_Timer.After. So every tooltip raise on his interface mints frames and re-derives, on the reference client too. Live frames rise by far less than the number created (137 against 480), so Lua reclaims most of them: a cost per raise, not an unbounded leak. Not acted on, deliberately. The lever would be letting a brand-new leaf join the cached graph without a full derive, which is a layout-engine change and wants its own measurement, not a change tacked onto a bug session. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn --- .../src/addon_harness/tick_bench_tests.rs | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/crates/benilla-app/src/addon_harness/tick_bench_tests.rs b/crates/benilla-app/src/addon_harness/tick_bench_tests.rs index ba9b4d67..648f4bee 100644 --- a/crates/benilla-app/src/addon_harness/tick_bench_tests.rs +++ b/crates/benilla-app/src/addon_harness/tick_bench_tests.rs @@ -1252,6 +1252,23 @@ fn hover_frame(s: &mut UiScript, company: Company, plates: &[PlateFeed], rebuild /// text (0.1 requests a frame, the same held or rebuilt): neither the patch chain nor the font /// engine is in this cost, so neither can explain the part of grom's 90-to-31 fps that the VM /// does not account for. Whatever the rest is, it is app-side, outside this harness. +/// +/// **What the derive tally and the frame count add** (both printed per window): a rebuild gives up +/// naming the layout graph exactly **once each**, always at `model.rs`'s `frame_id` — which is the +/// mint of a frame the model has not seen before, and the one write that genuinely cannot be named +/// precisely, since a new node has to join the cached graph. A held hover does not. +/// +/// The frame is not ours. `OctoPawn` hangs an `OnShow` on `GameTooltip` and, on every show, +/// creates an anonymous `CreateFrame("Frame")` purely to own a one-shot `OnUpdate` delay — twice, +/// from `Scoring/ScoringCore.lua:540` and `UI/Paperdoll/Pdmo.lua:101`, which is the ordinary 1.12 +/// way to write `C_Timer.After`. So **every** tooltip raise on his interface mints frames and +/// therefore re-derives, on the reference client too. The live frame count rises by far less than +/// the number created (137 against 480 here), so Lua reclaims most of them; it is a cost per +/// raise, not an unbounded leak. +/// +/// Kept as instrumentation rather than acted on: the lever would be letting a brand-new leaf node +/// join the graph without a full derive, which is a layout-engine change and wants its own +/// measurement. #[test] #[ignore = "bench — needs WOW_DATA and the player's AddOns; run explicitly with --release"] fn a_mouseover_plate_rebuilt_every_frame() { @@ -1273,6 +1290,7 @@ fn a_mouseover_plate_rebuilt_every_frame() { font_us: 0.0, }); install_live_asset_probes(&mut s, &data, &root, &PROBES); + let _ = s.take_layout_derive_sites(); // the first take is what arms the tally let plates = plate_feeds(); let per = f64::from(FRAMES); @@ -1327,6 +1345,17 @@ fn a_mouseover_plate_rebuilt_every_frame() { (after.2 - before.2) as f64 / per, (after.3 - before.3) / per / 1000.0, ); + for (site, n) in s.take_layout_derive_sites().iter().take(5) { + println!("[hover] {shape} {label} {n:5} derives from {site}"); + } + println!( + "[hover] {shape} {label} counts {:?}", + s.tick_profile_counts() + .iter() + .filter(|(l, _)| *l == "frames" || *l == "regions") + .map(|(l, n)| format!("{l} {n:.0}")) + .collect::>() + ); let errs = s.take_errors(); if !errs.is_empty() { println!(