port: play the keyframe timeline, with the time unit authored in one place

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.
This commit is contained in:
Sylpheed port agent
2026-08-28 19:51:57 +00:00
parent 146a604500
commit 0db45b4c66
4 changed files with 182 additions and 8 deletions

View File

@@ -30,6 +30,17 @@ static func locate() -> ExportTree:
return t
# `authored/` sits beside `export/`, never inside it: it is hand-written and
# committed, and a re-export must not be able to touch it.
func authored(name: String) -> Variant:
var path := root.path_join("../authored").simplify_path().path_join(name)
var text := FileAccess.get_file_as_string(path)
if text == "":
error = "cannot read %s" % path
return null
return JSON.parse_string(text)
func read_json(rel: String) -> Variant:
var path := root.path_join(rel)
var text := FileAccess.get_file_as_string(path)