Settled by elimination with two controls. The 15 loadout names appear as a field VALUE nowhere on the disc -- 0 occurrences across every pak -- while the control Arbalest_155KG, which is referenced, appears 150 times as a value in the same pak. They occur only as the field names of the UNITS record. They are not in the executable either; control: WEAPONS (a section key) is, Arbalest_155KG is not. So nothing references a loadout by name. The selection is which pak entry the Hangar loads. GP_HANGAR_ARSENAL.pak has 180 IDXD entries in 10 shapes; 168 carry a UNITS roster = 28 stages x 6 languages, and the other 12 = 2 x 6. Each entry is one stage's whole Hangar config, and its UNITS roster is the flight for that stage -- 5 distinct rosters over 15 / 5 / 5 / 2 / 1 stages, including one where Rhino2-Katana flies alone. Every count is a multiple of 6 and they sum to 28, the disc's stage count by a fourth independent route. Corrects the previous commit: it read the pak with setdefault, so '15 loadouts / 24 allow-lists / STANDARD_ARM1 has 11 entries' is the union of first-seen records, not one table. Contents vary per entry -- PlayerSET_ARM1 has 5 distinct contents, STANDARD_ARM1 3, ExSET_NOSE 4. The chain and both controls (60/60, 70/88) are per-record and unaffected; only the per-list sizes were over-generalised. Not settled: the entry filenames. name_hash probing over 8 templates x 40 indices x 6 languages resolved 0, while the same probe's controls <lang>\weapon.tbl and <lang>\strings.tbl resolved 12/12. Artefact +40 lines / 1 changed heading; the other four regenerate byte-identical.
7.2 KiB
✅ The Hangar loadout system — loadout → per-slot allow-list → arsenal item
Finishes the Hangar. The item chain resolved an
Arsenal item down to a Weapon record; this is the layer above it — who flies
with what, and what the Hangar is allowed to offer for each slot.
Artefact ../data/hangar-loadouts.txt, regenerator
tools/re-capture/hangar_loadouts.py.
⚠️ CORRECTION (2026-08-27, same day) — the pak is STAGE-SCOPED, and the
counts below are a UNION, not one table
GP_HANGAR_ARSENAL.pakhas 180 IDXD entries in 10 distinct shapes, and 168 of them = 28 stages × 6 languages carry aUNITSroster. Each entry is one stage's Hangar configuration: its ownUNITSroster, its own loadout records, its own allow-lists. The first pass read the pak withsetdefault, keeping only the first-seen copy of each record name — so "15 loadouts / 24 allow-lists /STANDARD_ARM1has 11 entries" is the union of first-seen, not a single table. The allow-list contents genuinely vary:PlayerSET_ARM1has 5 distinct contents across entries,STANDARD_ARM13,ExSET_NOSE4; theSNNSET_*andNULL_*sets have exactly 1 each.The structure of the chain below is unaffected — the 60/60 and 70/88 controls are per-record and still hold on the first-seen slice — but treat the per-list sizes as "one stage's", not "the game's".
GP_HANGAR_ARSENAL.pak carries 15 loadout records — Bird1-Sandra,
Bird2-Billy, Bird3-Antonius, Bird4-Carl, Rhino1-Katana,
Rhino1-Raymond, Rhino2-Ellen, Rhino2-Gene, Rhino2-Katana,
Rhino3-Ellen, Rhino3-Gene, Rhino3-Katana, Rhino4-Brandon,
Rhino4-Ellen, Rhino4-Yoji — one per flight position × pilot, each with
Arm1, Arm2, Arm3, Nose and UnitID.
🔑 Same trap as PlayerWeapon, one level up: Arm1/Arm2/Arm3/Nose do
not name items. They name a per-slot ALLOW-LIST record — one of the 24
whose only named field is Type (the slot kind: Arm1/Arm2/Arm3/Nose).
The candidate items live in that record's positional, unnamed fields, in
order. So the full path is four hops:
Rhino4-Yoji.Arm1 ──▶ STANDARD_ARM1 (an allow-list, Type=Arm1)
──▶ [Falcon_9AM, Condor_105AM, …] (arsenal item names, in order)
──▶ item.PlayerWeapon = Turret_NNN (a hardpoint slot)
──▶ slot.WeaponID = Weapon.ID
🧪 The controls
loadout.Arm1/2/3/Nose names an allow-list record |
60 / 60 |
| allow-list positional entries name an arsenal item | 70 / 88 |
| — every one of the 18 misses | the single sentinel No_Equipment |
loadout.UnitID resolves |
5 a unit Generic.ID, 8 a character (as Character+value), 2 neither |
No_Equipment is one of the four WEAPONS-roster values that have no item
record (the item chain), so the residual is the
empty-slot sentinel appearing once per list — not a decode failure.
The six allow-list sets
| set | Arm1 | Arm2 | Arm3 | Nose | used by |
|---|---|---|---|---|---|
STANDARD_* |
11 | 9 | 9 | 8 | Rhino 2/3/4 wingmen |
ExSET_* |
10 | 8 | 4 | 7 | — (not referenced by any loadout here) |
S01SET_* |
2 | 1 | 1 | 1 | Bird flight, Rhino1-Raymond, Rhino3-Ellen |
S02SET_* |
3 | 2 | 2 | 1 | — |
PlayerSET_* |
2 | 1 | 1 | 1 | the three …-Katana rows |
NULL_* |
1 | 1 | 1 | 1 | — (No_Equipment, No_Equipment, No_Equipment, Stiletto_BG1) |
So a wingman's weapon choice is drawn from a small, ordered, per-slot list,
and the SNNSET_ sets are stage-scoped variants of it — S01SET_ARM1 offers
two Arm1 items, STANDARD_ARM1 offers eleven.
UnitID is two ID spaces at once
- 5 rows name a unit —
UN_f001_TCAF_DeltaSaber_T,…_T_Player,…_f002_W_Player. The three-Katanarows are the player: they carry an extra (empty)PlayerUnitfield and point at a_Playercraft. - 8 rows name a character —
SANDRA,BILLY,ANTONIUS,CARL,RAYMOND,GENE×2,YOJI. None is aGeneric.IDon its own; all resolve asCharacter+ the value into the 64-record character table (theGenericpartition).
🟡 Two values that resolve to nothing
Rhino2-Ellen.UnitID = UN_f001_TCAF_DeltaSaber_W. No such unit exists — checked as aGeneric.IDacross every pak, and as a record name: 0 hits.UN_f002_TCAF_DeltaSaber_Wdoes exist, andRhino1-KatanausesUN_f002_TCAF_DeltaSaber_W_Player. Consistent with a shippedf001/f002typo — but what the game does when the lookup fails is not shown here, so this is reported, not diagnosed.Rhino4-Brandon.UnitID = BRANDON— noCharacterBRANDONamong the 64.CharacterKATANAdoes exist, and Katana is the player.
Both are single rows against 13 that resolve, so they are exceptions in the data, not a wrong reading of the field.
✅ Who selects a loadout — the pak ENTRY does
Settled the same day, by elimination with two controls:
- The 15 loadout names appear as a field value nowhere on the disc — 0
occurrences across every pak. Positive control in the same loop: the item
name
Arbalest_155KG, which is referenced, appears 150 times as a value in the same pak. They appear only as the field names of theUNITSrecord, i.e.UNITSis the index over them. - They are not in the executable either. Control:
WEAPONS(a section key) is in the executable;Arbalest_155KGis not — so the executable holds table keys, not row names.
So nothing references a loadout by name. The selection is which pak entry the
Hangar loads, and there is one per stage per language (168 = 28 × 6). The
entry's UNITS roster is the flight for that stage, and it changes across the
campaign exactly as the story does:
| stages | pilots in UNITS |
|---|---|
| 15 | Bird 1–4 (Sandra, Billy, Antonius, Carl) + Rhino1-Katana, Rhino2-Ellen, Rhino3-Gene, Rhino4-Yoji |
| 5 | Bird 1–4 + Rhino1-Raymond, Rhino2-Gene, Rhino3-Katana, Rhino4-Ellen |
| 5 | Rhino2-Katana alone |
| 2 | Bird 1–4 + Rhino1-Raymond, Rhino2-Katana, Rhino3-Ellen |
| 1 | Rhino1-Katana, Rhino2-Ellen, Rhino3-Gene, Rhino4-Brandon |
Every count is a multiple of 6 and they sum to 28 — the disc's stage count, reached here by a fourth independent route.
🟡 The entry FILENAMES are not recovered. Probing name_hash over 8 templates ×
40 indices × 6 languages resolved 0; the same probe's controls
<lang>\weapon.tbl and <lang>\strings.tbl resolved 12/12, so the method
works and the naming convention is simply not one of the eight guessed.
🟡 Not settled
- Which entry is which stage — see the filename note above. The names encode flight position and pilot,
and the stage definition table has
AI_TCAF_RhinoFlight/AI_TCAF_BirdFlight— the join is not shown here. ExSET_*,S02SET_*andNULL_*are referenced by no loadout in this pak.- The
PlayerUnitfield is present but empty on all three-Katanarows. IGNORE(13 item names) — a blocklist for something unread.