#!/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. # # 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)"; export SD 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 . "$(dirname "${BASH_SOURCE[0]}")/ensure_single_emulator.sh" ensure_single_emulator || exit 3 "$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 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)" 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