diff --git a/docs/re/BACKLOG.md b/docs/re/BACKLOG.md index fb87fa5..9a2a5ce 100644 --- a/docs/re/BACKLOG.md +++ b/docs/re/BACKLOG.md @@ -171,14 +171,18 @@ available in the container. **And a second screen is NO LONGER BLOCKED, but it is not routine either.** The main menu has been reached (screenshot in [`canary-scripted-input-traps.md`](canary-scripted-input-traps.md)), so the -"Ⓐ is dead" reading is withdrawn. But the scripted reproduction **failed**: 40 -presses at 1/s move nothing, and neither do START/B/BACK/X/Y, with every press -verifiably delivered and nothing swallowed. The single success came on a title -that appeared ~83 s into a warm boot; the failures on titles that appeared after -a full attract cycle. The reading that the attract-loop title is a -non-interactive presentation is a **hypothesis**, and the cheap test is a -`log_ui_draws` capture in each state — if the interactive one draws `ptbtn00` -and the attract one does not, that is the tell. +"Ⓐ is dead" reading is withdrawn. **Now routine**: the title that ends the boot sequence accepts a single Ⓐ (2 of +2 runs); the title the attract loop returns to accepts nothing (Ⓐ, START, B, +BACK, X, Y — dozens of delivered presses). The proposed tell was refuted on the +way: the two states draw **13 identical quads**, `ptbtn00` included, so they +differ only to the guest. Recipe: first title after boot, one tap, and never tap +during the boot (88 presses over the intro ends on a permanent black screen). + +**The second screen is captured** — the main menu, `GP_TITLE` build 5 — and it +does not discriminate: its background sits at declaration indices 1–2, 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 title's Ⓐ leads into a content/save path that crashes the guest with diff --git a/tools/re-capture/menu_draw_capture.sh b/tools/re-capture/menu_draw_capture.sh index 1da1177..4f910da 100755 --- a/tools/re-capture/menu_draw_capture.sh +++ b/tools/re-capture/menu_draw_capture.sh @@ -41,16 +41,14 @@ while [ $SECONDS -lt $deadline ]; do done [ "$s" = "title" ] || { echo "NEVER REACHED THE TITLE"; exit 1; } -# 2. tap until it takes -taps=0 -while [ $taps -lt 40 ]; do - python3 "$SD/pad.py" tap A 0.2; taps=$((taps+1)) - sleep 1 - s="$(screen)" - if [ "$s" != "title" ]; then - sleep 3; s="$(screen)" - [ "$s" = "menu" ] && { echo "MENU after $taps taps"; break; } - fi +# 2. ONE tap on this title. Measured: the title that ends the boot sequence +# accepts a single (A) (2 of 2 runs); the title the attract loop returns to +# accepts nothing at all, and 40 taps at 1/s do not change that. So the tap must +# land on the FIRST title, and repeating is pointless. +python3 "$SD/pad.py" tap A 0.3 +for _ in 1 2 3 4 5 6; do + sleep 4; s="$(screen)"; echo " after A: $s" + [ "$s" = "menu" ] && { echo "MENU"; break; } done shot "$OUT/menu.png" [ "$s" = "menu" ] || { echo "NO MENU after $taps taps (screen=$s)"; exit 2; } diff --git a/tools/re-capture/screen_id.py b/tools/re-capture/screen_id.py index 8d2c7c0..636900f 100755 --- a/tools/re-capture/screen_id.py +++ b/tools/re-capture/screen_id.py @@ -61,8 +61,19 @@ def classify(f): # Flight first: the HUD paints far more green than any menu. if f["green"] > 0.004: return "flight" - # The main menu is dark and strongly blue, with the five white labels. - if f["b"] - f["r"] > 30 and f["r"] < 45 and 0.015 < f["white"] < 0.075: + # The main menu is dark and strongly blue. TWO measured signatures, not one: + # + # 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" # 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: diff --git a/tools/re-capture/title_states_capture.sh b/tools/re-capture/title_states_capture.sh new file mode 100755 index 0000000..03b3633 --- /dev/null +++ b/tools/re-capture/title_states_capture.sh @@ -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 + 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)"