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