This repository has been archived on 2026-09-16. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Syplheed-Reborn/tools/re-capture/launch_mission.sh
Sylpheed RE agent 8bea94a47f tools: launch_mission reaches Stage 02 flight unattended again - wait for screens, not seconds
Two separate reasons the scripted route stopped, both measured rather than guessed:

* LOAD -> READY ROOM is not 28 s. Both runs on 2026-08-23 overran it, so the next
  press was eaten by the transition and the run ended up in OPTIONS once and
  BRIEFINGS once. wait_screen.sh now waits for the screen, with an optional
  --tap that clears a dialog the caller cannot know about (a freshly restored
  profile inserts "Auto-Save is active. OK?" here).
* The READY ROOM is DRAWN long before it is USABLE: it comes up with a
  "Preparing to Sortie" spinner and TAKE OFF greyed out. The two states differ by
  1.7 units of blue whole-image, so screen_id.py cannot separate them and should
  not try. take_off_armed.py tests the label instead: 0.0000 bright pixels while
  preparing, 0.1633 once armed, on three captures from two runs. It carries its
  own position check - the always-enabled BRIEFINGS label below reads 0.1027 in
  all three, to four decimals, so if that reference is dark the boxes are off the
  labels and the answer is "unknown", not a confident wrong one.

screen_id.py gains a "readyroom" class from the same measurements; nothing else
reclassifies.

Verified end to end and unattended: boot -> title -> LOAD GAME -> slot 01 ->
READY ROOM -> TAKE OFF -> "IN FLIGHT at 34s", pilot bound and engaging.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE
2026-08-23 18:02:12 +00:00

114 lines
5.7 KiB
Bash
Executable File

