diff --git a/docs/re/BACKLOG.md b/docs/re/BACKLOG.md index 53c40876..e67c0041 100644 --- a/docs/re/BACKLOG.md +++ b/docs/re/BACKLOG.md @@ -267,12 +267,26 @@ search cannot find a *schedule*. on-disc keys are not the source paths. * πŸ”΄ **`STAGES = Static.slb` is a false lead.** It is a field of the *sound* table; `.slb` is an XACT sound bank ([`structures/sound-slb.md`](structures/sound-slb.md)). -* ❔ **Not yet searched:** the ~800 unnamed objects in `GP_MAIN_GAME_.pak`, - and the executable itself. +* βœ… **FOUND β€” it is the `GP_MAIN_GAME_.pak` unnamed objects.** + Sweeping the 811 unnamed entries by schema turned up **schema `3c9ae32e`, the + per-stage definition record**: one per stage (`Stage_S01.xpr` … `Stage_S29.xpr`), + naming that stage's background, resource package, collision set, message set, + nameplates, `MapMesh`/`MapPath`, and β€” the point β€” its + **`EnumerateSquadron = UnitGroup_S.tbl`**. + `stage\UnitGroup_S02.tbl` (`0x019fd129`) is the Stage 02 roster: **112 records**, + 112 squadron IDs (`TCN001`, `ADN101`, `ADT102`, …), and a field vocabulary of + `FormationID` / `AIID` / `SideID` / `Count` / **`DisableInterval`** plus the unit + model (`UN_e010_ADAN_Attacker_S`, matching our XBG7 names), the `MessageSet_*`, + and the pilot character. `DisableInterval` is the first direct evidence of the + timing knob. Same pass also settled `MapPath = test.rgn` β†’ the `REGN` objects. + See [`structures/stage-definition-table.md`](structures/stage-definition-table.md). + πŸ”΄ Refuted along the way: the 16-byte record key is **not** the squadron + ID's name hash (0 of 112). -**First step:** dump the smallest `REGN` object's four sections (counts -`8, 6, 18`) and the three unnamed `DefTables` schemas β€” both are pure static -work, and either could carry per-map trigger or spawn records. +**First step (next):** read the `UnitGroup` per-record payload β€” three u32s per +entry β€” and find where the interval *values* live (the file's second section, +`Formation_*.tbl`, or `EnumSquadron_Test.tbl`). Also: S17–S23 have no stage +record in `GP_MAIN_GAME_E.pak`; find where they are. ## The dynamic-RE state is not in git, and it was gone diff --git a/docs/re/structures/regn-map-grid.md b/docs/re/structures/regn-map-grid.md index 6b0b37a3..f44d4530 100644 --- a/docs/re/structures/regn-map-grid.md +++ b/docs/re/structures/regn-map-grid.md @@ -130,3 +130,16 @@ thing is indexed *by*. πŸ”΄ **But nothing here shows spawn parameters yet.** The header is a spatial partition and no more; the sections are unread. Treat this as the location of the world's spatial data, not as the wave table. + +## βœ… Update: `REGN` is a stage's **MapPath** + +The per-stage definition record (see +[stage-definition-table.md](stage-definition-table.md)) has a field +`MapPath = test.rgn`, and `name_hash("test.rgn") = 0x3506e972`, which is one of +the `REGN` objects in `MiscBin.pak`. The sibling field `MapMesh = test.col` +hashes to `0x2cf7eb47`, an `MCOL` object in the same pak. + +So `.rgn`/`REGN` is stage navigation/path data referenced by the stage record, +and `.col`/`MCOL` is the stage collision mesh. This does not by itself validate +either of the two refuted cell-payload readings recorded above, but it does +explain why the payload looks like a grid of route data. diff --git a/docs/re/structures/stage-definition-table.md b/docs/re/structures/stage-definition-table.md new file mode 100644 index 00000000..d722aebe --- /dev/null +++ b/docs/re/structures/stage-definition-table.md @@ -0,0 +1,137 @@ +# Stage definition table and the squadron (`UnitGroup`) roster + +Status: βœ… for the record vocabulary and the stageβ†’table wiring; +🟑 for the `UnitGroup` record payload; ❔ for the arrival/interval schedule itself. + +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. + +## βœ… The per-stage definition record + +`GP_MAIN_GAME_.pak` contains one IDXD object per stage. Dumped with +`sylpheed-cli pak dump`, a record reads (Stage 02, hash `0x90822a39`): + +``` +BackGroundPackage = game:\hidden\Resource3D\BG_Earth.xpr +StageResourcePackage = game:\hidden\Resource3D\Stage_S02.xpr +EnumerateLocalString = EnumLocalString_Test.tbl +EnumerateUnit = EnumUnit_Test.tbl +EnumerateWeapon = EnumWeapon_Test.tbl +EnumerateCharacter = EnumCharacter_Test.tbl +EnumerateNullFrame = Null_Test.tbl +EnumerateFormation = Formation_Test.tbl +EnumerateSquadron = EnumSquadron_Test.tbl +MessageSet = StageMessageSet_test.tbl +NamePlate = nameplate_test.tbl +CollisionMeshes = CollisionSet_test.bin +MotionEnumTable = game:\hidden\DefTables.pak+EnumGameModel_test.tbl +LodEnumTable = game:\hidden\DefTables.pak+EnumLODSet_test.tbl +MapMesh = test.col +MapPath = test.rgn +AsteroidDefinition = test_s8p1_asteroid.tbl +``` + +23 such records were found in `GP_MAIN_GAME_E.pak`, one per stage, plus one +`_Test`-suffixed Stage_S02 template (`0x7b2e67cd`): + +| stage | squadron table | record hash | +|---|---|---| +| S01–S09 | `UnitGroup_S01..S09.tbl` | `0x8f7c0139` … `0x97ad4939` | +| S10–S16 | `UnitGroup_S10..S16.tbl` | `0x8f9efd2f` … `0x95c3f32f` | +| S24–S29 | `UnitGroup_S24..S29.tbl` | `0x94e0c625` … `0x99ff9325` | + +**Not settled:** S17–S23 have no stage record in `GP_MAIN_GAME_E.pak`. They may +live in another pak, or be named differently. Not yet searched. + +## βœ… `MapPath` / `MapMesh` name the MiscBin objects + +Hashing the literal strings with the recovered `name_hash` resolves them: + +| string | hash | found in | +|---|---|---| +| `test.rgn` | `0x3506e972` | `MiscBin.pak` | +| `test.col` | `0x2cf7eb47` | `MiscBin.pak` | +| `CollisionSet_test.bin` | `0x9ea1fff7` | `MiscBin.pak` | +| `EnumGameModel_test.tbl` | `0xefa15c41` | `DefTables.pak` | + +This settles what the `REGN` blobs in `MiscBin.pak` are for: `.rgn` is the +stage's **MapPath**, not a generic grid β€” see [regn-map-grid.md](regn-map-grid.md). +`.col` (`MCOL`) is the **MapMesh**, i.e. stage collision. + +The unqualified `UnitGroup_S02.tbl` does *not* resolve; the archive key is the +path-qualified form **`stage\UnitGroup_S02.tbl`** (`0x019fd129`), present in all +six `GP_MAIN_GAME_{D,E,F,I,J,S}.pak`. + +## βœ… The squadron roster: `stage\UnitGroup_S02.tbl` + +17898 bytes uncompressed. Its string table gives the complete field vocabulary +of one squadron record: + +* `FormationID` β†’ `Formation_2_Rhino1`, `Formation_TCAF_ArrowHead03_64`, + `Formation_ADAN_Turret07_30`, `Formation_1_only`. The trailing number tracks + squadron size (`_01`, `_02`, `_03`, `_12`, `_30`, `_32`, `_64`). +* `AIID` β†’ `AI_TCAF_RhinoFlight`, `AI_TCAF_RhinoLeader`, `AI_TCAF_BirdFlight`, + `AI_TCAF_CraftSquadron`, `AI_TCAF_Fleet`, `AI_TCAF_Fleet_Escort`, + `AI_TCAF_Acropolis`, `AI_ADAN_CraftSquadron_{Rookie,Veteran}`, + `AI_ADAN_BomberSquadron_Rookie`, `AI_ADAN_Fleet`, `AI_ADAN_Frigates`, + `AI_Structure`, `AI_TraceRoute`. +* `SideID` β†’ `TCAF`, `ADAN`, `Neutral`. +* `Count` β†’ integer. +* `DisableInterval` β†’ `Yes`/`No`. +* unit model, as `UN___`, which matches the XBG7 mesh names we + already decode: `UN_f001_TCAF_DeltaSaber_T_Player`, `UN_f003_TCAF_ArrowHead`, + `UN_f105_TCAF_Cruiser`, `UN_f106_TCAF_Destroyer`, `UN_f201_TCAF_Tanker`, + `UN_e007_ADAN_Turret`, `UN_e010_ADAN_Attacker_S`, `UN_e105_ADAN_Cruiser`, + `UN_e106_ADAN_Destroyer`, `UN_e108_ADAN_ASFrigate`, `UN_e201_ADAN_ISCMissile`, + `UN_bf001_TCAF_SchlosBase`. +* `MessageSet_*` β†’ radio chatter set, per named pilot (`MessageSet_Ellen`, + `_Raymond`, `_Gene`, `_Sandra`, `_Billy`, `_Antonius`, `_Carl`, `_Katana`) or + per faction group (`MessageSet_ADAN_plA/plB/plC/plBC/plAC`, + `MessageSet_TCAF_ShipA/B/C`, `MessageSet_TCAF_FleetAA`, `_Supply`). +* character id β†’ `ELLEN`, `RAYMOND`, `GENE`, `SANDRA`, `BILLY`, `ANTONIUS`, + `CARL`, plus `Character_Player_Test`. +* nameplates for capital ships β†’ `NP_Charon`, `NP_Caliban`, `NP_Amalthea`. + +`DisableInterval` is the first direct evidence of the timing knob the mission +scheduler uses: squadrons are gated on an *interval*, and individual records can +opt out of it. That is consistent with the observed behaviour that a mission's +live enemy count rises and falls rather than being fixed. + +## 🟑 Container layout + +The file is a multi-section IDXD container, and `pak dump`'s current labels for +it are wrong β€” it prints the first section's key as "schema". + +``` +0x0000 "IDXD" +0x0004 u32 be = 0x70 = 112 record count +0x0008 112 x 16-byte entries: u32 key, u32 a, u32 b, u32 c +0x0708 next section: u32 tag (0x c5c9f110), 0,0, u32 count=0x6f, u32 size=0x46a, + then the SAME 112 keys again with a narrower payload +... +0x3c1d 112 squadron-ID strings: TCN001.. ADN101.. ADT102.. TCS151.. ADS151.. + (prefixes ADN, ADS, ADT, TCN, TCS, TCT) +0x3f26 field-name / value string pool (the vocabulary above) +``` + +The 112Γ—16 entry array was confirmed by boundary, not assumed: keys are strictly +increasing for exactly 112 entries and the run breaks at `0x708`, where the next +section header sits. + +### ❌ Refuted: the record key is not the squadron-ID name hash + +Tested directly β€” `name_hash("TCN001") = 0xd639f1a4`, but the first keys are +`0x659aff47, 0x659b0046, 0x669b0047, …`. **0 of 112** keys match any squadron +ID's hash. The keys are something else; their byte structure (`b0` ramping, +`b2` taking small signed values `ff,00,01,02,03`) suggests a packed tuple rather +than a hash, but that is untested. + +## What is still open + +* The per-record payload: which of the three words per entry selects the + formation, the AI, the side, the count. +* The meaning of the record key. +* **The interval schedule itself.** `DisableInterval` names the mechanism but + the interval *values* have not been located β€” they may be in the second + section, in `Formation_*.tbl`, or in `EnumSquadron_Test.tbl`. +* S17–S23 stage records.