Files
Sylpheed/tools/re-capture/wave_probe.py
Sylpheed RE agent 27e0b6ebc1 re: find the arrival timetable; refute the entity-count proxy for waves
The routes' first-keyframe time is the arrival schedule. It is not always zero,
and grouping Stage 02's 120 route records by phase and first-frame time gives a
timetable: phase 1 releases 25 routes at t=0 then 3, 3, 3, 2, 1 at t = 90, 120,
170, 210, 240. Phase 2 has every one of its 37 routes at t=0, which is what
pins the meaning: t is measured from the start of its phase, not of the mission.
Entering a phase releases that phase's t=0 group and the rest follow on the
offsets.

That completes the data side of the question this line of work started from --
the schedule is data, split across UnitGroup (who) and Route (when, and the path
flown in), with no fixed enemy count anywhere.

Refuted: counting spawned-entity records does not reveal arrivals. One Stage 02
flight, 210 s sampled every 15 s, counting aligned 0x820af030 in an 8.3 MB span:
flat at 116 throughout, no step at 90, 120, 170, 210 or anywhere.

The reason looks more useful than the refutation. UnitGroup_S02's Count fields
sum to exactly 116 members, and there are exactly 116 records from the first
sample on, so the game most likely allocates one record per roster member at
mission load and a route arrival activates an existing record rather than
creating one. Kept at 🟡, not promoted: n=1, and the obvious refutation -- check
another stage's record count against its member sum (S01=42, S16=2, S29=95) --
needs a save for another stage, and only slot 01 / Stage 02 exists. Noted as the
blocker rather than worked around.

Not settled: whether the timetable's t is frames or seconds (at 30 Hz t=240 is
8 s; as seconds it is 4 min), and where an arrival is observable in memory. The
live flag is presumably a field inside those 116 records, which is a well-scoped
next probe now that the record set is bounded and located.

Operational note recorded: cold boot spent 204 s in the title movie, so a 300 s
probe overran the turn and the first attempt died with its output still in the
pipe. Log to a file rather than piping to tail.
2026-08-24 11:57:16 +00:00

76 lines
2.8 KiB
Python
Executable File

#!/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())