port: delete exit_ramp_units, invert the format's own rule, and guard a scale-0 leaf

FOUR THINGS, and the first is what MISSION section 3 calls the measure of
progress.

DELETED `exit_ramp_units` AND `exit_ramp_seconds`. They were authored because the
disc had no time slot on a group's final keyframe, so the ramp into it was the
one unknown duration per screen. Under the corrected record layout that keyframe
does not exist -- a group is an 8-byte header then frames x {u32 time; 36-byte
pose} and every pose is timed. VERIFIED DEAD BEFORE DELETING: setting it to 9999
(166 s) moved the boot's transitions by 0.04 s, which is wall-clock jitter, and
both uses in ScreenView are gated on a condition that no longer fires on any of
the export's 866 keyframes.

INVERTED THE FORMAT'S OWN RULE. `check.rs` enforced "the final keyframe has no
`t`; the disc has no time slot there" and FORMAT.md stated it. Both are now
backwards, and the validator fired 150 times on a re-export. I had not run
`check` between pinning the tag and measuring against the oracle -- the pixel
harness was green while the format validator was failing on every screen with a
multi-keyframe group. A correctness harness does not replace a format one; they
fail at different layers.

GUARDED A SCALE-0 LEAF, which the Decoder hit in its own renderer: its leaf
branch marked the element drawn unconditionally while the blit returned early on
zero scale, so a scale-0 leaf suppressed its parent and blanked the element --
live on all four loading screens. This port did not have the bug only because
authored/rendering.json happens not to list pgloading_loop5. That is an accident
of a gate written for another reason, not a defence, so `_draw_leaf` now reports
whether it drew and `_draw` falls back to the parent.

ISOLATED THE PACING QUESTION rather than leaving it as a suspected regression.
Legacy association: publisher 4.70 agrees, developer 3.92 DIFFERS. Corrected:
publisher 4.26 DIFFERS, developer 3.62 agrees. Both misses are ~0.03 s outside a
composite bound. The association traded which screen is marginally out; it did
not regress the pacing.

Bumped the pin c -> d for the parser and audio changes. Its headline renderer
change does not reach this port: sylpheed-cli builds from the workspace crate.

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:19:46 +00:00
parent 16ab24480d
commit ace071dd7b
8 changed files with 189 additions and 102 deletions

View File

@@ -212,10 +212,25 @@ from: the quad is `pivot × 2`, and its colour is the keyframe's `fade_argb`.
**`keyframes`** carry the on-disc time verbatim in `t`. A keyframe is the
**start of a ramp toward the next**, not a pose that is held, and the ramp is
linear. The **last keyframe of a group has no `t`** — the disc has no time slot
there — and a file that puts one on it is wrong, not merely odd. The unit of `t`
is measured, not on the disc, and so lives in `authored/` and is applied in
exactly one place.
linear.
🔴 **Every keyframe has a `t`, including the last**, and this paragraph said the
exact opposite until 2026-08-29. A placement group is an 8-byte header followed
by `frames` × `{u32 time; 36-byte pose}`, so **pose 0's time is the group's
lead-in word** and no pose is untimed. The old reading — that a group's data
stopped four bytes short of its final block's time slot — paired every pose with
the *next* pose's time, and `sylpheed-export check` enforced it as a rule. A file
with an untimed keyframe is now the wrong one.
⚠️ Two things went with that correction. The **exit ramp is gone**: there is no
untimed final keyframe to give a synthetic time to, so `authored/timing.json`'s
`exit_ramp_units` — an authored *measured* constant since P3 — is **deleted**,
which is what MISSION §3 means by a deletion being the measure of progress. And
`rest.t` moved on several screens: `publisher_logo` settles at t=30 rather than
t=235.
The unit of `t` is still measured rather than on the disc, so it stays in
`authored/` and is applied in exactly one place.
**`rotation_deg`** is screen-plane rotation in degrees, clockwise-positive,
decoded from the keyframe's `+12`. **The game renders it**, confirmed twice by