Files
Sylpheed/tools/re-capture/poke_control.sh
Sylpheed RE agent 75ff2878f7 re: the poke control PASSES -- writes reach the guest, hull is authoritative
Hammering settles what a single write could not: hull 0x44BB8000 (1500.0f),
944,387 writes of 1 over 15s, and afterwards the value HELD at 1 -- the game
stopped rewriting it. The screen left 'flight', the HUD is gone, the ship is
burning, and Natalie radios 'I've lost contact with Rhino 3!', the player's own
callsign. The game read the poked value and killed the player.

Established: writes to /dev/shm reach the running guest; hull at pos+0x154 is
authoritative, not a readout; and a single write loses a race against the game's
own continuous writes.

This upgrades two earlier results from inconclusive to genuine negatives. The
unit-record pokes were downgraded because I could not tell 'ignored' from 'never
arrived'. The write arrives -- and those pokes persisted untouched for 60s, so
the game genuinely saw state=4 and handle=0 on all three objective squadrons and
did nothing. That is real evidence the phase-1 condition coroutine is not polling
and its checks run only when a trigger starts them.

Withdrawn: last iteration's claim that the pilot's hull= is a different field or
scale. I read 1000.0f at pos+0x154 and inferred a mismatch with the logged 1500;
this run reads 1500.0f at the same offset. Same field, different value per run.
2026-08-25 17:48:47 +00:00

84 lines
3.8 KiB
Bash
Executable File

#!/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
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
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