From 36c00f1a5c30d774948fc5b650b7126e7b5269dd Mon Sep 17 00:00:00 2001 From: sylph-decoder Date: Sun, 30 Aug 2026 15:43:29 +0000 Subject: [PATCH] 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) Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v --- docs/game/navigation.md | 9 +++++ docs/re/structures/ui-resting-pose.md | 39 ++++++++++++++++--- tools/re-capture/menu_loop_rest.sh | 56 +++++++++++++++++++++++++++ 3 files changed, 99 insertions(+), 5 deletions(-) create mode 100755 tools/re-capture/menu_loop_rest.sh diff --git a/docs/game/navigation.md b/docs/game/navigation.md index db0e7c3c..185250f8 100644 --- a/docs/game/navigation.md +++ b/docs/game/navigation.md @@ -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 diff --git a/docs/re/structures/ui-resting-pose.md b/docs/re/structures/ui-resting-pose.md index 36f209fa..039d6480 100644 --- a/docs/re/structures/ui-resting-pose.md +++ b/docs/re/structures/ui-resting-pose.md @@ -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 diff --git a/tools/re-capture/menu_loop_rest.sh b/tools/re-capture/menu_loop_rest.sh new file mode 100755 index 00000000..8e5b14c0 --- /dev/null +++ b/tools/re-capture/menu_loop_rest.sh @@ -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"