diff --git a/docs/re/captures/loadgame-slot01-headerless-install.png b/docs/re/captures/loadgame-slot01-headerless-install.png new file mode 100644 index 00000000..b8f737bc Binary files /dev/null and b/docs/re/captures/loadgame-slot01-headerless-install.png differ diff --git a/docs/re/captures/stage02-inflight-restored-state.png b/docs/re/captures/stage02-inflight-restored-state.png new file mode 100644 index 00000000..726b296b Binary files /dev/null and b/docs/re/captures/stage02-inflight-restored-state.png differ diff --git a/docs/re/dynamic-re-state-restore.md b/docs/re/dynamic-re-state-restore.md new file mode 100644 index 00000000..ef997bd6 --- /dev/null +++ b/docs/re/dynamic-re-state-restore.md @@ -0,0 +1,135 @@ +# The container's dynamic-RE state is not durable — how to rebuild it + +**Status: ✅ `CONFIRMED` by rebuilding it** (2026-08-23). Everything the dynamic +side of this corpus runs on — the baseline emulator binary, the Xbox profile, the +Stage 02 save, the shader/code caches — lives outside the two git repos and was +**gone** at the start of this session. None of it is recoverable from a git pull, +and `sylph-doctor` reports "all good" without it, so the first symptom is a boot +that dies instantly with `NO PROFILE on disc`. + +This file is the recipe, measured rather than remembered. + +## What was missing, and what it blocks + +| gone | consequence | +|---|---| +| `/sylph-home/re/pure/xenia-canary` (the `auto/upstream-baseline` worktree) and its build | no baseline binary. The instrumented branch stops at the Stage 02 briefing under 384 crash dumps ([`upstream-baseline.md`](upstream-baseline.md)), so **no in-mission work is possible at all** | +| `~/.local/share/Xenia/` — the whole storage root | no profile, no save, no config, no caches | + +`launch_mission.sh` needs all of it: it signs in `$(ls "$XENIA_CONTENT")` and +navigates title → **LOAD GAME → slot 01** → READY ROOM → TAKE OFF. + +## 1. The baseline binary — rebuild it *incrementally*, not from scratch + +The branch itself survives (`auto/upstream-baseline`, tip `c1b57f93b` = upstream +`a5a18f5c7` + the three file-pad commits + the lost-resume fix). What is gone is +only the checkout and the build. + +The cheap move is **not** a new worktree: a fresh build directory means building +all of `third_party` again. Instead check the branch out in the shared +`/work/xenia-canary` and let ninja do an incremental build into the existing +`$XENIA_BUILD_DIR`, because the two branches differ in **202 files and zero +submodule gitlinks**: + +```bash +cd /work/xenia-canary && git worktree prune # the dead worktree still claims the branch +git checkout auto/upstream-baseline +build-canary Release # ~9 min at -j4 here, vs a scratch build +``` + +`git worktree prune` is not optional — without it the checkout fails with +*"'auto/upstream-baseline' is already used by worktree at /sylph-home/re/pure/…"* +for a directory that no longer exists. + +**Keep both binaries**, because switching the branch back costs another build: + +``` +/sylph-home/re/bin/baseline-c1b57f93b/xenia_canary XENIA_BIN=… run-canary +/sylph-home/re/bin/instrumented-a60fe7d11/xenia_canary +``` + +🔴 **Trap, paid for once here: keep the file name `xenia_canary`.** A copy saved +as `xenia_canary.baseline-…` runs fine and then survives every `pkill -x +xenia_canary` in the toolkit — `-x` matches the whole name. The emulator keeps +the `flock` on `/tmp/xenia-canary.lock`, and the next launch fails with +*"an emulator is already running"* while `ps -C xenia_canary` shows nothing. Put +the variant in a **directory** and leave the file name alone. + +**Tell the two apart by what is in them, not by the path**, since `build-canary` +writes both to the same place and [has reported success on a failed +build](upstream-baseline.md): + +```bash +strings -a "$BIN" | grep -c log_ui_draws # instrumented: 5 baseline: 0 +``` + +## 2. The profile — bootstrap it with the *instrumented* binary + +`--create_profile_if_none` is one of our 50 RE commits (`7dbb24e64`) and is **not +on the baseline branch**; the baseline cannot make a profile, and the Sign In +dialog cannot be driven from a script (an ImGui text field, no synthetic key +events reach it). So make the profile once with the instrumented binary and then +run the baseline against it — the content root is emulator-build independent: + +```bash +XENIA_BIN=/sylph-home/re/bin/instrumented-a60fe7d11/xenia_canary \ + run-canary --create_profile_if_none=SylphRE +# wait for: ProfileManager: RE bootstrap profile 'SylphRE' -> created +pkill -x xenia_canary +``` + +XUID is fixed at **`B13EBABEBABEBABE`**, which is what makes it scriptable, and +`launch_mission.sh` picks it up on its own. + +## 3. The Stage 02 save — from the repo, and **without** a `.header` + +`docs/re/captures/savedata-stage02-5pct.bin` is a complete `GDHA` container +(parses clean: `phase GP_BUNK`, slot 0 used, 2026-07-23 20:07). Drop it in at the +path [`structures/savegame-format.md`](structures/savegame-format.md) records: + +```bash +C=~/.local/share/Xenia/content/B13EBABEBABEBABE/535107D4/00000001/game01 +mkdir -p "$C" && cp docs/re/captures/savedata-stage02-5pct.bin "$C/savedata" +``` + +The companion `Headers/00000001/game01.header` is **not** in the repo and is +**not needed** — checked in the source rather than assumed: +`ContentManager::ListContent` (`src/xenia/kernel/xam/content_manager.cc`) +enumerates *directories*, and when `ReadContentHeaderFile` fails it synthesises +the record from the directory name. The slot is listed either way; only the +display string differs (`game01` instead of +`Game01 07/23/2026 21:08 STAGE02 EASY`). So there is no need to fabricate a +328-byte Xbox content header, which would have been a guess with no oracle. + +## Verified end to end — and one new trap in the scripted route + +The restored state was checked by **driving it**, not by inspecting files: + +1. `LOAD GAME` lists the slot (![](captures/loadgame-slot01-headerless-install.png)) + as `01 · 07/23/2026 21:08 · EASY · 000:05:24 · 5 %`, with the Details panel + reading `STAGE02 — Declaration of War`, `At Standby`, `4101 P`. Note what that + settles: **every detail on that screen comes out of `savedata`**, not out of + the Xbox content header that was not installed. +2. → READY ROOM → TAKE OFF → launch cinematic → objective card → + **Stage 02 flight** (![](captures/stage02-inflight-restored-state.png)), + `TIME 00:18.01`, `REMAINING OB 004`, hull 1500 / shield 400. `pilot.py` bound + and flew it (`ENGAGE … tgt=e010_ADAN_Attacker_S`), so the whole live-memory + toolchain works against the restored profile. + +🔴 **A fresh profile inserts an extra dialog, and it desynchronises +`launch_mission.sh`.** After `LOAD GAME → slot 01 → YES` the title puts up +**"Auto-Save is active. OK?"** (YES preselected) before the READY ROOM. The +launcher's press sequence is fixed and has no slot for it, so every later press +lands one screen early — this run ended up sitting in **OPTIONS → GAME SETTINGS**, +and `wait_flight.sh` correctly reported `NEVER REACHED FLIGHT`. The mission above +was reached by driving the menus by hand from there. + +**Checked on the next boot: the dialog does not return** — the auto-save answer +is kept in the profile. The launcher gets further (READY ROOM instead of +OPTIONS) and still misses: it lands on **BRIEFINGS**, i.e. its +`step up; tap A` for TAKE OFF is swallowed by the READY ROOM transition, because +what precedes it is a fixed `sleep 28` rather than a wait for the screen. So +`launch_mission.sh` is **one wait away** from working unattended on restored +state, and both runs here were finished by hand from the screen it stopped on. +That repair is left for whoever needs the launcher, and is noted in +[`BACKLOG.md`](BACKLOG.md); nothing here depends on it.