re: the main menu remembers its cursor across menu -> title -> menu

F1 TUTORIAL, two delivery-confirmed DOWNs to EXTRAS, B to the title, A back:
F3 is EXTRAS. Re-entry restores the item you left.

Reframes the initial-focus disagreement rather than settling it: if focus
persists, any 'initial focus' reading not taken on a fresh boot's first menu entry
measures history. It still says nothing about what the menu opens on -- this run's
F1 was itself carried over from a prior probe's press.

Reached on the plate-pulse gate, not boot_menu.sh, whose stillness test cannot fire
on this title -- TITLE at 422.7 s on a boot skip_intro could not gate at all.

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 21:29:01 +00:00
parent 78befb6c50
commit 12c9f04ff8
6 changed files with 344 additions and 58 deletions

View File

@@ -10,6 +10,32 @@ delivered.
## ✅ 2026-08-30 — the main menu remembers its cursor; re-entry is not a reset
**measured, and it changes what `on_cancel` should do.** Ⓑ from the menu to the
title and Ⓐ back returns you to **the item you left**, not to a default.
| step | focus |
|---|---|
| on the menu | `TUTORIAL` |
| after 2× DOWN (both delivery-confirmed) | `EXTRAS` |
| after Ⓑ → title → Ⓐ → menu | **`EXTRAS`** |
Control passed: two DOWN presses moved the cursor exactly two items, and the run
is discarded if they do not.
[`data/focus-persists-across-title.txt`](../re/data/focus-persists-across-title.txt)
📌 **This also reframes the initial-focus warning I sent earlier today.** If focus
persists, an "initial focus" reading not taken on a fresh boot's *first* menu entry
is measuring history — so the records that disagree need not disagree about the
game. **It still does not say what the menu opens on.** Your authored `NEW GAME`
stands on its own reasoning; nothing here confirms or refutes it.
⚠️ Reach: one boot, one round trip, one direction. Persistence across a **reboot**
is untested and is the reading that would matter for authoring a default.
## 🔴 2026-08-30 — do not hardcode the menu's initial focus; the sources disagree
**New, and it is a warning rather than an answer.** `menu-navigation-semantics.md`

View File

@@ -0,0 +1,62 @@
# Does the main menu REMEMBER its cursor across menu -> title -> menu?
# MEASURED 2026-08-30. Answer: YES.
#
# WHY IT MATTERS: menu-navigation-semantics.md records initial focus as TUTORIAL
# (2/2 boots) while boot_menu.sh's closing line says NEW GAME and
# menu-state-in-memory.md reaches EXTRAS in four downs, which only counts from
# NEW GAME. If focus PERSISTS, then any "initial focus" reading not taken on a
# fresh boot's FIRST menu entry is measuring history, not initial focus -- which
# would explain the disagreement without any of the records being wrong.
#
# HARNESS: tools/re-capture/focus_persistence.py, on the path b_from_menu.py
# validated -- plate-pulse title detector, glyph-327 menu detector, and every
# press confirmed from the guest's own [RE-INPUT] log rather than from the pad.
# NOT boot_menu.sh: its title gate tests for stillness and this title never
# stills (harness-title-gate-assumes-a-static-title.md). The plate-pulse gate
# reached TITLE at 422.7 s on the boot skip_intro could not gate at all.
#
# ✅ CONTROL, and it is the reason run 1 is discarded rather than reported:
# two DOWN presses must move the cursor exactly two items. If they do not,
# the reader is not tracking and F3 may not be read.
#
# [ 1.0s] MENU (glyph 327) F1 = TUTORIAL ring 130 76 254 69 64
# [ 7.3s] DOWN delivered (attempt 1)
# [ 8.2s] DOWN delivered (attempt 1)
# [ 1.0s] after 2x DOWN F2 = EXTRAS ring 130 76 66 69 254
# ✅ CONTROL PASSED: 2x DOWN moved TUTORIAL -> EXTRAS
# [ 13.3s] B delivered (attempt 1)
# [ 24.0s] BACK AT TITLE (glyph 1403)
# [ 25.8s] A delivered (attempt 1)
# [ 33.6s] MENU AGAIN F3 = EXTRAS ring 130 76 66 69 254
#
# F1=TUTORIAL F2=EXTRAS F3=EXTRAS => FOCUS PERSISTS
#
# The ring vector is menu_focus.py's geometry byte for byte: the peak moves from
# index 2 to index 4 and STAYS at 4 across the round trip.
#
################################################################################
# 🔴 RUN 1 IS DISCARDED, AND ITS FAILURE IS THE USEFUL PART.
#
# The first attempt read F1 = LOAD GAME and F2 = LOAD GAME -- the cursor had not
# moved -- and the control refused to report F3. The cause was NOT the reader:
# the two frames genuinely differ by 911 px, so they are different frames. It is
# that run 1 sent the DOWN presses through pad.py with NO delivery confirmation,
# while A and B were confirmed. The guest's own log settles it:
#
# vk=5811 (dpad down) flags=0001 ... delivered ONCE for TWO presses
# vk=5800 (A) flags=0001 ... delivered once
#
# So a press that leaves the harness is not a press the guest received, and the
# corpus already knew that for A and B and had not applied it to the d-pad.
# Fixed: every press in this probe is now confirmed the same way, and the run
# above shows 2/2 DOWN delivered.
#
# ⚠️ REACH. One boot, one round trip, one direction. NOT tested:
# * persistence across a full REBOOT -- this is within one boot;
# * whether F1 = TUTORIAL here is an initial focus. IT IS NOT: run 1 had
# already moved the cursor LOAD GAME -> TUTORIAL with its one delivered
# press, and run 2 found it still on TUTORIAL. That is a second, incidental
# confirmation of persistence -- across two separate probe processes -- but
# it means this run says NOTHING about what the menu opens on.
# * whether a longer absence, or a submenu round trip, behaves the same.
# (Ⓑ from a submenu restoring the entered-from item is already 4/4.)

