#!/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} # COUNT the green (A) glyph over the whole frame; do not probe one absolute # pixel and do not trust screen_id.py here. The pixel probe used a 1280x720 # coordinate against the 1279x675 game surface and always read the copyright # line (600s TIMEOUT with the title on screen). screen_id.py fixed that and # then over-matched the other way: it called the SQUARE ENIX publisher logo # "title" 151s into a boot, and this script spent its one press there. # is_title.py counts glyph pixels, which only the interactive title has. # AND the frame must be STATIC. A glyph count alone still fired at 113-173s, # during the intro movie, and the press was wasted — measured by the loader # thread that the title handler ALWAYS creates being absent entirely on those # runs. The resting title barely changes between two grabs 0.6s apart; a movie # does, and `$d` is already computed above for exactly that distinction. if [ "$d" -le 1500 ] && python3 "$SD_SI/is_title.py" /tmp/f2.png 800 >/dev/null 2>&1; 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