re: WITHDRAW "ptloop01/02 do not free-run" -- I measured a pivot, not an extent
sylpheed-port noted that build 5's ptloop parent can be static while the leaf record animates, and asked me to check it against my table. My own corpus refutes my claim outright. ptloop-leaf-sweep-positions.txt -- written earlier in this same corpus -- records ptloop01.rat's nested record at loop length 600, whose leaf pteff03.t32 sweeps a 400 px-wide quad with its centre running x~921->1041 over t=340..370. The parent's declared rect is (441,270) 200x90. The leaf draws 300 px outside it: the parent rect is a PIVOT ANCHOR, not the drawn extent. Checked against the two JP captures: my measured rect differs by 0 px -- and so does the whole dead region y 270..450 x 480..960 around it -- while the band the sweep actually occupies (x 721..1241) differs by 44 025 px. The zero was measured where nothing happens. So the port's reading is right and now confirmed from the disc: parent static, leaf animates, and the two nested records cycle at DIFFERENT lengths, 600 and 720. My "single static keyframe" described the parent only. The era adjudication is unaffected -- its box overlaps the sweep band only at x 721..776, which shows no between-session differences. The menu-loop question is still unsettled after a second attempt, and the second attempt's failure REFUTES my diagnosis of the first. menu_loop_probe.py gated on the plate pulse (glyph in [500,2500] held 12 samples), fired at t=484.5 s with glyph 1723 -- a verified settled BOOT title, not the attract one -- pressed A, and the press was delivered ([file-pad] keystroke vk=5800 down/up, 8 RE-INPUT lines). Twenty seconds later all five frames still classified as the title (rmse ~67-70, margins 0.06-0.16, the "neither" signature; screen_id says title). So "the attract title accepts nothing" does not explain attempt 1, and the corpus's "the boot title accepts a single A, 2 of 2 runs" is no longer 2 of 2. METHOD: a declared rect can be an anchor, not an extent -- confirm an element draws in a region before diffing that region to ask whether it moves. navigation.md: confirm the screen changed, do not infer it from a delivered press. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
This commit is contained in:
@@ -321,6 +321,16 @@ Traps that read as bugs but are not, all measured ✅:
|
||||
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.
|
||||
* 🔴 **Ⓐ on a settled boot title does NOT reliably reach the menu.** This page's
|
||||
§1 and `canary-scripted-input-traps.md` record "the boot title accepts a single
|
||||
Ⓐ (2 of 2 runs)". A run on 2026-08-30 gated on the **plate pulse** (glyph in
|
||||
[500, 2500] held 12 consecutive samples), fired at t=484.5 s with glyph 1723 —
|
||||
a verified settled boot title, not the attract one — pressed Ⓐ, and **the press
|
||||
was delivered** (`[file-pad] keystroke vk=5800 down` / `up`, 8 `[RE-INPUT]`
|
||||
lines). Twenty seconds later every captured frame still classified as the title.
|
||||
So the sample is no longer 2 of 2, and a script that presses once and proceeds
|
||||
can be left on the title with nothing in its log to say so. **Confirm the screen
|
||||
changed; do not infer it from a delivered press.**
|
||||
* **`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
|
||||
|
||||
@@ -345,6 +345,16 @@ agent's loop prompt, i.e. nowhere durable. See [`README.md`](README.md) for the
|
||||
precision against the quantity before quoting the number, and prefer comparing
|
||||
*frames* to comparing a statistic about them.
|
||||
|
||||
* **A declared rect can be an ANCHOR, not an extent.** A UI element's placement
|
||||
`(x, y)` plus its `w×h` reads like the box it draws in. For a `RATC` parent with
|
||||
a nested record it is not: `ptloop01.rat` declares `(441,270)` 200×90 while its
|
||||
**leaf** sweeps a 400 px-wide quad whose centre runs x≈921→1041 — 300 px outside
|
||||
the parent's box. Measuring "does this element move" over the parent's rect
|
||||
returned **0 differing pixels** across two sessions, in a region where *nothing*
|
||||
moves, and that zero was published. ⚠️ Before diffing a region to ask whether an
|
||||
element animates, confirm the element **draws there** — from a leaf sweep, a draw
|
||||
capture, or the rendered quad's own coordinates. A pivot is not a bounding box.
|
||||
|
||||
## Runtime / emulator
|
||||
|
||||
* **Look at the PNG** — and check its dimensions.
|
||||
|
||||
@@ -135,7 +135,30 @@ 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.
|
||||
|
||||
### ✅ And `ptloop01/02` do NOT free-run on the settled title
|
||||
### 🔴 WITHDRAWN — "`ptloop01/02` do not free-run on the settled title"
|
||||
|
||||
**Retracted 2026-08-30, same day, by my own corpus.** The claim below measured the
|
||||
parent's declared rect at `(441,270)` 200×90. **That rect is a PIVOT ANCHOR, not
|
||||
the drawn extent.** [`data/ptloop-leaf-sweep-positions.txt`](../data/ptloop-leaf-sweep-positions.txt)
|
||||
— written earlier in this same corpus — records `ptloop01.rat`'s nested record at
|
||||
loop length **600**, whose leaf `pteff03.t32` sweeps a **400 px-wide** quad with
|
||||
its centre running x≈921→1041 over t=340..370. That is nowhere inside x 441..640.
|
||||
|
||||
Checked against the two JP captures: the rect I measured differs by **0 px** — but
|
||||
so does the entire region y 270..450 × x 480..960 around it, a dead zone — while
|
||||
the band the sweep actually occupies (x 721..1241) differs by **44 025 px**. The
|
||||
zero was measured where nothing happens and proves nothing about the loops.
|
||||
|
||||
✅ **`sylpheed-port`'s reading is right and is confirmed from the disc**: the
|
||||
parent is static while the **leaf record animates**, and the two nested records
|
||||
cycle at *different* lengths, **600 and 720**. My "single static keyframe" was a
|
||||
statement about the parent only.
|
||||
|
||||
⚠️ The era adjudication is unaffected: its box is x 389..776, and the overlap with
|
||||
the sweep band (x 721..776) shows no between-session differences — the 645 in-box
|
||||
differing pixels all sit at y 99..128.
|
||||
|
||||
### ~~✅ And `ptloop01/02` do NOT free-run on the settled title~~ (see above)
|
||||
|
||||
The transfer above was flagged as uncertain because build 7 carries
|
||||
`ptloop01/02.rat`, which might animate inside the adjudication box where the EN
|
||||
|
||||
111
tools/re-capture/menu_loop_probe.py
Executable file
111
tools/re-capture/menu_loop_probe.py
Executable file
@@ -0,0 +1,111 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Do `ptloop01/02` animate on the SETTLED main menu?
|
||||
|
||||
`sylpheed-port` found those two leaves free-running in their renderer on the menu
|
||||
path and pinned them, noting that pinning picks one pose rather than the game's --
|
||||
"a capture question, not a harness one". On the title it is answered: two captures
|
||||
from different sessions are byte-identical over the loop rect (0 of 18 000). The
|
||||
MENU is a different bundle and is where their row actually drifted.
|
||||
|
||||
⚠️ A first attempt used `screen_id.py`'s single `title` classification as the cue
|
||||
to press Ⓐ, hit a title at t=146 s, and Ⓐ never took across six tries -- that was
|
||||
the ATTRACT loop's title, which accepts nothing, and the classifier cannot tell it
|
||||
from the boot title. This gates on the same signal `jp_title_capture.py` uses: the
|
||||
green Ⓐ-plate glyph count inside a band, HELD for 12 consecutive samples (~3 s).
|
||||
|
||||
menu_loop_probe.py OUTDIR [wait_s]
|
||||
"""
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
|
||||
OUT = sys.argv[1]
|
||||
WAIT = float(sys.argv[2]) if len(sys.argv) > 2 else 700
|
||||
W, H = 1280, 720
|
||||
SURF_Y = 45
|
||||
NEED, CEIL, HOLD = 500, 2500, 12
|
||||
LOOP = (441, 270, 200, 90) # ptloop01/02 rest rect, design space
|
||||
|
||||
|
||||
def _open():
|
||||
return subprocess.Popen(
|
||||
["ffmpeg", "-loglevel", "error", "-f", "x11grab", "-draw_mouse", "0",
|
||||
"-video_size", f"{W}x{H}", "-i", ":98", "-r", "4",
|
||||
"-f", "rawvideo", "-pix_fmt", "rgb24", "-"],
|
||||
stdout=subprocess.PIPE, bufsize=W * H * 3 * 2)
|
||||
|
||||
|
||||
def glyph(a):
|
||||
r, g, b = a[:, :, 0], a[:, :, 1], a[:, :, 2]
|
||||
return int(((g > 130) & (g - r > 45) & (g - b > 45)).sum())
|
||||
|
||||
|
||||
def tap(btn, secs=0.5):
|
||||
import os
|
||||
for state in (f"press={btn}", ""):
|
||||
with open("/tmp/xenia_pad.txt.tmp", "w") as f:
|
||||
f.write(state)
|
||||
os.replace("/tmp/xenia_pad.txt.tmp", "/tmp/xenia_pad.txt")
|
||||
if state:
|
||||
time.sleep(secs)
|
||||
|
||||
|
||||
T0 = time.time()
|
||||
p, n, seg, streak = _open(), W * H * 3, time.time(), 0
|
||||
state, pressed_at, frames = "wait_title", None, []
|
||||
while True:
|
||||
el = time.time() - T0
|
||||
if el > WAIT:
|
||||
print("TIMEOUT in state " + state, flush=True); break
|
||||
if time.time() - seg > 30:
|
||||
p.kill(); p = _open(); seg = time.time()
|
||||
buf = p.stdout.read(n)
|
||||
if len(buf) < n:
|
||||
p.kill(); p = _open(); seg = time.time(); continue
|
||||
a = np.frombuffer(buf, np.uint8).reshape(H, W, 3).astype(np.uint8)
|
||||
c = glyph(a.astype(int))
|
||||
|
||||
if state == "wait_title":
|
||||
streak = streak + 1 if NEED <= c <= CEIL else 0
|
||||
if streak >= HOLD:
|
||||
print(f"[{el:7.1f}s] BOOT TITLE SETTLED (plate pulse, glyph {c}) — pressing A",
|
||||
flush=True)
|
||||
tap("A", 0.5)
|
||||
pressed_at, state, streak = time.time(), "wait_menu", 0
|
||||
elif state == "wait_menu":
|
||||
# the menu has no green plate; wait for the glyph count to fall and stay
|
||||
# down, which is the plate leaving, then let the build-in finish.
|
||||
streak = streak + 1 if c < NEED else 0
|
||||
if streak >= HOLD and time.time() - pressed_at > 8:
|
||||
print(f"[{el:7.1f}s] MENU (glyph {c}) — taking 5 frames 2 s apart", flush=True)
|
||||
for k in range(5):
|
||||
t0 = time.time()
|
||||
while time.time() - t0 < 2.0:
|
||||
b2 = p.stdout.read(n)
|
||||
if len(b2) < n:
|
||||
p.kill(); p = _open(); break
|
||||
a = np.frombuffer(b2, np.uint8).reshape(H, W, 3).astype(np.uint8)
|
||||
Image.fromarray(a).save(f"{OUT}/menu-{k}.png")
|
||||
frames.append(a.astype(int))
|
||||
print(f" frame {k}: glyph {glyph(a.astype(int))}", flush=True)
|
||||
break
|
||||
p.kill()
|
||||
|
||||
if len(frames) == 5:
|
||||
x, y, w, h = LOOP
|
||||
rois = [f[SURF_Y + y:SURF_Y + y + h, x:x + w] for f in frames]
|
||||
print("\nptloop01/02 rect, 200x90 at design (441,270) — do they move?")
|
||||
for i in range(1, 5):
|
||||
d = np.abs(rois[i] - rois[0])
|
||||
print(f" frame {i} vs 0: max |d| {d.max():3d}, px differing >8: "
|
||||
f"{int((d.max(axis=2) > 8).sum()):5d} / {d.shape[0]*d.shape[1]}", flush=True)
|
||||
print(" whole frame, as the CONTRAST CONTROL (if this is 0 too, the")
|
||||
print(" instrument is blind and the rect's 0 means nothing):")
|
||||
for i in range(1, 5):
|
||||
d = np.abs(frames[i] - frames[0]).max(axis=2)
|
||||
print(f" frame {i} vs 0: px differing >8: {int((d > 8).sum()):7d}", flush=True)
|
||||
else:
|
||||
print("NO FRAMES — nothing measured", flush=True)
|
||||
21
tools/re-capture/menu_loop_session.sh
Executable file
21
tools/re-capture/menu_loop_session.sh
Executable file
@@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env bash
|
||||
# Boot, then run menu_loop_probe.py. Asserts the emulator is ALIVE before the
|
||||
# probe enters its wait loop -- a previous run polled a dead display for 484 s
|
||||
# because run-canary had refused on a lockfile orphaned by `kill -9`, and
|
||||
# "no emulator" is indistinguishable from "not the title yet" to a classifier.
|
||||
set -u
|
||||
export HOME=/sylph-home/re SDL_AUDIODRIVER=dummy DISPLAY=:98
|
||||
SD="$(cd "$(dirname "$0")" && pwd)"
|
||||
OUT="${1:-/sylph-home/re/menuloop3}"; mkdir -p "$OUT"
|
||||
ps -o pid= -C xenia_canary | xargs -r kill # plain kill: it clears its own lock
|
||||
rm -f /tmp/xenia-canary.lock
|
||||
( cd "$OUT" && nohup run-canary --mem_watch=false \
|
||||
--logged_profile_slot_0_xuid=B13EBABEBABEBABE \
|
||||
>"$OUT/canary.stdout" 2>"$OUT/canary.stderr" & )
|
||||
sleep 10
|
||||
if ! ps -C xenia_canary >/dev/null 2>&1; then
|
||||
echo "EMULATOR DID NOT START -- stderr says:"; tail -3 "$OUT/canary.stderr"; exit 4
|
||||
fi
|
||||
echo "emulator alive, starting probe"
|
||||
timeout 900 python3 "$SD/menu_loop_probe.py" "$OUT" 780
|
||||
echo "MENU LOOP PROBE DONE"
|
||||
Reference in New Issue
Block a user