re: ptloop01/02 do not free-run on the settled title -- and the menu is not settled

sylpheed-port found ptloop01/02 free-running in their renderer on the menu path,
pinned them, and was explicit that pinning picks one pose rather than the game's:
"a capture question, not a harness one". It is, and it lands in my lane.

On the title it is now answered. Those leaves rest at (441,270) 200x90, INSIDE
the box the ptlogo_eff3 era adjudication uses, and across my two JP captures from
different sessions they are byte-identical: 0 of 18 000 px, max |d| 0, against a
whole-frame contrast of 116 492 px differing. So they are static at rest, and the
in-box between-session noise of 0.32 is not theirs -- the 645 differing pixels
all lie in a 30-row band at y 99..128, nowhere near the loop rect. That also
closes the reach caveat on the EN->JP noise transfer.

The MENU is a different bundle and is not settled. Build 5 declares the same rect
with a single static keyframe, and that is where their row drifted.
menu_loop_rest.sh was written to capture five settled menu frames and diff the
rect; it did not complete. The run reached a title at t=146 s and (A) did not
take across six attempts -- the documented intermittency where the attract loop's
title accepts nothing, unlike the boot title. Recorded rather than re-rolled.

Two committed main-menu captures cannot substitute: they differ across 57 % of
the surface (different geometries and capture paths), so the 88 % differing on
the loop rect measures the mismatch, not the loops. The control fails and the
comparison is void.

navigation.md gains the trap that cost this iteration a run: kill -9 on xenia
orphans /tmp/xenia-canary.lock, the next run-canary refuses to STDERR where a
polling script never looks, and a probe then sampled a dead display for 484 s
reporting `other` every 4 s -- because screen_id.py on an empty screen returns
`other` and "not the title yet" is indistinguishable from "there is no emulator".
Kill plainly so it clears its own lock, and assert the emulator is alive before
entering any wait loop.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
This commit is contained in:
sylph-decoder
2026-08-30 15:43:29 +00:00
parent a4a089cea1
commit 36c00f1a5c
3 changed files with 99 additions and 5 deletions

View File

@@ -312,6 +312,15 @@ Traps that read as bugs but are not, all measured ✅:
dies before the emulator does, silently, with no output at all. Kill by process
**name**: `ps -o pid= -C xenia_canary | xargs -r kill -9`. (The same trap is in
`METHOD.md` for `pgrep` wait-loops; it cost another launch on 2026-08-30.)
* **`kill -9` on xenia ORPHANS `/tmp/xenia-canary.lock`**, and the next
`run-canary` refuses with *"an emulator is already running"* — to **stderr**,
where a polling script never looks. A probe of mine then sampled a dead display
for **484 s**, reporting `other` every 4 s, because `screen_id.py` on an empty
screen returns `other` and *"not the title yet"* is indistinguishable from
*"there is no emulator"*. Kill with a plain `kill` so it can clear its own lock;
if you must use `-9`, `rm -f /tmp/xenia-canary.lock` after. **And assert the
emulator is alive before entering any wait loop** — `ps -C xenia_canary` — so
the loop cannot spend its whole deadline on nothing.
* **`screen_id.py` reports `menu` during the attract loop.** Two boots on
2026-08-30 logged `menu` at t=106 s and t=418 s while the game was still in the
intro movie. A wait-loop that breaks on `menu` will act on the wrong screen; the

View File

@@ -135,11 +135,40 @@ measurement lives in the box, and the in-box between-session noise is **0.32**,
first stated — the "strengthening" was reaching for a number that was to hand
rather than the one that applies.
⚠️ **Reach of the transfer.** This is the *EN* title's capture noise applied to the
*JP* title's box. Build 7 carries `ptloop01/02.rat`, which may animate inside that
region where the EN plate does not — so the in-box 0.000 is a measurement on a
neighbouring screen, not on the one adjudicated. A second JP capture would settle
it and has not been taken.
### ✅ And `ptloop01/02` do NOT free-run on the settled title
The transfer above was flagged as uncertain because build 7 carries
`ptloop01/02.rat`, which might animate inside the adjudication box where the EN
plate does not. `sylpheed-port` then found those same two leaves free-running in
*their renderer* on the menu path, and was explicit that pinning a phase picks one
pose rather than the game's — *"a capture question, not a harness one"*. It is, and
two captures from different sessions answer it:
```
ptloop01/02 rest at (441,270), 200x90 -- INSIDE the adjudication box
differing px between session 1 and session 2: 0 of 18 000, max |d| 0
contrast, whole frame: 116 492 px differ, max |d| 51
```
**Byte-identical across sessions**, while 12.6 % of the frame moves. So on the
settled title the loops are static, and the in-box capture noise of 0.32 is not
theirs — the 645 differing pixels all lie in a 30-row band at `y 99..128`, nowhere
near the loop rect.
🟡 **The menu is a different bundle and is NOT settled by this.** `GP_TITLE` build
5 declares `ptloop01/02` at the same rect with a single static keyframe, and that
is the screen the port's row actually drifted on. A probe to capture five settled
menu frames and diff that rect was written
([`menu_loop_rest.sh`](../../tools/re-capture/menu_loop_rest.sh)) and **did not
run to completion**: the run reached a title at t=146 s and Ⓐ did not take across
six attempts. That is the documented intermittency — the *attract loop's* title
accepts nothing, unlike the boot title
([`canary-scripted-input-traps.md`](../canary-scripted-input-traps.md)) — so the
question is open, with the instrument written and one successful run away.
⚠️ Two committed main-menu captures exist but **cannot** answer it: they differ
across **57 %** of the surface (different geometries and capture paths), so the
88 % differing on the loop rect measures the mismatch, not the loops.
⚠️ **Reach.** One screen, one capture. The absolute residual is ~40 RMSE even at
the best instant, because the JP title carries live animation a static render

