Files
Sylpheed/docs/re/mission-phase-membership.md
Sylpheed RE agent 82ce0cb9ad re: Turret-biased run verified live -- 38 kills, still no phase advance
Checked liveness before investing this time (animating, max_delta=254) and the
run was real: 823 distinct speeds, hull 1500->1485, escort 100%->30.7%, ADAN
population 127->89 (38 destroyed) over ~1064s, no GUEST FROZEN banner. The phase
mirror stayed 0 throughout.

But this still does not test the prediction. The claim is that phase 1 clears
when ADN110/111/112 -- 27 specific Turrets -- die; what was measured is that 38
enemies of some kind died. SYLPH_PREFER biases target choice but the pilot went
to DEFEND and spent the run on e010_ADAN_Attacker_S protecting the escort.

The missing instrument is per-squadron liveness: nothing maps a live craft back
to its roster squadron, the same gap roster-to-craft-link.md records as unsolved.
So 'no advance after 38 kills' is compatible with both 'the condition is wrong'
and 'the right 27 were never killed'. The decisive experiment is blocked on
attribution, not on flying time.
2026-08-25 14:11:50 +00:00

13 KiB
Raw Blame History

Which squadrons fly in which phase — and the ~300 craft are ~150

Status: the phase map, read out of Route_S<NN>.tbl record names; the 387-vs-300 gap explained — the live-craft probe double-counts; what advances a phase (nothing static encodes it).

The phase map is in the route names

stage\Route_S02.tbl has 121 records: 120 routes + 1 roster, and every route name matches Route_<squadron>_p<N><kind>120/120:

Route_ADN101_p1F   Route_TCN003_p2S   Route_ADS351_p3M

The phase lives in the name and nowhere else; a route record's only named field is FrameCount. So the squadron→phase mapping was on the disc all along, one string-parse away, in a table that was being read for fly-in geometry.

108 of 111 S02 squadrons match a route name exactly. The three that do not — TCN207, TCN208, TCS252 — are typos in the route table (Route_TCT207_p2S, Route_TCT208_p2S, Route_TCN252_p2F), plus two orphan routes naming no squadron (Route_ADS352_p3F, Route_kichi_p3F). All three near-misses are phase 2, so phase 1 is unaffected.

Phase partition of Stage 02 (Σn = 387 overall):

phase set squadrons Σn
{1} 33 142
{2} 31 110
{3} 39 118
{1,3} 2 5
{2,3} 1 1
{1,2,3} 2 4
none (the 3 typos) 3 7
per phase 37 / 34 / 44 151 / 115 / 128

The 387-vs-~300 gap: the probe counts each craft TWICE

roster-to-craft-link records four failed attempts to explain how the roster becomes "~300 live craft". The premise was wrong — there were never 300 craft.

Phase 1's member-level n multiset against the measured craft-per-record fan-in (41 records, 300 craft):

phase-1 members (42):   n=1 x25   n=2 x1   n=4 x4   n=9 x12    sum 151
observed craft/record:    2 x24     4 x1     8 x4    18 x12    sum 300
observed / 2:             1 x24     2 x1     4 x4     9 x12    sum 150

Every bucket matches shape for shape. The only discrepancy is 24 vs 25 records at the bottom — the already-documented 41-vs-42 off-by-one, and it is an n=1 record. So 300 = 2 × (151 1). Unit type corroborates independently: the 12 records showing 18 craft are all UN_e007_ADAN_Turret, and phase 1 has exactly 12 turret squadrons at n=9.

The factor of 2 is an artifact of the probe, not real units

Two static arguments, and the second is decisive because it is a hard capacity bound:

  1. Named pilots. Phase 1's eight f001 members all have n=1 and carry distinct identities — Character_Player_Test, ELLEN, RAYMOND, GENE, SANDRA, BILLY, ANTONIUS, CARL. A 2× reading means 16 Delta Sabers for 8 named people.

  2. Formation slots. Squadrons cannot exceed their formation's FrameCount (see unit-group-table):

    reading violations among the 37 phase-1 squadrons
    Σn ≤ FrameCount (1×) 0
    2·Σn ≤ FrameCount (2×) 20

    Including 16 squadrons on Formation_1_only — one slot, n=1 — which a 2× reading would have placing two craft in a one-slot formation.

So the true phase-1 population is ~150, and the 296 / 298 / 300 spread is 2 × a 148 / 149 / 150 population after kills. That all three measurements are even is a further tell a 1:1 count would not produce half the time.

Best explanation of the missing 42nd record: the player's own tuple (TCN001 member 0, Character_Player_Test), which plausibly is not in the AI-craft pool the +0x130 def-pointer scan walks. That makes the chain exact: 300 = 2 × (151 1 player).

