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.
277 lines
13 KiB
Markdown
277 lines
13 KiB
Markdown
# 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](roster-to-craft-link.md) 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](structures/unit-group-table.md)):
|
||
|
||
| 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** (`ADN101` → `0x659aff47`). Same hash as
|
||
[idxd-tag-hash](structures/idxd-tag-hash.md); 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 `AVOID`ed
|
||
`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](structures/isl-builtins.md), `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](mission-freeze-resume-spin.md)
|
||
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.py` → `animating, 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](roster-to-craft-link.md) 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.
|