This repository has been archived on 2026-09-16. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Syplheed-Reborn/docs/re/script-runtime-probe.md
Sylpheed RE agent ab08c77388 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.
2026-08-25 15:01:24 +00:00

5.7 KiB

Reading the live script state — the real phase counter, and per-squadron liveness

Status: ScriptMission and ScriptPhase located in a running mission with no debugger, validated arithmetically; the true phase ordinal read live; 🟡 the per-unit state encoding needs care.

This unblocks mission-phase-membership, which was stuck because "38 enemies died" could not say whether the right ones did. Chasing craft→squadron was the wrong angle: the script VM already keeps that table, indexed by the .ssb symbol-table-2 index.

Tool: tools/re-capture/squadron_state.py.

Locating the objects, without gdb

  1. Find the .ssb header in guest memory — 20 bytes of version + code offset
    • the two symbol-table offsets, distinctive enough to hit once. For a Stage 02 run it sat at 0xAB840010.
  2. code base = file base + header code offset (0x24) → 0xAB840034.
  3. [ScriptMission+24] is that code base, so scan for a word equal to it.
  4. Validate arithmetically, not by eye: [ScriptMission+44] must equal file base + symtab1 offset + 4. Measured 0xAB874C94; predicted 0xAB840010 + 0x34C80 + 4 = 0xAB874C94. Exact.

That check is what makes this trustworthy — the candidate is confirmed against a number taken from the file on disc, not against "it looks like a pointer". A second candidate that also pointed at the code base failed it and was discarded.

ScriptMission 0xBC7A2A20
  +4  ScriptPhase*  = 0xBE14DD80      +20 state     = 1  ("phase running")
  +24 code base     = 0xAB840034      +28 pc        = 0xAB84007C
  +40 PHASE ORDINAL = 1               +44 symtab1   = 0xAB874C94  ✓
ScriptPhase 0xBE14DD80
  +196 finished = 0    +244 symtab1 = 0xAB874C94    +324 unit array = 0xBC43B560

ScriptPhase+324+4 → an array of per-unit records. It holds 122 records — exactly the size of Stage 02's symbol table 2, which is an independent confirmation that the index space is the one the bytecode uses.

The real phase counter reads 1 — the mirror was the wrong field

[ScriptMission+40] reads 1 in a phase-1 mission. The runtime mirror at [*(0x828F35F8)+236], which three earlier runs polled, reads 0 — because ChangePhase is only posted once the ordinal exceeds 1.

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 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:

ADN110  idx=1  obj=True  state=1
ADN111  idx=2  obj=True  state=1
ADN112  idx=5  obj=True  state=1
records with state==2 (active): 27-29 of 122

The built-in table describes +16 as "2 = active; 1/3/4 = gone/dead/invalid". But these three have a live object pointer and state 1, in a mission that has barely started and where nothing has been shot. So either state 1 does not mean "gone", or it means "not yet deployed" — not settled, and worth pinning before any conclusion is drawn from it. Reading state != 2 as "destroyed" would be exactly the kind of plausible-but-wrong inference this corpus keeps catching.

What this makes possible

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.