#!/usr/bin/env bash
# Boot Canary and drive it into the Stage 02 mission from save slot 01, then
# LEAVE IT RUNNING (unlike grab_tutorial.sh, which snapshots and exits) so a
# live control loop can attach to /dev/shm.
#
# Nav is the verified route: title -> LOAD GAME -> slot 01 -> YES -> READY ROOM
# -> TAKE OFF. With `--hangar` it stops in the HANGAR instead so a loadout can
# be picked first.
set -u
MODE="${1:-fly}"
export HOME=/sylph-home/re SDL_AUDIODRIVER=dummy DISPLAY=:98
SD="$(cd "$(dirname "$0")" && pwd)"
SHOTS=/sylph-home/re/shots
alive(){ ps -o pid=,stat= -C xenia_canary 2>/dev/null | awk '$2 !~ /^Z/ {print $1}'; }
# DO NOT `setsid` THE DISPLAY OR THE EMULATOR. They used to be detached into
# their own sessions so they would outlive the shell that started them. What
# that actually bought was the opposite: a process nothing owns is a process
# nothing keeps alive, and both were being reaped a couple of minutes in — the
# long-standing "Xvfb and the emulator die on their own every few minutes" note.
# MEASURED WRONG 2026-08-10: a harness-tracked BACKGROUND task does not protect
# them either — the display was lost 11 s in, at the turn boundary. Run this
# whole script as ONE BLOCKING FOREGROUND call and leave Xvfb, openbox
# and xenia as its children: they then live exactly as long as the session does.
# `nohup` still shields them from a stray HUP; the exit-status wrapper means a
# death is reported with the server's own account instead of being inferred.
ensure_display(){
if ! xdpyinfo -display "$DISPLAY" >/dev/null 2>&1; then
rm -f "/tmp/.X${DISPLAY#:}-lock" 2>/dev/null || true
nohup bash -c 'Xvfb "$0" -screen 0 1280x720x24 -ac -nolisten tcp \
+extension GLX +extension RANDR >/tmp/xvfb98.log 2>&1
echo "$(date +%T) XVFB EXIT $? (128+N means signal N)" >>/tmp/xvfb-exit.log' \
"$DISPLAY" </dev/null >/dev/null 2>&1 &
for _ in $(seq 1 50); do xdpyinfo -display "$DISPLAY" >/dev/null 2>&1 && break; sleep 0.2; done
nohup env DISPLAY="$DISPLAY" HOME=/sylph-home openbox </dev/null >/tmp/openbox98.log 2>&1 &
sleep 1
fi
xdpyinfo -display "$DISPLAY" >/dev/null 2>&1 || { echo "DISPLAY UNAVAILABLE"; exit 1; }
}
# pad.py, NOT `vgamepad`: that command no longer exists (the uinput pad was
# replaced by the --hid=file driver) and every call here failed silently, so the
# whole scripted route pressed nothing. See canary-scripted-input-traps.md.
step(){ python3 "$SD/pad.py" dpad "$1" 0.08; sleep 0.9; }
tap(){ python3 "$SD/pad.py" tap "$1" 0.3 || { echo "PAD PRESS FAILED"; exit 6; }; }
shot(){ screenshot "$SHOTS/$1" >/dev/null 2>&1; }
pkill -x xenia_canary 2>/dev/null; sleep 2
[ -n "$(alive)" ] && { kill -9 $(alive) 2>/dev/null; sleep 2; }
rm -f /dev/shm/xenia_memory_* /dev/shm/xenia_code_cache_* 2>/dev/null
ensure_display
cd /sylph-home/re
# Sign in a profile that EXISTS. A XUID with nothing behind it opens a sign-in
# dialog and IsUIActive() then swallows every keystroke for the whole run — 8.4
# million of them, measured. And KEEP the log: /dev/null is what hid that.
XUID="${SYLPH_XUID:-$(ls "${XENIA_CONTENT:-$HOME/.local/share/Xenia/content}" 2>/dev/null | head -1)}"
[ -n "$XUID" ] || { echo "NO PROFILE on disc"; exit 2; }
LOG="${LAUNCH_MISSION_LOG:-$SHOTS/launch_mission-canary.stdout}"
echo "signing in profile $XUID; emulator log: $LOG"
nohup run-canary --apu=sdl --log_mask="${LOG_MASK:-13}" --log_level="${LOG_LEVEL:-2}" \
--logged_profile_slot_0_xuid="$XUID" </dev/null >"$LOG" 2>&1 &
sleep 5
"$SD/skip_intro.sh" 600 || { echo "BOOT FAILED (skip_intro exit $?)"; exit 1; }
sleep 14 # main menu is not input-ready before this
step down # NEW GAME -> LOAD GAME
tap A; sleep 8 # save list, slot 01 preselected
tap A; sleep 4 # "Load game?" -- cursor starts on NO
step up
tap A
# NOT a fixed sleep. LOAD -> READY ROOM took longer than 28 s in both runs on
# 2026-08-23, so the next press was eaten by the transition and the run ended up
# in OPTIONS (once) and BRIEFINGS (once); and a freshly restored profile inserts
# an extra "Auto-Save is active. OK?" dialog here, which the `--tap A` clears
# without the script having to know whether it is there.
# KNOWN LIMIT, stated rather than hidden: those blind A taps are safe only
# because everything before this point still lands. If the save-list presses
# ever drift and the run is still sitting on the MAIN MENU here, a blind A would
# start a NEW GAME. The real fix is to wait for each screen on the route rather
# than only for this one; only this transition has actually been measured to
# overrun, so only this one is waited for.
"$SD/wait_screen.sh" readyroom 300 --tap A \
|| { echo "NEVER REACHED READY ROOM"; exit 1; }
# ...and the READY ROOM is DRAWN before it is usable: it comes up with a
# "Preparing to Sortie" spinner and TAKE OFF greyed out, for tens of seconds.
# Pressing during that does nothing, and the press after it then lands on the
# wrong item. The two states are the same image to within 1.7 units of blue, so
# they need the label test rather than screen_id.py.
armed=0
for _ in $(seq 1 60); do
shot "lm-readyroom.png"
if python3 "$SD/take_off_armed.py" "$SHOTS/lm-readyroom.png" >/dev/null; then
armed=1; break
fi
sleep 3
done
[ $armed -eq 1 ] || { echo "TAKE OFF NEVER ARMED"; exit 1; }
if [ "$MODE" = "--hangar" ]; then
step down; step down # BRIEFINGS -> HANGAR
tap A; sleep 12
shot "lm-hangar.png"
echo "STOPPED IN HANGAR"; exit 0
fi
step up # BRIEFINGS -> TAKE OFF
tap A
# The launch cinematic + stage load + objective card is NOT a fixed 75 s under
# lavapipe; wait for the flight HUD itself.
"$SD/wait_flight.sh" 300 || { echo "NEVER REACHED FLIGHT"; exit 1; }
shot "lm-flight.png"
echo "IN FLIGHT (emulator left running)"