diff --git a/docs/re/script-runtime-probe.md b/docs/re/script-runtime-probe.md index 3c7ee79..98a9973 100644 --- a/docs/re/script-runtime-probe.md +++ b/docs/re/script-runtime-probe.md @@ -322,3 +322,44 @@ rather than informative, and the two null results above must be read as **What still stands** from these runs is only what was *observed*, not poked: the arrival transitions, the destruction of ADN111, and the mission-over branch. + +## 🟡 The positive control ran — and is still inconclusive + +`tools/re-capture/poke_control.sh` (self-retrying; **succeeded on attempt 1**, no +freeze) poked the player's hull at `position + 0x154` to `1`, on the theory that +the game visibly reacts to a dead player. + +``` +hull before: 0x447A0000 (= 1000.0f) +poke 0x447A0000 -> 0x00000001 OK +hull after : 0x447A0000 (12 s later -- the game put it back) +screen before: flight screen after: flight +``` + +**What it establishes:** the hull word is **continuously rewritten by the game**, +so a poke there cannot persist — unlike the unit-record fields, which held our +value for 60 s untouched. That asymmetry is itself informative: it separates +fields the game actively maintains from fields nobody is writing. + +**Why it is still not a control.** I looked for a visible reaction and found a +red `WARNING` banner in the after-frame — but the before-frame already shows +`MISSILE ALERT`, i.e. the ship was under attack in both. **The banner is not +attributable to the poke**, and I am not going to count it. A value being +overwritten proves the game writes that address; it does not prove the game +*read* ours. + +**New fact, and a correction:** hull at `pos + 0x154` reads `0x447A0000` — a +**float, 1000.0** — not the `1500` the pilot logs. The pilot's `hull=` is a +different field or scale, and the two should not be conflated. + +### The refinement that would settle it + +Poke in a **tight loop** for several seconds so the value is low whenever the +game samples it, rather than once between two of its own writes. If hull is +authoritative, the ship dies and the screen goes to `GAME OVER` — unambiguous. If +it survives a sustained low hull, the field is a readout and the authoritative +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. diff --git a/tools/re-capture/poke_control.sh b/tools/re-capture/poke_control.sh new file mode 100755 index 0000000..e17480b --- /dev/null +++ b/tools/re-capture/poke_control.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash +# POSITIVE CONTROL for the poke method: does a write to /dev/shm reach the guest? +# +# Two poke experiments produced no observable effect, and without this control +# "the game ignored it" cannot be told from "the write never arrived" +# (script-runtime-probe.md). The stick test only proves the value persisted in +# the shared-memory FILE. +# +# The control has to be something the GAME visibly reacts to. Hull is ideal: +# it lives at `player position + 0x154`, and dropping it to 1 should destroy the +# player -- an unambiguous screen change, no OCR needed. +# +# 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)" +ATTEMPTS="${1:-3}" + +alive_and_moving(){ python3 -c " +import sys; sys.path.insert(0,'$SD') +import frozen; d,_=frozen.frozen(5.0); sys.exit(1 if d else 0)"; } + +for a in $(seq 1 "$ATTEMPTS"); do + echo "=== attempt $a/$ATTEMPTS ($(date +%T))" + pkill -9 -x xenia_canary 2>/dev/null; pkill -9 -f '[p]ilot.py' 2>/dev/null; sleep 2 + rm -f /tmp/xenia-canary.lock + "$SD/launch_mission.sh" fly >/tmp/pc-boot.log 2>&1 || { echo " boot failed"; continue; } + if ! alive_and_moving; then echo " guest already frozen after boot"; continue; fi + + pos="" + for t in 1 2 3; do + python3 "$SD/pad.py" set "rt=1" >/dev/null 2>&1; sleep 4 + python3 "$SD/pad.py" clear >/dev/null 2>&1 + pos=$(timeout 120 python3 "$SD/entities2.py" self 0x130 2>/dev/null \ + | sed -n 's/.*pos va \(0x[0-9a-fA-F]*\).*/\1/p' | head -1) + [ -n "$pos" ] && break + echo " locate attempt $t: no player" + done + [ -n "$pos" ] || { echo " could not locate the player"; continue; } + if ! alive_and_moving; then echo " froze during locate"; continue; fi + + hull=$((pos + 0x154)) + echo " player pos $pos, hull $(printf '0x%X' $hull)" + 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 + 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)" + echo " screen after : $(python3 "$SD/screen_id.py" /tmp/pc-after.png | head -1)" + python3 "$SD/frozen.py" --pair /tmp/pc-before.png /tmp/pc-after.png 2>&1 | tail -1 + echo "CONTROL RAN"; exit 0 +done +echo "CONTROL DID NOT RUN in $ATTEMPTS attempts"; exit 1