diff --git a/docs/re/captures/poke-control-kill.png b/docs/re/captures/poke-control-kill.png new file mode 100644 index 0000000..e7a4cb9 Binary files /dev/null and b/docs/re/captures/poke-control-kill.png differ diff --git a/docs/re/script-runtime-probe.md b/docs/re/script-runtime-probe.md index 98a9973..c14520d 100644 --- a/docs/re/script-runtime-probe.md +++ b/docs/re/script-runtime-probe.md @@ -363,3 +363,47 @@ copy is elsewhere. Either answer is worth having. ✅ **The self-retrying harness works** and is the reusable part of this iteration: boot → verify animating → locate → act, with a freeze at any step costing one retry instead of a whole iteration. + +## ✅ THE CONTROL PASSES — pokes reach the guest, and hull is authoritative + +Hammering the write instead of doing it once settles it: + +``` +hull before: 0x44BB8000 (= 1500.0f) +hammered hull=1 for 15 s -- 944,387 writes +hull after : 0x00000001 (the game STOPPED rewriting it) +screen: flight -> other +``` + +The after-frame (`captures/poke-control-kill.png`) shows the **flight HUD gone**, +the ship trailing fire, and a radio line: **"I've lost contact with Rhino 3!"** — +Rhino 3 being the player's own callsign. **The game read the poked value and +killed the player.** + +So, established: + +* **Writes to `/dev/shm/xenia_memory_*` do reach the running guest.** The + plumbing works. +* **Hull at `player position + 0x154` is authoritative**, not a readout. +* **A single write loses a race** — the game rewrites hull continuously, so one + poke lands between two of its own writes. Hammering wins; 15 s was ample. + +### ✅ This upgrades two earlier "inconclusive" results to genuine negatives + +The poke experiments on the unit records were downgraded to inconclusive because +I could not tell "the game ignored it" from "the write never arrived". **The +write arrives.** And those pokes *persisted untouched for 60 s* — nothing +overwrote them — so the game genuinely saw `state = 4` and `handle = 0` on all +three objective squadrons and **did nothing**. + +That is now real evidence for the standing explanation: the phase-1 condition +coroutine **is not polling** during ordinary flight, and the polls at `0xF524` +run only when a trigger starts them. + +### 🔴 Withdrawn: "the pilot's `hull=` is a different field" + +Last iteration I read `0x447A0000` (1000.0f) at `pos + 0x154` and concluded it +could not be the `1500` the pilot logs. **Wrong.** This run reads +`0x44BB8000` = **1500.0f** at the same offset. It is the same field; the value +simply differs between runs (craft or loadout). The "different field or scale" +note is retracted. diff --git a/tools/re-capture/poke_control.sh b/tools/re-capture/poke_control.sh index e17480b..dbf0094 100755 --- a/tools/re-capture/poke_control.sh +++ b/tools/re-capture/poke_control.sh @@ -10,13 +10,24 @@ # it lives at `player position + 0x154`, and dropping it to 1 should destroy the # player -- an unambiguous screen change, no OCR needed. # +# A SINGLE write is not enough: measured 2026-08-25, the game rewrote hull from +# 1 back to 1000.0f within 12 s, so one poke lands between two of the game's own +# writes and is gone before anything samples it. This HAMMERS the value in a +# tight loop for HAMMER_S seconds so it is low whenever the game looks. +# +# ship dies / GAME OVER -> the guest reads our writes, and hull is +# authoritative. The poke method is validated. +# nothing happens -> hull is a readout and the real copy is elsewhere; +# the method is still unproven, but that is a fact +# about the field rather than about the plumbing. +# # Self-retrying, because roughly two runs in three freeze and a freeze was # costing a whole iteration. Each attempt: boot, check the guest is animating, # locate the player, poke, look. A freeze at any step costs a retry. set -u export HOME=/sylph-home/re SDL_AUDIODRIVER=dummy DISPLAY=:98 export PYTHONPATH=/sylph-home/.local/lib/python3.12/site-packages -SD="$(cd "$(dirname "$0")" && pwd)" +SD="$(cd "$(dirname "$0")" && pwd)"; export SD ATTEMPTS="${1:-3}" alive_and_moving(){ python3 -c " @@ -47,8 +58,21 @@ for a in $(seq 1 "$ATTEMPTS"); do before=$(python3 "$SD/gpoke.py" r32 "$hull" 1 2>/dev/null | tail -1) echo " hull before: $before" screenshot /tmp/pc-before.png >/dev/null 2>&1 - python3 "$SD/gpoke.py" w32 "$hull" 1 2>&1 | tail -2 - sleep 12 + HAMMER_S="${HAMMER_S:-15}" + echo " hammering hull=1 for ${HAMMER_S}s..." + python3 - "$hull" "$HAMMER_S" <<'PY' +import os, struct, sys, time +sys.path.insert(0, os.environ.get('SD', '.')) +import gmem +va = int(sys.argv[1]); secs = float(sys.argv[2]) +off = gmem.va_to_off(va) +n = 0 +with open(gmem.mem_path(), 'r+b', buffering=0) as f: + t0 = time.time() + while time.time() - t0 < secs: + f.seek(off); f.write(struct.pack('>I', 1)); n += 1 +print(' wrote hull=1 %d times' % n) +PY screenshot /tmp/pc-after.png >/dev/null 2>&1 echo " hull after : $(python3 "$SD/gpoke.py" r32 "$hull" 1 2>/dev/null | tail -1)" echo " screen before: $(python3 "$SD/screen_id.py" /tmp/pc-before.png | head -1)"