#!/usr/bin/env python3 """Count live spawned-entity records over a mission and look for wave arrivals. Route_S02.tbl gives a per-phase arrival timetable: phase 1 has 25 routes at t=0 then groups of 3, 3, 3, 2, 1 at t = 90, 120, 170, 210, 240 (units unknown -- that is what this measures). If those are seconds of phase time, a live entity count should step up at roughly those offsets. If they are frames at 30 Hz, every arrival lands inside the first ~8 s and the count is flat afterwards. Counts occurrences of the spawned-entity vtable 0x820af030 (docs/re/structures/unit-struct-runtime.md). """ import sys, time, struct, importlib.util SD = __file__.rsplit('/', 1)[0] spec = importlib.util.spec_from_file_location('gmem', SD + '/gmem.py') gmem = importlib.util.module_from_spec(spec); spec.loader.exec_module(gmem) VT = struct.pack('>I', 0x820AF030) def scan(fd, size, span=None): """Count aligned occurrences of VT; return (count, lo_off, hi_off).""" import os f = os.fdopen(os.dup(fd), 'rb', closefd=True) n = 0; lo = hi = None ext = [span] if span else gmem.extents(fd, size) for start, end in ext: pos = start while pos < end: f.seek(pos) buf = f.read(min(1 << 24, end - pos)) if not buf: break i = buf.find(VT) while i != -1: off = pos + i if off % 4 == 0: n += 1 if lo is None or off < lo: lo = off if hi is None or off > hi: hi = off i = buf.find(VT, i + 1) pos += len(buf) f.close() return n, lo, hi 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 10 path = gmem.mem_path() import os fd = os.open(path, os.O_RDONLY) size = os.fstat(fd).st_size n, lo, hi = scan(fd, size) if not n: print('NO ENTITY RECORDS -- not in a mission'); return 2 span = (max(0, lo - (1 << 20)), min(size, hi + (1 << 20))) print('initial entities: %d span 0x%x..0x%x (%.1f MB)' % (n, span[0], span[1], (span[1] - span[0]) / 2**20)) t0 = time.time(); series = [] while True: el = time.time() - t0 if el > secs: break c, _, _ = scan(fd, size, span) series.append((round(el), c)) print(' t=%4ds entities=%d' % (round(el), c), flush=True) time.sleep(max(0, every - (time.time() - t0 - el))) print('\n--- steps up (arrivals) ---') for i in range(1, len(series)): d = series[i][1] - series[i-1][1] if d > 0: print(' t=%4ds +%d (%d -> %d)' % (series[i][0], d, series[i-1][1], series[i][1])) print('\nseries:', series) return 0 if __name__ == '__main__': sys.exit(main())