re: decode the stage table set — phases, routes, sub-objectives, AI parameters

Following the real stage record (not the _Test template dumped earlier) reaches
the whole mission-parameter layer, all of it in the same self-describing IDXD
container as the squadron roster.

The big one: a stage is divided into Phase_N blocks -- three for Stage 02, each
with its own map path, map mesh, asteroid definition and background -- and
Route_S<NN>.tbl holds the arrival paths, with records named

    Route_<squadron>_p<phase><kind>

tying a UnitGroup squadron id to a phase and to a time-stamped keyframed path of
(time, quat x4, pos x3). Route_ADN101_p1F is 3 frames at t = 0, 20, 30. The
identity len(fields) == FrameCount * 8 + 1 holds for 1449 of 1449 route records
across the 28 stages that have one, and 16/16 for FormationSet_S02.

Also decoded: SUBObjectiveSettings (per-objective bonus points by difficulty,
unlock item id, HUD strings) and AIParams (34 profiles, firing/guard/muster/
counter ranges plus 14 manoeuvre weights for Squad-type AI). The AIParams
numbers are exact original values from static RE and are portable as they are.

Adds tools/re-capture/stagetbl.py, which resolves a stage record by content and
can --follow every table it names, and commits two dumps as evidence.

Refuted and kept: the eight-value keyframe is the common case, not universal.
Formation_Fleet_01 has FrameCount=1 with 136 positional fields and
Formation_Fleet_02 has FrameCount=8 with 32, so a parser must not assume the
stride.

Corrects stage-definition-table.md, which was written from the _Test template
and is missing EnumerateSubobjective, EnumerateAIParams, BackGroundID and the
WingmanIconID fields the real record carries.

Not settled: what advances a phase -- the stage declares Phase_1..3 and routes
are phase-tagged, but nothing static says what ends one. That is a question for
the oracle, not for more static reading. Also open: the route-name kind letters
F/S/A/M/B, what activates a sub-objective, and StageMessageSet_S<NN>.tbl, which
does not resolve in GP_MAIN_GAME_E.pak.
This commit is contained in:
Sylpheed RE agent
2026-08-24 11:15:34 +00:00
parent b39aabf5c3
commit cdb9e5a001
6 changed files with 2761 additions and 1 deletions

View File

@@ -6,6 +6,14 @@ Status: ✅ for the record vocabulary and the stage→table wiring;
This is the answer to "where does the game say *which* enemies appear in a
mission, and when". It is data, not code, and it lives in the language paks.
> **Correction (2026-08-24):** the record dumped below is the `_Test` *template*,
> not a real stage record. The real `Stage_S02` record has six sub-records —
> including `Phase_1..3` — and 19 `StageResource` fields, several of which the
> template does not have at all (`EnumerateSubobjective`, `EnumerateAIParams`,
> `BackGroundID`, `WingmanIconID_0..3`). See
> [stage-mission-tables.md](stage-mission-tables.md) for the real one. The
> stage→`UnitGroup` wiring and the hash table below are unaffected.
## ✅ The per-stage definition record
`GP_MAIN_GAME_<lang>.pak` contains one IDXD object per stage. Dumped with

View File

