diff --git a/crates/sylpheed-formats/examples/settle_midramp_census.rs b/crates/sylpheed-formats/examples/settle_midramp_census.rs new file mode 100644 index 00000000..eeb4ac15 --- /dev/null +++ b/crates/sylpheed-formats/examples/settle_midramp_census.rs @@ -0,0 +1,75 @@ +//! How often does posing at the SCREEN's settle instant catch an element +//! mid-ramp? The adversarial census of my own proposal. +//! +//! The port agent found `ptmsg` — the main menu's footer — at alpha **127.5 of +//! 255** at that screen's settle instant, because the longest keyframe-free +//! interval ends exactly as the footer starts to arrive. `screen render --settle` +//! already prints "⚠️ narrow — this bundle may never settle" there: the window is +//! **12 units**. +//! +//! ⚠️ **And my `rest_vs_settle` filter was too permissive**: it dropped bundles +//! with a window under 10 units, so a 12-unit window passed while the tool itself +//! was flagging it. This splits by width instead of picking one cutoff. +//! +//! "Mid-ramp" = at the settle instant the element sits strictly inside an interval +//! whose two endpoint poses DIFFER — it is interpolating, not held. +//! +//! cargo run -p sylpheed-formats --example settle_midramp_census +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(); + // buckets by settle-window width + let edges = [0u32, 10, 20, 30, 60, u32::MAX]; + let names = ["< 10", "10–19", "20–29", "30–59", ">= 60"]; + let mut els = [0usize; 5]; + let mut mid = [0usize; 5]; + let mut bundles = [0usize; 5]; + 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 }; + let w = hi - lo; + let bi = edges.windows(2).position(|p| w >= p[0] && w < p[1]).unwrap_or(4); + bundles[bi] += 1; + let st = lo + w / 2; + for el in &b.elements { + let k = &el.keyframes; + if k.len() < 2 { continue } + els[bi] += 1; + // the interval containing the settle instant + let mut interpolating = false; + for pair in k.windows(2) { + if let (Some(t0), Some(t1)) = (pair[0].time, pair[1].time) { + if t0 <= st && st <= t1 && t0 != t1 { + let same = pair[0].fade == pair[1].fade + && pair[0].x == pair[1].x && pair[0].y == pair[1].y + && pair[0].scale_x == pair[1].scale_x + && pair[0].scale_y == pair[1].scale_y; + if !same && st != t0 && st != t1 { interpolating = true } + break; + } + } + } + if interpolating { mid[bi] += 1 } + } + } + } + println!("{:8}{:>10}{:>10}{:>12}{:>10}", "window", "bundles", "elements", "mid-ramp", "share"); + for i in 0..5 { + if els[i] == 0 { continue } + println!("{:8}{:>10}{:>10}{:>12}{:>9.1}%", names[i], bundles[i], els[i], mid[i], + 100.0 * mid[i] as f64 / els[i] as f64); + } + let te: usize = els.iter().sum(); let tm: usize = mid.iter().sum(); + println!("{:8}{:>10}{:>10}{:>12}{:>9.1}%", "ALL", bundles.iter().sum::(), te, tm, + 100.0 * tm as f64 / te as f64); + println!("\n--- END (if this line is missing, the run did not finish) ---"); +} diff --git a/docs/re/data/settle-midramp-census.txt b/docs/re/data/settle-midramp-census.txt new file mode 100644 index 00000000..1e56ce52 --- /dev/null +++ b/docs/re/data/settle-midramp-census.txt @@ -0,0 +1,48 @@ +# The settle-instant pose's OWN failure mode, censused. 2026-08-30. +# examples/settle_midramp_census.rs +# +# The port agent found ptmsg -- the main menu's footer -- at alpha 127.5 of 255 +# at that screen's settle instant, because the longest keyframe-free interval +# ends exactly as the footer arrives. VERIFIED here: build 5's window is +# [44, 56] = 12 units, instant 50, and screen render --settle ALREADY PRINTS +# 'narrow -- this bundle may never settle'. +# +# 'Mid-ramp' = at the settle instant the element sits strictly inside an +# interval whose endpoint poses DIFFER: it is interpolating, not held. +# +window bundles elements mid-ramp share +< 10 896 3571 1460 40.9% +10–19 478 1755 791 45.1% +20–29 281 935 297 31.8% +30–59 393 4084 476 11.7% +>= 60 265 3646 548 15.0% +ALL 2313 13991 3572 25.5% + +--- END (if this line is missing, the run did not finish) --- +# +# 🔴 AND THE OBVIOUS READING OF THAT TABLE IS WRONG. 'Narrow window means the +# settle pose is bad' is refuted by the screens that motivated the proposal: +# +# build 4 title window 76 wide port: settle wins 9x +# build 5 main menu window 12 narrow port: settle loses 1.2x +# build 6 EXTRAS window 12 narrow port: confounded +# build 10 publisher window 8 NARROWER port: settle wins 75x +# build 11 developer window 8 NARROWER port: settle wins 33x +# build 0 loading window 4 narrowest +# +# The two splashes have an 8-unit window -- narrower than the main menu's 12 -- +# and the settle pose beats rest() there by 75x and 33x. So width does not +# predict quality, and I nearly published that it did. +# +# The predictor is the one the port already stated: the settle pose wins +# DECISIVELY where rest() lands on a transient's PEAK (the splashes: rest() +# returns a=212 / a=255 on flashes that are over by t=45), and loses SLIGHTLY +# where rest() is already sound and an element arrives after the window closes +# (the menus: the footer caught at a=127). Those are independent of width. +# +# 🔴 AND MY OWN FILTER WAS WRONG IN BOTH DIRECTIONS. rest_vs_settle dropped +# bundles with a window under 10 units. That admitted the 10-19 bucket, which +# is the WORST at 45.1 % mid-ramp -- and it EXCLUDED the two splashes at width +# 8, which are the strongest evidence FOR the proposal. A threshold chosen +# from a documented rule of thumb, applied without checking which screens it +# admitted and which it threw away. diff --git a/docs/re/structures/ui-resting-pose.md b/docs/re/structures/ui-resting-pose.md index 3a2f1a99..6ed3a0fc 100644 --- a/docs/re/structures/ui-resting-pose.md +++ b/docs/re/structures/ui-resting-pose.md @@ -415,6 +415,47 @@ mine**: its publisher splash against a committed capture, **settle-instant pose differing area, against the game. **That** is the evidence for the proposal; the numbers above describe its effect and do not establish it. +### 🟡 The candidate's own failure mode — censused, and the obvious explanation is wrong + +The port agent found `ptmsg`, the main menu's footer, at **alpha 127.5 of 255** at +that screen's settle instant. Verified: build 5's window is **[44, 56] = 12 units**, +and `screen render --settle` **already prints** *"narrow — this bundle may never +settle"*. + +Disc-wide, elements caught **mid-ramp** at their screen's settle instant +([`../data/settle-midramp-census.txt`](../data/settle-midramp-census.txt)): + +| settle window | elements | mid-ramp | share | +|---|---|---|---| +| < 10 | 3 571 | 1 460 | 40.9 % | +| **10–19** | 1 755 | 791 | **45.1 %** | +| 20–29 | 935 | 297 | 31.8 % | +| 30–59 | 4 084 | 476 | 11.7 % | +| ≥ 60 | 3 646 | 548 | 15.0 % | +| **all** | **13 991** | **3 572** | **25.5 %** | + +🔴 **The obvious reading — "a narrow window means the settle pose is bad" — is +refuted by the very screens that motivated the proposal**, and I nearly published +it: + +| build | screen | window | port's measurement | +|---|---|---|---| +| 4 | title | 76 | settle wins **9×** | +| 5 | main menu | 12 | settle loses 1.2× | +| **10** | **publisher** | **8** | settle wins **75×** | +| **11** | **developer** | **8** | settle wins **33×** | + +The splashes are **narrower than the menu** and the settle pose wins by 75×. Width +does not predict quality. The predictor is the port's own: it wins decisively where +`rest()` lands on a **transient's peak**, and loses slightly where `rest()` is +already sound and an element **arrives after the window closes**. + +🔴 **And my `rest_vs_settle` filter was wrong in both directions.** Dropping bundles +with a window under 10 units admitted the **10–19** bucket — the *worst*, at 45.1 % — +and **excluded both splashes at width 8**, the strongest evidence *for* the proposal. +A threshold taken from a documented rule of thumb and applied without checking which +screens it admitted and which it threw away. + ### 🔴 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)):