Files
Sylpheed/docs/re/structures/hangar-loadout-system.md
Sylpheed RE agent 2b9709eaa0 re: who selects a loadout -- the pak entry does; the Arsenal pak is stage-scoped
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.
2026-08-27 13:09:07 +00:00

141 lines
7.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ✅ The Hangar loadout system — loadout → per-slot allow-list → arsenal item
Finishes the Hangar. [The item chain](arsenal-item-weapon-chain.md) 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.pak` has **180 IDXD entries in 10 distinct shapes**, and
> **168 of them = 28 stages × 6 languages** carry a `UNITS` roster. Each entry is
> **one stage's Hangar configuration**: its own `UNITS` roster, its own loadout
> records, its own allow-lists. The first pass read the pak with `setdefault`,
> keeping only the **first-seen** copy of each record name — so "15 loadouts / 24
> allow-lists / `STANDARD_ARM1` has 11 entries" is the union of first-seen, not a
> single table. The allow-list contents genuinely vary: `PlayerSET_ARM1` has **5**
> distinct contents across entries, `STANDARD_ARM1` **3**, `ExSET_NOSE` **4**;
> the `SNNSET_*` and `NULL_*` 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](arsenal-item-weapon-chain.md)), 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 `-Katana` rows are the **player**:
they carry an extra (empty) `PlayerUnit` field and point at a `_Player` craft.
* **8 rows name a character** — `SANDRA`, `BILLY`, `ANTONIUS`, `CARL`,
`RAYMOND`, `GENE` ×2, `YOJI`. None is a `Generic.ID` on its own; all resolve
as **`Character` + the value** into the 64-record character table
([the `Generic` partition](unit-datasheet-static.md)).
## 🟡 Two values that resolve to nothing
* **`Rhino2-Ellen.UnitID = UN_f001_TCAF_DeltaSaber_W`.** No such unit exists —
checked as a `Generic.ID` across *every* pak, and as a record name: 0 hits.
`UN_f002_TCAF_DeltaSaber_W` does exist, and `Rhino1-Katana` uses
`UN_f002_TCAF_DeltaSaber_W_Player`. **Consistent with a shipped `f001`/`f002`
typo** — but what the game does when the lookup fails is not shown here, so
this is reported, not diagnosed.
* **`Rhino4-Brandon.UnitID = BRANDON`** — no `CharacterBRANDON` among the 64.
`CharacterKATANA` does 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 the `UNITS` record**,
i.e. `UNITS` is the index over them.
* They are **not in the executable** either. **Control:** `WEAPONS` (a section
key) *is* in the executable; `Arbalest_155KG` is 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 14 (Sandra, Billy, Antonius, Carl) + `Rhino1-Katana`, `Rhino2-Ellen`, `Rhino3-Gene`, `Rhino4-Yoji` |
| 5 | Bird 14 + `Rhino1-Raymond`, `Rhino2-Gene`, `Rhino3-Katana`, `Rhino4-Ellen` |
| 5 | `Rhino2-Katana` **alone** |
| 2 | Bird 14 + `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](stage-definition-table.md) has
`AI_TCAF_RhinoFlight` / `AI_TCAF_BirdFlight` — the join is not shown here.
* `ExSET_*`, `S02SET_*` and `NULL_*` are referenced by no loadout in this pak.
* The `PlayerUnit` field is present but empty on all three `-Katana` rows.
* `IGNORE` (13 item names) — a blocklist for something unread.