The link from the previous iteration gives a per-record live strength, and the measurement is internally sound: 11 records at 2, 2 at 8 and 11 at 12 sums to exactly the 170 craft counted, with 24 of 42 records deployed. It does not reproduce. The previous run saw 116 roster records and 300 craft with strengths 2/4/8/18; this one sees 42 and 170 with 2/8/12 -- same disc, same save slot, same launch script, same stage. Two explanations were checked and both fail. The save has not drifted: game01/savedata is unmodified since 2026-08-23 and only the profile .gpd files were written today. The guest was not frozen: the pilot's telemetry over the same 190 s shows speed varying across dozens of values and a live engagement with an e007 Turret at 259-680 m. So the discrepancy is real and unexplained, and until it is understood per-record counts cannot be used as a time series. Recorded as the blocker rather than worked around. The same within-run-only discipline already noted for the global craft count now applies one level down. Also recorded: zero arrivals and zero losses across 190 s. That is weak evidence against clock-driven arrivals at t = 90/120/170 seconds, since a 0 -> n transition should have appeared and none did. It is no test of the event-gated model at all, because nothing was killed -- fc=0, no record lost strength, and the player's hull never moved off 1500. The pilot closes to 259 m and misses, so the gap is accuracy rather than engagement.
105 lines
4.0 KiB
Python
Executable File
105 lines
4.0 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Per-record craft counts over a mission: catch arrivals and kills by squadron.
|
|
|
|
The link is a pointer at roster_base + 0x08, verified 300/300 by independent
|
|
unit-type agreement (roster-to-craft-link.md). So a roster member's live
|
|
strength is simply how many craft point at it:
|
|
|
|
0 -> n the member deployed (an ARRIVAL)
|
|
n -> n-1 one of its craft died (a KILL)
|
|
|
|
Both are attributable to a UnitGroup member, which is what ties an observation
|
|
back to Route_S<NN>.tbl. This watches those counts instead of any global total.
|
|
"""
|
|
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 = 0x130, 0x400, 0x08
|
|
|
|
def scan_vt(fd, size, vt):
|
|
out = []
|
|
for a, b in gmem.extents(fd, size):
|
|
pos = a
|
|
while pos < b:
|
|
n = min(1 << 24, b - pos)
|
|
blob = os.pread(fd, n, pos)
|
|
i = blob.find(vt)
|
|
while i != -1:
|
|
if (pos + i) % 4 == 0: out.append(pos + i)
|
|
i = blob.find(vt, i + 1)
|
|
pos += n
|
|
return sorted(out)
|
|
|
|
def counts(fd, defs, want):
|
|
"""craft per roster VA, this instant"""
|
|
lo, hi = gmem.va_to_off(entities2.ENT_VA_LO), gmem.va_to_off(entities2.ENT_VA_HI)
|
|
per = collections.Counter(); pos = lo; n = 0
|
|
while pos < hi:
|
|
m = min(1 << 24, hi - pos)
|
|
blob = os.pread(fd, m, pos)
|
|
for k in range(0, len(blob) - 3, 4):
|
|
if blob[k:k+4] in defs:
|
|
base = pos + k - DELTA
|
|
n += 1
|
|
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
|
|
pos += m
|
|
return per, n
|
|
|
|
def main():
|
|
secs = int(sys.argv[1]) if len(sys.argv) > 1 else 200
|
|
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)
|
|
f = os.fdopen(os.dup(fd), 'rb')
|
|
label, want = {}, {}
|
|
for o in roster:
|
|
va = gmem.primary_va(o)
|
|
if va is None: continue
|
|
label[va] = wave3.resolve_id(f, w.size, o)[0] or '?'
|
|
want[va + LINK] = va
|
|
print('roster records: %d' % len(label))
|
|
|
|
log = open('/tmp/wave5-per-record.tsv', 'w')
|
|
log.write('t\tva\tunit\tfrom\tto\n')
|
|
prev, total0 = None, None
|
|
t0 = time.time()
|
|
while time.time() - t0 < secs:
|
|
per, n = counts(fd, defs, want)
|
|
el = round(time.time() - t0)
|
|
if prev is None:
|
|
print('t=%4ds craft=%d deployed records=%d/%d strength histogram %s'
|
|
% (el, n, len(per), len(label),
|
|
sorted(collections.Counter(per.values()).items())))
|
|
else:
|
|
arr = [(va, per[va]) for va in per if prev.get(va, 0) == 0 and per[va] > 0]
|
|
kil = [(va, prev[va], per[va]) for va in prev
|
|
if per.get(va, 0) < prev[va]]
|
|
print('t=%4ds craft=%d deployed=%d ARRIVALS=%d losses=%d'
|
|
% (el, n, len(per), len(arr), len(kil)), flush=True)
|
|
for va, c in arr:
|
|
print(' ARRIVAL %#010x %-30s 0 -> %d' % (va, label.get(va, '?'), c))
|
|
log.write('%d\t%#x\t%s\t0\t%d\n' % (el, va, label.get(va, '?'), c))
|
|
for va, a, b in kil:
|
|
print(' loss %#010x %-30s %d -> %d' % (va, label.get(va, '?'), a, b))
|
|
log.write('%d\t%#x\t%s\t%d\t%d\n' % (el, va, label.get(va, '?'), a, b))
|
|
log.flush()
|
|
prev = per
|
|
time.sleep(max(0, every - (time.time() - t0 - el)))
|
|
log.close()
|
|
return 0
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main())
|