Files
Sylpheed/tools/re-capture/early_probe.py
Sylpheed RE agent cb6e0fe7f9 re: sample from the first moment — still flat, and the wait variant caught the ready room
early_probe defers everything expensive: no witness calibration, no per-record
labelling, and the heap scan uses bytes.find. Setup now completes in 0.5 to 0.8
seconds instead of about 25, so the first sample lands essentially at flight
detection.

It is still flat. Deployed reads 41 at flight+0.8 s and every sample after, with
the only change in 252 s being 41 to 40 when one squadron was wiped out. No
climb at any point. Under the frames reading that is expected, since the whole
phase-1 schedule would be finished within eight seconds of mission start.

The attempt to get ahead of flight detection did not work, and the reason is
worth recording. A second run started the probe before the launch and waited for
the roster to appear. It appeared with 116 records, but the numbers were
deployed 39 and craft 276, flat for the whole window. That is the ready room:
the roster is built before take-off, so waiting for it catches the pre-flight
scene, and the probe's window expired around the time flight actually began.
Waiting for the roster is not the same as catching mission start, and the test
as designed does not do what it claims.

The two runs together do suggest something, held at 🟡 because they are
different runs. The ready room shows 39 deployed and 276 craft while flight
shows 41 and 300, so two records and twenty-four craft appear between them. That
points at deployment being a single step at take-off rather than a schedule
unfolding during the mission.

Next is one run of about 400 s with the probe waiting for the roster, long
enough to span ready room, take-off and flight in a single continuous series,
which would show the 39 to 41 step directly or refute it.
2026-08-24 20:28:15 +00:00

108 lines
4.4 KiB
Python
Executable File

#!/usr/bin/env python3
"""Does `deployed` climb in the first seconds of flight?
If the route table's times are FRAMES at 30 Hz, Stage 02's phase-1 entries at
t = 90/120/170/210/240 are 3-8 seconds, so every arrival happens before the
normal probe's first sample (~25 s in, after enumeration and calibration). This
samples from the very first moment instead, with the expensive setup deferred:
* no witness calibration up front -- it costs two 32 MB reads;
* no per-record labelling -- names are not needed to count deployed records;
* `deployed` = distinct roster records referenced by a live craft, which needs
one heap scan (bytes.find, ~seconds) plus ~300 small preads.
Frames reading -> deployed climbs during the first ~10 s, then is flat.
Seconds reading -> deployed is flat early and climbs at 164 s+ of wall-clock.
"""
import os, sys, time, struct, collections
sys.path.insert(0, __file__.rsplit('/', 1)[0])
import gmem, gworld, entities2
ROSTER_VT = struct.pack('>I', 0x820AF030)
DELTA, WIN, LINK, HULL = 0x130, 0x400, 0x08, 0x154
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 deployed(fd, defs, want):
lo, hi = gmem.va_to_off(entities2.ENT_VA_LO), gmem.va_to_off(entities2.ENT_VA_HI)
per = collections.Counter(); tot = 0; pos = lo
while pos < hi:
m = min(1 << 24, hi - pos)
blob = os.pread(fd, m, pos)
for needle in defs:
i = blob.find(needle)
while i != -1:
if (pos + i) % 4 == 0:
base = pos + i - DELTA
tot += 1
try:
alive = struct.unpack('>f', os.pread(fd, 4, base + HULL))[0] > 0
except Exception:
alive = False
if alive:
head = os.pread(fd, WIN, base)
for j in range(0, len(head) - 3, 4):
(p,) = struct.unpack_from('>I', head, j)
if p in want: per[want[p]] += 1; break
i = blob.find(needle, i + 1)
pos += m
return len(per), tot, per
def main():
args = [a for a in sys.argv[1:] if not a.startswith('--')]
fast_for = int(args[0]) if len(args) > 0 else 60
total = int(args[1]) if len(args) > 1 else 240
wait = '--wait' in sys.argv
w = gworld.World(); fd = w.fd
if wait:
# Flight DETECTION lags the mission start, so even a sample at
# flight+0.8 s may be several seconds of game time late -- and under the
# frames reading the entire phase-1 schedule is 8 s long. Start before
# the mission exists and poll for the roster to appear instead.
print('waiting for a mission to load...', flush=True)
while True:
r = scan_vt(fd, w.size, ROSTER_VT)
if len(r) >= 30:
print('roster appeared: %d records' % len(r), flush=True); break
time.sleep(0.5)
t0 = time.time()
defs = entities2.definitions(w)
roster = scan_vt(fd, w.size, ROSTER_VT)
want = {}
for o in roster:
va = gmem.primary_va(o)
if va is not None: want[va + LINK] = o
print('setup done at +%.1fs: %d roster records, %d definitions'
% (time.time() - t0, len(roster), len(defs)), flush=True)
if not roster or not defs:
print('NOT IN A MISSION'); return 2
series = []
while time.time() - t0 < total:
el = time.time() - t0
d, tot, per = deployed(fd, defs, want)
series.append((round(el, 1), d, tot))
print(' +%6.1fs deployed=%3d craft=%3d strengths %s'
% (el, d, tot, sorted(collections.Counter(per.values()).items())), flush=True)
time.sleep(1 if time.time() - t0 < fast_for else 15)
d0 = series[0][1]; dmax = max(s[1] for s in series)
print('\nfirst deployed=%d max deployed=%d climb=%d' % (d0, dmax, dmax - d0))
print('verdict: %s' % ('DEPLOYED CLIMBED -- consistent with arrivals happening'
if dmax > d0 else
'flat -- no arrival observed in this window'))
return 0
if __name__ == '__main__':
sys.exit(main())