port: pose a settled screen at ONE instant -- title 1.81% -> 0.26%, splashes to 0.01%

The Decoder's finding, applied. `rest()` returns each element's last hold
keyframe chosen independently of every other element: right for anything that
ends the screen settled, exactly wrong for a transient. The title's
ptlogo_back2eff1 is a two-frame flash (0 until t52, 255 at t54-56, 0 by t58), so
its last hold IS the flash peak and rest() left it burning -- five of them, drawn
at once.

The settled instant is the longest interval containing no keyframe time, over a
bundle's TOP-LEVEL elements. Reproduced here before adopting: title [160, 236],
midpoint 198, the Decoder's number to the unit. The top-level restriction is what
makes it match -- including the ptloop leaves gives [269, 540].

AGAINST THE ORACLE:

  title            20.92 RMSE  1.81%  ->  14.61 RMSE  0.26%
  publisher_logo    9.05       0.75%  ->   2.17       0.01%
  developer_logos   8.86       0.33%  ->   3.05       0.01%
  main_menu                    0.08%  ->              0.08%   window too narrow
  extras                       0.19%  ->              0.19%   window too narrow

Seven times fewer differing pixels on the title, seventy-five times fewer on the
publisher splash, whose differing region is now a 13x18 box. The largest
correctness gain this port has had, and none of it is mine -- it is a decode
computed from the keyframe table with no reference to any capture.

APPLIED ONLY WHERE THE WINDOW IS WIDE, and the bar is not invented. This export's
widths split with nothing in between: 214, 190, 145, 76, then 12, 12, 8, 4. The
bar is 30 units -- the Decoder's disc-wide census puts the knee there (30% of
bundles >= 30, 42% under 10, the latter mostly loop* fragments meant to be in
motion) and this export's screens sit 4x either side with nothing between 12 and
46. Two independent populations agreeing on where to cut.

Checked unbroken: boot pacing unmoved, scripted walk runs end to end with focus
restored.

Also recorded: my "34 focus-record elements, only 2 varying" is right for
GP_TITLE and reads as a fact about the format -- disc-wide it is 210 varying, 202
with rest() at the peak, concentrated in the paks a wider port reaches next. And
their sharper framing, which I have adopted: a pulsing element has no resting
pose at all, so rest() is MALFORMED rather than mis-answered on one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N7FiFFFwbvG2uxdcEh8HyF
This commit is contained in:
Sylpheed port agent
2026-08-29 19:52:00 +00:00
parent 761071520a
commit 4226505222
3 changed files with 158 additions and 1 deletions

View File

