Chasing why (A) does not advance the title turned up four harness bugs, one real
root cause, and a refutation of that root cause as the explanation.
FIXED, each verified:
* boot_menu.sh sent the emulator stdout to /dev/null, which is why none of the
rest was visible. It now keeps a log and prints its path.
* skip_intro.sh probed pixel (625,618) for the green (A) glyph - a 1280x720
coordinate. screenshot returns the 1279x675 game surface, where that point
is on the copyright line and reads (8,17,31). Symptom: a 600s TIMEOUT with
the title on screen. Now classified with screen_id.py; next boot printed
"TITLE at 239s -> A".
* skip_intro.sh no longer taps through the movies. Making that press real (the
vgamepad fix) was a REGRESSION: the boot then reached the title in 90s and
that title accepted nothing. menu_draw_capture.sh records the same finding
independently.
* boot_menu.sh signs in whichever profile exists instead of a hard-coded XUID.
ROOT CAUSE FOUND: naming a XUID with no profile behind it opens a sign-in
dialog, xam_dialogs_shown_ goes to 1, and IsUIActive() then discards every
XamInputGetKeystrokeEx. Measured 8,388,601 swallowed calls in one boot with the
pad log showing presses arriving normally throughout. Only B13EBABEBABEBABE
exists on disc; the script asked for E0300000EFBEA3D4. Signing in the real
profile takes the swallow count 8,388,601 -> 0.
AND REFUTED: that is not why the title is stuck. With a valid profile and zero
swallowed keystrokes, a single (A) on the title that ends the boot still does
nothing. The dialog bug was real and worth fixing; it is not the explanation.
Next probe is specific: --log_mask=13 records no kernel calls, so it is not even
known whether the game polls at the title. A log on the SUCCESSFUL
XamInputGetKeystrokeEx return, mirroring the one already on the swallow path,
answers it in one boot.
73 lines
4.1 KiB
Bash
Executable File
73 lines
4.1 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Drive the boot sequence to the main menu without a human in the loop.
|
|
# - movie playing (two grabs 0.6s apart differ a lot) -> tap A to skip
|
|
# - static screen -> if the green "PRESS (A) BUTTON" glyph is there, tap A and stop
|
|
# Static logo screens are left alone, so a stray tap can never land on NEW GAME.
|
|
#
|
|
# LIVENESS IS CHECKED EVERY ITERATION, and that is not a nicety. When the
|
|
# display died 3 minutes into a run, `screenshot` started failing silently and
|
|
# left /tmp/f1.png and /tmp/f2.png at their last contents — two *stale* files,
|
|
# which compare to a constant non-zero RMSE, which reads exactly like "the
|
|
# screen is changing a lot". So the loop reported "movie -> skip A" every 5 s
|
|
# for the full 600 s timeout with no emulator and no X server running. A dead
|
|
# display and a playing movie must never be able to look the same.
|
|
set -u
|
|
export HOME=/sylph-home/re
|
|
DISP="${DISPLAY:-:98}"
|
|
alive(){ ps -o pid=,stat= -C xenia_canary 2>/dev/null | awk '$2 !~ /^Z/ {print $1}'; }
|
|
# Press through pad.py, NOT `vgamepad`. That command no longer exists — the
|
|
# uinput pad was replaced by the `--hid=file` driver — and because this script
|
|
# runs without `set -e`, calling it failed silently: the title branch pressed
|
|
# nothing and still `exit 0`, so a caller was told "TITLE -> A" while the game
|
|
# sat on the title screen forever. Fail loudly instead.
|
|
SD_SI="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
tapA(){ python3 "$SD_SI/pad.py" tap A 0.25 || { echo "PAD PRESS FAILED"; exit 6; }; }
|
|
# The X root keeps the DEAD session's last frame, so a fresh launch would be
|
|
# detected as "already at the title". Blank it, and wait for the new window.
|
|
xsetroot -solid black 2>/dev/null || true
|
|
until xdotool search --name "Xenia-canary" >/dev/null 2>&1; do
|
|
xdpyinfo -display "$DISP" >/dev/null 2>&1 || { echo "DISPLAY LOST at ${SECONDS}s (before the window appeared)"; exit 3; }
|
|
[ -n "$(alive)" ] || { echo "EMULATOR GONE at ${SECONDS}s (before the window appeared)"; exit 4; }
|
|
sleep 1
|
|
done
|
|
deadline=$(( SECONDS + ${1:-900} ))
|
|
while [ $SECONDS -lt $deadline ]; do
|
|
rm -f /tmp/f1.png /tmp/f2.png
|
|
screenshot /tmp/f1.png >/dev/null 2>&1; sleep 0.6
|
|
screenshot /tmp/f2.png >/dev/null 2>&1
|
|
if [ ! -s /tmp/f1.png ] || [ ! -s /tmp/f2.png ]; then
|
|
xdpyinfo -display "$DISP" >/dev/null 2>&1 \
|
|
|| { echo "DISPLAY LOST at ${SECONDS}s"; exit 3; }
|
|
echo "SCREENSHOT FAILED at ${SECONDS}s with the display up"; exit 5
|
|
fi
|
|
[ -n "$(alive)" ] || { echo "EMULATOR GONE at ${SECONDS}s"; exit 4; }
|
|
d=$(compare -metric RMSE /tmp/f1.png /tmp/f2.png null: 2>&1 | sed 's/ .*//' | cut -d. -f1)
|
|
d=${d:-0}
|
|
# Classify the whole frame, do NOT probe one absolute pixel. The old test
|
|
# sampled (625,618) for the green (A) glyph, which is a coordinate from a
|
|
# 1280x720 grab; `screenshot` now returns the 1279x675 GAME SURFACE, where
|
|
# that point lands on the copyright line and reads (8,17,31). The symptom was
|
|
# a 600s TIMEOUT with the title sitting on screen the entire time — measured
|
|
# 2026-08-19, with screen_id.py calling it "title" on the same frames.
|
|
if [ "$(python3 "$SD_SI/screen_id.py" /tmp/f2.png | awk '{print $1}')" = "title" ]; then
|
|
echo "TITLE at ${SECONDS}s -> A"; tapA; exit 0
|
|
fi
|
|
# DO NOT tap through the movies. This branch used to, and for a long time it
|
|
# was harmless only because `vgamepad` did not exist and the call failed
|
|
# silently. Once the press became real, the boot reached the title in 90s
|
|
# instead of 434s — and that title accepts NOTHING. menu_draw_capture.sh
|
|
# records the same thing from the other direction: "a run that tapped (A)
|
|
# every 4s through the boot delivered 88 presses and ended on a black screen
|
|
# that never came back", and "the title the attract loop returns to accepts
|
|
# nothing at all, and 40 taps at 1/s do not change that".
|
|
#
|
|
# So: wait the intro out (~3.5 min) and spend the single press on the title
|
|
# that ends the boot sequence, which is the one that accepts it.
|
|
if [ "$d" -gt 1500 ]; then
|
|
echo "movie (rmse $d) at ${SECONDS}s -> waiting it out (tapping breaks the title)"
|
|
sleep 3
|
|
fi
|
|
sleep 1
|
|
done
|
|
echo "TIMEOUT"; exit 1
|