View File

@@ -30,6 +30,30 @@ with [`tools/re-capture/menu_focus.py`](../../tools/re-capture/menu_focus.py).
Wrap holds on both screens tested — the 5-item main menu and the 3-item `EXTRAS`
submenu — so it is a menu rule, not a per-screen table.
## ✅ 2026-08-30 — the menu REMEMBERS its cursor across menu → title → menu
**measured** — [`data/focus-persists-across-title.txt`](data/focus-persists-across-title.txt),
`tools/re-capture/focus_persistence.py`. One run, control passed:
| step | focus | ring vector |
|---|---|---|
| F1, on the menu | `TUTORIAL` | 130 76 **254** 69 64 |
| F2, after 2× DOWN (**both delivery-confirmed**) | `EXTRAS` | 130 76 66 69 **254** |
| F3, after Ⓑ → title → Ⓐ → menu | **`EXTRAS`** | 130 76 66 69 **254** |
**F3 == F2, so focus persists** — the menu is re-entered on the item you left,
not on a fixed one.
📌 **And this reframes the disagreement below rather than settling it.** If focus
persists, then *any* "initial focus" reading not taken on a fresh boot's **first**
menu entry is measuring history. The records need not disagree about the game at
all — they may differ in what the cursor had already been moved to. **Nothing here
says what the menu opens on**; this run's F1 was itself carried over from a prior
probe's press.
⚠️ Reach: one boot, one round trip, one direction. Persistence across a **reboot**
is untested, and is the reading that would matter for authoring a default.
⚠️ **2026-08-30 — and the sources DISAGREE, which nothing here had noticed.**
`boot_menu.sh`'s own closing line says *"AT MAIN MENU (cursor on NEW GAME)"*, and
[`menu-state-in-memory.md`](menu-state-in-memory.md)'s run reaches `EXTRAS` in

View File