@@ -0,0 +1,144 @@
# The stage table set — phases, routes, sub-objectives and AI parameters
Status: ✅ the table set and how the stage record reaches it, validated across
all 28 stages; ✅ the route/formation keyframe layout; ❔ what the route-name
phase suffixes mean and where a phase *advances*.
This is the layer that answers "which enemies and objectives come into play,
and when". It is all data, in `GP_MAIN_GAME_<lang>.pak`, in the same
self-describing IDXD container as the squadron roster
([unit-group-table.md](unit-group-table.md)).
Tool: `tools/re-capture/stagetbl.py` (pure static, runs no emulator).
```
python3 tools/re-capture/stagetbl.py --follow S02 # stage record + every table it names
python3 tools/re-capture/stagetbl.py --name 'stage\AIParams_S02.tbl'
```
Committed dumps: [`../data/stage-s02-tables.txt`](../data/stage-s02-tables.txt)
and [`../data/aiparams-s02.txt`](../data/aiparams-s02.txt).
## ✅ The real stage record is much richer than the `_Test` template
[stage-definition-table.md](stage-definition-table.md) was written from the
`_Test` template record, which is a stripped stand-in. The **real** `Stage_S02`
record has six sub-records, not one, and 19 fields in its `StageResource` rather
than the template's 13:
```
StageResource EnumerateLocalString = EnumLocalString_S02.tbl
EnumerateWeapon = EnumWeapon_Test.tbl
CollisionMeshes = CollisionSet_S02.bin
EnumerateNullFrame = Route_S02.tbl
EnumerateCharacter = EnumCharacter_S02.tbl
EnumerateUnit = EnumUnit_S02.tbl
EnumerateFormation = FormationSet_S02.tbl
BackGroundID = Lebendorf
WingmanIconID_0..3 = "", PGHUD_WING4, "", ""
NamePlate = nameplate_S02.tbl
EnumerateSubobjective = SUBObjectiveSettings_S02.tbl
BackGroundPackage = game:\hidden\Resource3D\BG_Lebendorf_near.xpr
EnumerateAIParams = AIParams_S02.tbl
StageResourcePackage = game:\hidden\Resource3D\Stage_S02.xpr
MessageSet = StageMessageSet_S02.tbl
EnumerateSquadron = UnitGroup_S02.tbl
LodResource LodEnumTable = DefTables.pak+EnumLODSet_test.tbl
Phase_1 MapPath = mapmesh_box_500km.rgn, MapMesh = mapmesh_box_500km.col,
Phase_2 AsteroidDefinition = "", BackgroundResourceID = ""
Phase_3
MotionResource MotionEnumTable = DefTables.pak+EnumGameModel_test.tbl
```
**A stage is divided into `Phase_N` blocks** — three for Stage 02 — each naming
its own map path, map mesh, asteroid definition and background. `--follow`
resolves every table above except `StageMessageSet_S02.tbl`, which is not in
`GP_MAIN_GAME_E.pak` under that name (❔, probably localised elsewhere).
## ✅ `Route_S<NN>.tbl` — the arrival paths, bound to squadron *and* phase
121 records for Stage 02, 1449 across the corpus. Records are named
**`Route_<squadron>_p<phase><kind>`**, e.g. `Route_ADN101_p1F`,
`Route_ADT102_p1A`, `Route_ADN310_p3F` — tying a route to a squadron id from
[`UnitGroup`](unit-group-table.md) and to a phase from the stage record.
A record is a **time-stamped keyframed path**: `FrameCount` frames of eight
values each,
```
(time, quat.x, quat.y, quat.z, quat.w, pos.x, pos.y, pos.z)
```
`Route_ADN101_p1F` is 3 frames at t = 0, 20, 30, running from
(-2500, -100, -2500) to (4241.8, 100, -1017.7).
**Self-check: `len(fields) == FrameCount * 8 + 1` holds for 1449 of 1449 route
records** across the 28 stages that have a `Route_S<NN>.tbl` (S17 has none, as
it has no stage record or `UnitGroup` either). It also holds 16/16 for
`FormationSet_S02.tbl` and 9/9 for `Null_Test.tbl`.
Values are stored as *text* in the string pool (`"0"`, `"-0.011578"`), not as
binary floats.
Phase/kind suffix counts across the corpus:
| | F | S | A | M | B |
|---|---|---|---|---|---|
| p1 | 397 | 404 | 54 | 72 | 9 |
| p2 | 125 | 107 | 17 | 59 | |
| p3 | 48 | 48 | | 19 | |
❔ What `F`, `S`, `A`, `M`, `B` stand for is **not settled**. Every phase has F
and S; A and B appear only in phases 12.
### 🔴 The eight-value frame is the common case, not universal
`Formation_Test.tbl` breaks it: `Formation_Fleet_01` has `FrameCount = 1` with
136 positional fields, and `Formation_Fleet_02` has `FrameCount = 8` with 32
(four per frame). So `FrameCount` does not always index an 8-tuple, and any
parser must not assume it. `Formation_100` (100 frames, 800 fields) and all of
`FormationSet_S02` do follow the 8-value layout.
## ✅ `SUBObjectiveSettings_S<NN>.tbl` — the sub-objectives
Stage 02 declares nine (`SUBOBJ_005``SUBOBJ_014`), each with:
| field | example |
|---|---|
| `BonusPoint_EASY` / `_NORMAL` / `_HARD` | 500 / 1000 / 2000 |
| `UNLockItemID` | 0, or 13 for `SUBOBJ_013` |
| `HUDString01` / `HUDString02` | `SUBOBJ_010_Mes_L1` / `_L2` |
This is the objective *reward and presentation* table. It does **not** say what
completes an objective or when one becomes active — that trigger is still ❔.
## ✅ `AIParams_S<NN>.tbl` — combat tuning, directly portable
34 AI profiles for Stage 02, referenced by `AIID` in `UnitGroup`. Two shapes:
* `Type = Fleet` — 6 fields: `FiringLength`, `GuardLength`, `AutoGuardLength`,
`MusterLength`, `CounterLength`, `Type`.
* `Type = Squad` — those six plus 14 manoeuvre weights:
`ManeuverRatio_Cutoff`, `CounterManeuverRatio`,
`BootDefManeuverRatio_{Aimed,Fired,Missiled}`,
`DefManeuverRatio_{BoostAway,BarrelRoll,TurnAttack,TurnAway,SideRoll,NoAction}`,
`OffManeuverRatio_{HoldPosition,Through,NoAction}`.
e.g. `AI_ADAN_CraftSquadron_Ace` fires at 3000.0, guards at 10000.0, musters at
10000.0, and weights `DefManeuverRatio_BoostAway = 0.88` against
`AI_ADAN_Elan_GR_Violeta`'s 2.81. Full table in
[`../data/aiparams-s02.txt`](../data/aiparams-s02.txt).
These are exact original values obtained by static RE, so they are usable in the
port directly.
## What is still open
* **The phase advance condition.** The stage record declares `Phase_1..3` and
routes are tagged by phase, but nothing found so far says what *ends* a phase.
* The route-name kind letters `F`, `S`, `A`, `M`, `B`.
* What activates or completes a sub-objective.
* `StageMessageSet_S<NN>.tbl` does not resolve in `GP_MAIN_GAME_E.pak`.
* The `UnitGroup` record key and its member field `n` remain ❔ from
[unit-group-table.md](unit-group-table.md). `n` is now a little more
suspicious: routes are per-squadron, so `n` is unlikely to be a spawn count.