diff --git a/docs/re/BACKLOG.md b/docs/re/BACKLOG.md index ce6e45d5..2d1f43e3 100644 --- a/docs/re/BACKLOG.md +++ b/docs/re/BACKLOG.md @@ -720,6 +720,7 @@ search cannot find a *schedule*. 6500 candidates → 32 witnesses at 21/s, and the flags have **zero contradictions** with the loss data (the one loss is unflagged; every flagged sample has no losses). That is the check the three previous versions failed. +* ✅ **ROOT-CAUSED 2026-08-26 — see [`mission-freeze-heap-exhaustion.md`](mission-freeze-heap-exhaustion.md).** The freeze is a **guest 128 MB physical allocation being refused** (`MmAllocatePhysicalMemoryEx`, `parent free 28969/131072 pages` ≈ 113 MB) after which the guest throws a C++ exception and stops; the emulator keeps spinning at 399 % CPU, which is why it reads as a hang. **Independent of the cleared-stage mask poke and of the stage** — a `SYLPH_NO_POKE=1` control on Stage 01 froze with the *identical* free-page count as the poked Stage 02 run. This also refutes `challenge-mission-gate.md` §5.6's "poking only real story ids does not blow the heap". * 🔴 **The freeze is now THE blocker.** Onset across runs: **27, 45, 83, 183, 255 s** (median ~83 s) — usable window is 1–4 min and unpredictable. This run froze at 45 s with 1 loss, useless for arrivals despite a working instrument. diff --git a/docs/re/mission-freeze-heap-exhaustion.md b/docs/re/mission-freeze-heap-exhaustion.md index 0c87bfce..78b726e5 100644 --- a/docs/re/mission-freeze-heap-exhaustion.md +++ b/docs/re/mission-freeze-heap-exhaustion.md @@ -53,14 +53,35 @@ cleared-stage mask blows the heap on a 128 MB request, and concludes: This run poked **`0x0001FFFE`** — the value called safe — and hit the **same 128 MB failure**, with `parent free 28969/131072` against that page's `30633/131072`. -🟡 The two are not identical and the refutation is bounded: there, the failure was -on entering `MISSION SELECT`; here `MISSION SELECT` and the stage list worked, and -the failure came later, during the **take-off load**. So the correct reading is -not "the safe value is unsafe" but that **the poke value is not what decides it** — -both values fail the same 128 MB request, at whichever load happens to arrive when -free pages have drained below it. That page's own open question — *"repeat without -the poke to see whether it fails the same way regardless"* — is now the load-bearing -experiment, and it is still unrun. +### ✅ And the control run settles it: the poke is irrelevant + +That page ends with the right experiment — *"repeat without the poke to see whether +it fails the same way regardless"* — so I ran it. `nav_to_flight.sh` gained +`SYLPH_NO_POKE=1`, which skips the write; only Stage 1 is selectable without it, +so the control also changes stage, which makes the agreement stronger rather than +weaker. + +| run | mask | stage | outcome | +|---|---|---|---| +| poked | `0x0001FFFE` | Stage 02 | frozen; 128 MB request refused | +| **control** | **untouched, reads `0x0`** | **Stage 01** | **frozen; 128 MB request refused** | + +Both logs carry the identical line — not merely the same error, the **same +numbers**: + + requested 134217728 bytes, parent free 28969/131072 pages + +`28969` in both, across two stages and two boots. The control was verified three +ways: the mask read back `0x0` from the live mapping, the log was this run's, and +the liveness test was repeated with the stale process killed so exactly one +emulator was running (three frames, rmse 0.00; an 8 MB slab, 0 bytes changed in +3 s). + +🔴 **So the poke does not cause it, and neither does the stage.** The guest +reproducibly arrives at a 128 MB physical request with ~113 MB free and throws. +The identical free-page count across independent runs says the allocation pattern +up to that point is deterministic — this is not a race and not memory pressure +from the host. ## ⚠️ A frozen game passes the screen classifier diff --git a/tools/re-capture/nav_to_flight.sh b/tools/re-capture/nav_to_flight.sh index e9216c2f..feac5d16 100755 --- a/tools/re-capture/nav_to_flight.sh +++ b/tools/re-capture/nav_to_flight.sh @@ -60,8 +60,15 @@ for i in $(seq 1 "$MENU_TIMEOUT"); do 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 +# SYLPH_NO_POKE=1 skips the mask poke, so a run can test whether the 128 MB +# heap failure in mission-freeze-heap-exhaustion.md depends on it. Only Stage 1 +# is selectable without it. +if [ "${SYLPH_NO_POKE:-0}" = "1" ]; then + say "SYLPH_NO_POKE=1 — NOT poking the cleared-stage mask (Stage 1 only)" +else + say "poking the cleared-stage mask so every story stage is selectable" + poke w32 0x828F40C0 0x0001FFFE +fi # main menu -> EXTRAS -> MISSION SELECT for _ in 1 2 3 4; do pad dpad down 0.06; sleep 0.35; done