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:
@@ -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
|
||||
|
||||
144
docs/re/structures/stage-mission-tables.md
Normal file
144
docs/re/structures/stage-mission-tables.md
Normal 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 1–2.
|
||||
|
||||
### 🔴 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.
|
||||
Reference in New Issue
Block a user