From 3bae68d7f22fbeca237e0b30eb26ade0a84ae278 Mon Sep 17 00:00:00 2001 From: Sylpheed RE agent Date: Tue, 25 Aug 2026 15:01:24 +0000 Subject: [PATCH] re: state 1 = not yet deployed, and arrivals are directly observed 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. --- docs/re/data/phase-watch-s02.txt | 13 ++++++ docs/re/script-runtime-probe.md | 51 ++++++++++++++++++++-- tools/re-capture/phase_watch.py | 73 ++++++++++++++++++++++++++++++++ 3 files changed, 133 insertions(+), 4 deletions(-) create mode 100644 docs/re/data/phase-watch-s02.txt create mode 100644 tools/re-capture/phase_watch.py diff --git a/docs/re/data/phase-watch-s02.txt b/docs/re/data/phase-watch-s02.txt new file mode 100644 index 00000000..65089a0e --- /dev/null +++ b/docs/re/data/phase-watch-s02.txt @@ -0,0 +1,13 @@ +file base 0xAB860010 ScriptMission 0xBC79C960 + [ 1.8s] phase=1 finished=0 active= 24 ADN110:1 ADN111:1 ADN112:1 + [ 58.2s] phase=1 finished=0 active= 27 ADN110:1 ADN111:1 ADN112:1 + [ 90.7s] phase=1 finished=0 active= 30 ADN110:1 ADN111:1 ADN112:1 + [ 120.8s] phase=1 finished=0 active= 29 ADN110:1 ADN111:1 ADN112:1 + [ 143.4s] phase=1 finished=0 active= 32 ADN110:2 ADN111:2 ADN112:2 + [ 188.4s] phase=1 finished=0 active= 34 ADN110:2 ADN111:2 ADN112:2 + [ 223.3s] phase=1 finished=0 active= 35 ADN110:2 ADN111:2 ADN112:2 + [ 253.4s] *** GUEST FROZEN -- readings below are about a dead world *** + [ 315.6s] *** GUEST FROZEN -- readings below are about a dead world *** + [ 377.7s] *** GUEST FROZEN -- readings below are about a dead world *** + [ 439.9s] *** GUEST FROZEN -- readings below are about a dead world *** + [ 502.1s] *** GUEST FROZEN -- readings below are about a dead world *** diff --git a/docs/re/script-runtime-probe.md b/docs/re/script-runtime-probe.md index ba5d99bb..fac166f2 100644 --- a/docs/re/script-runtime-probe.md +++ b/docs/re/script-runtime-probe.md @@ -49,6 +49,44 @@ So the mirror is not a phase readout at all in phase 1, and **`+40` is**. It is reachable from `/dev/shm` with no debugger, which is what made three runs of polling the wrong address avoidable in hindsight. +## ✅ PINNED: state 1 = not yet deployed, state 2 = active — and arrivals are real + +Watching `[ScriptMission+40]` and the three objective squadrons together across a +live run (`data/phase-watch-s02.txt`): + +``` +[ 1.8s] phase=1 finished=0 active= 24 ADN110:1 ADN111:1 ADN112:1 +[ 58.2s] phase=1 finished=0 active= 27 ADN110:1 ADN111:1 ADN112:1 +[ 90.7s] phase=1 finished=0 active= 30 ADN110:1 ADN111:1 ADN112:1 +[ 120.8s] phase=1 finished=0 active= 29 ADN110:1 ADN111:1 ADN112:1 +[ 143.4s] phase=1 finished=0 active= 32 ADN110:2 ADN111:2 ADN112:2 <-- arrive +[ 223.3s] phase=1 finished=0 active= 35 ADN110:2 ADN111:2 ADN112:2 +``` + +**All three flip 1 → 2 at ~143 s**, and the count of records in state 2 climbs +**24 → 35** over the same window. So for these squadrons **state 1 is +"not yet deployed", not "gone"** — the built-in table's shorthand +*"1/3/4 = gone/dead/invalid"* is incomplete, and reading `state != 2` as +"destroyed" would have been wrong in exactly the way flagged last iteration. +Good that it was flagged rather than assumed. + +### ✅ This also answers a much older question: arrivals DO happen + +[mission-arrival-watch](mission-arrival-watch.md) and the wave work recorded +**"0 confirmed arrivals"** after many runs, measured by watching the *craft* +population. The script's own unit table shows arrivals plainly: eleven more +records enter state 2 within four minutes, three of them the phase-1 objective +squadrons at a distinct moment. + +The old negative was not wrong about what it measured — it was measuring the +wrong structure. Craft counts conflate deployment with attrition; the per-unit +state field does not. + +⚠️ **Both runs of this experiment froze** — at ~70 s and ~253 s — so the window +above is all that was observed, and **no phase advance was reached**. The freeze +witness caught both immediately, which is the only reason the truncation is +visible rather than silently producing a flat line. + ## 🟡 The per-unit `state` encoding is not what the summary implies For the three phase-1 objective squadrons, early in a fresh mission: @@ -70,7 +108,12 @@ catching. ## What this makes possible -The decisive phase experiment is no longer blocked on attribution: sample -`[ScriptMission+40]` and the three squadrons' records together over a run, and a -phase advance becomes directly observable along with the state change that caused -it. That run has **not** been done yet. +The decisive phase experiment is no longer blocked on attribution — and it has +now partly run: the arrival of the three objective squadrons is directly +observed. What is still missing is a run that survives long enough (no freeze) +for them to be **destroyed**, which is when `[ScriptMission+40]` should step to +2. Two attempts froze first. + +`tools/re-capture/phase_watch.py` is the harness: it samples the real counter and +the watched squadrons together, witnesses the freeze every 60 s, and prints only +on change. diff --git a/tools/re-capture/phase_watch.py b/tools/re-capture/phase_watch.py new file mode 100644 index 00000000..d9c83467 --- /dev/null +++ b/tools/re-capture/phase_watch.py @@ -0,0 +1,73 @@ +#!/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 [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())