tools: a working route to the menu, and the classifier that could not see it

screen_id.py called the main menu "other". Its menu rule required a near-white
fraction above 1.5%, measured in 2026-07; the menu reached from the boot title
measures 0.03% (mean 13,26,59 — dark, strongly blue, essentially green-free).
That is worse than a cosmetic miss: a script that waits for "menu" and never
sees it reports the navigation as failed while the menu is on screen, which is
exactly what happened here. Both measured signatures are now documented in the
code and both classify.

menu_draw_capture.sh now taps ONCE on the first title rather than up to 40 times:
repeating was measured to be useless (the attract title accepts nothing) and the
first title accepts a single press.

title_states_capture.sh is new — it captures the draw list in both title states
in one run, which is what refuted the "the attract title omits the button plate"
theory.
This commit is contained in:
Sylpheed RE agent
2026-08-18 23:10:29 +00:00
parent 9e16331155
commit b9062ea3bc
4 changed files with 92 additions and 20 deletions

View File

@@ -171,14 +171,18 @@ available in the container.
**And a second screen is NO LONGER BLOCKED, but it is not routine either.** The **And a second screen is NO LONGER BLOCKED, but it is not routine either.** The
main menu has been reached (screenshot in main menu has been reached (screenshot in
[`canary-scripted-input-traps.md`](canary-scripted-input-traps.md)), so the [`canary-scripted-input-traps.md`](canary-scripted-input-traps.md)), so the
"Ⓐ is dead" reading is withdrawn. But the scripted reproduction **failed**: 40 "Ⓐ is dead" reading is withdrawn. **Now routine**: the title that ends the boot sequence accepts a single Ⓐ (2 of
presses at 1/s move nothing, and neither do START/B/BACK/X/Y, with every press 2 runs); the title the attract loop returns to accepts nothing (Ⓐ, START, B,
verifiably delivered and nothing swallowed. The single success came on a title BACK, X, Y — dozens of delivered presses). The proposed tell was refuted on the
that appeared ~83 s into a warm boot; the failures on titles that appeared after way: the two states draw **13 identical quads**, `ptbtn00` included, so they
a full attract cycle. The reading that the attract-loop title is a differ only to the guest. Recipe: first title after boot, one tap, and never tap
non-interactive presentation is a **hypothesis**, and the cheap test is a during the boot (88 presses over the intro ends on a permanent black screen).
`log_ui_draws` capture in each state — if the interactive one draws `ptbtn00`
and the attract one does not, that is the tell. **The second screen is captured** — the main menu, `GP_TITLE` build 5 — and it
does not discriminate: its background sits at declaration indices 12, so
"declaration order" and "background first" predict the same sequence. Same
failure mode as `GP_READY_ROOM`/`GP_OPTIONS`. The next screen worth capturing is
one whose background sits **late** in its table, as the title's does.
The earlier reading, kept because it is what the evidence looked like: the The earlier reading, kept because it is what the evidence looked like: the
title's Ⓐ leads into a content/save path that crashes the guest with title's Ⓐ leads into a content/save path that crashes the guest with

View File

@@ -41,16 +41,14 @@ while [ $SECONDS -lt $deadline ]; do
done done
[ "$s" = "title" ] || { echo "NEVER REACHED THE TITLE"; exit 1; } [ "$s" = "title" ] || { echo "NEVER REACHED THE TITLE"; exit 1; }
# 2. tap until it takes # 2. ONE tap on this title. Measured: the title that ends the boot sequence
taps=0 # accepts a single (A) (2 of 2 runs); the title the attract loop returns to
while [ $taps -lt 40 ]; do # accepts nothing at all, and 40 taps at 1/s do not change that. So the tap must
python3 "$SD/pad.py" tap A 0.2; taps=$((taps+1)) # land on the FIRST title, and repeating is pointless.
sleep 1 python3 "$SD/pad.py" tap A 0.3
s="$(screen)" for _ in 1 2 3 4 5 6; do
if [ "$s" != "title" ]; then sleep 4; s="$(screen)"; echo " after A: $s"
sleep 3; s="$(screen)" [ "$s" = "menu" ] && { echo "MENU"; break; }
[ "$s" = "menu" ] && { echo "MENU after $taps taps"; break; }
fi
done done
shot "$OUT/menu.png" shot "$OUT/menu.png"
[ "$s" = "menu" ] || { echo "NO MENU after $taps taps (screen=$s)"; exit 2; } [ "$s" = "menu" ] || { echo "NO MENU after $taps taps (screen=$s)"; exit 2; }

View File

