#!/usr/bin/env bash # Boot -> TITLE -> arm the UI draw/blend capture there -> (A) -> MAIN MENU -> arm again. # # Completes the blend table started in docs/re/structures/ui-blend-mode-measured.md, # whose stated reach was "two screens, one session" and explicitly NOT the title. # The second arm at the menu is deliberate: it replicates the main-menu result in a # SECOND session, which is the part of that page's reach a rerun can actually widen. # # Differences from menu_blend_capture.sh, each one paid for: # # * DEADLINE defaults to 1200 s, not 420. On 2026-08-31 a 420 s wait reported # "NEVER REACHED THE TITLE" and the emulator, left running, was at the settled # title minutes later and took (A) first try. A timeout measures the timeout. # * The title arm is gated on the PLATE PULSE (wait_plate_pulse.py), not on # screen_id alone: build 2's `PRESS (A)` plate is part of what is being # measured, and it pulses. # * On timeout it LEAVES THE EMULATOR UP and says so, so the run can be rescued # by attaching instead of rebooting. # * No --log_ui_draws. Canary's own source records that arming is unconditional # now and that launching with the flag correlates with the title refusing (A), # 0 of 7 runs against 4 of 5 without. # # F10 opens Xenia's menu bar, and any Xenia UI makes IsUIActive() true, after # which XamInputGetKeystrokeEx returns SUCCESS with a zeroed keystroke and the # guest's unbounded pump queues one entry per poll -- the crash in # docs/re/structures/title-a-press-fault.md. So the surface is clicked # immediately after every F10, and stdout is size-checked afterwards. # # Usage: title_blend_capture.sh [out_dir] set -u export HOME=/sylph-home/re SDL_AUDIODRIVER=dummy DISPLAY=:98 SD="$(cd "$(dirname "$0")" && pwd)" OUT="${1:-/sylph-home/re/titleblend}" DEADLINE="${DEADLINE:-1200}" mkdir -p "$OUT"; rm -f "$OUT"/xenia_re_ui_draws_*.log alive(){ ps -o pid=,stat= -C xenia_canary 2>/dev/null | awk '$2 !~ /^Z/ {print $1}'; } shot(){ screenshot "$1" >/dev/null 2>&1; } screen(){ shot /tmp/tbc.png; python3 "$SD/screen_id.py" /tmp/tbc.png | awk '{print $1}'; } arm(){ # $1 = label xdotool windowactivate --sync "$win"; sleep 1 xdotool key F10; sleep 2 xdotool mousemove 900 400 click 1; sleep 2 echo "ARMED ($1) at ${SECONDS}s -- stdout $(stat -c%s "$OUT/canary.stdout") bytes" } ( cd "$OUT" && nohup run-canary --mem_watch=false \ --ui_draw_capture_frames="${FRAMES:-4}" --ui_draw_capture_max="${MAXDRAWS:-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)" echo "WINDOW=$win" s="" until [ "$s" = "title" ]; do [ $SECONDS -lt $DEADLINE ] || { echo "TIMEOUT at ${SECONDS}s, screen=$s -- EMULATOR LEFT RUNNING, attach rather than reboot"; exit 1; } s="$(screen)"; echo "t=${SECONDS}s $s" [ "$s" = "title" ] || sleep 4 done echo "TITLE at ${SECONDS}s" # The plate is part of the title. Gate on its pulse before arming. timeout 180 python3 "$SD/wait_plate_pulse.py" || echo " (plate gate did not fire; arming anyway)" shot "$OUT/title.png" arm title ls -l "$OUT"/xenia_re_ui_draws_*.log 2>/dev/null || echo "NO TITLE LOG -- F10 was lost" # One tap. The title that ENDS the boot accepts a single (A); 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" ] && break done shot "$OUT/menu.png" if [ "$s" = "menu" ]; then arm menu else echo "NO MENU (screen=$s) -- title capture kept" fi ls -l "$OUT"/xenia_re_ui_draws_*.log 2>/dev/null grep -i "UI-CAP" "$OUT/canary.stdout" | tail -6 echo "stdout $(stat -c%s "$OUT/canary.stdout") bytes (a guest fault dumps ~100 MB/30 s)" pkill -x xenia_canary 2>/dev/null; sleep 2 echo "DONE at ${SECONDS}s"