re: decode the UnitGroup squadron roster, and correct yesterday's container reading

stage\UnitGroup_S<NN>.tbl is now fully readable. A squadron record is Count
member tuples -- (unit model, message set, n, identity/nameplate) -- followed by
five named fields: Count, SideID, AIID, FormationID, DisableInterval. The
property entries carry their own field names inline, so the tag hash never has
to be inverted.

Two independent self-checks validate it corpus-wide, 1160/1160 each across all
28 stage tables on the disc:

  - the length identity len(fields) == Count * 4 + 5, which is what pins the
    member-tuple width at 4 and the named-field count at 5;
  - agreement with the file's own Enumerate_Squadrons roster, which maps record
    key to squadron id independently of the per-record string offset.

Adds tools/re-capture/unitgroup.py (pure static, runs no emulator) with a
--all --check self-check mode, and commits the Stage 02 dump as evidence.

Corrections to the container layout written yesterday, all three wrong:

  - the 20-byte "(tag, 0, 0, count, size) section header" does not exist. It
    was the file's last 16-byte record followed by a plain npool word. The
    corrected layout is uniform across all 28 files; the old one failed on 9.
  - squadron ids do not use a separate string base. Every offset in the file is
    relative to the one string pool. The earlier "109 of 111" score was an
    artefact of the uniform 7-byte id stride and had silently shifted every
    name by three entries, which is why 17 TC*-named squadrons came out as
    SideID=ADAN. The roster record refuted it outright.
  - the roster is not always the last record; 9 stages put it elsewhere, so it
    is found by its missing Count.

Refuted and kept: the 4-byte record key is not the squadron id's name hash
(0 of 112).

Not settled: what the key encodes, the member tuple's third field n, and where
the arrival interval values live. DisableInterval is only a per-squadron flag
(Yes for 31 of 1160); the durations, triggers and arrival positions are not in
this file. Formation_*.tbl and EnumSquadron_Test.tbl are next.
This commit is contained in:
Sylpheed RE agent
2026-08-24 11:07:33 +00:00
parent 4dcb2f2719
commit ecf6887b76
5 changed files with 528 additions and 28 deletions

View File

@@ -283,10 +283,27 @@ search cannot find a *schedule*.
🔴 Refuted along the way: the 16-byte record key is **not** the squadron
ID's name hash (0 of 112).
**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: S17S23 have no stage
record in `GP_MAIN_GAME_E.pak`; find where they are.
***`UnitGroup` is fully decoded** (2026-08-24). Container and field semantics
in [`structures/unit-group-table.md`](structures/unit-group-table.md), tool
`tools/re-capture/unitgroup.py`, Stage 02 dump committed at
[`data/unitgroup-s02.txt`](data/unitgroup-s02.txt). A squadron record is
`Count` member tuples — (unit model, message set, `n`, identity/nameplate) —
followed by five named fields `Count / SideID / AIID / FormationID /
DisableInterval`. Validated corpus-wide by two independent self-checks, each
**1160/1160** across all 28 stage tables: the `Count*4+5` length identity, and
agreement with the file's own `Enumerate_Squadrons` roster.
🔴 Two refutations recorded: the record key is **not** the squadron-ID name
hash, and an earlier "109/111" reading of the squadron-id string base was an
artefact of the uniform 7-byte id stride (it shifted every name by three).
**First step (next):** find where the arrival *interval values* live.
`DisableInterval` is only a per-squadron flag (`Yes` for 31 of 1160); the
durations, spawn triggers and arrival positions are not in `UnitGroup`. The
stage record names two untouched candidates: `Formation_*.tbl` (formation
geometry, possibly its timing) and `EnumSquadron_Test.tbl`. Also still open: the
4-byte record key, the member field `n`, and the missing S17S23 stage records
(S17 has no `UnitGroup_S17.tbl` either, so the whole stage is absent from
`GP_MAIN_GAME_E.pak`).
## The dynamic-RE state is not in git, and it was gone