wave6_probe refuses to interpret a run whose roster count is not the reproduced baseline of 116, per the discard rule. This run passed and its first sample is identical to the earlier link run: 116 records, 300 craft, 41 deployed, strength histogram 2x24, 4x1, 8x4, 18x12. The deployment is deterministic at mission start. The pilot failed to bind, which accidentally supplied the control condition the kill-versus-no-kill experiment needed. With nobody flying, not one craft was destroyed in four minutes -- exactly 300 across all 22 samples -- against 16-20 losses in each piloted run. So losses are attributable to the player being in the fight, and NPC crossfire destroys nothing by itself. That was an open question two iterations ago. Still no arrival. Zero 0 -> n transitions in either condition, across roughly fifteen minutes of cumulative Stage 02 flight and windows up to 240 s. The 75 records holding no craft at mission start still hold none at the end. Against a route table scheduling phase-1 arrivals at t = 90/120/170/210/240, that is now a strong negative rather than a null result. Three readings survive: the timetable's t is not seconds (at 30 Hz the whole phase-1 schedule finishes inside 8 s, before any first sample); arrivals are event-gated and no run supplied the trigger; or the mission is not advancing its phase clock at all. The third has never been checked and is the cheapest to eliminate, so it is now the prime suspect. Nothing in six runs has confirmed that mission time advances: the craft count freezes without a pilot, REMAINING OB has never read as a counter, and no clock has been located. Every "no arrival" observation is consistent with a scheduler that simply is not running, in which case the arrival results so far are measuring a stopped clock.
105 lines
4.3 KiB
Python
Executable File
105 lines
4.3 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Per-record arrival/loss watch against the verified 116/300 baseline.
|
|
|
|
Supersedes wave5_probe.py, which reported 42 records in a run nobody could
|
|
reproduce. Changes:
|
|
* refuses to interpret a run whose roster count is not the reproduced
|
|
baseline (mission-per-record-strength.md: discard, do not interpret);
|
|
* keys records by file offset rather than by primary VA;
|
|
* samples faster and for longer, since losses appear at ~1 per 10 s and no
|
|
arrival has been seen in five runs.
|
|
|
|
An arrival is a record going 0 -> n; a loss is n -> n-1.
|
|
"""
|
|
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
|
|
BASELINE = 116
|
|
|
|
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 sample(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 k in range(0, len(blob) - 3, 4):
|
|
if blob[k:k+4] not in defs: continue
|
|
tot += 1
|
|
head = os.pread(fd, WIN, pos + k - DELTA)
|
|
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, tot
|
|
|
|
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 10
|
|
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: roster count is not the reproduced baseline. '
|
|
'Per mission-per-record-strength.md this run is not interpreted.')
|
|
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
|
|
log = open('/tmp/wave6.tsv', 'w'); log.write('t\tkind\toff\tunit\tfrom\tto\n')
|
|
prev = None; t0 = time.time(); arr_n = loss_n = 0
|
|
while time.time() - t0 < secs:
|
|
per, tot = sample(fd, defs, want)
|
|
el = round(time.time() - t0)
|
|
if prev is None:
|
|
print('t=%4ds craft=%d deployed=%d/%d strengths %s'
|
|
% (el, tot, len(per), len(roster),
|
|
sorted(collections.Counter(per.values()).items())), flush=True)
|
|
else:
|
|
arr = [(o, per[o]) for o in per if prev.get(o, 0) == 0 < per[o]]
|
|
los = [(o, prev[o], per.get(o, 0)) for o in prev if per.get(o, 0) < prev[o]]
|
|
arr_n += len(arr); loss_n += len(los)
|
|
print('t=%4ds craft=%d deployed=%d ARRIVALS=%d losses=%d (cum %d/%d)'
|
|
% (el, tot, len(per), len(arr), len(los), arr_n, loss_n), flush=True)
|
|
for o, c in arr:
|
|
print(' ARRIVAL %-30s 0 -> %d' % (label.get(o, '?'), c), flush=True)
|
|
log.write('%d\tarrival\t%#x\t%s\t0\t%d\n' % (el, o, label.get(o, '?'), c))
|
|
for o, a, b in los:
|
|
print(' loss %-30s %d -> %d' % (label.get(o, '?'), a, b), flush=True)
|
|
log.write('%d\tloss\t%#x\t%s\t%d\t%d\n' % (el, o, label.get(o, '?'), a, b))
|
|
log.flush()
|
|
prev = per
|
|
time.sleep(max(0, every - (time.time() - t0 - el)))
|
|
log.close()
|
|
print('\nTOTAL arrivals=%d losses=%d over %ds' % (arr_n, loss_n, secs))
|
|
return 0
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main())
|