tools: sample a candidate REMAINING OB address against the HUD in one run
ob_sample.py pairs a live read of a guest VA with a screenshot and a crop of the HUD counter, and reads the word again AFTER the shot so a sample whose two reads disagree can be thrown away instead of believed -- that race is what left the first three-snapshot filter with zero survivors. ob_session.sh is fly_session.sh plus that sampler, because the evidence this needs is a transition, and an unattended craft is dead in about a minute. Its header records that launch_mission.sh does not yet finish unattended on restored state, so nobody discovers that halfway through a boot. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE
This commit is contained in:
79
tools/re-capture/ob_sample.py
Executable file
79
tools/re-capture/ob_sample.py
Executable file
@@ -0,0 +1,79 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Sample a candidate `REMAINING OB` address paired with a HUD screenshot.
|
||||
|
||||
`structures/mission-objective-counter.md` found the counter at a guest VA in one
|
||||
Stage 02 run and left cross-run stability untested. Testing it needs the RAM word
|
||||
and the HUD digits from the SAME moment, which is the trap that already cost one
|
||||
pass: the value moves between the memory read and the screenshot that is supposed
|
||||
to confirm it. So every sample reads the word, takes the shot, and reads the word
|
||||
AGAIN — a row whose two reads disagree is thrown away rather than believed.
|
||||
|
||||
The HUD counter is cropped out of each shot (the region is fixed: the plate sits
|
||||
in the top-right, x >= 0.72 W, 0.32 H .. 0.45 H) so the digits can be read without
|
||||
squinting at a 1280x720 frame.
|
||||
|
||||
Usage: ob_sample.py <va> <out.csv> <shot-dir> <tag> [interval_s] [count]
|
||||
"""
|
||||
import os
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
import gmem # noqa: E402
|
||||
|
||||
CROP = (0.72, 0.32, 1.0, 0.45) # HUD "REMAINING OB" plate, fractions of W/H
|
||||
|
||||
|
||||
def read_word(path, off):
|
||||
try:
|
||||
with open(path, "rb") as f:
|
||||
f.seek(off)
|
||||
b = f.read(4)
|
||||
return struct.unpack(">I", b)[0] if len(b) == 4 else None
|
||||
except OSError:
|
||||
return None
|
||||
|
||||
|
||||
def crop_hud(shot, out):
|
||||
try:
|
||||
from PIL import Image
|
||||
except ImportError:
|
||||
return False
|
||||
im = Image.open(shot)
|
||||
w, h = im.size
|
||||
c = im.crop((int(CROP[0] * w), int(CROP[1] * h), int(CROP[2] * w), int(CROP[3] * h)))
|
||||
c.resize((c.width * 2, c.height * 2)).save(out)
|
||||
return True
|
||||
|
||||
|
||||
def main():
|
||||
va = int(sys.argv[1], 0)
|
||||
out_csv, shot_dir, tag = sys.argv[2], sys.argv[3], sys.argv[4]
|
||||
interval = float(sys.argv[5]) if len(sys.argv) > 5 else 30.0
|
||||
count = int(sys.argv[6]) if len(sys.argv) > 6 else 8
|
||||
|
||||
off = gmem.va_to_off(va)
|
||||
mem = gmem.mem_path()
|
||||
os.makedirs(shot_dir, exist_ok=True)
|
||||
t0 = time.time()
|
||||
with open(out_csv, "w") as f:
|
||||
f.write("t_s,ram_before,ram_after,stable,shot\n")
|
||||
for i in range(count):
|
||||
before = read_word(mem, off)
|
||||
shot = os.path.join(shot_dir, f"{tag}-{i:02d}.png")
|
||||
subprocess.run(["screenshot", shot], stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL)
|
||||
after = read_word(mem, off)
|
||||
crop_hud(shot, os.path.join(shot_dir, f"{tag}-{i:02d}-ob.png"))
|
||||
row = (f"{time.time()-t0:.1f},{before},{after},"
|
||||
f"{int(before is not None and before == after)},{shot}")
|
||||
f.write(row + "\n")
|
||||
f.flush()
|
||||
print(row, flush=True)
|
||||
time.sleep(max(0.0, interval - (time.time() - t0) % interval))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
45
tools/re-capture/ob_session.sh
Executable file
45
tools/re-capture/ob_session.sh
Executable file
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env bash
|
||||
# Boot -> Stage 02 flight -> fly the survival pilot, sampling the candidate
|
||||
# REMAINING OB address against the HUD the whole time.
|
||||
#
|
||||
# fly_session.sh with one addition: ob_sample.py runs alongside the pilot, so
|
||||
# the RAM word and the HUD digits come from the same run rather than from two.
|
||||
# The pilot is not optional decoration -- an unattended craft is dead in about a
|
||||
# minute (upstream-baseline.md), and a dead craft produces no transitions, which
|
||||
# is exactly the evidence this test needs.
|
||||
#
|
||||
# NOTE (2026-08-23): launch_mission.sh does not currently finish unattended on a
|
||||
# freshly restored profile -- it reaches the READY ROOM and stops on BRIEFINGS
|
||||
# (docs/re/dynamic-re-state-restore.md). Until that fixed `sleep 28` becomes a
|
||||
# wait for the screen, this script inherits the same stop and the sampler never
|
||||
# starts; drive the last two presses by hand, or fix the launcher first.
|
||||
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)"
|
||||
SECS="${1:-240}"
|
||||
TAG="${2:-ob}"
|
||||
VA="${OB_VA:-0xbdb59668}"
|
||||
SHOTS=/sylph-home/re/shots/$TAG
|
||||
CFG=/tmp/nav-live.json
|
||||
|
||||
"$SD/launch_mission.sh" fly || { echo "BOOT FAILED"; exit 1; }
|
||||
mkdir -p "$SHOTS"
|
||||
|
||||
# Sample immediately -- the first reading is the cross-run test and must not
|
||||
# depend on the pilot binding, which can fail on its own.
|
||||
python3 "$SD/ob_sample.py" "$VA" "/tmp/$TAG-ob.csv" "$SHOTS" "$TAG" 25 \
|
||||
"$(python3 -c "import math;print(max(2,math.ceil($SECS/25)))")" &
|
||||
SAMPLER=$!
|
||||
|
||||
if python3 "$SD/entities2.py" self 0x130 "$CFG"; then
|
||||
echo "--- config: $(cat "$CFG")"
|
||||
python3 "$SD/pilot.py" "$CFG" "$SECS" || echo "PILOT EXITED $?"
|
||||
else
|
||||
echo "BIND FAILED -- sampling an unattended craft, expect a short run"
|
||||
fi
|
||||
|
||||
wait $SAMPLER 2>/dev/null
|
||||
echo "--- samples: /tmp/$TAG-ob.csv"
|
||||
cat "/tmp/$TAG-ob.csv"
|
||||
echo "SESSION DONE"
|
||||
Reference in New Issue
Block a user