Files
Sylpheed/tools/re-capture/ob_watch.py
Sylpheed RE agent 70b1e453a7 re: OB address is run-dependent; watcher hunts it, HUD reader gated on confidence
ob_watch.py verifies the address against a confidence-gated HUD reading before
reporting anything, and two consecutive fresh launches settle the question:
0xbdb59668 held 3165285888 against a HUD of 4 on one, and exactly 4 on the next.
The address is not stable across launches, the old note that it recurs in about
five runs of seven was right, and the gate did its job by refusing to report a
series from an address that did not describe that run. The watcher now hunts the
address on the current run when confirmation fails, using the same intersection
method, so it no longer depends on a lucky launch.

The HUD reader is also gated now. ob_read returns a best and second score per
digit and those were printed but never checked, which is how one misread
poisoned an intersection and produced a wrong refutation of big-endian u32. A
reading is accepted only if every digit scores at least 0.80 with a margin of at
least 0.05, the rule ob_read's own docstring states.

The measurement itself is a negative. With a clean witness, zero stalled samples
of fifty, OB held at 4 for 250 seconds while the pilot targeted e010 for 1964
ticks and fired on 1635 of them. Constant fire at the marked attackers and not
one decrement, so it destroyed none, which matches the roughly two marked kills
per five minutes measured earlier. The fire rate itself rose from 4.6 % of ticks
in an earlier diagnosis to 83 % here without producing more kills.

Recorded as unreproduced rather than explained away: the run that found the
address saw the counter rise 4, 8, 12 over five minutes, and that reading was
confirmed against the HUD. This run was flat over a comparable window. Both
observations are sound and they disagree, so the rise is not a stable property
of the mission's first five minutes and presumably depends on progress this run
never reached.
2026-08-25 06:02:33 +00:00

120 lines
4.6 KiB
Python
Executable File

#!/usr/bin/env python3
"""Watch REMAINING OB (big-endian u32 at 0xbdb59668) across a mission.
The address is confirmed (remaining-ob-hunt.md), so this costs one 4-byte read
per sample instead of a 32 MB scan -- cheap enough to sample often and run long
without provoking the freezes heavy probes cause.
Two things this does that earlier probes did not:
* **verify the address before trusting it.** It was once recorded as
run-dependent, so the watcher requires at least one confident HUD reading to
agree with memory before it reports anything.
* **gate the HUD reader on confidence.** ob_read returns (best, second) per
digit and those scores were printed but never checked; one misread poisoned
an entire intersection. Accept a reading only if every digit scores >= 0.80
with a >= 0.05 margin -- the rule ob_read's own docstring states.
"""
import os, struct, subprocess, sys, time
sys.path.insert(0, __file__.rsplit('/', 1)[0])
from probeharness import Probe
import gmem, ob_read
OB_VA = 0xBDB59668
SHOT = '/tmp/ob_watch.png'
FLOOR, MARGIN = 0.80, 0.05
def hud_confident():
subprocess.run(['screenshot', SHOT], capture_output=True, timeout=60)
try:
txt, scores = ob_read.read(SHOT)
except Exception:
return None
t = (txt or '').strip()
if not t.isdigit() or not scores:
return None
for s in scores:
best, second = (s if isinstance(s, (tuple, list)) else (s, 0.0))[:2]
if best < FLOOR or (best - second) < MARGIN:
return None
return int(t)
def main():
secs = int(sys.argv[1]) if len(sys.argv) > 1 else 400
every = int(sys.argv[2]) if len(sys.argv) > 2 else 5
p = Probe(baseline=116)
if not p.ok:
print(p.why); return 3
print(p.summary(), flush=True)
off = gmem.va_to_off(OB_VA)
if off is None:
print('OB VA does not map'); return 4
read = lambda: struct.unpack('>I', os.pread(p.fd, 4, off))[0]
# confirm the address on this run before reporting anything from it
ok = False
for _ in range(6):
h = hud_confident()
m = read()
if h is not None:
print('confirm: HUD=%d mem=%d %s' % (h, m, 'MATCH' if h == m else 'MISMATCH'),
flush=True)
if h == m:
ok = True
break
time.sleep(5)
if not ok:
# The address is run-dependent -- 0xbdb59668 held 3165285888 on a fresh
# launch while the HUD showed 4. So find it on THIS run before watching:
# intersect heap positions equal to the confidently-read HUD value until
# one survives. Same method as ob_hunt2, inline, so the watcher is
# self-sufficient instead of depending on a lucky address.
import numpy as np
print('address not valid this run -- hunting it', flush=True)
cand = None
while p.tick(every=20, secs=secs):
h = hud_confident()
if h is None:
print('t=%4ds HUD not confidently readable%s' % (p.elapsed, p.status()),
flush=True)
continue
buf, hbase = p.heap()
a = np.frombuffer(buf, dtype='>u4')
hit = np.nonzero(a == h)[0].astype(np.int64) * 4
cand = hit if cand is None else np.intersect1d(cand, hit, assume_unique=True)
print('t=%4ds HUD=%-4d candidates=%d%s'
% (p.elapsed, h, len(cand), p.status()), flush=True)
if len(cand) == 1:
off = hbase + int(cand[0])
va = gmem.primary_va(off)
print('FOUND this run: va %s' % (('%#010x' % va) if va else '?'), flush=True)
read = lambda: struct.unpack('>I', os.pread(p.fd, 4, off))[0]
ok = True
break
if len(cand) == 0:
print('intersection empty -- a HUD reading disagreed with every '
'candidate; restarting the hunt', flush=True)
cand = None
if not ok:
print('ADDRESS NOT FOUND on this run -- not reporting a series it '
'might not describe.')
return 5
p.log('t\tob\tstalled', '/tmp/ob_watch.tsv')
prev = read()
print('t= 0s OB=%d' % prev, flush=True)
p.emit('0\t%d\t' % prev)
while p.tick(every, secs):
v = read()
if v != prev:
print('t=%4ds OB %d -> %d (%+d)%s'
% (p.elapsed, prev, v, v - prev, p.status()), flush=True)
prev = v
p.emit('%d\t%d\t%s' % (p.elapsed, v, p.status().strip()))
print('\n%s' % p.summary())
print('final OB=%d' % prev)
return 0
if __name__ == '__main__':
sys.exit(main())