re(units): S10 closes the story campaign for real -- 69 units, 7 204 values

roster_target had flagged a contradiction: S10, a STORY stage, still fielded an
unharvested unit, which the "story campaign complete at 68 units" claim did not
account for. S10 was simply never flown. Flying it settles it:
UN_e005_ADAN_ElanTypeQ_Margras is in the snapshot, 144 rows, and the merge takes
the file 68 -> 69 units, 9 393 -> 9 537 rows, 7 115 -> 7 204 defaulted-on-disc
values, with 154 disc cross-checks agreeing and 0 disagreeing. roster_target now
reports S10: 0 missing, and every remaining gap is a CHALLENGE stage
(S24/S25/S27/S28/S29).

A much simpler way to fly a story stage, replacing the save-editing route
(tools/re-capture/fly_stage.sh): poke ONE word -- 0x828F40C0 = 0x0001FFFE marks
stages 1-16 cleared -- and MISSION SELECT will launch any of them. Nothing is
written to disc, so there is no save to back up and restore, and TRAP 1 from the
old recipe (launch_mission.sh silently loading the last-used slot) cannot happen.

Route, learned by screenshotting each step: MISSION SELECT -> pick stage -> A ->
mission briefing (A: Continue) -> READY ROOM -> TAKE OFF -> flight. The READY
ROOM carries an "EXTRA" watermark, which is the static analysis's mission-KIND
= 3 ("EXTRA" config section, docs/re/challenge-mission-gate.md section 4) visible
on screen -- an independent confirmation of that field's meaning.

Also recorded: a snapshot taken at the BRIEFING yields 0 runtime objects. Unit
definitions are instantiated at stage load proper, so the snapshot has to wait
for flight; the briefing screen is too early.
This commit is contained in:
2026-08-13 21:14:21 +00:00
parent aac017dd0d
commit 4cbce6bd21
3 changed files with 234 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

View File

@@ -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,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,UsingChaffRatio,0x234,f32,0.0,defaulted-on-disc,layout-derived
UN_e004_ADAN_ElanPlus_NF,YawDragFactor,0x104,f32,3.0,disc,verified 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_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_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 UN_e006_ADAN_Vindicator_Margras,AA_PitchMinus_Max,0x0c8,f32,0.1745329350233078,disc,verified
Can't render this file because it is too large.

90
tools/re-capture/fly_stage.sh Executable file
View File

@@ -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 <stage 1..16> [boot_timeout_s]
set -u
STAGE="${1:?usage: fly_stage.sh <stage 1..16>}"
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"