diff --git a/docs/re/captures/ready-room-extra-mode.png b/docs/re/captures/ready-room-extra-mode.png new file mode 100644 index 0000000..4f7540e Binary files /dev/null and b/docs/re/captures/ready-room-extra-mode.png differ diff --git a/docs/re/captures/unit-runtime-fields.csv b/docs/re/captures/unit-runtime-fields.csv index 454c664..00ffe4f 100644 --- a/docs/re/captures/unit-runtime-fields.csv +++ b/docs/re/captures/unit-runtime-fields.csv @@ -3023,6 +3023,150 @@ UN_e004_ADAN_ElanPlus_NF,TurnAway_Time_Minimum,0x1b0,f32,0.5,defaulted-on-disc,l UN_e004_ADAN_ElanPlus_NF,Turn_AngularVelocity,0x1ac,f32,1.0471975803375244,disc,verified UN_e004_ADAN_ElanPlus_NF,UsingChaffRatio,0x234,f32,0.0,defaulted-on-disc,layout-derived UN_e004_ADAN_ElanPlus_NF,YawDragFactor,0x104,f32,3.0,disc,verified +UN_e005_ADAN_ElanTypeQ_Margras,AA_AxisMode_Max,0x134,f32,0.0,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,AA_AxisMode_Min,0x138,f32,0.0,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,AA_PitchMinus_Max,0x0c8,f32,0.1745329350233078,disc,verified +UN_e005_ADAN_ElanTypeQ_Margras,AA_PitchMinus_Min,0x0cc,f32,0.6981317400932312,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,AA_PitchPlus_Max,0x0b8,f32,0.5235987901687622,disc,verified +UN_e005_ADAN_ElanTypeQ_Margras,AA_PitchPlus_Min,0x0bc,f32,1.7453293800354004,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,AA_Roll_Max,0x0e8,f32,1.0471975803375244,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,AA_Roll_Min,0x0ec,f32,2.6179940700531006,disc,verified +UN_e005_ADAN_ElanTypeQ_Margras,AA_Yaw_Max,0x0d8,f32,0.8726646900177002,disc,verified +UN_e005_ADAN_ElanTypeQ_Margras,AA_Yaw_Min,0x0dc,f32,4.188790321350098,disc,verified +UN_e005_ADAN_ElanTypeQ_Margras,AB_AA_PitchMinus,0x158,f32,0.0,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,AB_AA_PitchPlus,0x150,f32,0.0,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,AB_AA_Roll,0x168,f32,0.0,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,AB_AA_Yaw,0x160,f32,0.0,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,AB_AV_PitchMinus,0x154,f32,0.0,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,AB_AV_PitchPlus,0x14c,f32,0.0,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,AB_AV_Roll,0x164,f32,0.0,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,AB_AV_Yaw,0x15c,f32,0.0,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,AB_ConsumeShield,0x148,f32,0.0,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,AB_ConsumeShield_Begin,0x144,f32,0.0,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,AV_AxisMode_Max,0x12c,f32,0.0,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,AV_AxisMode_Min,0x130,f32,0.0,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,AV_PitchMinus_Max,0x0c0,f32,0.6981317400932312,disc,verified +UN_e005_ADAN_ElanTypeQ_Margras,AV_PitchMinus_Min,0x0c4,f32,2.094395160675049,disc,verified +UN_e005_ADAN_ElanTypeQ_Margras,AV_PitchPlus_Max,0x0b0,f32,1.3962634801864624,disc,verified +UN_e005_ADAN_ElanTypeQ_Margras,AV_PitchPlus_Min,0x0b4,f32,3.490658760070801,disc,verified +UN_e005_ADAN_ElanTypeQ_Margras,AV_Roll_Max,0x0e0,f32,2.094395160675049,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,AV_Roll_Min,0x0e4,f32,2.792526960372925,disc,verified +UN_e005_ADAN_ElanTypeQ_Margras,AV_Yaw_Max,0x0d0,f32,0.4363323450088501,disc,verified +UN_e005_ADAN_ElanTypeQ_Margras,AV_Yaw_Min,0x0d4,f32,1.0471975803375244,disc,verified +UN_e005_ADAN_ElanTypeQ_Margras,AccPitchFactor,0x124,f32,30.0,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,Acceleration,0x0a8,f32,600.0,disc,verified +UN_e005_ADAN_ElanTypeQ_Margras,ArterBurner_Acc,0x11c,f32,2.0,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,ArterBurner_Vc,0x114,f32,2.0,disc,verified +UN_e005_ADAN_ElanTypeQ_Margras,AttackCraftPoint,0x2b8,f32,0.800000011920929,disc,verified +UN_e005_ADAN_ElanTypeQ_Margras,AttackVesselPoint,0x2b4,f32,0.0010000000474974513,disc,verified +UN_e005_ADAN_ElanTypeQ_Margras,BarrelRoll,0x178,word,1,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,BarrelRoll_CountMaximum,0x180,f32,5.0,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,BarrelRoll_CountMinimum,0x17c,f32,0.5,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,BarrelRoll_Radius,0x188,f32,250.0,disc,verified +UN_e005_ADAN_ElanTypeQ_Margras,BarrelRoll_Time,0x184,f32,1.2000000476837158,disc,verified +UN_e005_ADAN_ElanTypeQ_Margras,BoostAway,0x1b8,word,1,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,BoostAway_Time_Maximum,0x1c0,f32,4.0,disc,verified +UN_e005_ADAN_ElanTypeQ_Margras,BoostAway_Time_Minimum,0x1bc,f32,1.0,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,ChargeDelay,0x23c,f32,3.0,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,ChargeDelay_Break,0x240,f32,10.0,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,ChargeSpeed,0x244,f32,25.0,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,Color_B,0x048,f32,0.10000000149011612,disc,verified +UN_e005_ADAN_ElanTypeQ_Margras,Color_G,0x044,f32,0.5,disc,verified +UN_e005_ADAN_ElanTypeQ_Margras,Color_R,0x040,f32,1.0,disc,verified +UN_e005_ADAN_ElanTypeQ_Margras,CruisingVelocity,0x0a4,f32,700.0,disc,verified +UN_e005_ADAN_ElanTypeQ_Margras,CutoffTimeMax,0x19c,f32,5.0,disc,verified +UN_e005_ADAN_ElanTypeQ_Margras,CutoffTimeMin,0x198,f32,0.6000000238418579,disc,verified +UN_e005_ADAN_ElanTypeQ_Margras,DecPitchFactor,0x128,f32,30.0,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,Deceleration,0x0ac,f32,400.0,disc,verified +UN_e005_ADAN_ElanTypeQ_Margras,DefencePoint,0x2bc,f32,0.0002500000118743628,disc,verified +UN_e005_ADAN_ElanTypeQ_Margras,Delay,0x248,f32,0.0,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,DelayAdjustment,0x24c,f32,0.0,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,DestroyMotionTime,0x270,f32,5.0,disc,verified +UN_e005_ADAN_ElanTypeQ_Margras,DragFactorThreshold,0x110,f32,0.5,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,DryMass,0x274,f32,100.0,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,ExplosionSE,0x284,word,601,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,FCSRange,0x2a4,f32,2500.0,disc,verified +UN_e005_ADAN_ElanTypeQ_Margras,FiringRange,0x2a8,f32,0.0,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,GrossMass,0x278,f32,200.0,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,HP,0x054,f32,3200.0,disc,verified +UN_e005_ADAN_ElanTypeQ_Margras,HQRatio,0x058,f32,1.0,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,HoldPosition,0x1c4,word,1,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,HoldPosition_BackRatio,0x1dc,f32,0.5,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,HoldPosition_CancelTime,0x1e4,f32,20.0,disc,verified +UN_e005_ADAN_ElanTypeQ_Margras,HoldPosition_CutoffRatio,0x1e0,f32,0.5,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,HoldPosition_LengthMax,0x1cc,f32,400.0,disc,verified +UN_e005_ADAN_ElanTypeQ_Margras,HoldPosition_LengthMin,0x1c8,f32,150.0,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,HoldPosition_MaximumTime,0x1d4,f32,10.0,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,HoldPosition_MinimumTime,0x1d0,f32,5.0,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,HoldPosition_SideRatio,0x1d8,f32,0.30000001192092896,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,HomingResistAdjustment,0x230,f32,0.6000000238418579,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,IsDestructible,0x064,word,1,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,JumpIn,0x288,word,4294967295,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,JumpOut,0x28c,word,4294967295,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,LowerHPSE,0x294,word,4294967295,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,LowerHPThresholdRatio,0x298,f32,0.30000001192092896,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,MaxValue,0x238,f32,100.0,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,MaximumBank_Normal,0x100,f32,1.0471975803375244,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,MaximumVelocity,0x0a0,f32,1000.0,disc,verified +UN_e005_ADAN_ElanTypeQ_Margras,MinimumVelocity,0x09c,f32,100.0,disc,verified +UN_e005_ADAN_ElanTypeQ_Margras,MountedFCS,0x2ac,word,1,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,MountedShieldGenerator,0x070,word,0,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,PitchDragFactor,0x108,f32,3.0,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,PowerCutConsumeShield,0x13c,f32,0.0,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,PowerCutDeceleration,0x140,f32,0.0,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,RadarRange,0x2a0,f32,7000.0,disc,verified +UN_e005_ADAN_ElanTypeQ_Margras,ResistanceParalyze,0x084,f32,0.9800000190734863,disc,verified +UN_e005_ADAN_ElanTypeQ_Margras,ResistanceToExplosion,0x07c,f32,0.10000000149011612,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,ResistanceToOptics,0x074,f32,0.10000000149011612,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,ResistanceToPlayer,0x080,f32,1.0,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,ResistanceToShell,0x078,f32,0.10000000149011612,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,ReverseThrust_Acc,0x120,f32,2.0,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,ReverseThrust_Vc,0x118,f32,-0.5,disc,verified +UN_e005_ADAN_ElanTypeQ_Margras,RollDragFactor,0x10c,f32,3.0,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,SELength,0x29c,f32,2000.0,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,ShieldRatio,0x05c,f32,1.0,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,ShipEnvironmentSE,0x290,word,4294967295,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,SideRoll,0x16c,word,1,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,SideRoll_Length,0x174,f32,750.0,disc,verified +UN_e005_ADAN_ElanTypeQ_Margras,SideRoll_Time,0x170,f32,0.5,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,SideThrustAcceleration,0x0fc,f32,1000.0,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,SideThrustVelocity_Max,0x0f8,f32,1000.0,disc,verified +UN_e005_ADAN_ElanTypeQ_Margras,SideThruster,0x280,word,4294967295,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,Size_Radius,0x050,f32,19.0,disc,verified +UN_e005_ADAN_ElanTypeQ_Margras,Size_X,0x030,f32,24.0,disc,verified +UN_e005_ADAN_ElanTypeQ_Margras,Size_Y,0x034,f32,15.0,disc,verified +UN_e005_ADAN_ElanTypeQ_Margras,Size_Z,0x038,f32,38.0,disc,verified +UN_e005_ADAN_ElanTypeQ_Margras,Slalom,0x1e8,word,0,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,Slalom_CutoffRatio,0x1ec,f32,0.0,disc,verified +UN_e005_ADAN_ElanTypeQ_Margras,Slalom_TurnCount_Max,0x1f4,f32,0.0,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,Slalom_TurnCount_Min,0x1f0,f32,0.0,disc,verified +UN_e005_ADAN_ElanTypeQ_Margras,SolidCutoff,0x220,word,1,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,SolidCutoff_LengthMax,0x22c,f32,2500.0,disc,verified +UN_e005_ADAN_ElanTypeQ_Margras,SolidCutoff_LengthMin,0x228,f32,500.0,disc,verified +UN_e005_ADAN_ElanTypeQ_Margras,SolidCutoff_Ratio,0x224,f32,0.5,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,Through,0x1f8,word,1,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,Through_AngleMaximum,0x204,f32,1.0471975803375244,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,Through_AngleMinimum,0x200,f32,0.5235987901687622,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,Through_CutoffRatio,0x1fc,f32,0.5,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,Through_LengthMaximum,0x21c,f32,500.0,disc,verified +UN_e005_ADAN_ElanTypeQ_Margras,Through_LengthMinimum,0x218,f32,250.0,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,Through_Time1Max,0x208,f32,2.0,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,Through_Time1Min,0x20c,f32,5.0,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,Through_Time2Max,0x210,f32,1.5,disc,verified +UN_e005_ADAN_ElanTypeQ_Margras,Through_Time2Min,0x214,f32,5.0,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,Thruster,0x27c,word,512,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,ThrusterRatio,0x060,f32,1.0,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,TurnAttack,0x18c,word,1,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,TurnAttack_CutoffRatio,0x190,f32,0.4000000059604645,disc,verified +UN_e005_ADAN_ElanTypeQ_Margras,TurnAttack_DoubleRatio,0x194,f32,0.30000001192092896,disc,verified +UN_e005_ADAN_ElanTypeQ_Margras,TurnAttack_DoubleTimeMax,0x1a4,f32,2.5,disc,verified +UN_e005_ADAN_ElanTypeQ_Margras,TurnAttack_DoubleTimeMin,0x1a0,f32,0.5,disc,verified +UN_e005_ADAN_ElanTypeQ_Margras,TurnAway,0x1a8,word,1,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,TurnAway_Time_Maximum,0x1b4,f32,2.0,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,TurnAway_Time_Minimum,0x1b0,f32,0.5,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,Turn_AngularVelocity,0x1ac,f32,2.094395160675049,disc,verified +UN_e005_ADAN_ElanTypeQ_Margras,UsingChaffRatio,0x234,f32,0.4000000059604645,defaulted-on-disc,layout-derived +UN_e005_ADAN_ElanTypeQ_Margras,YawDragFactor,0x104,f32,3.0,disc,verified UN_e006_ADAN_Vindicator_Margras,AA_AxisMode_Max,0x134,f32,0.0,defaulted-on-disc,layout-derived UN_e006_ADAN_Vindicator_Margras,AA_AxisMode_Min,0x138,f32,0.0,defaulted-on-disc,layout-derived UN_e006_ADAN_Vindicator_Margras,AA_PitchMinus_Max,0x0c8,f32,0.1745329350233078,disc,verified diff --git a/tools/re-capture/fly_stage.sh b/tools/re-capture/fly_stage.sh new file mode 100755 index 0000000..91f657d --- /dev/null +++ b/tools/re-capture/fly_stage.sh @@ -0,0 +1,90 @@ +#!/usr/bin/env bash +# Launch any STORY stage from MISSION SELECT and snapshot guest RAM in flight. +# +# Supersedes the save-editing route (patch GHAD +52 in every slot, boot, LOAD +# GAME, TAKE OFF): poking ONE word marks every story stage cleared, and MISSION +# SELECT will then launch any of them — +# 0x828F40C0 = 0x0001FFFE cleared-stage mask, bits 1..16 +# See docs/re/challenge-mission-gate.md. Nothing is written to disc, so there is +# no save to back up and restore afterwards. +# +# Usage: fly_stage.sh [boot_timeout_s] +set -u +STAGE="${1:?usage: fly_stage.sh }" +BOOT_TIMEOUT="${2:-400}" +export HOME=/sylph-home/re DISPLAY=:99 SDL_AUDIODRIVER=dummy +export XENIA_PAD_FILE=/tmp/xenia_pad.txt + +HERE="$(cd "$(dirname "$0")" && pwd)" +pad() { python3 "$HERE/pad.py" "$@"; } +poke() { python3 "$HERE/gpoke.py" "$@"; } +SHOTS="$HOME/shots"; mkdir -p "$SHOTS" +say() { echo "[$(date +%H:%M:%S)] $*"; } +shot() { screenshot "$SHOTS/fly$STAGE-$1.png" >/dev/null 2>&1; } +px() { convert /tmp/nav-probe.png -format \ + "%[fx:int(255*p{$1}.r)] %[fx:int(255*p{$1}.g)] %[fx:int(255*p{$1}.b)]" info: 2>/dev/null; } + +# A screen is identified by a PATTERN of points, never one pixel — a single +# bright-pixel test once matched a white loading flash and the run navigated a +# menu that was not on screen. +at_menu() { + screenshot /tmp/nav-probe.png >/dev/null 2>&1 || return 1 + read -r r g b < <(px "648,221"); [ -n "${r:-}" ] || return 1 + [ "$r" -gt 230 ] && [ "$g" -gt 230 ] && [ "$b" -gt 230 ] || return 1 + read -r r2 g2 b2 < <(px "560,300"); [ -n "${r2:-}" ] || return 1 + [ "$r2" -lt 120 ] && [ "$b2" -gt "$r2" ] +} +at_title() { + screenshot /tmp/nav-probe.png >/dev/null 2>&1 || return 1 + read -r r g b < <(px "625,618"); [ -n "${g:-}" ] || return 1 + [ "$g" -gt 130 ] && [ $((g - r)) -gt 45 ] && [ $((g - b)) -gt 45 ] +} + +pkill -9 -x xenia_canary 2>/dev/null; sleep 1 +rm -f /dev/shm/xenia_* 2>/dev/null +: > "$XENIA_PAD_FILE" + +say "launching canary for stage $STAGE" +run-canary --audio --apu=sdl --log_mask=13 \ + --logged_profile_slot_0_xuid=E0300000EFBEA3D4 \ + --hid=file --pad_file="$XENIA_PAD_FILE" & +# Xvfb keeps the previous instance's framebuffer until the new one draws. +xsetroot -solid black 2>/dev/null || true + +say "waiting for the main menu" +MENU=0 +for i in $(seq 1 "$BOOT_TIMEOUT"); do + [ "$i" -lt 40 ] && { sleep 1; continue; } + if at_menu && sleep 1 && at_menu; then say "MAIN MENU after ${i}s"; MENU=1; break; fi + at_title && { say " title — tapping A"; pad tap A 0.25; sleep 2; } + sleep 1 +done +[ "$MENU" = 1 ] || { say "TIMEOUT: no main menu"; shot 00-timeout; exit 1; } + +say "poking the cleared-stage mask so every story stage is selectable" +poke w32 0x828F40C0 0x0001FFFE + +# main menu -> EXTRAS -> MISSION SELECT +for _ in 1 2 3 4; do pad dpad down 0.06; sleep 0.35; done +sleep 0.5; pad tap A 0.15; sleep 3; shot 01-extras +pad tap A 0.15; sleep 4; shot 02-missionselect + +# the list starts on Stage01 +say "stepping to Stage$STAGE" +for _ in $(seq 2 "$STAGE"); do pad dpad down 0.06; sleep 0.30; done +sleep 1; shot 03-selected +pad tap A 0.20; sleep 3; shot 04-after-A +pad tap A 0.20; sleep 3; shot 05-after-A2 + +say "waiting for flight (up to 240s) — snapshotting when the guest settles" +for i in $(seq 1 240); do + sleep 1 + [ $((i % 30)) -eq 0 ] && shot "06-wait-$i" +done +shot 07-final +SHM=$(ls /dev/shm/xenia_memory_* 2>/dev/null | head -1) +if [ -n "$SHM" ]; then + OUT="$HOME/snap-stage$STAGE.bin" + cp --sparse=always "$SHM" "$OUT" && say "snapshot -> $OUT ($(du -h "$OUT" | cut -f1) actual)" +fi +say "shots: $SHOTS/fly$STAGE-*.png"