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 7dc5117a09
commit 81352459e3
5 changed files with 119 additions and 2 deletions

View File

@@ -810,3 +810,18 @@ roughly a second unaccounted for.
detector could see? If the latter, what is the first-paint time? I am not
authoring a delay to close this - a `2.13 s` authored delay in this same block
was already refuted once by arithmetic.
### A capture of the Japanese title (`GP_TITLE` build 7)
**Derived from Decoder `daf8f47`.**
`verify-screen` has `title_jp` at max 233 / over3 61208 against a committed
baseline of 155 / 20498. Established: deterministic, on the Godot side (your
byte-identical-renders finding rules out the reference), and localized to one
350x396 block at (405, 74) - the logo stack, where the port draws `ptlogo_jp`,
`ptlogo3a/b/c` and five `ptlogo_back2eff*` layers.
**The ask:** a capture of the Japanese title at rest. Without one there is no
oracle for this screen, and agreement with `sylpheed-cli` is not correctness -
so I can say the two renderers moved apart but not which one moved, and I am not
going to pick a direction.

View File

@@ -8508,3 +8508,63 @@ arithmetic. Left alone; recorded as an ask.
* `title/on_cancel` — Ⓑ on a **settled** title does nothing, twenty seconds
confirmed. This cell has now been `MEASURED` (wrongly), `AUTHORED` (honestly),
and `MEASURED` (truly), with the value `null` the whole way through.
## 🔴 `verify-screen` was nondeterministic, and it looked fine most of the time
Running the full set after the plate fix, two rows had moved off the committed
baseline. One of them was not a regression at all — it was the harness.
`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 happened to fall — while the
reference renderer cannot pulse at all. **A detector that answers differently
each run is worse than one that fails**: it teaches its reader to ignore it.
⚠️ **The port is not the thing that was wrong.** A thing that pulses does not stop
because the screen has arrived, and the pulse is measured. What was wrong was
comparing a moving frame against a static one and calling the difference a
regression. So `ScreenView.loop_phase_units` pins the phase, negative means
free-running, that stays the default everywhere, and only the harness passes
`--loop-phase=0`.
✅ Controlled, and the control is what makes the fix trustworthy:
| | |
|---|---|
| pinned, 3 runs | **identical md5** |
| free-running, 4 runs | 3 identical, **1 different** |
🟡 That 3-of-4 is the finding worth keeping. **It is usually stable**, which is
exactly why it survived — a flake that fires one run in four reads as a real
regression that "went away", and a `--loop-phase` that changed nothing would have
been indistinguishable from a fix without that negative control.
✅ With the phase pinned, `press_start` reads **max 1 / over3 0 OK** — *the
recorded baseline exactly*, not some new number. Fifteen of sixteen rows now
match the committed baseline.
### The sixteenth: `title_jp` has genuinely drifted, and I cannot say which side
| | max | over3 |
|---|---|---|
| committed baseline | 155 | 20 498 |
| now | **233** | **61 208** |
What is established:
***deterministic** — 233 / 61 208 twice, so not the phase.
***not the reference** — the Decoder reports `screen render` is byte-identical
across the stale and rebuilt binaries (max per-channel 0), so the reference is
stable and the movement is on the Godot side.
***localized** — the differing region is a single **350×396 block at
(405, 74)**, the logo stack. `title` is untouched at max 6 / over3 790, which
rules out anything shared by both title screens (the forced-backdrop rule
among them).
* The port draws `ptlogo_jp`, `ptlogo3a/b/c` and the five `ptlogo_back2eff*`
layers here that are transparent at rest on the English title.
🔴 **What I cannot do is say which renderer is right.** There is no capture of the
Japanese title in the corpus, and this script's own header is explicit that
agreement with the reference is not correctness and a `DIFFERS` is not
automatically the port's fault. Guessing a direction here is precisely the move
the mission forbids. Asked, not resolved.

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]))

View File

@@ -138,8 +138,24 @@ print(json.load(open("export/"+f))["source"]["build"])' "$name")
tbm=$("$CLI" screen info "$DISC/dat/GP_TITLE.pak" --build "$build" --all 2>/dev/null \
| grep -ioc '\.tbm' || true)
# 🔴 `--loop-phase=0` PINS THE PULSE, AND WITHOUT IT THIS SCRIPT WAS
# NONDETERMINISTIC. `press_start` returned `over3` **5021, 8919, 5021** on
# three identical runs: the plate's looping focus record rides `time_units`,
# 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. A thing that pulses does not
# stop because the screen has arrived, and the pulse is measured. What was
# wrong is comparing a moving frame against a static one and calling the
# difference a regression -- a detector that answers differently each run
# teaches its reader to ignore it, which is worse than one that fails.
#
# So the phase is pinned HERE, in the harness, and nothing about playback
# changes: `loop_phase_units` defaults to free-running everywhere else.
# ⚠️ It is usually stable -- 3 of 4 control runs agreed -- which is exactly
# why this survived: it looks deterministic most of the time.
godot --path port --resolution 1280x720 -- \
"--screen=$name" --pose=rest "--capture=$OUT/$name.godot.png" >"$OUT/$name.log" 2>&1
"--screen=$name" --pose=rest --loop-phase=0 "--capture=$OUT/$name.godot.png" >"$OUT/$name.log" 2>&1
convert "$OUT/$name.godot.png" "$OUT/$name.ref.png" \
-compose difference -composite -colorspace Gray -auto-level "$OUT/$name.diff.png"