re: REMAINING OB found and verified — big-endian u32 at 0xbdb59668
Ported onto the shared harness, the HUD changed from 4 to 8 and the intersection collapsed in a single step: one u32be survivor at 0xbdb59668, with the u16be and u8 hits at 0xbdb5966a and 0xbdb5966b being the low half and low byte of that same word. Verified live rather than asserted. Reading screenshot and memory together three times, the one legible HUD frame showed 012 against mem@0xbdb59668 = 12; the other two frames were unreadable rather than mismatched. This independently rediscovers the address the earliest sessions found by digit-transition hunting, by a completely different method. It also corrects an earlier conclusion. A previous run intersected HUD readings of 4 and then 11, got zero survivors in every encoding, and that was written up as eliminating big-endian u32 for the whole region. This run shows u32be holds the counter, so the refutation was wrong. The likely cause is the input: ob_digits.png has templates for 0 1 2 4 8 only, so values containing other digits are misread rather than rejected, and "11" was probably one of those. A single bad reading poisons an intersection permanently, because it removes the true address and nothing later can restore it. The lesson is that an intersection method needs individually verifiable inputs -- the reader's confidence scores were printed but never gated on. One observation reopens the arrival question in a useful way: the counter increases, 4 then 8 then 12 across about five minutes, measured in memory so not a digit misread. A count of remaining marked targets that rises means targets are being added during the mission. That does not contradict the deployment finding, since the roster is fixed at load, but it does mean the game marks new objective targets as the mission proceeds. Watching this one address across a whole mission is now the obvious next experiment and costs almost nothing.
This commit is contained in:
96
tools/re-capture/ob_hunt2.py
Executable file
96
tools/re-capture/ob_hunt2.py
Executable file
@@ -0,0 +1,96 @@
|
||||
#!/usr/bin/env python3
|
||||
"""REMAINING OB by HUD value, on the shared harness.
|
||||
|
||||
Same idea as ob_by_hud.py -- read the counter off screen, keep heap positions
|
||||
equal to it, intersect across readings -- but the witness, the incremental save
|
||||
and the baseline discard now come from probeharness.Probe rather than being
|
||||
re-implemented (and, twice, forgotten).
|
||||
|
||||
Candidates persist to /tmp/ob_candidates.json after EVERY reading, so a chained
|
||||
attach continues the same intersection on the same mission. Offsets are only
|
||||
valid within one emulator instance; the session clears the file at launch.
|
||||
"""
|
||||
import json, os, subprocess, sys
|
||||
sys.path.insert(0, __file__.rsplit('/', 1)[0])
|
||||
from probeharness import Probe
|
||||
import gmem
|
||||
import numpy as np
|
||||
import ob_read
|
||||
|
||||
SHOT = '/tmp/ob_hud.png'
|
||||
CAND = '/tmp/ob_candidates.json'
|
||||
ENCODINGS = [('u32be', '>u4', 4, 0), ('u32le', '<u4', 4, 0),
|
||||
('u16be', '>u2', 2, 0), ('u16be@1', '>u2', 2, 1),
|
||||
('u16le', '<u2', 2, 0), ('u16le@1', '<u2', 2, 1),
|
||||
('u8', 'u1', 1, 0)]
|
||||
|
||||
def hud():
|
||||
subprocess.run(['screenshot', SHOT], capture_output=True, timeout=60)
|
||||
try:
|
||||
txt, _ = ob_read.read(SHOT)
|
||||
except Exception as e:
|
||||
return None, str(e)
|
||||
t = (txt or '').strip()
|
||||
return (int(t), 'ok') if t.isdigit() else (None, 'unreadable %r' % txt)
|
||||
|
||||
def matches(buf, value):
|
||||
out = {}
|
||||
for name, dt, w, skew in ENCODINGS:
|
||||
if value > (1 << (8 * w)) - 1:
|
||||
continue
|
||||
n = (len(buf) - skew) // w * w
|
||||
a = np.frombuffer(buf[skew:skew + n], dtype=dt)
|
||||
out[name] = (np.nonzero(a == value)[0].astype(np.int64) * w + skew)
|
||||
return out
|
||||
|
||||
def main():
|
||||
secs = int(sys.argv[1]) if len(sys.argv) > 1 else 300
|
||||
every = int(sys.argv[2]) if len(sys.argv) > 2 else 25
|
||||
p = Probe(baseline=116)
|
||||
if not p.ok:
|
||||
print(p.why); return 3
|
||||
print(p.summary(), flush=True)
|
||||
p.log('t\thud\tencoding\tcandidates\tstalled', '/tmp/ob_hunt2.tsv')
|
||||
|
||||
cand, base, seen = None, None, []
|
||||
if os.environ.get('SYLPH_OB_RESUME') == '1' and os.path.exists(CAND):
|
||||
raw = json.load(open(CAND))
|
||||
cand = {k: np.array(v, dtype=np.int64) for k, v in raw['cand'].items()}
|
||||
seen = raw['seen']
|
||||
print('resumed: %s' % {k: len(v) for k, v in cand.items()}, flush=True)
|
||||
|
||||
while True:
|
||||
v, note = hud()
|
||||
buf, base = p.heap()
|
||||
st = p.status()
|
||||
if v is None:
|
||||
print('t=%4ds HUD unreadable (%s)%s' % (p.elapsed, note, st), flush=True)
|
||||
else:
|
||||
hit = matches(buf, v)
|
||||
cand = hit if cand is None else {
|
||||
k: np.intersect1d(cand[k], hit[k], assume_unique=True)
|
||||
for k in cand if k in hit}
|
||||
seen.append(v)
|
||||
json.dump({'cand': {k: v2.tolist() for k, v2 in cand.items()},
|
||||
'seen': seen}, open(CAND, 'w'))
|
||||
desc = ' '.join('%s:%d' % (k, len(cand[k])) for k in cand)
|
||||
print('t=%4ds HUD=%-4d %s%s' % (p.elapsed, v, desc, st), flush=True)
|
||||
for k in cand:
|
||||
p.emit('%d\t%d\t%s\t%d\t%s' % (p.elapsed, v, k, len(cand[k]), st.strip()))
|
||||
if not p.tick(every, secs):
|
||||
break
|
||||
|
||||
print('\n%s' % p.summary())
|
||||
print('HUD values seen: %s' % sorted(set(seen)))
|
||||
if cand:
|
||||
for k in sorted(cand, key=lambda k: len(cand[k])):
|
||||
c = cand[k]
|
||||
print(' %-8s %d' % (k, len(c)))
|
||||
if 0 < len(c) <= 8 and len(set(seen)) >= 2:
|
||||
for off in c:
|
||||
va = gmem.primary_va(base + int(off))
|
||||
print(' va %s' % (('%#010x' % va) if va else '?'))
|
||||
return 0
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
19
tools/re-capture/obhunt2_attach.sh
Executable file
19
tools/re-capture/obhunt2_attach.sh
Executable file
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env bash
|
||||
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)"
|
||||
pgrep -x xenia_canary >/dev/null || { echo "NO EMULATOR"; exit 1; }
|
||||
CFG=/tmp/nav-obhud2.json
|
||||
for try in 1 2 3; do
|
||||
python3 "$SD/pad.py" set "rt=1" >/dev/null 2>&1 || true; sleep 3
|
||||
python3 "$SD/pad.py" clear >/dev/null 2>&1 || true
|
||||
if python3 "$SD/entities2.py" self 0x130 "$CFG" >/dev/null 2>&1; then
|
||||
SYLPH_HUNT=1 SYLPH_KILL_TURRETS=1 SYLPH_KEEPOUT=1400 SYLPH_PREFER=e010 \
|
||||
nohup python3 "$SD/pilot.py" "$CFG" "${1:-400}" </dev/null >/tmp/obhud2-pilot.log 2>&1 &
|
||||
P=$!; echo "--- pilot re-attached"; break
|
||||
fi
|
||||
done
|
||||
SYLPH_OB_RESUME=1 python3 "$SD/ob_hunt2.py" "${1:-400}" "${2:-25}"
|
||||
[ -n "${P:-}" ] && kill "$P" 2>/dev/null
|
||||
echo "OBHUD ATTACH DONE"
|
||||
20
tools/re-capture/obhunt2_session.sh
Executable file
20
tools/re-capture/obhunt2_session.sh
Executable file
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env bash
|
||||
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/launch_mission.sh" fly || { echo "BOOT FAILED"; exit 1; }
|
||||
CFG=/tmp/nav-obhud.json
|
||||
for try in 1 2 3; do
|
||||
python3 "$SD/pad.py" set "rt=1" >/dev/null 2>&1 || true; sleep 3
|
||||
python3 "$SD/pad.py" clear >/dev/null 2>&1 || true
|
||||
if python3 "$SD/entities2.py" self 0x130 "$CFG" >/dev/null 2>&1; then
|
||||
SYLPH_HUNT=1 SYLPH_KILL_TURRETS=1 SYLPH_KEEPOUT=1400 SYLPH_PREFER=e010 \
|
||||
nohup python3 "$SD/pilot.py" "$CFG" "${1:-240}" </dev/null >/tmp/obhud-pilot.log 2>&1 &
|
||||
P=$!; echo "--- pilot flying"; break
|
||||
fi
|
||||
done
|
||||
rm -f /tmp/ob_candidates.json
|
||||
python3 "$SD/ob_hunt2.py" "${1:-240}" "${2:-25}"
|
||||
[ -n "${P:-}" ] && kill "$P" 2>/dev/null
|
||||
echo "OBHUD DONE"
|
||||
Reference in New Issue
Block a user