# Where the runtime phase state is *not* Status: āœ… the stage tables are resident in guest RAM, so the static decode matches what the game actually loads; šŸ”“ the "phase state sits next to the loaded table strings" hypothesis is refuted; ā” what advances a phase is still unknown. Follows [structures/stage-mission-tables.md](structures/stage-mission-tables.md), which found that a stage is divided into `Phase_1..3` and that every arrival route is tagged with a phase — but nothing static says what *ends* one. That made it an oracle question. Tools: `tools/re-capture/phase_session.sh` (boot → Stage 02 flight → fly → probe) and `tools/re-capture/phase_probe.py`. ## āœ… The tables are resident, and findable by name One Stage 02 flight, 160 s under the survival pilot. Every string the static decode predicted is present in live guest memory: | needle | hits | |---|---| | `Phase_1` | 3 | | `Phase_2` | 5 | | `Route_ADN101_p1F` | 2 | | `SUBOBJ_010` | 2 | | `AI_ADAN_CraftSquadron_Veteran` | 7 | | `UnitGroup_S02.tbl` | 1 | This is worth stating plainly because it is the first *dynamic* confirmation of the whole static table layer: the game loads exactly the tables the stage record names, under exactly the names we resolved, and they can be located in RAM by content. Anything decoded there can now be cross-checked against a running mission. ## šŸ”“ Refuted: the phase counter is not adjacent to the tables The probe captured 512 words around each anchor, then re-diffed every 20 s. The naive reading of the result is "862 words changed, something is happening here". That reading is wrong, and the raw values say so: ``` va=0xbd8e4ec0 bd8e4ea4 -> bd8e4ea0 va=0xbd8e4ec4 bd8e4ea8 -> bd8e4ea4 va=0xbd8e4ec8 bd8e4eac -> bd8e4ea8 va=0xbd8e4ecc bd8e4eb0 -> bd8e4eac ``` Every word takes its *predecessor's* previous value, and each value is a pointer into the same region. That is a block being shifted down by four bytes — one `memmove` in a pointer list — not 862 independent counters. It happened once, between t = 66 s and t = 89 s, and nothing changed before or after. So: **the runtime phase state is not stored next to the loaded table strings.** The tables sit in a region that is effectively read-only for the mission's duration; the mission's mutable state lives elsewhere. Diffing around a string anchor was the cheap thing to try and it did not work. ## šŸ”“ A defect in my own probe, recorded `phase_probe.py` originally scraped hit addresses with `0x([0-9a-f]{8})`. But `gmem.py find` prints two columns — the backing-file offset *and* the guest VA — so that pattern matched both, and half of the twelve "anchors" were file offsets being read as addresses. The counts in the table above are the corrected ones (the run reported doubles). The tool now matches `va 0x…` only. This did not change the conclusion — the one anchor that produced the shift, `0xbd8e4e…`, was a genuine VA — but it means roughly half the probe's work that run was spent reading meaningless addresses, and a "no hits there" from those anchors would have been worthless evidence. ## What to try next Not another string-anchored diff. The better handles: * **Watch a route fire.** `Route_ADN101_p1F` is a 3-frame path at t = 0, 20, 30 from (-2500, -100, -2500) to (4241.8, 100, -1017.7). Squadron `ADN101` should therefore appear along that path early in phase 1. Confirming that against entity positions would tie the static route table to observed spawns, and a route that fires only after some event is direct evidence of the trigger. * **The sub-objective HUD strings.** `SUBOBJ_010_Mes_L1` is displayed when an objective becomes active; finding *that* string's consumer is a path into the objective state machine. * The `REMAINING OB` HUD counter at `0xbdb59668` ([structures/mission-objective-counter.md](structures/mission-objective-counter.md)) is already known to live in the mutable region — the phase state is more likely near it than near the tables.