Files
Sylpheed/tools/re-capture/timer_probe.py
Sylpheed RE agent 97daa3e493 re: the mission clock is running — refute the stopped-clock explanation
The prime suspect from the previous iteration was that the phase scheduler
simply is not running, which would have made every arrival result meaningless.
It is refuted.

timer_probe.py takes three equally-spaced snapshots of the 32 MB game heap with
no pilot -- exactly the condition where nothing had been observed to change --
and keeps words whose two successive deltas are both positive and agree within
12 %, so linear rather than merely noisy. 286 words qualify, with a large
cluster advancing in lockstep at 16.5 per second.

That rate is not a coincidence: the existing performance notes put Canary
playback on this box at roughly 14-19 fps, and the dominant counter sits inside
that band, so these read as per-frame counters.

Which gives a mundane explanation for six arrival-free runs. If the scheduler is
frame-driven and the title targets 30 Hz, game time advances at about 55 % of
wall-clock here, so the 168, 190 and 240 second runs covered roughly 92, 105 and
132 seconds of game time. Route_S02 schedules phase-1 arrivals at 90, 120, 170,
210 and 240, so the longest run passed the first two and came nowhere near the
last three. No model has to be wrong for the observations to be empty.

Kept at 🟡, not promoted: two links are assumed rather than measured -- that
these counters are frame counters, and that the game's tick is 30 Hz. The values
also do not fit a naive frame count, since the cluster read 14193 about 255 s
after emulator start, which matches neither 16.5 nor 30 per second, so their
origin is genuinely unknown and no counter is claimed to be the mission clock.

Next is one long run, ~350 s of probe, watching for a 0 -> n transition near
t = 163 s and 218 s wall, where the t = 90 and t = 120 route entries land at 55 %
speed. If 350 s still yields nothing, the frame-rate explanation is itself
refuted and the event-gated model returns as the front-runner. The ~210 s title
movie at boot is the binding constraint on how much game time one turn can
observe.
2026-08-24 14:54:57 +00:00

70 lines
2.6 KiB
Python
Executable File

#!/usr/bin/env python3
"""Does mission time advance at all? Scan for a linearly-increasing counter.
Six runs have shown no arrival, and one surviving explanation is that the
mission's phase clock is simply not running under these conditions
(mission-arrival-watch.md). That has never been checked. If nothing in the game
heap advances linearly, the clock is stopped and every arrival result so far is
measuring a stopped clock. If something does, that explanation is dead and a
timer has been located as a bonus.
Three snapshots, equally spaced. A counter is a word whose two successive deltas
are both positive and agree within a tolerance -- i.e. it is linear, not just
noisy -- at a plausible rate.
"""
import os, sys, time, struct, collections
sys.path.insert(0, __file__.rsplit('/', 1)[0])
import gmem, gworld
LO, HI = 0xBC000000, 0xBE000000 # the heap holding roster records + craft
def snap(fd):
lo, hi = gmem.va_to_off(LO), gmem.va_to_off(HI)
out, pos = bytearray(), lo
while pos < hi:
n = min(1 << 24, hi - pos)
out += os.pread(fd, n, pos)
pos += n
return bytes(out), lo
def main():
dt = int(sys.argv[1]) if len(sys.argv) > 1 else 20
w = gworld.World(); fd = w.fd
print('scanning %#x..%#x, %ds apart' % (LO, HI, dt), flush=True)
a, base = snap(fd); ta = time.time()
time.sleep(dt)
b, _ = snap(fd); tb = time.time()
time.sleep(dt)
c, _ = snap(fd); tc = time.time()
d1, d2 = tb - ta, tc - tb
print('actual gaps: %.1fs, %.1fs region %.1f MB' % (d1, d2, len(a) / 2**20))
n = min(len(a), len(b), len(c))
cand = []
for k in range(0, n - 3, 4):
va = struct.unpack_from('>I', a, k)[0]
vb = struct.unpack_from('>I', b, k)[0]
vc = struct.unpack_from('>I', c, k)[0]
if not (va < vb < vc): continue
e1, e2 = vb - va, vc - vb
if e1 > 1 << 28 or e2 > 1 << 28: continue
r1, r2 = e1 / d1, e2 / d2
if r1 < 0.4 or r1 > 200: continue
if abs(r1 - r2) > 0.12 * max(r1, r2): continue # linear, not noisy
cand.append((k, va, vb, vc, (r1 + r2) / 2))
print('\nlinearly-increasing words: %d' % len(cand))
if not cand:
print('NONE -- nothing in this heap advances at a steady rate.')
return 0
rates = collections.Counter(round(r[4]) for r in cand)
print('rate histogram (per second):', rates.most_common(10))
print('\nsample candidates:')
for k, va, vb, vc, r in cand[:14]:
gva = gmem.primary_va(base + k)
print(' va %s %10d -> %10d -> %10d %.1f/s'
% (('%#010x' % gva) if gva else '?', va, vb, vc, r))
return 0
if __name__ == '__main__':
sys.exit(main())