@@ -0,0 +1,196 @@
#!/usr/bin/env python3
"""Does the main menu REMEMBER its cursor across menu -> title -> menu?
menu-navigation-semantics.md carries this 🟡: "Initial focus is reproducible but
not established as invariant. Both of my boots opened on TUTORIAL, and both used
boot_menu.sh." Two runs through one harness are not two samples. And the sources
DISAGREE: boot_menu.sh's own closing line says NEW GAME, and
menu-state-in-memory.md reaches EXTRAS in four downs, which only counts from NEW
GAME. Two say NEW GAME, one says TUTORIAL.
⚠️ THIS DELIBERATELY DOES NOT USE boot_menu.sh. Its title gate admits a "static"
screen at d <= 1500 between grabs 0.6 s apart, and the title never stills -- the
sweep leaves free-run. Minimum observed 1551 over 72 samples, 0 able to pass.
See harness-title-gate-assumes-a-static-title.md. Everything here is instead the
harness b_from_menu.py validated: plate-pulse title detector, glyph-327 menu
detector, delivery confirmed from [RE-INPUT] rather than from the pad.
SEQUENCE
F1 focus when the menu first appears <- re-measures initial focus
F2 focus after 2x DOWN <- CONTROL for the focus reader
F3 focus after B (to title) then A (back) <- persist or reset?
F3 == F2 => the menu restores where you were. F3 == F1 => it resets.
🔴 CONTROL GATE: if F2 is not exactly two items below F1 (with wrap), the reader
is not tracking the cursor and NOTHING after it may be read. The run says so and
stops rather than reporting a number it cannot justify.
focus_persistence.py LOG OUTDIR [wait_s]
"""
import os, re, subprocess, sys, time
import numpy as np
from PIL import Image
LOG, OUT = sys.argv[1], sys.argv[2]
WAIT = float(sys.argv[3]) if len(sys.argv) > 3 else 900
W, H = 1280, 720
NEED, CEIL, HOLD = 500, 2500, 12 # title plate pulse band
MENU_LO, MENU_HI, MENU_HOLD = 250, 420, 6 # glyph-327 menu detector
PAD = os.path.join(os.path.dirname(os.path.abspath(__file__)), "pad.py")
# menu_focus.py's geometry, byte for byte: the focus RING sits in the gutter
# left of the label and nothing else is bright there.
NAMES = ["NEW GAME", "LOAD GAME", "TUTORIAL", "OPTIONS", "EXTRAS"]
YS = [166, 241, 315, 390, 465]
def focus(a):
g = np.asarray(Image.fromarray(a.astype(np.uint8)).convert("L"), dtype=float)
v = [g[y - 20:y + 20, 500:542].max() for y in YS]
return int(np.argmax(v)), v
def deliveries(vk):
pat = re.compile((r"RE-INPUT\] XamInputGetKeystrokeEx -> user=\d+ vk=%s flags=0001" % vk).encode())
try:
return len(pat.findall(open(LOG, "rb").read()))
except FileNotFoundError:
return 0
def press(btn, vk, tries=5):
for k in range(tries):
before = deliveries(vk)
subprocess.run([sys.executable, PAD, "tap", btn, "0.5"], check=False)
for _ in range(20):
time.sleep(0.25)
if deliveries(vk) > before:
print(f"[{time.time()-T0:7.1f}s] {btn} delivered (attempt {k+1})", flush=True)
return True
print(f"[{time.time()-T0:7.1f}s] {btn} NOT delivered (attempt {k+1})", flush=True)
return False
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 fresh(proc):
"""A CURRENT frame, not a buffered one.
⚠️ Reading one frame from the pipe after a sleep returns whatever ffmpeg
buffered while we were not reading. Reopening the stream is the only cheap
way to be sure the frame is now.
"""
proc.kill()
q = _open()
a = None
for _ in range(3):
buf = q.stdout.read(W * H * 3)
if len(buf) == W * H * 3:
a = np.frombuffer(buf, np.uint8).reshape(H, W, 3).astype(int)
return q, a
def glyph(a):
r, g, b = a[:, :, 0], a[:, :, 1], a[:, :, 2]
return int(((g > 130) & (g - r > 45) & (g - b > 45)).sum())
T0 = time.time()
p, n, seg = _open(), W * H * 3, time.time()
os.makedirs(OUT, exist_ok=True)
log = open(f"{OUT}/series.tsv", "w"); log.write("# t_s\tglyph\tphase\n")
phase, streak, mark = ("frommenu" if "--from-menu" in sys.argv else "wait"), 0, None
F1 = F2 = F3 = None
while True:
el = time.time() - T0
if el > WAIT:
print(f"TIMEOUT in phase {phase}", 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(int)
c = glyph(a)
log.write(f"{el:.3f}\t{c}\t{phase}\n"); log.flush()
if phase == "frommenu":
# already sitting on the menu: fall straight into the menu handler
streak = MENU_HOLD; phase = "tomenu"
elif phase == "wait":
streak = streak + 1 if NEED <= c <= CEIL else 0
if streak >= HOLD:
print(f"[{el:7.1f}s] TITLE (glyph {c})", flush=True)
press("A", "5800"); phase, streak = "tomenu", 0
elif phase == "tomenu":
streak = streak + 1 if MENU_LO <= c <= MENU_HI else 0
if streak >= MENU_HOLD:
time.sleep(2.0) # let the menu settle
p, a = fresh(p)
Image.fromarray(a.astype(np.uint8)).save(f"{OUT}/1-F1.png")
F1, v = focus(a)
print(f"[{el:7.1f}s] MENU (glyph {c}) F1 = {NAMES[F1]} ring "
+ " ".join(f"{x:5.0f}" for x in v), flush=True)
# 🔴 Run 1 pressed DOWN twice through pad.py with NO delivery
# confirmation and the guest logged vk=5811 exactly ONCE. A and B
# were confirmed; the d-pad was not, so the run was unreadable.
# Confirm every press the same way.
ok = all(press("DOWN", "5811") for _ in range(2))
if not ok:
print("🔴 a DOWN was never delivered — refusing to read F2", flush=True)
break
time.sleep(1.5)
p, a = fresh(p)
Image.fromarray(a.astype(np.uint8)).save(f"{OUT}/2-F2.png")
F2, v = focus(a)
print(f"[{el:7.1f}s] after 2x DOWN F2 = {NAMES[F2]} ring "
+ " ".join(f"{x:5.0f}" for x in v), flush=True)
want = (F1 + 2) % len(NAMES)
if F2 != want:
print(f"🔴 CONTROL FAILED: 2x DOWN from {NAMES[F1]} should give "
f"{NAMES[want]}, read {NAMES[F2]}. The reader is not tracking "
f"the cursor; refusing to report F3.", flush=True)
break
print(f"✅ CONTROL PASSED: 2x DOWN moved {NAMES[F1]} -> {NAMES[F2]}", flush=True)
press("B", "5801"); mark = time.time(); phase, streak = "backtitle", 0
elif phase == "backtitle":
streak = streak + 1 if NEED <= c <= CEIL else 0
if streak >= HOLD:
Image.fromarray(a.astype(np.uint8)).save(f"{OUT}/3-title.png")
print(f"[{el:7.1f}s] BACK AT TITLE (glyph {c})", flush=True)
press("A", "5800"); phase, streak = "remenu", 0
elif time.time() - mark > 60:
print(f"[{el:7.1f}s] B did not reach the title in 60 s (glyph {c})", flush=True)
break
elif phase == "remenu":
streak = streak + 1 if MENU_LO <= c <= MENU_HI else 0
if streak >= MENU_HOLD:
time.sleep(2.0)
p, a = fresh(p)
Image.fromarray(a.astype(np.uint8)).save(f"{OUT}/4-F3.png")
F3, v = focus(a)
print(f"[{el:7.1f}s] MENU AGAIN F3 = {NAMES[F3]} ring "
+ " ".join(f"{x:5.0f}" for x in v), flush=True)
print(f"\nF1={NAMES[F1]} F2={NAMES[F2]} F3={NAMES[F3]}")
if F3 == F2:
print("=> FOCUS PERSISTS across menu -> title -> menu")
elif F3 == F1:
print("=> FOCUS RESETS to its initial item")
else:
print("=> NEITHER — F3 matches neither F1 nor F2; unexplained")
break
p.kill()
print("FOCUS PERSISTENCE RUN DONE", flush=True)

View File

@@ -1,58 +0,0 @@
#!/usr/bin/env bash
# Does the main menu REMEMBER its cursor across menu -> title -> menu?
#
# menu-navigation-semantics.md carries this 🟡: "Initial focus is reproducible
# but not established as invariant. Both of my boots opened on TUTORIAL, and
# both used boot_menu.sh." Two runs through one harness are not two samples --
# the same shape as two captures through one gate (plate-pulse-phase-lock.md).
#
# And the sources DISAGREE. boot_menu.sh's own final line says "cursor on NEW
# GAME"; the page says TUTORIAL 2/2; menu-state-in-memory.md reaches EXTRAS in
# four downs, which only counts from NEW GAME. Two say NEW GAME, one says
# TUTORIAL.
#
# ONE BOOT settles both, and the round trip is the part no run has done:
# F1 focus when the menu first appears <- re-measures initial focus
# F2 focus after 2x DOWN <- CONTROL for the reader
# F3 focus after B (to title) then A (back) <- persist or reset?
#
# F3 == F2 => the menu restores where you were; F3 == F1 => it resets.
# If F2 is not two items below F1 the reader is not tracking the cursor and
# NOTHING below it may be read. Run as ONE BLOCKING FOREGROUND call.
set -u
export HOME=/sylph-home/re SDL_AUDIODRIVER=dummy DISPLAY=:98
SD="$(cd "$(dirname "$0")" && pwd)"
OUT="${OUT:-/sylph-home/re/focuspersist}"; mkdir -p "$OUT"
NAMES=(NEW_GAME LOAD_GAME TUTORIAL OPTIONS EXTRAS)
. "$SD/ensure_single_emulator.sh"
echo "── EFFECTIVE CONFIG ──────────────────────────────"
echo " out dir = $OUT"
echo " harness = boot_menu.sh (the one under suspicion)"
echo " sequence = F1 -> 2x DOWN -> F2 -> B -> plate pulse -> A -> F3"
BOOT_MENU_LOG="$OUT/boot.stdout" "$SD/boot_menu.sh" fp >"$OUT/boot.log" 2>&1 \
|| { echo "BOOT FAILED"; tail -5 "$OUT/boot.log"; exit 1; }
tail -2 "$OUT/boot.log"
shot(){ screenshot "$OUT/$1.png" >/dev/null 2>&1; }
focus(){ python3 "$SD/menu_focus.py" "$OUT/$1.png" | tail -1; }
shot f1; echo "F1 $(focus f1)"
python3 "$SD/pad.py" dpad down 0.06; sleep 1
python3 "$SD/pad.py" dpad down 0.06; sleep 2
shot f2; echo "F2 $(focus f2)"
echo "-- B to the title --"
python3 "$SD/pad.py" tap B 0.25
python3 "$SD/wait_plate_pulse.py" 120 >"$OUT/pulse.log" 2>&1 \
&& echo " title settled (plate pulse)" || echo " ⚠️ NO PLATE PULSE in 120 s"
shot title
echo "-- A back to the menu --"
python3 "$SD/pad.py" tap A 0.25; sleep 12
shot f3; echo "F3 $(focus f3)"
echo "FOCUS PERSISTENCE RUN DONE"

View File

@@ -0,0 +1,36 @@
#!/usr/bin/env bash
# Boot, then run focus_persistence.py. NO skip_intro -- the probe finds the title
# itself by the plate pulse, which is the gate that works on a screen whose sweep
# leaves never stop (harness-title-gate-assumes-a-static-title.md).
set -u
export HOME=/sylph-home/re SDL_AUDIODRIVER=dummy DISPLAY=:98
SD="$(cd "$(dirname "$0")" && pwd)"
OUT="${OUT:-/sylph-home/re/focuspersist}"; mkdir -p "$OUT"
LOG="$OUT/canary.stdout"
bash "$SD/ensure_single_emulator.sh"
if ! xdpyinfo -display "$DISPLAY" >/dev/null 2>&1; then
rm -f "/tmp/.X${DISPLAY#:}-lock" 2>/dev/null || true
nohup bash -c 'Xvfb "$0" -screen 0 1280x720x24 -ac -nolisten tcp \
+extension GLX +extension RANDR >/tmp/xvfb98.log 2>&1' "$DISPLAY" </dev/null >/dev/null 2>&1 &
for _ in $(seq 1 50); do xdpyinfo -display "$DISPLAY" >/dev/null 2>&1 && break; done
nohup env DISPLAY="$DISPLAY" HOME=/sylph-home openbox </dev/null >/tmp/openbox98.log 2>&1 &
fi
xdpyinfo -display "$DISPLAY" >/dev/null 2>&1 || { echo "DISPLAY UNAVAILABLE"; exit 1; }
# The profile is NOT optional: naming a XUID with no profile opens a sign-in
# dialog, and IsUIActive() then swallows every keystroke for the rest of the run.
XUID="${SYLPH_XUID:-$(ls "${XENIA_CONTENT:-$HOME/.local/share/Xenia/content}" 2>/dev/null | head -1)}"
[ -n "$XUID" ] || { echo "NO PROFILE"; exit 2; }
echo "── EFFECTIVE CONFIG ──────────────────────────────"
echo " out dir = $OUT"
echo " profile = $XUID"
echo " title gate= plate pulse (NOT skip_intro's stillness test)"
echo " log = $LOG [RE-INPUT] delivery confirmation reads this"
cd /sylph-home/re
nohup run-canary --apu=sdl --log_mask=13 --log_level=2 \
--logged_profile_slot_0_xuid="$XUID" </dev/null >"$LOG" 2>&1 &
python3 "$SD/focus_persistence.py" "$LOG" "$OUT" "${WAIT:-900}"