View File

@@ -0,0 +1,56 @@
#!/usr/bin/env bash
# Do ptloop01/02 ANIMATE on the settled main menu?
#
# sylpheed-port pinned a leaf phase for these and was explicit that doing so
# picks ONE pose rather than the one the game shows -- "a capture question, not a
# harness one". On the JP title, two captures from different sessions are
# byte-identical over the ptloop rect (0 of 18 000), so they do not free-run
# there. The menu is a different bundle and is where their row actually drifted.
#
# If the loops animate at all, consecutive frames of a SETTLED menu differ over
# their rect. A whole-frame count runs alongside as the contrast control: if the
# frame is entirely static the instrument cannot see motion and the zero is void.
#
# Usage: menu_loop_rest.sh OUTDIR
set -u
export HOME=/sylph-home/re SDL_AUDIODRIVER=dummy DISPLAY=:98
SD="$(cd "$(dirname "$0")" && pwd)"
OUT="${1:-/sylph-home/re/menuloop}"; mkdir -p "$OUT"
alive(){ ps -o pid=,stat= -C xenia_canary 2>/dev/null | awk '$2 !~ /^Z/ {print $1}'; }
shot(){ screenshot "$1" >/dev/null 2>&1; }
screen(){ shot /tmp/mlr.png; python3 "$SD/screen_id.py" /tmp/mlr.png | awk '{print $1}'; }
ps -o pid= -C xenia_canary | xargs -r kill -9
( cd "$OUT" && nohup run-canary --mem_watch=false \
--logged_profile_slot_0_xuid=B13EBABEBABEBABE \
>"$OUT/canary.stdout" 2>"$OUT/canary.stderr" & )
sleep 8
deadline=$(( SECONDS + 900 )); s=""
while [ $SECONDS -lt $deadline ]; do
s="$(screen)"; echo "t=${SECONDS}s $s"; [ "$s" = "title" ] && break; sleep 4
done
[ "$s" = "title" ] || { echo "NEVER REACHED THE TITLE"; exit 1; }
python3 "$SD/pad.py" tap A 0.5
for _ in 1 2 3 4 5 6; do sleep 4; s="$(screen)"; echo " after A: $s"; [ "$s" = "menu" ] && break; done
[ "$s" = "menu" ] || { echo "NO MENU (screen=$s)"; exit 2; }
sleep 6 # let the menu settle past its build-in
for i in 0 1 2 3 4; do shot "$OUT/menu-$i.png"; sleep 2; done
python3 - "$OUT" <<'PY'
import sys, numpy as np
from PIL import Image
out=sys.argv[1]
def load(i):
a=np.asarray(Image.open(f"{out}/menu-{i}.png").convert("RGB"),dtype=int)
return a, (45 if a.shape[0]>=716 else 0)
a0,o0=load(0)
print("\nptloop01/02 rect, 200x90 at design (441,270) -- do they move?")
for i in (1,2,3,4):
ai,oi=load(i)
la=a0[o0+270:o0+360, 441:641]; lb=ai[oi+270:oi+360, 441:641]
d=np.abs(la-lb)
h=min(a0.shape[0]-o0, ai.shape[0]-oi); w=min(a0.shape[1], ai.shape[1])
dw=np.abs(a0[o0:o0+h,:w]-ai[oi:oi+h,:w]).sum(axis=2)
print(f" frame {i} vs 0: loop rect {int((d.sum(axis=2)>0).sum()):6d}/18000 px max |d| {int(d.max()):3d}"
f" | CONTRAST whole frame {int((dw>0).sum()):7d} px")
PY
echo "MENU LOOP PROBE DONE"