From 1fa3ffd08bd24bccbd789f80736905b9a3a4d97e Mon Sep 17 00:00:00 2001 From: sylph-decoder Date: Sun, 30 Aug 2026 22:08:10 +0000 Subject: [PATCH] tools: focus_persistence.py used the reader that was two items out Now uses the shared measured reader, which refuses to name a row outside the calibration rather than guessing. Replaying this run's stored frames through it gives NEW GAME / TUTORIAL / TUTORIAL, which is what the corrected record says. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v --- tools/re-capture/focus_persistence.py | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/tools/re-capture/focus_persistence.py b/tools/re-capture/focus_persistence.py index a1349c9b..829ee46d 100755 --- a/tools/re-capture/focus_persistence.py +++ b/tools/re-capture/focus_persistence.py @@ -39,16 +39,23 @@ 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] +# 🔴 This used menu_focus.py's DESIGN-SPACE rows [166,241,315,390,465] against +# x11grab frames, which carry Xenia's window chrome and a surface scaled 1.060 -- +# and reported item names TWO POSITIONS OUT for a whole session. The control +# ("2 DOWNs move 2 items") could not catch it, because a constant offset +# preserves relative motion exactly. See data/menu-focus-reader-offset.txt. +# Now measured, via the shared reader, which REFUSES to name an out-of-range row. +import os as _os +sys.path.insert(0, _os.path.dirname(_os.path.abspath(__file__))) +from ring_row import ring_row as _ring_row, main_menu_item as _mmi, NAMES 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 + y = _ring_row(Image.fromarray(a.astype(np.uint8))) + i = _mmi(y) + if i is None: + return None, [y] + return i, [y] def deliveries(vk): @@ -139,6 +146,8 @@ while True: p, a = fresh(p) Image.fromarray(a.astype(np.uint8)).save(f"{OUT}/1-F1.png") F1, v = focus(a) + if F1 is None: + print(f"🔴 no main-menu ring row in the F1 frame (y={v[0]}) — stopping", flush=True); break 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 @@ -153,6 +162,8 @@ while True: p, a = fresh(p) Image.fromarray(a.astype(np.uint8)).save(f"{OUT}/2-F2.png") F2, v = focus(a) + if F2 is None: + print(f"🔴 no main-menu ring row in the F2 frame (y={v[0]}) — stopping", flush=True); break 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) @@ -181,6 +192,8 @@ while True: p, a = fresh(p) Image.fromarray(a.astype(np.uint8)).save(f"{OUT}/4-F3.png") F3, v = focus(a) + if F3 is None: + print(f"🔴 no main-menu ring row in the F3 frame (y={v[0]}) — stopping", flush=True); break 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]}")