port: verify-screen was nondeterministic; pin the pulse phase in the harness

Running the full set after the plate fix, press_start returned over3 5021, 8919,
5021 on three identical runs. The plate's looping focus record takes its phase
from time_units, which free-runs, so the captured frame lands wherever the grab
fell -- while the reference renderer cannot pulse at all.

The port is not the thing that is wrong: the pulse is measured and a thing that
pulses does not stop because the screen arrived. ScreenView.loop_phase_units pins
it, negative means free-running and stays the default everywhere, and only the
harness passes --loop-phase=0.

Controlled: pinned, 3 runs identical; free-running, 3 of 4 identical and one
different. That 3-of-4 is why it survived -- it looks deterministic most of the
time, and without the negative control a no-op flag would have been
indistinguishable from a fix.

With the phase pinned press_start reads max 1 / over3 0 OK -- the recorded
baseline exactly. Fifteen of sixteen rows now match.

The sixteenth, title_jp, has genuinely drifted: 155/20498 -> 233/61208,
deterministic, on the Godot side, localized to one 350x396 block at (405,74).
There is no capture of the Japanese title, so I can say the renderers moved apart
but not which moved. Recorded as an ask, not resolved.

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-30 11:29:19 +00:00
parent b9667c6c9a
commit 8ae0ec2287
5 changed files with 119 additions and 2 deletions

View File

@@ -89,6 +89,10 @@ func _ready() -> void:
if mods != "":
print(mods)
# Parsed HERE, before the first `ScreenView` is configured -- it was briefly
# read further down and every `--screen` run silently ignored it.
if args.has("loop-phase"):
_loop_phase = float(args["loop-phase"])
_flow = export_tree.authored("flow.json")
if _flow == null and (args.has("boot") or args.has("menu")):
push_error(export_tree.error)
@@ -224,6 +228,7 @@ func _ready() -> void:
viewport.add_child(view)
view.looping_focus = _looping_for(name)
view.loop_phase_units = _loop_phase
view.draw_leaf_for = _draw_leaf_for
view.loop_leaf = _loop_leaf_screens.has(name)
view.focused_id = _force_focus
@@ -469,6 +474,7 @@ func _advance() -> void:
view.holding = true
view.time_units = 0.0
view.looping_focus = _looping_for(name)
view.loop_phase_units = _loop_phase
view.draw_leaf_for = _draw_leaf_for
view.loop_leaf = _loop_leaf_screens.has(name)
if not view.load_screen(view.tree, name):
@@ -725,6 +731,7 @@ func _menu_arrive() -> void:
view.holding = true
view.time_units = 0.0
view.looping_focus = _looping_for(name)
view.loop_phase_units = _loop_phase
view.draw_leaf_for = _draw_leaf_for
view.loop_leaf = _loop_leaf_screens.has(name)
if not view.load_screen(view.tree, name):
@@ -1072,6 +1079,10 @@ var _overlay_t0 := 0.0
var _overlay_view_t0 := 0.0
## Wall-clock second at which it is raised, measured from the screen's settle.
var _overlay_due: float = 0.0
## `--loop-phase=<units>` pins the looping record's phase. Negative is
## free-running, which is the default and what a player gets. Only the
## regression harness passes it -- see `ScreenView.loop_phase_units`.
var _loop_phase: float = -1.0
func _overlay_process(delta: float) -> void:
@@ -1166,6 +1177,7 @@ func _raise_overlay(name: String) -> void:
overlay.units_per_second = view.units_per_second
overlay.exit_ramp_units = view.exit_ramp_units
overlay.looping_focus = _looping_for(name)
overlay.loop_phase_units = _loop_phase
overlay.draw_leaf_for = _draw_leaf_for
overlay.loop_leaf = _loop_leaf_screens.has(name)
overlay.holding = true

View File

@@ -99,6 +99,19 @@ var frozen := false
var looping_focus: Dictionary = {}
## Pin the looping record's phase instead of taking it from `time_units`.
##
## 🔴 WHY THIS EXISTS. The pulse is CORRECT -- a thing that pulses does not stop
## because the screen has arrived -- but it rides the wall clock, so a captured
## frame lands wherever the grab happened to fall. `verify-screen press_start`
## returned `over3` **5021, 8919, 5021** on three identical runs: a regression
## detector that answers differently each time teaches its reader to ignore it.
##
## The port is not the thing that is wrong here, so the port's behaviour does not
## change: negative means "free-running", which stays the default everywhere. The
## HARNESS pins a phase so the comparison is deterministic.
var loop_phase_units: float = -1.0
## Element ids whose nested `.rat` leaf the runtime actually draws.
##
## The exporter flags `leaf_carries_geometry` on 15 elements -- a census fact.
@@ -593,7 +606,8 @@ func _draw_focus(element: Dictionary) -> void:
and String(loop.get("record_element", "")) == String(fe.get("id", "")):
var was := holding
holding = false
pose = pose_at(fe, fposmod(time_units, float(loop["period_units"])))
var lt: float = time_units if loop_phase_units < 0.0 else loop_phase_units
pose = pose_at(fe, fposmod(lt, float(loop["period_units"])))
holding = was
var pivot := _vec(fe.get("pivot", [0, 0]))
var pos := _vec(pose.get("pos", [0, 0]))