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:
90
tools/re-capture/fly_stage.sh
Executable file
90
tools/re-capture/fly_stage.sh
Executable 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"
|
||||
Reference in New Issue
Block a user