⚠️ This is a static argument about a dynamic measurement. It says the probe's number cannot mean what it was taken to mean; it does not identify what the probe is double-counting. That needs a run — the counting rule in the craft scan is the place to look, not the game.

🔴 Refuted along the way

  • UnitGroup carries no phase, spawn time or arrival delay. The raw field dump accounts for every slot: 1019 = sum(Count)·4 + 5·111, with positional entries tagged by sequential index (0x0,0x1,0x2,…), not hashes. There is no spare field.
  • DisableInterval cannot explain Stage 02 — it is No for all 111 squadrons. Corpus-wide its 31 Yes records sit in just two stages, S04 (5) and S14 (26), all on GNN*** squadrons.
  • stage\EnumSquadron_Test.tbl does not exist (0x213f4a7e is not in the TOC). It was listed as a candidate for the arrival-interval values; it is dead.
  • AIParams_S02.tbl is keyed by AI profile, not squadron, and carries only combat tuning. SUBObjectiveSettings_S02.tbl is keyed SUBOBJ_005..014 with only points/unlock/HUD strings — no squadron, phase or trigger.

Bonus: the UnitGroup record key, confirmed corpus-wide

A squadron record's key is tag_hash(squadron_id)1160/1160 across all 28 stages (ADN1010x659aff47). Same hash as idxd-tag-hash; this is an independent confirmation of it on a second kind of table.

Still open: what advances a phase

Nothing static encodes a trigger. Swept the stage record and every table it names — Route, AIParams, SUBObjectiveSettings, FormationSet, nameplate, EnumUnit, EnumCharacter, EnumLocalString, EnumWeapon — plus the message\ family, for field names matching interval|time|phase|delay|wave|spawn|arrival|trigger|start|appear|event|condition. The only hits anywhere are FrameCount and PresetMessage_Phase1/2/3 in message\UnitMessageSet_S02.tbl. The trigger is almost certainly in the executable, not the data.

Falsifiable predictions for the next run

  • Per-record craft counts, in probe units (2×): 4 records at 8 = ADS151, ADT102, ADT107, ADT113 (all UN_e010_ADAN_Attacker_S); 1 record at 4 = TCN105 (UN_f106_TCAF_Destroyer); 24 records at 2 = the remaining phase-1 n=1 members.
  • A phase advance should move the deployed set toward phase 2: 34 squadrons, 36 members, Σn 115 (≈230 in probe units).
  • TCN207, TCN208 and TCS252 may fail to arrive in phase 2, because their routes are misnamed.

🟡 2026-08-25 — first long live observation: no phase advance, and a GAME OVER

Flew Stage 02 with an actively hunting pilot for 936 s while polling the runtime phase mirror [*(0x828F35F8) + 236] every 3 s (phase_probe.py).

The mirror never left 0. That is what the static work predicts rather than a contradiction: CScriptInterpreter::ChangePhase (opcode 995) is only posted when the ordinal is > 1 (the guard in mission-bytecode opcode 0x83), so a run that stays in phase 1 never writes the mirror. It does mean the mirror is useless as a "which phase am I in" readout for phase 1 — it reads 0, not 1. [ScriptMission+40] is the real counter and needs a debugger.

The run ended in GAME OVER — and the pilot's hull was still 1500/1500, so the player did not die. A script lose-branch fired, and the pilot log says which one is likely: the tracked asset fell from 100.8 % to 23.4 %, with ASSET-HIT events logged, while the pilot repeatedly AVOIDed f101_TCAF_Acropolis — a friendly capital ship — 2588 times.

