P2 shipped the wrong rule and the title is the counter-example. I had reasoned
that the exit is the final untimed keyframe, so anything timed was still the
entry. It is not: `pteff02` on the title holds at t=46 with the 25 % dim quad at
alpha 0x40 and then ramps to 0x00 by t=236. The exit can be a long run of TIMED
keyframes, and running to the end drops the dim and leaves the whole screen
~13/255 too bright -- exactly the luminance excess P2 recorded (oracle 64.1,
rest 62.8, timeline 80.0) and filed as an unexplained "glow slab".
A group is pre-roll -> ramp in -> hold -> ramp out -> post-roll, and a screen
that has arrived sits on the hold. `settle_units()` is now `rest.t`, the
decoders' own identification of that hold.
The check is that a disagreement vanishes: on ALL TWELVE screens the settled
timeline is now byte-identical to the `--pose=rest` render, where before this
four differed by up to 247/255. The timeline's endpoint should BE `rest` -- the
animation is what it adds, not a different destination -- so this is the
property to want, and it holds with no special case.
Credit where due: this came out of the RE agent measuring the title's dim quad
against a plate-free capture, in pursuit of a different question.
P2. A keyframe is the start of a linear ramp toward the next; `ScreenView` walks
them at `time_units` and `boot.gd` advances that in real time, or freezes it with
`--time=<seconds>`.
`authored/timing.json` holds the ONE constant this needs. HANDOFF Q1 is answered
-- linear, 2 units per rendered frame, 1 unit = 1/60 s -- but that conversion was
MEASURED off the running game, not read from a file, so it is authored rather
than exported and it says so at length. Expressed as units-per-second, because
60 is exact and 0.01666... is a decimal a reader has to recognise.
The timeline stops at the last TIMED keyframe and never plays the exit. Every
group's final keyframe carries no `t` -- across this export it is a fade-out for
116 of 134 elements, a scale-and-slide exit for 12, and identical for 6 -- so
playing into it would mean inventing how long the ramp takes. That duration is
the screen transition, it is measured at ~0.4 s, and it is P3's to author with
its own evidence. `exit_ramp_seconds` is therefore null on purpose, not missing.
`--pose=rest` keeps the P1 behaviour available: since the port's default is now
the timeline and the two DISAGREE, renderer-vs-renderer diffing has to be able to
ask for the same assumption the reference renderer makes.
The interpolation is checked by where it lands: on 8 of the 12 screens the
settled timeline is byte-identical to the rest render.
P1. The project reads only `export/` -- the manifest, a screen's JSON and its
PNGs -- and draws every element at `rest`, in the export's own `paint_order`.
Three choices worth the words:
* `ExportTree` addresses screens by manifest NAME, never by path, and checks
`format` on the manifest and on each screen before drawing. Textures are
decoded from bytes at runtime rather than Godot-imported: `export/` is
gitignored and regenerated wholesale, and a `.import` per sprite would be
derived state next to derived state, invalidated on every re-export.
* One CanvasItem draws the whole screen. `paint_order` is already back-to-front,
so honouring it is a loop; spreading it across sixteen nodes' z-indices would
hide the one unresolved thing about that order -- the ties -- behind Godot's
sibling rules.
* The screen renders into a SubViewport sized to the export's `design` rect.
Capturing the window instead gave 1280x720 of screen minus a window manager's
title bar: 1235x695. A gate that rescales that to compare against a 1280x720
composite is measuring the compositor.
Nearest-neighbour filtering, because the export is a 1:1 copy of the disc's
texels, elements draw at up to 500 %, and it is what `ui_layout::blit` does --
so a filter difference cannot masquerade as a placement difference in the diff.
No keyframe interpolation and no focus state: both depend on constants that are
MEASURED rather than decoded (HANDOFF Q1, Q5), and a pixel-diff gate must not
have one of those inside it. P2 and P5.