Watching [ScriptMission+40] and the three phase-1 objective squadrons together: all of ADN110/111/112 flip state 1 -> 2 at ~143s, while records in state 2 climb 24 -> 35 over four minutes. So state 1 means 'not yet deployed' for these, not 'gone'. The built-in table's '1/3/4 = gone/dead/invalid' shorthand is incomplete, and reading state != 2 as destroyed would have been wrong exactly as flagged last iteration. This also answers a much older question: mission-arrival-watch.md and the wave work recorded '0 confirmed arrivals' across many runs by watching the CRAFT population. The script's own unit table shows arrivals plainly -- eleven records enter state 2 within four minutes. The old negative measured the wrong structure; craft counts conflate deployment with attrition, the per-unit state field does not. Both attempts froze (at ~70s and ~253s), so no phase advance was reached. The freeze witness caught both immediately, which is why the truncation is visible instead of a silently flat line. New harness tools/re-capture/phase_watch.py.
74 lines
2.7 KiB
Python
74 lines
2.7 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.
|
|
|
|
Usage: phase_watch.py <Stage02.ssb> [secs] [every_s]
|
|
"""
|
|
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
|
|
path = gmem.mem_path()
|
|
size = os.path.getsize(path)
|
|
import frozen
|
|
t0 = time.time()
|
|
with open(path, 'rb', buffering=0) as f:
|
|
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())
|