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:
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]))
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user