This repository has been archived on 2026-09-16. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Syplheed-Reborn/tools/re-capture/wave7_probe.py
Sylpheed RE agent ce235e153b re: cheap probe verified; its one "arrival" is flicker
First run of wave7_probe: zero stalled samples across the whole run, against
three consecutive heavy-probe runs that stalled at roughly 27, 83 and 255 s. The
fix works. The guest is also visibly healthier -- 19 losses against 8 in a
heavy-probe run of comparable length -- so starving the emulator had been
suppressing the very activity the probe existed to watch.

It also reported the first arrival of the whole line of work, and that arrival
does not survive inspection. A record went 1 -> 0 at t=229, 0 -> 2 at t=259 and
2 -> 0 at t=274. Two craft appearing and vanishing within fifteen seconds is not
a wave.

The same log contains the giveaway: at t=60 a record read 13 and at t=75 the
same record read 14, an increase, with nothing printed, because the probe only
surfaced decreases. The hull-based liveness read flickers, and a flicker that
straddles zero was indistinguishable from an arrival under the old rule. The
count therefore stands at zero confirmed arrivals in eleven runs.

Two changes, neither yet exercised: every increase is printed rather than only
those from zero, and an increase from zero counts only if it persists across two
consecutive samples, with a candidate that returns to zero discarded as flicker.

Recorded because it was close: under the old rule this run would have been
written up as "first arrival observed", which would have been the
strongest-looking result so far and wrong.
2026-08-24 16:28:59 +00:00

134 lines
5.9 KiB
Python
Executable File

