From 5b218e6887a6b3d8f276e0dcb202ef32651dd405 Mon Sep 17 00:00:00 2001 From: Sylpheed RE agent Date: Sat, 29 Aug 2026 04:15:58 +0000 Subject: [PATCH] re(ui): refute my own fix for rest(), and correct the defect rate by 65% Two corrections from one experiment. A keyframe group is entry -> hold -> exit, and the exit ends invisible: on the five port screens the final keyframe is invisible for 21/24 (title), 8/16 (main menu), 12/18 (EXTRAS), 2/3 and 6/7 (splashes). So the screen as seen is the HOLD, which is why rest_plateau is the right primary rule and why "rest = last keyframe" would empty every screen. That suggested a fix: an element with no hold has no representative pose, so draw nothing rather than guess an endpoint. Tested through compose's visible mask and correlated against the live captures: title +0.9500 -> +0.6839 -0.2661 main menu +0.9460 -> +0.9037 -0.0423 EXTRAS +0.9440 -> +0.9094 -0.0346 Refuted on all three, and the reason invalidates a number I published. An element with a SINGLE keyframe has no adjacent pair, so the plateau test marks it plateau-less -- but its one pose is unambiguously its rest. Suppressing those removes backgrounds and full-screen layers, which is the title's -0.27. no plateau (as published) 3 807 (24.57 %) ... single-keyframe 1 502 trivially at rest, not a guess genuinely ambiguous 2 305 (14.88 %) So rest() guesses for 2 305 elements, not 3 807 -- the figure I gave the port overstated the defect by 65%. Corrected in HANDOFF and the page. METHOD: a predicate over adjacent PAIRS silently misclassifies a one-element list; and acting on a claim is a better test of it than re-reading it -- this flaw survived a census, a write-up and a handoff row, and died the moment the rule was used to change a rendering. --- .../examples/plateauless_suppression.rs | 44 ++++++++++++++++ docs/port/HANDOFF.md | 7 ++- docs/re/METHOD.md | 12 +++++ docs/re/REFUTED.md | 10 ++++ docs/re/structures/ui-resting-pose.md | 51 ++++++++++++++++++- 5 files changed, 120 insertions(+), 4 deletions(-) create mode 100644 crates/sylpheed-formats/examples/plateauless_suppression.rs diff --git a/crates/sylpheed-formats/examples/plateauless_suppression.rs b/crates/sylpheed-formats/examples/plateauless_suppression.rs new file mode 100644 index 00000000..0a004368 --- /dev/null +++ b/crates/sylpheed-formats/examples/plateauless_suppression.rs @@ -0,0 +1,44 @@ +//! Does DRAWING NOTHING beat guessing, for an element with no held pose? +//! +//! A keyframe group is entry → hold → exit, and the exit ends invisible (on the +//! five port screens the final keyframe is invisible for 21/24, 8/16, 12/18, 2/3 +//! and 6/7 elements). So the screen "as seen" is the HOLD — which is why +//! `rest_plateau` is the primary rule. An element with **no** plateau has no +//! hold, and `rest()` currently falls back to guessing an endpoint of a movement. +//! +//! This renders each screen twice — as-is, and with every plateau-less element +//! suppressed via `compose`'s `visible` mask — and correlates both against the +//! live capture. If suppression wins, the fallback should draw nothing. +//! Writes both composites as raw RGBA (`/entryNN_{asis,suppressed}.raw`, +//! 1280x720) so the correlation is done outside — this crate has no image +//! decoder and the comparison is not worth a dependency. +use sylpheed_formats::{pak, ui_layout}; + +fn main() { + let pak_path = std::env::args().nth(1).expect("usage: ..."); + let ar = pak::PakArchive::open(&pak_path).expect("open"); + let entries: Vec<_> = ar.entries().to_vec(); + let outdir = std::env::args().nth(2).expect("outdir"); + std::fs::create_dir_all(&outdir).ok(); + for spec in std::env::args().skip(3) { + let idx: usize = spec.parse().unwrap(); + let bytes = ar.read(&entries[idx]).expect("read"); + let Some(build) = ui_layout::parse_build(&bytes) else { continue }; + // plateau-less = rest() had to guess: no two adjacent keyframes share a pose + let mask: Vec = build.elements.iter().map(|e| { + let k = &e.keyframes; + (0..k.len().saturating_sub(1)).any(|i| { + k[i].fade == k[i+1].fade && k[i].scale_x == k[i+1].scale_x + && k[i].scale_y == k[i+1].scale_y && k[i].x == k[i+1].x && k[i].y == k[i+1].y + }) + }).collect(); + let suppressed = mask.iter().filter(|m| !**m).count(); + let opts = ui_layout::ComposeOptions::default(); + let a = ui_layout::compose(&build, &bytes, opts, None); + let b = ui_layout::compose(&build, &bytes, opts, Some(&mask)); + std::fs::write(format!("{outdir}/entry{idx:02}_asis.raw"), &a.rgba).unwrap(); + std::fs::write(format!("{outdir}/entry{idx:02}_suppressed.raw"), &b.rgba).unwrap(); + println!("entry {idx:2} {}x{} elements {:2} plateau-less suppressed {suppressed}", + a.width, a.height, build.elements.len()); + } +} diff --git a/docs/port/HANDOFF.md b/docs/port/HANDOFF.md index 63b2c26e..9beb042c 100644 --- a/docs/port/HANDOFF.md +++ b/docs/port/HANDOFF.md @@ -398,8 +398,11 @@ authored version can be deleted. pose k **to** pose k+1; neither is held unless they are equal — which is a plateau, and the plateau path has already returned by then. **So any element with no two adjacent identical poses has a guessed rest pose**, in our renderer - and in anything built from it. Measured disc-wide: **3 807 of 15 493 elements - (24.57 %)**, of which **195 get a degenerate `scale = 0 %` pose**, and the two + and in anything built from it. Measured disc-wide: **2 305 of 15 493 elements + (14.88 %)** — ⚠️ **corrected 2026-08-29 down from a published 3 807 (24.57 %)**, + which counted 1 502 *single-keyframe* elements as guesses; those have one pose + and it is unambiguously their rest. Of the real 2 305, **195 get a degenerate + `scale = 0 %` pose**, and the two candidate rules agree only **50.2 %** of the time. ✅ **This does not block you.** On the five screens the port needs, 14 elements are affected and the candidate rules **agree on 13**. The single disagreement diff --git a/docs/re/METHOD.md b/docs/re/METHOD.md index 9bbaf557..382c1112 100644 --- a/docs/re/METHOD.md +++ b/docs/re/METHOD.md @@ -750,3 +750,15 @@ agent's loop prompt, i.e. nowhere durable. See [`README.md`](README.md) for the had three discriminating measurements; pose-selection had a heuristic guessing. When a conclusion is blocked, check whether the blocker is actually evidence about the same thing. +* **A predicate over adjacent PAIRS silently misclassifies a one-element list.** + "Has a plateau" was implemented as *any two adjacent keyframes share a pose* — + which is false for a single-keyframe element, so 1 502 static elements were + counted as having a *guessed* rest pose and the published defect rate was 65 % + too high. The error only surfaced when acting on it: suppressing those elements + dropped the title's correlation by 0.27, because they include the backgrounds. + Whenever a rule quantifies over pairs, ask what it says about a list of one. +* **Acting on a claim is a better test of it than re-reading it.** The + single-keyframe flaw survived a disc-wide census, a write-up and a handoff row. + It died the moment the rule was used to change a rendering, because the result + was visibly worse. If a measurement implies an action, take the action on + something you can score. diff --git a/docs/re/REFUTED.md b/docs/re/REFUTED.md index 0e74e30f..ccd3a03a 100644 --- a/docs/re/REFUTED.md +++ b/docs/re/REFUTED.md @@ -528,3 +528,13 @@ neighbourhood, not just the line. ≥77, against 6–8 predicted by the current reading and 80–102 by the shifted one. The `_eff` glows fit both and argue against neither. [`ui-keyframe-time-unit.md`](ui-keyframe-time-unit.md) +* "an element with no held pose should be drawn as NOTHING rather than at a + guessed endpoint" → **mine, and refuted.** Suppressing every plateau-less + element and re-correlating against the live captures: title +0.9500 → +0.6839, + main menu +0.9460 → +0.9037, `EXTRAS` +0.9440 → +0.9094. Worse on all three. +* "`rest()` guesses for 24.57 % of elements (3 807 of 15 493)" → **mine, and + overstated by 65 %.** The plateau test marks a **single-keyframe** element as + plateau-less because it has no adjacent pair — but its one pose is + unambiguously its rest. 1 502 of the 3 807 are those; the genuinely ambiguous + population is **2 305 (14.88 %)**. + [`ui-resting-pose.md`](structures/ui-resting-pose.md) diff --git a/docs/re/structures/ui-resting-pose.md b/docs/re/structures/ui-resting-pose.md index 42f8c082..6545f012 100644 --- a/docs/re/structures/ui-resting-pose.md +++ b/docs/re/structures/ui-resting-pose.md @@ -332,7 +332,9 @@ them before it counts anything. Output: [`data/plateau-census.txt`](../data/plat | | | |---|---| | elements with a keyframe group, disc-wide | **15 493** | -| no plateau → **rest pose is guessed** | **3 807 (24.57 %)** | +| no plateau | 3 807 (24.57 %) — ⚠️ **overstated, see the correction below** | +| …of those, **single-keyframe** (trivially at rest, not a guess) | **1 502** | +| **genuinely ambiguous** (2+ keyframes, no plateau) | **2 305 (14.88 %)** | | of those, current rule returns an **invisible** pose | 1 711 (44.9 %) | | of those, current rule returns a **zero-scale** pose | **195 (5.1 %)** | | the two candidate rules **agree** | 1 911 (50.2 %) | @@ -378,9 +380,54 @@ siblings'. It sits above. ### 🟡 Where this leaves it -The **defect** is established and measured: `rest()` guesses for 24.57 % of +The **defect** is established and measured: `rest()` guesses for **14.88 %** of elements disc-wide and returns a degenerate zero-scale pose for 195 of them. The **fix** is not decided — "last keyframe" is refuted, and the current rule survives on the only captured element that discriminates. ⚠️ For the menu port specifically this is **not a blocker**: one element on one screen, and our current answer for it is the defensible one. + + +--- + +## 🔴 The 24.57 % was overstated, and my proposed fix is refuted + +**2026-08-29.** Two corrections, both from one experiment. + +### The prediction: draw nothing for an element with no held pose + +A keyframe group is **entry → hold → exit**, and the exit ends invisible — on the +five port screens the final keyframe is invisible for **21/24** (title), +**8/16** (main menu), **12/18** (`EXTRAS`), **2/3** and **6/7** (splashes). So +the screen "as seen" is the *hold*, which is why `rest_plateau` is the right +primary rule and why "rest = the last keyframe" would empty every screen. + +That suggested a fix: an element with **no** hold has no representative pose, so +draw nothing rather than guess an endpoint. Tested via `compose`'s `visible` mask +(`examples/plateauless_suppression.rs`), correlated against the live captures: + +| screen | as-is | plateau-less suppressed | Δ | +|---|---|---|---| +| title | +0.9500 | +0.6839 | **−0.2661** | +| main menu | +0.9460 | +0.9037 | −0.0423 | +| `EXTRAS` | +0.9440 | +0.9094 | −0.0346 | + +**Refuted, decisively, on all three.** + +### Why — and it invalidates the headline number + +An element with a **single keyframe** has no *adjacent pair*, so the plateau test +marks it plateau-less. But a single-keyframe element is not animated at all: its +one pose *is* its rest, unambiguously. Suppressing those removes backgrounds and +full-screen layers, which is where the title's −0.27 comes from. + +The same flaw is in the census this page published: + +| | | +|---|---| +| no plateau (as published) | 3 807 (24.57 %) | +| …of which **single-keyframe** | **1 502** — trivially at rest | +| **genuinely ambiguous** | **2 305 (14.88 %)** | + +So the guessed-rest population is **2 305, not 3 807** — the published figure +overstated it by **65 %**. The defect is real and smaller than reported.