Files
Sylpheed/tools/re-capture/phase_watch.py
Sylpheed RE agent 8438f2e5b6 re: counter-example weakens the probe-causes-freeze correlation
A pilot-only run froze at t~150.7s (frozen.py: max_pixel_delta=0), found by
accident when the sweep-free experiment aborted at startup and the run flew with
no script probe attached. The tally is now 3-of-3 frozen with the probe versus
1-of-3 without, not 3-versus-0. Still a lean, but not the clean separation the
previous entry claimed, and marked down accordingly -- the fourth time a freeze
conclusion here has had to be softened by one more run.

The sweep-free test itself could not run: ScriptMission is re-allocated per run,
so the address from an earlier run (0xBC7A2A20) read back all zeros. The cheap
self-consistency check ([m+44] must equal the phase's [+244]) rejected it
instead of reporting garbage, which is the part that worked.

Names the cheaper replacement: a BOUNDED pointer scan. Every ScriptMission seen
so far sits in 0xBC79xxxx-0xBC7Axxxx, so ~32MB instead of ~371MB would cut the
sweep cost roughly tenfold. Not yet implemented.
2026-08-25 15:38:23 +00:00

101 lines
4.2 KiB
Python

#!/usr/bin/env python3
"""Watch the REAL phase counter and every squadron's state, together.
Three earlier runs polled `[*(0x828F35F8)+236]` and saw 0 forever -- that mirror
is only written once the ordinal exceeds 1 (script-runtime-probe.md). This reads
`[ScriptMission+40]`, the counter itself, plus the per-unit records the phase
conditions actually test, so a phase advance and the state change that caused it
are visible in one sample.
It also does two things the earlier probes had to learn the hard way:
* witnesses the freeze every 60 s (a frozen guest produces a perfectly
clean-looking negative -- see mission-freeze-resume-spin.md);
* re-locates the ScriptMission if the pointer stops validating, rather than
silently reporting stale numbers.
`--mission 0x...` skips the two full guest-memory sweeps `find_mission()` does
and validates the given address with a handful of reads instead. That exists
because the sweeps are the leading suspect for the in-mission freeze: runs with
this probe attached froze at ~70/126/253 s, while the same runs without it went
936 s and 1064 s clean (mission-freeze-resume-spin.md). Sweep-free mode is the
experiment that separates "the sweeps do it" from "the per-sample reads do it".
Usage: phase_watch.py <Stage02.ssb> [secs] [every_s] [--mission 0xADDR]
"""
import os
import sys
import time
sys.path.insert(0, __file__.rsplit('/', 1)[0])
import gmem
import isl
import squadron_state as S
WATCH = ['ADN110', 'ADN111', 'ADN112']
def main():
ssb = isl.load(sys.argv[1])
sym2 = isl.symbols(ssb, 2)
secs = float(sys.argv[2]) if len(sys.argv) > 2 else 900
every = float(sys.argv[3]) if len(sys.argv) > 3 else 5
given = None
if '--mission' in sys.argv:
given = int(sys.argv[sys.argv.index('--mission') + 1], 0)
path = gmem.mem_path()
size = os.path.getsize(path)
import frozen
t0 = time.time()
with open(path, 'rb', buffering=0) as f:
if given is not None:
# Validate WITHOUT sweeping: the mission's symtab1 pointer must equal
# the phase's, and the code base must be a plausible pointer. Both
# are self-consistency checks internal to the two objects, so they
# cost four reads instead of two full scans.
code = S.u32(f, given + 24)
s44 = S.u32(f, given + 44)
ph = S.u32(f, given + 4)
p244 = S.u32(f, ph + 244) if ph else None
ok = (code and 0x10000 <= code < 0xFFFFFFF0 and s44 and s44 == p244)
if not ok:
print('given ScriptMission 0x%08X does not validate '
'(code=%s s44=%s phase244=%s)' % (given, code, s44, p244))
return 1
m, fb = given, None
print('ScriptMission 0x%08X (given, validated, NO sweeps)' % m, flush=True)
else:
m, fb = S.find_mission(f, size, ssb)
if m is None:
print('ScriptMission not located'); return 1
print('file base 0x%08X ScriptMission 0x%08X' % (fb, m), flush=True)
last = None
next_frozen = 0.0
while time.time() - t0 < secs:
now = time.time() - t0
if now >= next_frozen:
next_frozen = now + 60
try:
dead, _ = frozen.frozen(5.0)
except Exception:
dead = False
if dead:
print(' [%6.1fs] *** GUEST FROZEN -- readings below are about '
'a dead world ***' % now, flush=True)
r = S.read_states(f, m, sym2, WATCH)
key = (r['phase_ordinal'], r['finished'], r['active_records'],
tuple((n, (v or {}).get('state')) for n, v in r['units'].items()))
if key != last:
print(' [%6.1fs] phase=%s finished=%s active=%3d %s' % (
now, r['phase_ordinal'], r['finished'], r['active_records'],
' '.join('%s:%s' % (n, (v or {}).get('state'))
for n, v in r['units'].items())), flush=True)
last = key
time.sleep(every)
print('done', flush=True)
return 0
if __name__ == '__main__':
sys.exit(main())