@@ -61,8 +61,19 @@ def classify(f):
# Flight first: the HUD paints far more green than any menu. # Flight first: the HUD paints far more green than any menu.
if f["green"] > 0.004: if f["green"] > 0.004:
return "flight" return "flight"
# The main menu is dark and strongly blue, with the five white labels. # The main menu is dark and strongly blue. TWO measured signatures, not one:
if f["b"] - f["r"] > 30 and f["r"] < 45 and 0.015 < f["white"] < 0.075: #
# white 3.5% mean (25, 38, 70) the 2026-07 reference at the top
# white 0.03% mean (13, 26, 59) measured 2026-08-18, twice, on the menu
# reached from the boot title
#
# The second one classified as "other" under the original rule's
# `0.015 < white` floor, which is worse than it sounds: a script that waits
# for "menu" then never sees it reports the navigation as FAILED while the
# menu is plainly on screen. Both variants are dark, strongly blue and
# essentially green-free, so key on that and let the near-white fraction be
# anything below the title's 3.8%.
if f["b"] - f["r"] > 30 and f["r"] < 45 and f["white"] < 0.075:
return "menu" return "menu"
# The title is brighter, still blue-ish, and carries the green PRESS A text. # The title is brighter, still blue-ish, and carries the green PRESS A text.
if f["green"] > 0.0004 and f["b"] > 60 and f["white"] > 0.03: if f["green"] > 0.0004 and f["b"] > 60 and f["white"] > 0.03:

View File

@@ -0,0 +1,59 @@
#!/usr/bin/env bash
# Capture the UI draw list in BOTH title states, in one run:
# (1) the title that appears at the end of the boot sequence, and
# (2) the title the attract loop comes back to.
#
# The question is whether they composite differently — specifically whether the
# interactive one draws `ptbtn00` (the PRESS (A) BUTTON plate, GP_TITLE build 2)
# and the other does not. That would give an observable tell for the state in
# which (A) is accepted, which is currently a matter of luck.
#
# NO pad input at all: a press is what we are trying to explain, and tapping
# through the boot has ended a run on a permanent black screen before.
set -u
export HOME=/sylph-home/re SDL_AUDIODRIVER=dummy DISPLAY=:98
SD="$(cd "$(dirname "$0")" && pwd)"
OUT="${1:-/sylph-home/re/titlestates}"
mkdir -p "$OUT"; rm -f "$OUT"/xenia_re_ui_draws_*.log
shot(){ screenshot "$1" >/dev/null 2>&1; }
screen(){ shot /tmp/tsc.png; python3 "$SD/screen_id.py" /tmp/tsc.png | awk '{print $1}'; }
alive(){ ps -o pid=,stat= -C xenia_canary 2>/dev/null | awk '$2 !~ /^Z/ {print $1}'; }
( cd "$OUT" && nohup run-canary --mem_watch=false --log_ui_draws=true \
--ui_draw_capture_frames=4 --ui_draw_capture_max=4000 \
--logged_profile_slot_0_xuid=B13EBABEBABEBABE \
>"$OUT/canary.stdout" 2>"$OUT/canary.stderr" & )
sleep 8
until xdotool search --name "Xenia-canary" >/dev/null 2>&1; do
[ -n "$(alive)" ] || { echo "EMULATOR GONE"; exit 4; }; sleep 1
done
win="$(xdotool search --name "Xenia-canary" | tail -1)"
arm(){ # arm the capture and dismiss the menu bar F10 also opens
xdotool windowactivate --sync "$win"; sleep 1
xdotool key F10; sleep 3
xdotool mousemove 900 400 click 1; sleep 2
}
wait_for(){ # wait_for <screen> <timeout>
local want="$1" deadline=$(( SECONDS + $2 )) s
while [ $SECONDS -lt $deadline ]; do
s="$(screen)"; echo " t=${SECONDS}s $s (want $want)"
[ "$s" = "$want" ] && return 0
sleep 4
done
return 1
}
echo "== phase 1: the boot title"
wait_for title 420 || { echo "NO BOOT TITLE"; exit 1; }
shot "$OUT/title-boot.png"; arm
echo "== phase 2: wait for the attract movie to take over"
wait_for other 240 || echo " (never left the title)"
echo "== phase 3: the attract title"
wait_for title 420 || { echo "NO ATTRACT TITLE"; exit 2; }
shot "$OUT/title-attract.png"; arm
ls -l "$OUT"/xenia_re_ui_draws_*.log 2>/dev/null
grep -i "UI-CAP" "$OUT/canary.stdout" | tail -6
echo "TITLE STATES CAPTURE DONE (emulator left running)"