#!/usr/bin/env python3
"""Cheap per-record watch: the heavy rescan was stalling the guest.
A no-probe control ran 300 s clean while probed runs stalled at 27-255 s
(guest-stalls.md), so the 32 MB rescan every 12 s has to go. After one full
enumeration the craft bases and their roster links are known, and a sample only
needs the hull word at each base -- about 1.2 KB instead of 32 MB. A full rescan
runs only every RESCAN seconds to catch anything genuinely new.
"""
import os, sys, time, struct, collections, importlib.util
SD = __file__.rsplit('/', 1)[0]
sys.path.insert(0, SD)
import gmem, gworld, entities2
_w3 = importlib.util.spec_from_file_location('w3', SD + '/wave3_probe.py')
wave3 = importlib.util.module_from_spec(_w3); _w3.loader.exec_module(wave3)
ROSTER_VT = struct.pack('>I', 0x820AF030)
DELTA, WIN, LINK, HULL = 0x130, 0x400, 0x08, 0x154
BASELINE, RESCAN = 116, 90
def scan_vt(fd, size, vt):
out = []
for a, b in gmem.extents(fd, size):
pos = a
while pos < b:
m = min(1 << 24, b - pos)
blob = os.pread(fd, m, pos)
i = blob.find(vt)
while i != -1:
if (pos + i) % 4 == 0: out.append(pos + i)
i = blob.find(vt, i + 1)
pos += m
return sorted(out)
def enumerate_craft(fd, defs, want):
"""Heavy: full heap scan. Returns [(base, unit, roster_off)]."""
lo, hi = gmem.va_to_off(entities2.ENT_VA_LO), gmem.va_to_off(entities2.ENT_VA_HI)
out, pos = [], lo
while pos < hi:
m = min(1 << 24, hi - pos)
blob = os.pread(fd, m, pos)
for k in range(0, len(blob) - 3, 4):
nm = defs.get(blob[k:k+4])
if not nm: continue
base = pos + k - DELTA
head = os.pread(fd, WIN, base)
own = None
for j in range(0, len(head) - 3, 4):
(p,) = struct.unpack_from('>I', head, j)
if p in want: own = want[p]; break
out.append((base, nm, own))
pos += m
return out
def alive(fd, base):
try: return struct.unpack('>f', os.pread(fd, 4, base + HULL))[0] > 0
except Exception: return False
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 15
w = gworld.World(); fd = w.fd
defs = entities2.definitions(w)
if not defs: print('NOT IN A MISSION'); return 2
roster = scan_vt(fd, w.size, ROSTER_VT)
print('roster records: %d (baseline %d)' % (len(roster), BASELINE))
if len(roster) != BASELINE:
print('DISCARD: not the reproduced baseline'); return 3
f = os.fdopen(os.dup(fd), 'rb')
label, want = {}, {}
for o in roster:
va = gmem.primary_va(o)
if va is None: continue
label[o] = wave3.resolve_id(f, w.size, o)[0] or '?'
want[va + LINK] = o
tick = None
lo = gmem.va_to_off(0xBC000000)
a = os.pread(fd, 1 << 20, lo); time.sleep(2.0); b = os.pread(fd, 1 << 20, lo)
for k in range(0, len(a) - 3, 4):
va, vb = struct.unpack_from('>I', a, k)[0], struct.unpack_from('>I', b, k)[0]
if va < vb and 5 < (vb - va) / 2.0 < 1 << 18: tick = lo + k; break
print('tick witness: %s' % (('%#x' % tick) if tick else 'NONE'))
last_tick = struct.unpack('>I', os.pread(fd, 4, tick))[0] if tick else 0
craft = enumerate_craft(fd, defs, want)
print('craft %d, linked %d' % (len(craft), sum(1 for c in craft if c[2])))
prev = collections.Counter(c[2] for c in craft if c[2] and alive(fd, c[0]))
print('t= 0s deployed=%d strengths %s'
% (len(prev), sorted(collections.Counter(prev.values()).items())), flush=True)
t0 = time.time(); last_rescan = t0; arr = los = 0; stalls = 0
pending, confirmed = {}, 0
while time.time() - t0 < secs:
time.sleep(every)
el = round(time.time() - t0)
if time.time() - last_rescan > RESCAN:
craft = enumerate_craft(fd, defs, want); last_rescan = time.time()
cur = collections.Counter(c[2] for c in craft if c[2] and alive(fd, c[0]))
st = ''
if tick:
now = struct.unpack('>I', os.pread(fd, 4, tick))[0]
if now <= last_tick: st = ' *** GUEST STALLED ***'; stalls += 1
last_tick = now
# Report EVERY increase, not just 0 -> n. The first run logged a record
# reading 13 then 14 with no event printed, which is how flicker in the
# hull-based liveness read hides: only decreases were being surfaced, so
# a spurious 0 -> 2 looked like an arrival while 13 -> 14 looked like
# nothing. An arrival must also PERSIST to count.
a_ = [(o, prev.get(o, 0), cur[o]) for o in cur if cur[o] > prev.get(o, 0)]
l_ = [(o, prev[o], cur.get(o, 0)) for o in prev if cur.get(o, 0) < prev[o]]
for o, x, y in a_:
if x == 0: pending[o] = pending.get(o, 0) + 1
for o in list(pending):
if cur.get(o, 0) == 0: pending.pop(o, None) # vanished: flicker
elif pending[o] == 2:
confirmed += 1
print(' *** CONFIRMED ARRIVAL %-26s now %d (persisted 2 samples)'
% (label.get(o, '?'), cur[o]), flush=True)
pending[o] = 3
arr += sum(1 for x in a_ if x[1] == 0); los += len(l_)
print('t=%4ds deployed=%d up=%d down=%d (cum up %d / down %d, confirmed %d)%s'
% (el, len(cur), len(a_), len(l_), arr, los, confirmed, st), flush=True)
for o, x, y in a_:
print(' up %-30s %d -> %d%s' % (label.get(o, '?'), x, y,
' <- candidate arrival' if x == 0 else ''), flush=True)
for o, x, y in l_: print(' loss %-30s %d -> %d' % (label.get(o, '?'), x, y), flush=True)
prev = cur
print('\nTOTAL candidate-up=%d down=%d CONFIRMED arrivals=%d stalled samples=%d'
% (arr, los, confirmed, stalls))
return 0
if __name__ == '__main__':
sys.exit(main())