@@ -254,6 +254,35 @@ pub struct Screen {
/// **Geometric, not a decoded neighbour graph** — right for a vertical menu
/// and not to be trusted for anything else.
pub buttons: Vec<String>,
/// The instant every element of this screen is settled at, and the width of
/// the interval it was taken from — `[start, end, midpoint]` in keyframe
/// units, absent when the screen has fewer than two keyframe times.
///
/// 🔴 **A SETTLED SCREEN IS ONE INSTANT, AND THE DISC SAYS WHICH.** Posing
/// each element at its own `rest()` is right for anything that ends the
/// screen settled and **exactly wrong for a transient**: the title's
/// `ptlogo_back2eff1` is a two-frame flash — 0 until t52, 255 at t5456, 0
/// again by t58 — so its last *hold* is the flash peak and `rest()` leaves
/// it burning forever. There are five of these, and `rest()` draws all five
/// at once, saturating the light arc.
///
/// The window is the **longest interval containing no keyframe time**, over
/// this bundle's TOP-LEVEL elements only. Nested leaves are excluded, and
/// that exclusion is what reproduces the Decoder's independently computed
/// `[160, 236]` for the title: including the `ptloop` leaves gives
/// `[269, 540]` instead.
///
/// ⚠️ **Emitted for every screen; USABLE only where it is wide.** Across this
/// export the widths split with nothing in between — `press_start` 214,
/// `publisher_logo` 190, `developer_logos` 145, `title` 76, then
/// `main_menu` 12, `extras` 12, the loading screens 8 and 4. A 12-unit
/// "settle" on a menu that builds in until t=70 is not a settled pose, it is
/// a gap between staggered ramps. The Decoder's disc-wide census agrees on
/// the shape: only 30 % of bundles have a window ≥ 30 units and 42 % have
/// one under 10, the latter mostly `loop*` fragments meant to be in motion.
#[serde(skip_serializing_if = "Option::is_none")]
pub settle_window: Option<[i64; 3]>,
/// What this file does not answer. A consumer needing one of these must get
/// it from `authored/`.
pub unresolved: Vec<&'static str>,
@@ -553,6 +582,7 @@ pub fn export_build(
.collect();
buttons.sort_by(|a, b| a.0.cmp(&b.0).then_with(|| a.1.cmp(&b.1)));
let window = settle_window(&elements);
let screen = Screen {
format: "sylpheed.screen/3",
exporter: exporter.to_string(),
@@ -569,6 +599,7 @@ pub fn export_build(
elements,
paint_order: ui_layout::derived_paint_order(&b, bundle),
buttons: buttons.into_iter().map(|(_, n)| n).collect(),
settle_window: window,
unresolved: vec![
// The time unit is measured off the running game, not on the disc.
"keyframe_time_unit",
@@ -599,3 +630,25 @@ pub fn export_build(
missing,
})
}
/// The longest interval containing no keyframe time, over TOP-LEVEL elements.
///
/// See [`Screen::settle_window`] for why this is the settled instant and why
/// nested leaves are excluded. Returns `[start, end, midpoint]`.
fn settle_window(elements: &[Element]) -> Option<[i64; 3]> {
let mut times: Vec<i64> = elements
.iter()
.flat_map(|e| e.keyframes.iter().filter_map(|k| k.t.map(i64::from)))
.collect();
times.sort_unstable();
times.dedup();
if times.len() < 2 {
return None;
}
let (a, b) = times
.windows(2)
.map(|w| (w[0], w[1]))
.max_by_key(|(a, b)| b - a)?;
Some([a, b, (a + b) / 2])
}

View File

@@ -4421,3 +4421,79 @@ focus-record elements in the export, and only 2 have a varying alpha** — both
which is precisely the pathology described. The port does not hit it because the
plate is drawn through the loop path, and the other **32 are constant-alpha, so
`rest()` is safe for them**. Bounded, not hoped.
## ✅ A settled screen is ONE instant, and it collapsed three residuals at once
The Decoder's finding, applied: **`rest()` returns each element's last hold
keyframe chosen independently of every other element.** That is right for
anything that ends the screen settled and **exactly wrong for a transient**
the title's `ptlogo_back2eff1` is a two-frame flash (0 until t52, 255 at t5456,
0 by t58), so its last hold *is* the flash peak and `rest()` left it burning.
There are five of them, and `rest()` drew all five at once.
The settled instant is **the longest interval containing no keyframe time**, over
a bundle's **top-level** elements. Reproduced independently here before adopting:
title `[160, 236]`, midpoint **198** — the Decoder's number to the unit. ⚠️ The
top-level restriction is what makes it match: including the `ptloop` leaves gives
`[269, 540]` instead.
### Against the oracle
| screen | before | after |
|---|---|---|
| **`title`** | 20.92 RMSE, **1.81 %** | **14.61 RMSE, 0.26 %** |
| **`publisher_logo`** | 9.05, **0.75 %** | **2.17, 0.01 %** |
| **`developer_logos`** | 8.86, **0.33 %** | **3.05, 0.01 %** |
| `main_menu` | 0.08 % | 0.08 % — unchanged, window too narrow |
| `extras` | 0.19 % | 0.19 % — unchanged, window too narrow |
**Seven times fewer differing pixels on the title, seventy-five times fewer on
the publisher splash**, whose differing region is now a **13×18 box**. This is
the largest correctness gain the port has had, and none of it is mine: it is a
decode, computed from the keyframe table with no reference to any capture.
### ⚠️ It is applied only where the window is wide, and that bar is not invented
The widths in this export split with **nothing in between**: `press_start` 214,
`publisher_logo` 190, `developer_logos` 145, `title` 76 — then `main_menu` 12,
`extras` 12, the loading screens 8 and 4. A 12-unit "settle" on a menu that
builds in until t=70 is a gap between staggered ramps, not a settled pose.
The bar is **30 units**: the Decoder's disc-wide census puts the knee there (30 %
of bundles ≥ 30, 42 % under 10, the latter mostly `loop*` fragments meant to be
in motion), and this export's own screens sit **4× either side of it with nothing
between 12 and 46**. Two independent populations agreeing on where to cut is what
makes it a bar rather than a preference.
Checked unbroken: the boot pacing is unmoved (`developer_logos@4.26`,
`title@7.91`, developer agrees) and the scripted walk still runs end to end with
focus restored.
## Their census, and a framing of mine they sharpened
I reported *"34 focus-record elements in the export, only 2 with a varying
alpha"*. Disc-wide it is **210 varying, 202 with `rest()` at the peak**, across
1 130 focus records — 116 in `GP_DEBRIEFING_PILOTLOG`, 54 in `GP_MOVIE_THEATER`,
30 in `GP_HANGAR_ARSENAL`, 8 in `GP_LEADERBOARD`, and **2 in `GP_TITLE`**.
**My 2 is right because `GP_TITLE` has 2.** ⚠️ But *"only 2 have a varying alpha"*
reads as a fact about the format and is a fact about one pak — and the pathology
sits in exactly the screens a wider port reaches next. The sentence was true as
measured and false as remembered, which is the failure this corpus keeps
finding, and it was mine this time.
⚠️ **And they corrected a framing I had:** I called `rest.alpha == peak` "the
pathology". It is worse than that — **a pulsing element has no resting pose at
all.** The question `rest()` answers is *malformed* rather than mis-answered,
because the element's state is a phase, not a value. `pose_at(t)` with `t` inside
the record's own declared cycle is the only well-formed query on one.
🔴 Worth carrying for whenever this port grows: `GP_LEADERBOARD`'s
`py_ranking_btn01f` swings 255 → 127 → 255 with no two adjacent keyframes equal,
so `rest()` falls through to its longest-dwell rule and returns **244** — neither
peak nor trough. **A glow stuck at its peak is visibly wrong; one stuck at 244 of
a 127255 range looks entirely plausible, and nothing reports it.**
✅ And a free second instance of the loop-length decode from a pak neither of us
was looking at: `py_ranking_btn01f`'s ramp ends at **t=90 inside a declared 120**
— 30 units of hold, the same shape as the plate's 105-in-120.

View File

@@ -56,6 +56,27 @@ var exit_ramp_units: float = 24.0
## copyright notice pulse. Narrowed to focus records it matches exactly one
## distinct element, and a rule justified by n=1 is a special case wearing a
## rule's clothes.
## The one instant a settled screen is posed at, in keyframe units, or -1.
##
## 🔴 Replaces per-element `rest()` while `holding`, where the export gives a
## wide enough window. `rest()` returns each element's last HOLD keyframe chosen
## independently of every other element -- right for anything that ends the
## screen settled, and exactly wrong for a **transient**. The title's
## `ptlogo_back2eff1` is a two-frame flash (0 until t52, 255 at t54-56, 0 by
## t58), so its last hold IS the flash peak and `rest()` leaves it burning. There
## are five of them, and `rest()` draws all five at once.
##
## ⚠️ **Only where the window is wide.** Across this export the widths split with
## nothing in between: `press_start` 214, `publisher_logo` 190,
## `developer_logos` 145, `title` 76 -- then `main_menu` 12, `extras` 12, the
## loading screens 8 and 4. A 12-unit "settle" on a menu that builds in until
## t=70 is a gap between staggered ramps, not a settled pose. The bar is 30
## units: the Decoder's disc-wide census puts the knee there (30 % of bundles
## have a window >= 30, 42 % have one under 10), and this export's own screens
## sit 4x either side of it with nothing between 12 and 46.
var settle_instant: float = -1.0
const SETTLE_WINDOW_MIN := 30.0
var looping_focus: Dictionary = {}
## Element ids whose nested `.rat` leaf the runtime actually draws.
@@ -99,6 +120,10 @@ func load_screen(t: ExportTree, name: String) -> bool:
ProjectSettings.get_setting("display/window/size/viewport_height"))
if Vector2i(int(design[0]), int(design[1])) != viewport:
push_warning("screen %s is authored at %sx%s, viewport is %s" % [name, design[0], design[1], viewport])
var w: Array = screen.get("settle_window", [])
settle_instant = -1.0
if w.size() == 3 and float(w[1]) - float(w[0]) >= SETTLE_WINDOW_MIN:
settle_instant = float(w[2])
_load_textures()
queue_redraw()
return true
@@ -185,7 +210,10 @@ func pose_at(element: Dictionary, t: float) -> Dictionary:
# While holding, stop at the hold: past it the group is ramping out, and a
# screen that has arrived and is sitting there is not leaving.
if holding:
t = minf(t, settle_units(element))
# One instant for the whole screen where the disc gives a wide enough
# window; otherwise each element's own hold, which is what this port did
# everywhere until 2026-08-29.
t = settle_instant if settle_instant >= 0.0 else minf(t, settle_units(element))
# The exit. The final keyframe carries no `t` -- the disc has no slot for one
# -- so it is given a synthetic time `exit_ramp_units` after the last timed
# frame and then interpolated like any other. That keeps one code path: the