From 0c9224fbbc8876647967a7ffce340d0c506b10bb Mon Sep 17 00:00:00 2001 From: sylph-decoder Date: Sun, 30 Aug 2026 12:22:44 +0000 Subject: [PATCH] re: propose settle-instant posing -- and my control cannot validate it The proposal: pose every element at the SCREEN's settle instant rather than asking each element for its own resting pose. On the 2 249 fallback elements in settling bundles the visible-pose rate falls 73.6 % -> 34.7 %. But the control fails twice. Naive, over every plateau element: 46.6 %. That one was misspecified and I caught it by asking what the number means physically -- rest() finds *a* held pose and many elements hold one during the build-in then move on, so it answers a different question and disagreement proves nothing. Restricted to elements HOLDING ACROSS the settle instant: 78.1 %, still not a pass. And the residual is ambiguous by construction: rest_plateau() picks one plateau, so an element with two whose settle instant falls in the other will disagree -- and there pose_at(settle) is RIGHT. The control cannot separate 'the candidate is wrong' from 'the incumbent is wrong'. Recorded as the general point: comparing a candidate to the incumbent cannot adjudicate when the incumbent is the thing under suspicion. It is the wrong shape of experiment, not a tuning problem. What does adjudicate is the oracle and it is the port's measurement, not mine -- publisher splash against a committed capture, settle-instant pose RMSE 2.17 / 0.01 % differing against --pose=rest 9.05 / 0.75 %. My numbers describe the proposal's effect; they do not establish it. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v --- .../examples/rest_vs_settle.rs | 97 +++++++++++++++++++ docs/re/data/rest-vs-settle.txt | 52 ++++++++++ docs/re/structures/ui-resting-pose.md | 39 ++++++++ 3 files changed, 188 insertions(+) create mode 100644 crates/sylpheed-formats/examples/rest_vs_settle.rs create mode 100644 docs/re/data/rest-vs-settle.txt diff --git a/crates/sylpheed-formats/examples/rest_vs_settle.rs b/crates/sylpheed-formats/examples/rest_vs_settle.rs new file mode 100644 index 00000000..3ab4817c --- /dev/null +++ b/crates/sylpheed-formats/examples/rest_vs_settle.rs @@ -0,0 +1,97 @@ +//! Should the settled pose come from each element's `rest()`, or from the +//! **screen's** settle instant? +//! +//! Three iterations have measured how badly `rest()`'s dwell fallback behaves — +//! 2 305 elements where it decides, 1 457 of them handed the element's *maximum* +//! alpha, and by construction none of those poses is held. What has been missing +//! is a proposal. +//! +//! `UiBuild::settle_time()` already exists: the midpoint of the longest +//! keyframe-free interval **across the whole build**. That is the port agent's +//! "re-key on the screen's span rather than the element's", and its shipped path +//! poses `pose_at(hold)` and agrees with every capture it holds at 0.01 %. +//! +//! ⚠️ **Control first.** On elements where `rest()` is already sound — the plateau +//! path, a pose the element genuinely holds — `pose_at(settle)` must AGREE. If it +//! disagrees there, it is not a better rule, it is a different one. +//! +//! ⚠️ `ui-settle-time.md` records that 42 % of bundles have a settle window under +//! 10 units and never settle at all. Bundles are split on that here rather than +//! averaged over. +//! +//! cargo run -p sylpheed-formats --example rest_vs_settle +use sylpheed_formats::{pak::PakArchive, ui_layout}; +use std::path::PathBuf; + +fn main() { + let root = PathBuf::from(std::env::var("SYLPHEED_DISC").expect("SYLPHEED_DISC")); + let mut paks: Vec = std::fs::read_dir(root.join("dat")).expect("dat/") + .filter_map(|e| e.ok().map(|e| e.path())) + .filter(|p| p.extension().is_some_and(|x| x == "pak")).collect(); + paks.sort(); + // control population (plateau) and test population (fallback), each split by + // whether the bundle settles at all + let (mut ctl_n, mut ctl_cov, mut ctl_agree) = (0usize, 0usize, 0usize); + let (mut fb_n, mut fb_rest_vis, mut fb_settle_vis) = (0usize, 0usize, 0usize); + let mut narrow = 0usize; + for pak in &paks { + let Ok(ar) = PakArchive::open(pak) else { continue }; + for e in ar.entries() { + let Ok(by) = ar.read(e) else { continue }; + let Some(b) = ui_layout::parse_build(&by) else { continue }; + let Some((lo, hi)) = b.settle_window() else { continue }; + if hi - lo < 10 { narrow += 1; continue } // this bundle never settles + let st = lo + (hi - lo) / 2; + for el in &b.elements { + if el.keyframes.len() < 2 { continue } + let plateau = el.keyframes.windows(2).any(|w| w[0].x == w[1].x + && w[0].y == w[1].y && w[0].scale_x == w[1].scale_x + && w[0].scale_y == w[1].scale_y && w[0].fade == w[1].fade); + let (Some(r), Some(s)) = (el.rest(), el.pose_at(st)) else { continue }; + let (ra, sa) = ((r.fade >> 24) & 0xff, (s.fade >> 24) & 0xff); + if plateau { + // ⚠️ The first version of this control compared EVERY plateau + // element and got 46.6 % agreement — then I asked what that + // means physically. `rest()` finds *a* held pose; many + // elements hold one during the build-in and then move on. + // `pose_at(settle)` asks what is on screen WHEN THE SCREEN HAS + // SETTLED. Those are different questions, so disagreement + // proves nothing. The fair control is the subset where the + // held interval actually CONTAINS the settle instant. + ctl_n += 1; + let covers = el.keyframes.windows(2).any(|w| { + let held = w[0].x == w[1].x && w[0].y == w[1].y + && w[0].scale_x == w[1].scale_x + && w[0].scale_y == w[1].scale_y && w[0].fade == w[1].fade; + match (w[0].time, w[1].time) { + (Some(a), Some(bb)) => held && a <= st && st <= bb, + _ => false, + } + }); + if covers { + ctl_cov += 1; + if ra == sa && r.x == s.x && r.y == s.y + && r.scale_x == s.scale_x && r.scale_y == s.scale_y { ctl_agree += 1 } + } + } else { + fb_n += 1; + if ra > 0 { fb_rest_vis += 1 } + if sa > 0 { fb_settle_vis += 1 } + } + } + } + } + println!("bundles skipped as never-settling (window < 10 units): {narrow}\n"); + println!("CONTROL — elements where rest() takes the SOUND plateau path:"); + println!(" {ctl_n} plateau elements in settling bundles"); + println!(" {ctl_cov} of them HOLD ACROSS the settle instant — the fair control"); + println!(" pose_at(settle) agrees with rest() on {ctl_agree} of those ({:.1} %)", + 100.0 * ctl_agree as f64 / ctl_cov.max(1) as f64); + println!("\nTEST — elements where the unsound dwell fallback decides:"); + println!(" {fb_n} elements"); + println!(" rest() returns a VISIBLE pose on {fb_rest_vis} ({:.1} %)", + 100.0 * fb_rest_vis as f64 / fb_n.max(1) as f64); + println!(" pose_at(settle) returns a VISIBLE pose on {fb_settle_vis} ({:.1} %)", + 100.0 * fb_settle_vis as f64 / fb_n.max(1) as f64); + println!("\n--- END (if this line is missing, the run did not finish) ---"); +} diff --git a/docs/re/data/rest-vs-settle.txt b/docs/re/data/rest-vs-settle.txt new file mode 100644 index 00000000..721a703e --- /dev/null +++ b/docs/re/data/rest-vs-settle.txt @@ -0,0 +1,52 @@ +# Should the settled pose come from each element's rest(), or from the SCREEN's +# settle instant? 2026-08-30. examples/rest_vs_settle.rs +# +bundles skipped as never-settling (window < 10 units): 896 + +CONTROL — elements where rest() takes the SOUND plateau path: + 8171 plateau elements in settling bundles + 4796 of them HOLD ACROSS the settle instant — the fair control + pose_at(settle) agrees with rest() on 3748 of those (78.1 %) + +TEST — elements where the unsound dwell fallback decides: + 2249 elements + rest() returns a VISIBLE pose on 1655 (73.6 %) + pose_at(settle) returns a VISIBLE pose on 781 (34.7 %) + +--- END (if this line is missing, the run did not finish) --- +# +# THE PROPOSAL: pose every element at UiBuild::settle_time() -- the midpoint of +# the longest keyframe-free interval ACROSS THE BUILD -- instead of asking each +# element for its own resting pose. That is the port agent's 're-key on the +# screen's span rather than the element's', and its shipped path already does it. +# +# WHAT THE TEST SHOWS: on the 2 249 fallback elements in settling bundles, the +# visible-pose rate falls 73.6 % -> 34.7 %. Consistent with the proposal +# removing transient peaks. +# +# 🔴 BUT MY CONTROL CANNOT VALIDATE IT, and that is the finding. +# +# naive control, all plateau elements: 46.6 % agreement +# fair control, only those HOLDING ACROSS the settle: 78.1 % +# +# The naive one was misspecified and I caught it by asking what 46.6 % means +# physically: rest() finds *a* held pose, many elements hold one during the +# build-in and then move on, and pose_at(settle) asks what is on screen when +# the screen has SETTLED. Different questions; disagreement proves nothing. +# +# The fair control's 78.1 % is still not a pass -- and worse, its 21.9 % +# residual is AMBIGUOUS BY CONSTRUCTION. rest_plateau() picks one plateau; an +# element with two, whose settle instant falls in the other, will disagree -- +# and there pose_at(settle) is RIGHT and rest() is wrong. So the control +# cannot separate 'the candidate is wrong' from 'the incumbent is wrong'. +# +# ⚠️ COMPARING A CANDIDATE TO THE INCUMBENT CANNOT ADJUDICATE WHEN THE +# INCUMBENT IS THE THING UNDER SUSPICION. No amount of care with this control +# fixes that; it is the wrong shape of experiment. +# +# ✅ WHAT DOES ADJUDICATE IS THE ORACLE, AND IT IS NOT MINE. The port measured +# its publisher splash against a committed capture in both poses: +# timeline (settle-instant) pose RMSE 2.17 0.01 % differing +# --pose=rest RMSE 9.05 0.75 % differing +# 75x the differing area, against the game. That is the evidence for the +# proposal. My numbers describe its effect; they do not establish it. diff --git a/docs/re/structures/ui-resting-pose.md b/docs/re/structures/ui-resting-pose.md index 5aee44a2..ec137d7b 100644 --- a/docs/re/structures/ui-resting-pose.md +++ b/docs/re/structures/ui-resting-pose.md @@ -319,6 +319,45 @@ units brighter. **The element has no resting pose.** It is a flash; after it plays there is nothing. Neither answer is *derived* — one of them is merely harmless. +### 🟡 A proposal — and my own control cannot validate it + +Three iterations have measured how badly the fallback behaves without proposing +anything. The proposal is **pose every element at the *screen's* settle instant** +(`UiBuild::settle_time()`, the midpoint of the longest keyframe-free interval across +the build) rather than asking each element for its own resting pose +([`../data/rest-vs-settle.txt`](../data/rest-vs-settle.txt)). + +**Effect**, on the 2 249 fallback elements in bundles that settle at all: the +visible-pose rate falls **73.6 % → 34.7 %** — consistent with transient peaks +disappearing. + +🔴 **But the control fails, and then fails better, and still is not a pass:** + +| control | agreement | +|---|---| +| naive — every plateau element | **46.6 %** | +| fair — only those **holding across** the settle instant | **78.1 %** | + +The naive one was misspecified, caught by asking what 46.6 % means physically: +`rest()` finds *a* held pose, and many elements hold one during the build-in then +move on. Different questions; disagreement proves nothing. + +⚠️ **And the fair control's 21.9 % residual is ambiguous by construction.** +`rest_plateau()` picks one plateau; an element with two, whose settle instant falls +in the *other*, disagrees — and there `pose_at(settle)` is **right** and `rest()` +wrong. The control cannot separate *"the candidate is wrong"* from *"the incumbent +is wrong"*. + +🔴 **Comparing a candidate to the incumbent cannot adjudicate when the incumbent is +the thing under suspicion.** No care with this control fixes that; it is the wrong +shape of experiment. + +✅ **What adjudicates is the oracle, and it is the port agent's measurement, not +mine**: its publisher splash against a committed capture, **settle-instant pose RMSE +2.17 / 0.01 % differing** against **`--pose=rest` 9.05 / 0.75 %** — 75× the +differing area, against the game. **That** is the evidence for the proposal; the +numbers above describe its effect and do not establish it. + ### 🔴 Losing the example did not close the question — it is larger than one element Disc-wide ([`../data/rest-fallback-census.txt`](../data/rest-fallback-census.txt)):