So the escort was being destroyed throughout, and the mission failed with the player untouched. "Escort lost" is now a measured candidate rather than a guess, though the final asset value was 23.4 %, not 0, so the exact threshold (or whether a different unit's loss ended it) is not established.

So a phase advance still has not been observed. Two runs now — 530 s and 936 s — neither reached phase 2. Combined with the static reading that phase 1 polls unit_state on ADN110/ADN111/ADN112, the likeliest explanation is that those three squadrons were never destroyed; the pilot hunts whatever is nearest, not the objective. Next: bias the pilot at those squadrons' craft and re-run.

🔴 Refuted: sub_8226D740 does not read the flag register file

Chasing what reads the 32-entry flag file at [phase+120], the promising hit was three addi rN, r31, 120 inside sub_8226D740, the per-frame engine→script trigger edge. It is not the flag file. In that function r31 is a local trigger-record buffer whose fields are handed to sub_8226E220 as out-parameters (+80, +84, +88, +92, +112, +120), and lfd f1,120(r31) reads a double from the record. The phase is in r26/r29.

Offsets 88 and 120 are far too common (2091 and 5228 instructions image-wide, mostly stack frames) for an offset search to isolate the register files. Who reads the flags is still unknown, so the chain set_flag(8) → … → END_PHASE has a gap in the middle.

2026-08-25 — Stage 02 phase 1: destroy 27 Turrets (ADN110/111/112)

Resolving the three squadrons the phase-1 condition polls (isl-builtins, unit_state at 0xF524) against the roster:

squadron phases AI unit n
ADN110 1 AI_ADAN_CraftSquadron_Rookie UN_e007_ADAN_Turret 9
ADN111 1 AI_ADAN_CraftSquadron_Veteran UN_e007_ADAN_Turret 9
ADN112 1 AI_ADAN_CraftSquadron_Rookie UN_e007_ADAN_Turret 9

All three are phase 1 (from the Route_S02 name map), all fly the same craft, all n = 9 — so the condition watches 27 Turrets, and all three squadrons share Formation_ADAN_Turret09_30.

Three independent layers agree here, which is the point worth recording: the route names say these are phase-1 squadrons, the roster says what they fly and how many, and the script polls exactly these three before latching set_flag(8).

Confirms "Turret" is a craft type, not ship AA

The user's correction stands on the data: UN_e007_ADAN_Turret is flown by AI_ADAN_CraftSquadron_Rookie/_Veteran — a craft squadron AI, in a 30-slot craft formation — never AI_Structure. It is an enemy the player fights, not a gun bolted to a capital ship.

🔴 2026-08-25 — the Turret-biased run was VOID: the guest was frozen

Ran it: SYLPH_PREFER=Turret, escort intact (ast=100.0%), 1070 s. The phase field read 0 throughout and the pilot logged 4029 Turret-targeting samples. That looked like a clean negative — "hunting the objective squadrons does not advance the phase" — and it is worthless.

frozen.py says max_pixel_delta=0: the guest was frozen, and the pilot's very first sample at t=0.0 already shows spd=0 with the same yaw, pitch, target and d=7186 that it still had at 1070 s. The mission froze on entry to flight; nothing was ever shot. screen_id said flight the whole time, which is precisely the trap mission-freeze-resume-spin documents.

I was one step from writing this up as a result about the game. What saved it was running the freeze test rather than trusting a plausible-looking log — the same lesson that note already carries, arrived at again from the other side.

Fixed so it cannot recur silently

phase_probe.py now calls frozen.py every 60 s and prints *** GUEST FROZEN *** inline, so a dead-world reading is labelled in the data rather than discovered afterwards. ⚠️ frozen.frozen() returns a tuple (is_frozen, max_delta) — testing it directly is always truthy and would have made the witness fire on every check; it must be unpacked. Verified against the frozen guest: the banner fires, and the unpacked value is (True, 0).

The pilot log is itself a witness and was not being read as one: identical yaw/pitch/target across thousands of samples means a dead world, not a patient pilot.

🟡 2026-08-25 — Turret-biased run, VERIFIED LIVE: still no phase advance

Re-ran it with the freeze witness armed, and checked liveness before investing in the run this time (frozen.pyanimating, max_delta=254).

This one was real: 823 distinct speeds logged, the player's hull moved 1500 → 1485, the escort fell 100 % → 30.7 %, and the ADAN population dropped 127 → 89 — 38 enemies destroyed over ~1064 s. The probe printed no GUEST FROZEN banner.

The phase mirror stayed 0 throughout. So: 38 enemy losses, escort still alive, no advance.

🔴 But this still does not test the prediction

The prediction is that phase 1 clears when ADN110/ADN111/ADN112 — 27 specific Turrets — are destroyed. What was measured is that 38 enemies of some kind died. SYLPH_PREFER=Turret biases target choice, but the pilot switched to DEFEND and spent most of the run on e010_ADAN_Attacker_S, protecting the escort.

The missing instrument is per-squadron liveness. Nothing currently maps a live craft back to its roster squadron — that is the same gap roster-to-craft-link records as unsolved. Until a craft can be attributed to ADN110, a kill count cannot say whether the condition was met, and "no advance after 38 kills" is compatible with both "the condition is wrong" and "the right 27 were never killed".

Honest state: three live-run attempts, no phase advance observed, and the decisive experiment is still blocked on attribution rather than on flying time.

🟡 The prediction to test

If phase 1 clears on those three squadrons dying, then destroying 27 Turrets while the escort survives should push [ScriptMission+40] to 2 and make ChangePhase write the mirror at [*(0x828F35F8)+236]. Two runs so far killed whatever was nearest and never advanced.