diff --git a/docs/re/BACKLOG.md b/docs/re/BACKLOG.md index f877a9b9..070014ee 100644 --- a/docs/re/BACKLOG.md +++ b/docs/re/BACKLOG.md @@ -827,6 +827,41 @@ unknown, what evidence exists, and what the first step would be. Move an item in field; the stat keys in records not opened here (`MegaTons`, `BulletLimit`, `CrewCount`, `HP_CLASS`); why the roster lists 59 of 131. +* โœ… **(2026-08-27) WHO SELECTS A LOADOUT: THE PAK ENTRY DOES โ€” the Arsenal pak + is STAGE-SCOPED, 168 entries = 28 stages x 6 languages.** Also a **correction + to the entry below**. + [structures/hangar-loadout-system](structures/hangar-loadout-system.md), + artefact `data/hangar-loadouts.txt` (+40 lines, 1 changed heading). + ๐Ÿงช 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 x 6**, and the other 12 = 2 x 6 (the item + table and a sibling). Each entry is one stage's whole Hangar config. + ๐Ÿ”‘ The `UNITS` roster is **the flight for that stage**, and it changes across + the campaign as the story does โ€” 5 distinct rosters over 15 / 5 / 5 / 2 / 1 + stages: Bird 1-4 + `Rhino1-Katana`/`Rhino2-Ellen`/`Rhino3-Gene`/`Rhino4-Yoji`; + a Raymond/Gene/Katana/Ellen variant; **`Rhino2-Katana` ALONE** (5 stages); + a 7-pilot variant; and one with `Rhino4-Brandon`. **Every count is a multiple + of 6 and they sum to 28** โ€” the disc's stage count by a fourth independent + route. + โš ๏ธ **CORRECTION to the entry below**: 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. The contents genuinely vary per entry โ€” + `PlayerSET_ARM1` has **5** distinct contents, `STANDARD_ARM1` **3**, + `ExSET_NOSE` **4**; the `SNNSET_*`/`NULL_*` sets have exactly 1 each. 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 + `\weapon.tbl` and `\strings.tbl` resolved **12/12** โ€” the method + works, the convention is not one of the eight guessed. So which entry is which + stage is still unknown. + * โœ… **(2026-08-27) THE HANGAR LOADOUT SYSTEM โ€” the layer above the item chain.** [structures/hangar-loadout-system](structures/hangar-loadout-system.md), artefact `data/hangar-loadouts.txt`, regenerator diff --git a/docs/re/data/hangar-loadouts.txt b/docs/re/data/hangar-loadouts.txt index 1abcdec6..813613b4 100644 --- a/docs/re/data/hangar-loadouts.txt +++ b/docs/re/data/hangar-loadouts.txt @@ -2,9 +2,48 @@ # Regenerate: python3 tools/re-capture/hangar_loadouts.py # See docs/re/structures/hangar-loadout-system.md -## counts +## the pak is STAGE-SCOPED -- one config entry per stage per language + total pak entries 1538 IDXD entries 180 + entries carrying a UNITS roster 168 = 28 stages x 6 languages + entries without one 12 = 2 x 6 (the item table + siblings) + every roster count is a multiple of 6: True ; they sum to 28 stages + +## the 5 distinct UNITS rosters -- who is in the flight, per stage + 15 stage(s) 8 pilots: Bird1-Sandra, Bird2-Billy, Bird3-Antonius, Bird4-Carl, Rhino1-Katana, Rhino2-Ellen, Rhino3-Gene, Rhino4-Yoji + 5 stage(s) 8 pilots: Bird1-Sandra, Bird2-Billy, Bird3-Antonius, Bird4-Carl, Rhino1-Raymond, Rhino2-Gene, Rhino3-Katana, Rhino4-Ellen + 5 stage(s) 1 pilots: Rhino2-Katana + 2 stage(s) 7 pilots: Bird1-Sandra, Bird2-Billy, Bird3-Antonius, Bird4-Carl, Rhino1-Raymond, Rhino2-Katana, Rhino3-Ellen + 1 stage(s) 4 pilots: Rhino1-Katana, Rhino2-Ellen, Rhino3-Gene, Rhino4-Brandon + +## counts, UNION over all entries (each name first-seen) arsenal items 59 loadout records 15 per-slot allow-lists 24 +## allow-list CONTENT VARIES BY ENTRY -- distinct contents per name + ExSET_ARM1 3 + ExSET_ARM2 3 + ExSET_ARM3 3 + ExSET_NOSE 4 + NULL_ARM1 1 + NULL_ARM2 1 + NULL_ARM3 1 + NULL_NOSE 1 + PlayerSET_ARM1 5 + PlayerSET_ARM2 5 + PlayerSET_ARM3 4 + PlayerSET_NOSE 4 + S01SET_ARM1 1 + S01SET_ARM2 1 + S01SET_ARM3 1 + S01SET_NOSE 1 + S02SET_ARM1 1 + S02SET_ARM2 1 + S02SET_ARM3 1 + S02SET_NOSE 1 + STANDARD_ARM1 3 + STANDARD_ARM2 3 + STANDARD_ARM3 3 + STANDARD_NOSE 2 + ## loadout records Bird1-Sandra S01SET_ARM1 S01SET_ARM2 S01SET_ARM3 S01SET_NOSE UnitID=SANDRA (character) Bird2-Billy S01SET_ARM1 S01SET_ARM2 S01SET_ARM3 S01SET_NOSE UnitID=BILLY (character) diff --git a/docs/re/structures/hangar-loadout-system.md b/docs/re/structures/hangar-loadout-system.md index f5efe1cd..c65c0d10 100644 --- a/docs/re/structures/hangar-loadout-system.md +++ b/docs/re/structures/hangar-loadout-system.md @@ -7,6 +7,23 @@ 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`, @@ -79,9 +96,43 @@ two Arm1 items, `STANDARD_ARM1` offers eleven. 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 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 +`\weapon.tbl` and `\strings.tbl` resolved **12/12**, so the method +works and the naming convention is simply not one of the eight guessed. + ## ๐ŸŸก Not settled -* Who *selects* a loadout record. The names encode flight position and pilot, +* 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. diff --git a/tools/re-capture/hangar_loadouts.py b/tools/re-capture/hangar_loadouts.py index 1f848507..6f0279eb 100644 --- a/tools/re-capture/hangar_loadouts.py +++ b/tools/re-capture/hangar_loadouts.py @@ -43,12 +43,40 @@ def main(): elif set(SLOTS) <= set(n): loadouts.setdefault(nm, r) elif set(n) == {'Type'}: allow.setdefault(nm, r) + idxd = [(h, U.parse(b)) for h, b in pak_entries(ars) if b[:4] == b'IDXD'] + withu = [(h, r) for h, r in idxd if any(x['squadron'] == 'UNITS' for x in r)] + rosters = collections.Counter() + for h, recs in withu: + for r in recs: + if r['squadron'] == 'UNITS': + rosters[tuple(sorted(n for t, n, v in r['fields']))] += 1 + print("# The Hangar loadout system") print("# Regenerate: python3 tools/re-capture/hangar_loadouts.py") print("# See docs/re/structures/hangar-loadout-system.md") - print("\n## counts") + print("\n## the pak is STAGE-SCOPED -- one config entry per stage per language") + print(" total pak entries %4d IDXD entries %3d" % (len(pak_entries(ars)), len(idxd))) + print(" entries carrying a UNITS roster %3d = %d stages x 6 languages" + % (len(withu), len(withu) // 6)) + print(" entries without one %3d = %d x 6 (the item table + siblings)" + % (len(idxd) - len(withu), (len(idxd) - len(withu)) // 6)) + print(" every roster count is a multiple of 6: %s ; they sum to %d stages" + % (all(c % 6 == 0 for c in rosters.values()), sum(c // 6 for c in rosters.values()))) + print("\n## the %d distinct UNITS rosters -- who is in the flight, per stage" % len(rosters)) + for t, c in rosters.most_common(): + print(" %2d stage(s) %d pilots: %s" % (c // 6, len(t), ", ".join(t))) + + print("\n## counts, UNION over all entries (each name first-seen)") print(" arsenal items %3d loadout records %3d per-slot allow-lists %3d" % (len(items), len(loadouts), len(allow))) + var = collections.defaultdict(set) + for h, recs in idxd: + for r in recs: + if set(U.named(r)) == {'Type'}: + var[r['squadron']].add(tuple(v for t, n, v in r['fields'] if n is None)) + print("\n## allow-list CONTENT VARIES BY ENTRY -- distinct contents per name") + for k in sorted(var): + print(" %-18s %d" % (k, len(var[k]))) print("\n## loadout records") for nm in sorted(loadouts):