From 835acf930e95fb172c9f158fded30cbbab3dcfc5 Mon Sep 17 00:00:00 2001 From: Sylpheed port agent Date: Sun, 30 Aug 2026 11:46:46 +0000 Subject: [PATCH] port: WITHDRAW the claim that the port drifted away from the game -- wrong frame The previous entry scored verify-screen's title_jp frame against the oracle and concluded the port had moved away from the game. That frame is posed --pose=rest, which the port does not ship. Posed as it runs, the disputed block scores +0.9994 against the reference's +0.8727, and the whole surface +0.9652 against +0.9200 -- holding under gamma compensation and on the English control (+0.9946 vs +0.9560). The port is closer to the game than the reference on both title screens. Mechanism: ptlogo_back2eff1 is (0,0)(98,0)(100,255)(102,255)(104,0) -- a 4-unit sparkle whose rest.t is the peak of its own flash. Six of them stagger across the logo, so --pose=rest fires every sparkle at once. The 25.6% excess light was real and was in a frame nobody sees. verify-screen is not at fault: it poses rest deliberately, so that both renderers read one decoder and the run is a consistency check. I used a consistency-check frame for a correctness question. Its header now says its frames must never be scored against a capture. A second claim in that entry was also wrong -- both screens draw those layers under pose=rest; I had compared a --menu timeline log against a verify-screen rest log and read a mode difference as a screen difference. verify-capture takes a fifth per-row field, a capture crop, because this capture is a full display frame with the surface at +0+45 while the others are pre-cropped. With it title_jp reads RMSE 20.91 / 1.04%, beside title's 14.16 / 0.21%. The row prints 'no capture' until their branch merges. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01N7FiFFFwbvG2uxdcEh8HyF --- docs/port/DECISIONS.md | 72 ++++++++++++++++++++++++++++++++++++++- tools/port/verify-capture | 28 ++++++++++++++- tools/port/verify-screen | 14 ++++++++ 3 files changed, 112 insertions(+), 2 deletions(-) diff --git a/docs/port/DECISIONS.md b/docs/port/DECISIONS.md index decfde5a..c0cfae00 100644 --- a/docs/port/DECISIONS.md +++ b/docs/port/DECISIONS.md @@ -9,7 +9,7 @@ dies, which is what this file is for. -144 sections. Search this before re-deriving anything. +149 sections. Search this before re-deriving anything. * [P0 — the exporter, 2026-08-28](#p0--the-exporter-2026-08-28) * [P1 — Godot draws the screen, 2026-08-28](#p1--godot-draws-the-screen-2026-08-28) @@ -155,6 +155,11 @@ dies, which is what this file is for. * [State of the port, and a claim I built on for a week without checking](#state-of-the-port-and-a-claim-i-built-on-for-a-week-without-checking) * [Identifying their submenu capture: edges where intensity could not](#identifying-their-submenu-capture-edges-where-intensity-could-not) * [`on_cancel`: one half measured, and a MEASURED stamp removed from the other](#on_cancel-one-half-measured-and-a-measured-stamp-removed-from-the-other) +* [BLOCKED.md's five "blocking" rows were all answered, some days ago](#blockedmds-five-blocking-rows-were-all-answered-some-days-ago) +* [The plate came back in the game and not in the port](#the-plate-came-back-in-the-game-and-not-in-the-port) +* [🔴 `verify-screen` was nondeterministic, and it looked fine most of the time](#verify-screen-was-nondeterministic-and-it-looked-fine-most-of-the-time) +* [The JP title capture adjudicates `title_jp` — and it goes against the port](#the-jp-title-capture-adjudicates-title_jp--and-it-goes-against-the-port) +* [🔴 CORRECTION: the port did not move away from the game — I scored the wrong frame](#correction-the-port-did-not-move-away-from-the-game--i-scored-the-wrong-frame) ## P0 — the exporter, 2026-08-28 @@ -8625,3 +8630,68 @@ not bisected them. Naming one now would be a guess dressed as a cause. 📌 What this does settle: `title_jp`'s `DIFFERS` is **the port's**, not the reference's. That reverses this script's usual presumption, and it is the first row in the baseline whose direction has ever been established against a capture. + +## 🔴 CORRECTION: the port did not move away from the game — I scored the wrong frame + +The previous entry concluded, from the JP title capture, that *"the port moved, +and it moved away from the game"*. **That conclusion is withdrawn.** It is wrong, +and the way it was wrong is worth more than the answer. + +I scored `verify-screen`'s `title_jp` frame against the oracle. That frame is +posed `--pose=rest`, which this port **does not ship**. Posed as it actually +runs: + +| | disputed block | whole surface | +|---|---|---| +| port, `--pose=rest` (the frame I scored) | +0.7462 | — | +| **port, as shipped** | **+0.9994** | **+0.9652** | +| reference | +0.8727 | +0.9200 | + +✅ Holds under gamma compensation (+0.9928 at γ=1.34) and ✅ on the **English +control**, same method: port +0.9946 against the reference's +0.9560. The port is +closer to the game than the reference on **both** title screens. + +### Why `rest` produces a frame the game never shows + +`ptlogo_back2eff1` on the JP title is `(t, alpha) = (0,0) (98,0) (100,255) +(102,255) (104,0)` — a **4-unit sparkle**, and its `rest.t` is **100: the peak of +its own flash**. Six of these stagger across the logo. Posing at `rest` fires +every sparkle simultaneously at full brightness, which is exactly the "port puts +light on 25.6 % of the block the capture does not have" I reported as a defect. +The excess light was real; it was in a frame nobody sees. + +⚠️ `verify-screen` is not at fault — it poses `rest` **deliberately**, because +both renderers read `rest` through one decoder and that is what makes it a +consistency check. Its header said so. **I used a consistency-check frame to +answer a correctness question**, and the tool now says in its own header that its +frames must never be scored against a capture. + +### A second, smaller thing in that entry was also wrong + +It said the port draws layers "that are transparent at rest on the English +title". Both screens draw them under `--pose=rest`. I had compared a `--menu` +run's log (timeline pose) against a `verify-screen` log (rest pose) and read the +difference as a property of the screens rather than of the two modes. + +### What actually stands from that entry + +The alignment work survives intact — the measured dy=−45 with the English control +at (0,0), and the observation that the instrument is fair on a control strip. So +does the arithmetic. **What failed was choosing which frame to feed it**, and no +amount of control on the comparison could have caught that: every control I ran +was a control on the *metric*, and the error was upstream in the *input*. + +📌 The generalisation: **a control proves the instrument, not the sample.** Both +of my last two iterations' errors were of that shape — a live reader pointed at +the wrong field name, and a fair metric pointed at the wrong frame. + +### Wired so it cannot recur + +`tools/verify-capture` takes a fifth per-row field, a capture crop, because this +capture is a full 1280×720 display frame with the surface at +0+45 while every +other capture in that directory is pre-cropped to 1279×675 — comparing it whole +would score the port against a 45 px shift. With it, `title_jp` reads **RMSE +20.91, differing region 1.04 %**, beside `title`'s 14.16 / 0.21 %. + +⚠️ The row prints `no capture` until the Decoder's branch merges. Their capture is +theirs to commit; it was staged locally to test the row and removed. diff --git a/tools/port/verify-capture b/tools/port/verify-capture index 97afed4b..59af5e13 100755 --- a/tools/port/verify-capture +++ b/tools/port/verify-capture @@ -83,6 +83,19 @@ MAP=( # against 1.82 % here. Picking that one would be fitting the pose to the # score, which is the thing this harness exists not to do. "title:$CAPS/live-title-build4-no-plate.png:t357" + # The Japanese title at rest, Decoder 310bf86. Settled pose by omission -- + # the capture is demonstrated at rest (five frames over 6 s, 0 px change in + # the logo block while 5-8 % of the frame moves). + # + # 🔴 THIS ROW EXISTS BECAUSE SCORING THE WRONG FRAME COST A WRONG CONCLUSION. + # `verify-screen` poses at `--pose=rest`, which for this screen lights every + # `ptlogo_back2eff*` sparkle at its own peak simultaneously -- `rest` for those + # elements IS the peak of a 4-unit flash. That frame is fine for the + # consistency check it was built for and must never be scored against a + # capture: doing so put the port at r +0.7462 against the reference's +0.8727 + # and I wrote up that the port had moved away from the game. Posed as it + # SHIPS, the same block scores **+0.9994**. + "title_jp:$CAPS/live-title-jp-at-rest.png:settled::1279x675+1+45" # A SECOND title comparison, and the most sensitive row this tool has. # # `live-title-press-a.png` is the title WITH the plate. Posed at t=237 -- inside @@ -127,7 +140,7 @@ echo "REGION -- a missing or misplaced element is a large connected blob." echo printf '%-17s %-9s %-7s %-22s %s\n' screen raw-rmse diff region note for row in "${MAP[@]}"; do - IFS=: read -r name cap pose forced <<<"$row" + IFS=: read -r name cap pose forced capcrop <<<"$row" if [ ${#want[@]} -gt 0 ] && ! printf '%s\n' "${want[@]}" | grep -qx "$name"; then continue; fi [ -f "$cap" ] || { printf '%-17s %s\n' "$name" "no capture"; continue; } if [ "$pose" = band ]; then @@ -186,6 +199,19 @@ for row in "${MAP[@]}"; do else convert "$OUT/$name.render.png" -crop 1279x675+0+0 +repage "$OUT/$name.crop.png" fi + # ⚠️ A FIFTH FIELD, because not every capture is pre-cropped to the game + # surface. Every capture in `$CAPS` until now was already 1279x675, so the + # render was cropped and the capture used as-is. The JP title capture is a + # full 1280x720 DISPLAY frame with the surface at +0+45 -- comparing it whole + # would score the port against a 45px shift and report a catastrophe. + # + # The offset is MEASURED, not inherited from the earlier submenu capture: + # row/column profile correlation against the port, with the English pair as a + # control, gives (0,0) for the control at r 0.994 and dy=-45 for this frame. + if [ -n "$capcrop" ]; then + convert "$cap" -crop "$capcrop" +repage "$OUT/$name.cap.png" + cap="$OUT/$name.cap.png" + fi raw=$(convert "$OUT/$name.crop.png" "$cap" -metric RMSE -compare -format "%[distortion]" info: 2>&1 | tail -1) raw=$(python3 -c "print('%.2f' % (float('$raw')*255))" 2>/dev/null || echo "?") note="" diff --git a/tools/port/verify-screen b/tools/port/verify-screen index d78cc2c7..cc0a4fd9 100755 --- a/tools/port/verify-screen +++ b/tools/port/verify-screen @@ -15,6 +15,20 @@ # both), scale-0, and rest(). Each time the capture caught it and neither # renderer could have. # +# 🔴 AND ITS FRAMES MUST NEVER BE SCORED AGAINST A CAPTURE. This script poses +# `--pose=rest`, deliberately -- both renderers read `rest` through the same +# decoder, which is what makes it a test of the PORT against the REFERENCE. It +# is NOT the pose the port ships, and on some screens the two are very far +# apart: `rest` for each `ptlogo_back2eff*` sparkle is the peak of its own +# 4-unit flash, so `--pose=rest` lights all of them at once, a frame the game +# never shows. +# +# I scored this script's `title_jp` frame against the oracle capture and +# concluded the port had drifted away from the game -- r +0.7462 against the +# reference's +0.8727. Posed as it SHIPS, the same block scores **+0.9994**. +# The conclusion was an artefact of the pose, and it was written up as a finding. +# Correctness questions go to `tools/verify-capture`, which poses as shipped. +# # So: a DIFFERS row means "we moved apart, go find out which of us moved". It # does not mean the port is wrong. Where a capture and this tool disagree, the # capture wins. Use `tools/verify-capture` for the correctness question.