re: B on the main menu goes to the TITLE -- measured, filling an empty evidence cell
menu-navigation-semantics.md had this row at yellow with an EMPTY evidence cell,
and it is what the port still authors as on_cancel.
Delivery-confirmed via [RE-INPUT] (B is kXInputPadB = 0x5801), change detected
rather than timed. B delivered at 331.2 s; the glyph leaves 327 by 331.6 and
73.5 % of pixels differ. Both captures name themselves: PROJECT SYLPHEED with the
(C)2006,2007 SQUARE ENIX line.
Three things measured:
* B on the main menu goes to the title;
* latency <= 0.4 s at a 4 Hz sample rate, where the corpus previously had this
as 'not measured (a backlogged probe void)';
* NO loading screen in between -- the disc carries four pgloading_* bundles and
none appears on this path.
What the run CANNOT say, recorded in the table rather than glossed: 'B on the
title -> nothing' is still unevidenced. The second B was delivered during the
title's build-in, so the glyph 0 -> 154 change after it is the build-in
completing, not a response. A run that answers that row must wait for the title
to settle before pressing.
The 're-draws PRESS A after a beat' half of the first row is also still
unevidenced -- the run ended with the plate absent.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
This commit is contained in:
BIN
docs/re/captures/title-builds/live-b-on-menu-title-buildin.png
Normal file
BIN
docs/re/captures/title-builds/live-b-on-menu-title-buildin.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 876 KiB |
BIN
docs/re/captures/title-builds/live-b-on-menu-title-settled.png
Normal file
BIN
docs/re/captures/title-builds/live-b-on-menu-title-settled.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1011 KiB |
63
docs/re/data/b-on-main-menu.txt
Normal file
63
docs/re/data/b-on-main-menu.txt
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
# What Ⓑ does on the MAIN MENU -- measured 2026-08-30.
|
||||||
|
#
|
||||||
|
# menu-navigation-semantics.md had this row at 🟡 with an EMPTY evidence cell,
|
||||||
|
# and it is what the port still authors as on_cancel.
|
||||||
|
#
|
||||||
|
# Harness: tools/re-capture/b_from_menu.py -- the plate-pulse title detector,
|
||||||
|
# the glyph-327 menu detector, delivery confirmed from [RE-INPUT] rather than
|
||||||
|
# from the pad, and change detected rather than timed. B is kXInputPadB =
|
||||||
|
# 0x5801 (ui/virtual_key.h:323).
|
||||||
|
#
|
||||||
|
# [321.7s] TITLE (glyph 959)
|
||||||
|
# [322.7s] A delivered (attempt 1)
|
||||||
|
# [327.7s] MENU (glyph 327) -- pressing B
|
||||||
|
# [331.2s] B delivered (attempt 1)
|
||||||
|
# [335.0s] screen changed: 73.5 % of pixels differ, glyph 0
|
||||||
|
# [341.3s] glyph 154
|
||||||
|
#
|
||||||
|
# THE SERIES ACROSS THE PRESS (t : glyph):
|
||||||
|
# 326.2 : 0
|
||||||
|
# 326.5 : 327
|
||||||
|
# 326.7 : 327
|
||||||
|
# 326.9 : 327
|
||||||
|
# 327.2 : 327
|
||||||
|
# 327.4 : 327
|
||||||
|
# 327.7 : 327
|
||||||
|
# 331.2 : 327
|
||||||
|
# 331.6 : 0
|
||||||
|
# 332.4 : 0
|
||||||
|
# 332.8 : 0
|
||||||
|
# 333.3 : 0
|
||||||
|
# 333.8 : 0
|
||||||
|
# 334.1 : 0
|
||||||
|
# 334.5 : 0
|
||||||
|
# 335.0 : 0
|
||||||
|
# 340.2 : 0
|
||||||
|
# 340.5 : 54
|
||||||
|
# 340.7 : 154
|
||||||
|
# 340.8 : 154
|
||||||
|
# 340.9 : 154
|
||||||
|
# 341.0 : 154
|
||||||
|
# 341.1 : 154
|
||||||
|
# 341.2 : 154
|
||||||
|
# 341.3 : 154
|
||||||
|
#
|
||||||
|
# READING: B on the main menu goes to the TITLE. Both captures name
|
||||||
|
# themselves -- PROJECT SYLPHEED with the (C)2006,2007 SQUARE ENIX line.
|
||||||
|
# 2-after-B-on-menu.png the title MID-BUILD-IN: wordmark drawn, the green
|
||||||
|
# copyright line not yet -- which is why glyph is 0
|
||||||
|
# while 78.0 % of the surface is inked
|
||||||
|
# 3-after-B-again.png copyright drawn, glyph 154 = the plate-absent floor
|
||||||
|
#
|
||||||
|
# ✅ NO LOADING SCREEN between. The disc carries four pgloading_* bundles and
|
||||||
|
# none appears on this path.
|
||||||
|
#
|
||||||
|
# ✅ LATENCY <= 0.4 s: B delivered at 331.2, glyph leaves 327 by 331.6, at a
|
||||||
|
# 4 Hz sample rate. The corpus previously had this as 'not measured (a
|
||||||
|
# backlogged probe void)'.
|
||||||
|
#
|
||||||
|
# 🔴 WHAT THIS RUN CANNOT SAY: 'B on the title -> nothing'. The second B was
|
||||||
|
# delivered at 340.2, DURING the title's build-in, so the glyph 0 -> 154
|
||||||
|
# change that follows is the build-in completing and not a response. That row
|
||||||
|
# stays 🟡 with no evidence, and a run that answers it must wait for the title
|
||||||
|
# to SETTLE before pressing.
|
||||||
@@ -23,8 +23,8 @@ with [`tools/re-capture/menu_focus.py`](../../tools/re-capture/menu_focus.py).
|
|||||||
| **wrap at the bottom** | ⬇ from the last item goes to the **first** | same, panels 3→4, and 4 presses from `EXTRAS` landing on `OPTIONS` — i.e. wrapping — is what makes the count come out |
|
| **wrap at the bottom** | ⬇ from the last item goes to the **first** | same, panels 3→4, and 4 presses from `EXTRAS` landing on `OPTIONS` — i.e. wrapping — is what makes the count come out |
|
||||||
| **left / right** | **nothing**, on the main menu | cursor unmoved across one ⬅ and one ➡ |
|
| **left / right** | **nothing**, on the main menu | cursor unmoved across one ⬅ and one ➡ |
|
||||||
| **Ⓑ on a submenu** | returns to the parent **with focus restored to the item you entered from** — `LOAD GAME`→`LOAD GAME`, `TUTORIAL`→`TUTORIAL`, `OPTIONS`→`OPTIONS`, `EXTRAS`→`EXTRAS` | 4/4 |
|
| **Ⓑ on a submenu** | returns to the parent **with focus restored to the item you entered from** — `LOAD GAME`→`LOAD GAME`, `TUTORIAL`→`TUTORIAL`, `OPTIONS`→`OPTIONS`, `EXTRAS`→`EXTRAS` | 4/4 |
|
||||||
| **Ⓑ on the main menu** | 🟡 goes to the **title**, which re-draws `PRESS Ⓐ BUTTON` after a beat | **none** — and the main menu's own footer does not advertise Ⓑ; [downgraded below](#-refutation-attempt-2026-08-29--the-main-menus-own-footer-does-not-advertise-ⓑ) |
|
| **Ⓑ on the main menu** | ✅ goes to the **title** — measured 2026-08-30, delivery-confirmed, **≤ 0.4 s**, and with **no loading screen** in between. ⚠️ the *"re-draws `PRESS Ⓐ` after a beat"* half is still unevidenced: the run ended with the plate still absent | **1 run** — [`../re/data/b-on-main-menu.txt`](data/b-on-main-menu.txt) · [mid-build-in](captures/title-builds/live-b-on-menu-title-buildin.png) · [settled](captures/title-builds/live-b-on-menu-title-settled.png). The footer point below still stands |
|
||||||
| **Ⓑ on the title** | 🟡 **nothing** | **none** |
|
| **Ⓑ on the title** | 🟡 **nothing** | **none** — ⚠️ and the 2026-08-30 run **cannot** be counted here: its second Ⓑ was delivered *during* the title's build-in, so the glyph 0 → 154 that followed is the build-in completing, not a response. A run that answers this must wait for the title to **settle** first |
|
||||||
|
|
||||||
Wrap holds on both screens tested — the 5-item main menu and the 3-item `EXTRAS`
|
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.
|
submenu — so it is a menu rule, not a per-screen table.
|
||||||
|
|||||||
125
tools/re-capture/b_from_menu.py
Executable file
125
tools/re-capture/b_from_menu.py
Executable file
@@ -0,0 +1,125 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""What does Ⓑ do on the MAIN MENU, and then on the TITLE?
|
||||||
|
|
||||||
|
`menu-navigation-semantics.md` has those two rows at 🟡 with **empty evidence
|
||||||
|
cells** — "goes to the title, which re-draws PRESS Ⓐ after a beat" and "nothing" —
|
||||||
|
while `Ⓑ on a submenu` is 4/4 measured. They are what the port still authors as
|
||||||
|
`on_cancel`.
|
||||||
|
|
||||||
|
Everything here is the harness validated in `tbm-submenu-not-reached.md`:
|
||||||
|
the plate-pulse title detector, the glyph-327 menu detector, **delivery confirmed
|
||||||
|
from `[RE-INPUT]` rather than from the pad**, and change detected rather than timed.
|
||||||
|
Ⓑ is `kXInputPadB = 0x5801` (`ui/virtual_key.h:323`).
|
||||||
|
|
||||||
|
b_from_menu.py LOG OUTDIR [wait_s]
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import 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 480
|
||||||
|
W, H = 1280, 720
|
||||||
|
NEED, CEIL, HOLD = 500, 2500, 12
|
||||||
|
MENU_LO, MENU_HI, MENU_HOLD = 250, 420, 6
|
||||||
|
PAD = os.path.join(os.path.dirname(os.path.abspath(__file__)), "pad.py")
|
||||||
|
|
||||||
|
|
||||||
|
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 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()
|
||||||
|
log = open(f"{OUT}/series.tsv", "w"); log.write("# t_s\tglyph\tmean\tphase\n")
|
||||||
|
phase, streak, base, stable, mark = "wait", 0, None, 0, 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{a.mean():.3f}\t{phase}\n"); log.flush()
|
||||||
|
if 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:
|
||||||
|
Image.fromarray(a.astype(np.uint8)).save(f"{OUT}/1-menu.png")
|
||||||
|
base = a.astype(float)
|
||||||
|
print(f"[{el:7.1f}s] MENU (glyph {c}) — pressing B", flush=True)
|
||||||
|
time.sleep(2.0)
|
||||||
|
press("B", "5801"); mark = time.time(); phase, stable = "afterB1", 0
|
||||||
|
elif phase == "afterB1":
|
||||||
|
diff = float((np.abs(a - base).max(axis=2) > 12).mean())
|
||||||
|
if diff > 0.25:
|
||||||
|
stable += 1
|
||||||
|
if stable >= 8:
|
||||||
|
Image.fromarray(a.astype(np.uint8)).save(f"{OUT}/2-after-B-on-menu.png")
|
||||||
|
print(f"[{el:7.1f}s] B ON MENU CHANGED THE SCREEN: {100*diff:.1f}% differ, glyph {c}", flush=True)
|
||||||
|
base = a.astype(float); time.sleep(3.0)
|
||||||
|
press("B", "5801"); mark = time.time(); phase, stable = "afterB2", 0
|
||||||
|
else:
|
||||||
|
stable = 0
|
||||||
|
if time.time() - mark > 30:
|
||||||
|
Image.fromarray(a.astype(np.uint8)).save(f"{OUT}/2-after-B-on-menu.png")
|
||||||
|
print(f"[{el:7.1f}s] B ON MENU: NO CHANGE in 30 s ({100*diff:.1f}% differ, glyph {c})", flush=True)
|
||||||
|
base = a.astype(float); press("B", "5801"); mark = time.time(); phase, stable = "afterB2", 0
|
||||||
|
elif phase == "afterB2":
|
||||||
|
diff = float((np.abs(a - base).max(axis=2) > 12).mean())
|
||||||
|
if diff > 0.25:
|
||||||
|
stable += 1
|
||||||
|
if stable >= 8:
|
||||||
|
Image.fromarray(a.astype(np.uint8)).save(f"{OUT}/3-after-B-again.png")
|
||||||
|
print(f"[{el:7.1f}s] SECOND B CHANGED THE SCREEN: {100*diff:.1f}% differ, glyph {c}", flush=True)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
stable = 0
|
||||||
|
if time.time() - mark > 30:
|
||||||
|
Image.fromarray(a.astype(np.uint8)).save(f"{OUT}/3-after-B-again.png")
|
||||||
|
print(f"[{el:7.1f}s] SECOND B: NO CHANGE in 30 s ({100*diff:.1f}% differ, glyph {c})", flush=True)
|
||||||
|
break
|
||||||
|
p.kill(); log.close()
|
||||||
Reference in New Issue
Block a user