#!/usr/bin/env bash # Decompose a screen transition's ~0.4 s fade-out into RAMP + HOLD, at the # emulator's own frame granularity. # # docs/re/screen-transitions.md measures the fade-out as ~0.4 s (~24 units) while # `pteff00.prm`'s final declared ramp is 70->80 = 10 units. The remainder is # currently ARITHMETIC THAT FITS -- "the other 14 units must be the black hold" # -- and that page says so itself. This measures it instead. # # The design point: arm the UI draw capture ON THE MAIN MENU, then press (B). # One capture window then contains # * the menu's fade-OUT -- the unknown, and # * the title's fade-IN -- whose ramp IS decoded from the file (build 4's # pteff00.prm, t=16 a=255 -> t=261 a=0, 245 units), # so the run carries its own control: an instrument that cannot reproduce the # known fade-in cannot be trusted on the unknown fade-out. # # Traps inherited from menu_draw_capture.sh, both already paid for: # * F10 arms the capture AND opens the emulator menu bar; any Xenia UI makes # IsUIActive() true and every later guest keystroke is swallowed. Click the # game surface to dismiss before touching the pad. # * a 0.12 s tap gets missed; hold (B) 0.5 s and confirm [RE-INPUT] delivery. # # WHERE=menu (default) arms on the main menu and presses (B) -> menu -> title. # WHERE=title arms on the boot title and presses (A) -> title -> menu. # WHERE=extras navigates to EXTRAS, arms there, presses (B) -> menu. # Tests whether "(B) has no black interval" is a rule or # one screen pair -- sylpheed-port's BLOCKED.md ask #1. # The two transitions have DIFFERENT declared fade-ins for the incoming screen -- # build 4 is 0->16 (16 units, 8 frames), build 5 is 0->12 (12 units, 6 frames) -- # which is what makes the pair a discriminator rather than a fit. # # Usage: WHERE=title fade_decompose.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/fadecap}" 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/fdc.png; python3 "$SD/screen_id.py" /tmp/fdc.png | awk '{print $1}'; } ( cd "$OUT" && nohup run-canary --mem_watch=false --log_ui_draws=true \ --ui_draw_capture_frames="${FRAMES:-260}" \ --ui_draw_capture_max="${MAXDRAWS:-400000}" \ --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)" # 1. wait for the boot title; do not tap through the intro (a run that tapped # every 4 s delivered 88 presses and ended on a black screen). deadline=$(( SECONDS + 420 )); s="" while [ $SECONDS -lt $deadline ]; do s="$(screen)"; echo "t=${SECONDS}s $s" [ "$s" = "title" ] && break sleep 4 done [ "$s" = "title" ] || { echo "NEVER REACHED THE TITLE"; exit 1; } WHERE="${WHERE:-menu}" if [ "$WHERE" = "menu" ]; then # 2. one (A) on the boot title -> main menu; we arm THERE and press (B). python3 "$SD/pad.py" tap A 0.5 for _ in 1 2 3 4 5 6; do sleep 4; s="$(screen)"; echo " after A: $s" [ "$s" = "menu" ] && break done [ "$s" = "menu" ] || { echo "NO MENU (screen=$s)"; exit 2; } BTN=B elif [ "$WHERE" = "extras" ]; then python3 "$SD/pad.py" tap A 0.5 for _ in 1 2 3 4 5 6; do sleep 4; s="$(screen)"; echo " after A: $s" [ "$s" = "menu" ] && break done [ "$s" = "menu" ] || { echo "NO MENU (screen=$s)"; exit 2; } # EXTRAS is the only main-menu button that stays inside GP_TITLE, and initial # focus varies boot to boot, so search: press (A), ask which screen we got, and # if it is not EXTRAS come back with (B) and step the cursor. screen_id.py # cannot tell EXTRAS from the main menu -- both are dark blue GP_TITLE screens # -- so the check is which_title_screen.py, whose control separates them by # ~11 RMSE against ~18 within-class. found="" for try in 1 2 3 4 5 6; do python3 "$SD/pad.py" tap A 0.5; sleep 5 shot "$OUT/try$try.png" w="$(python3 "$SD/which_title_screen.py" "$OUT/try$try.png")" echo " try $try: $w" case "$w" in extras*) found=1; break;; esac python3 "$SD/pad.py" tap B 0.5; sleep 5 python3 "$SD/pad.py" dpad UP 0.3; sleep 2 done [ -n "$found" ] || { echo "NEVER REACHED EXTRAS"; exit 3; } BTN=B else # arm on the title itself and press (A): the incoming screen is then build 5, # whose declared fade-in is 12 units where build 4's is 16. BTN=A fi shot "$OUT/armed-on-$WHERE.png" # 3. arm the capture, dismiss the menu bar F10 opened, then press (B). # Everything between F10 and (B) is spent inside the capture window, so keep # it short: the window is FRAMES submitted frames, not seconds. xdotool windowactivate --sync "$win"; sleep 1 xdotool key F10; sleep 0.6 xdotool mousemove 900 400 click 1; sleep 0.6 echo "-- ($BTN) at $(date +%S.%N) --" python3 "$SD/pad.py" tap $BTN 0.5 sleep 12 shot "$OUT/after-press.png"; echo "screen after $BTN: $(screen)" grep -c "RE-INPUT" "$OUT/canary.stdout" 2>/dev/null | sed 's/^/[RE-INPUT] lines: /' ls -l "$OUT"/xenia_re_ui_draws_*.log 2>/dev/null || echo "NO CAPTURE LOG" grep -i "UI-CAP" "$OUT/canary.stdout" | tail -3 echo "FADE CAPTURE DONE (emulator left running)"