re: kill-free HUD route for REMAINING OB; big-endian u32 assumption refuted

The correlation route is gated on marked-fighter kills, which the pilot manages
at about two per five minutes. ob_read.py already reads the counter off the
screen, so ob_by_hud.py matches the displayed value against memory directly and
needs no kills at all: screenshot, read the digits, keep heap words equal to that
value, intersect across readings.

Four readings at value 4 narrowed 6156 candidates to 4312, the expected slow
drift. Then the HUD read 11 and the intersection collapsed to zero. A word
holding this counter must equal 4 at the first four samples and 11 at the last,
and none does, so within the entity heap read as big-endian u32 the counter does
not exist. It may be u16, u8, little-endian, or outside that region. Both
previous hunts assumed big-endian u32 there, so this eliminates the assumption
rather than merely failing to find anything.

The displayed value also went up, from 4 to 11 over about 340 seconds. A pure
countdown of remaining marked targets should not rise, and the deployment work
says phase 1 gains no new participants. Three readings are possible and none is
tested: the cell being read is not REMAINING OB, the digits are misread, or the
counter genuinely counts something that can increase. The two clean readings
scored 0.95 to 0.98 against their templates, but 4 and 11 use only digits that
are in the strip, which is exactly the selection effect that would hide a wrong
reading -- the template set covers 0 1 2 4 8 only, and most samples came back
unreadable.

Next is widening the scan to u16 and u8 and to little-endian, and beyond the
entity heap, which is a change to one function and costs no combat. Extending
ob_digits.png with the missing digits would also raise the sample yield, since
only two of eleven readings in a 480 s run were usable.
This commit is contained in:
Sylpheed RE agent
2026-08-24 22:58:55 +00:00
parent 7503066a91
commit 6fc082ca55
5 changed files with 179 additions and 0 deletions

71
tools/re-capture/ob_by_hud.py Executable file
View File

@@ -0,0 +1,71 @@
#!/usr/bin/env python3
"""Locate REMAINING OB from the HUD value instead of from kill events.
The correlation route works but needs marked-fighter kills, and the pilot gets
about two per five minutes (remaining-ob-hunt.md). The HUD already shows the
number, and ob_read.py already reads it, so match the displayed value against
memory directly -- no kills required.
Each reading intersects: keep heap words equal to the value the HUD shows at
that moment. A second reading at a DIFFERENT value collapses the set hard; even
repeated readings at the same value help, since unrelated words drift.
"""
import os, sys, time, subprocess, collections
sys.path.insert(0, __file__.rsplit('/', 1)[0])
import gmem, gworld
import numpy as np
import ob_read
LO, HI = 0xBD000000, 0xBE000000
SHOT = '/tmp/ob_hud.png'
def region(fd):
lo, hi = gmem.va_to_off(LO), gmem.va_to_off(HI)
out, pos = bytearray(), lo
while pos < hi:
n = min(1 << 24, hi - pos); out += os.pread(fd, n, pos); pos += n
return np.frombuffer(bytes(out), dtype='>u4'), lo
def hud_value():
subprocess.run(['screenshot', SHOT], capture_output=True, timeout=60)
try:
txt, scores = ob_read.read(SHOT)
except Exception as e:
return None, str(e)
t = (txt or '').strip()
if not t.isdigit(): return None, 'unreadable %r' % txt
return int(t), 'scores %s' % (scores,) # ob_read returns tuples, not floats
def main():
secs = int(sys.argv[1]) if len(sys.argv) > 1 else 240
every = int(sys.argv[2]) if len(sys.argv) > 2 else 25
w = gworld.World(); fd = w.fd
cand = None; base = None; seen = []
t0 = time.time()
while time.time() - t0 < secs:
v, note = hud_value()
el = round(time.time() - t0)
if v is None:
print(' t=%4ds HUD unreadable (%s)' % (el, note), flush=True)
else:
r, base = region(fd)
hit = set(np.nonzero(r == v)[0].tolist())
cand = hit if cand is None else (cand & hit)
seen.append(v)
print(' t=%4ds HUD=%-4d words==%d: %-8d -> candidates %d (%s)'
% (el, v, v, len(hit), len(cand), note), flush=True)
if len(cand) <= 40 and len(set(seen)) >= 2:
break
time.sleep(every)
print('\nHUD values seen: %s' % sorted(set(seen)))
if cand and base is not None:
print('candidates: %d' % len(cand))
for i in sorted(cand)[:20]:
va = gmem.primary_va(base + i * 4)
print(' va %s' % (('%#010x' % va) if va else '?'))
else:
print('no candidates (HUD never read, or value never matched a u32)')
return 0
if __name__ == '__main__':
sys.exit(main())

View 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
python3 "$SD/ob_by_hud.py" "${1:-400}" "${2:-25}"
[ -n "${P:-}" ] && kill "$P" 2>/dev/null
echo "OBHUD ATTACH DONE"

View 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)"
"$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
python3 "$SD/ob_by_hud.py" "${1:-240}" "${2:-25}"
[ -n "${P:-}" ] && kill "$P" 2>/dev/null
echo "OBHUD DONE"