re: the disc's stage numbers, and the player's craft per stage
The corpus knew '28 stages' and 'six tutorials with no AIParams'. It did not know the NUMBERS, and they are not 1..28. Hashing stage\UnitGroup_S%02d.tbl for N=0..39 against GP_MAIN_GAME_E.pak: 1-16 and 18-29 ship, 17 does not -- 28 files -- and the six with no AIParams_SNN.tbl are exactly 18-23. So S01-S16 story, S17 absent, S18-S23 tutorials, S24-S29 story: 22 + 6. eng\GP_HANGAR_ARSENAL_3D.tbl's ResourceID record keys a player-craft mesh by stage: Unit_St1_6 -> rou_f001 (DeltaSaber T), Unit_St7_16 -> rou_f002 (W), plus six further fields tagged with the raw numbers 24,25,26,27,28,29 -> rou_f002 x4, rou_f004 (DeltaSaber A) at 28, rou_f002. The bare tags are the last six story-stage numbers. Control in the same record: tag_hash(name) == tag for 11/11 named fields, so those six genuinely carry no name. Cross-check from a different file: grouping the Arsenal pak's 168 stage-scoped entries by which _Player craft their loadout mounts gives f001 = 6, f002 = 15, f004 = 1, tutorials 5+1 = 6, and every number closes against ResourceID -- 6 = Unit_St1_6; 15 = Unit_St7_16 (10) plus tags 24,25,26,27,29 (5); 1 = tag 28; 6+15+1+6 = 28. So the player flies the DeltaSaber A in exactly one mission, S28, and the DeltaSaber T only for the first six. Not settled: the Arsenal entry filenames, so which of the 168 is S24 vs S25 is constrained but not pinned. New structure doc, artefact and regenerator; the other five artefacts regenerate byte-identical.
This commit is contained in:
@@ -827,6 +827,35 @@ 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) THE DISC'S STAGE NUMBERS ARE NOT 1..28 — S01-S16, S17
|
||||
ABSENT, S18-S23 the tutorials, S24-S29 — and the player's craft per stage
|
||||
falls out with it.**
|
||||
[structures/stage-numbering-and-player-craft](structures/stage-numbering-and-player-craft.md),
|
||||
artefact `data/stage-numbering.txt`, regenerator
|
||||
`tools/re-capture/stage_numbering.py`.
|
||||
The corpus knew "28 stages" and "six tutorials with no `AIParams`"; it did not
|
||||
know the NUMBERS. Hashing `stage\UnitGroup_S%02d.tbl` for N=0..39:
|
||||
**1-16, 18-29 ship, 17 does not** — 28 files, and the six with no
|
||||
`AIParams_SNN.tbl` are exactly **18-23**. So 22 story stages + 6 tutorials.
|
||||
🔑 `eng\GP_HANGAR_ARSENAL_3D.tbl`'s **`ResourceID`** record keys a player-craft
|
||||
mesh by stage: `Unit_St1_6` -> `rou_f001` (DeltaSaber **T**), `Unit_St7_16` ->
|
||||
`rou_f002` (**W**), and **six further fields tagged with the raw numbers
|
||||
24,25,26,27,28,29** -> `rou_f002` x4, **`rou_f004` (DeltaSaber A) at 28**,
|
||||
`rou_f002`. The bare tags ARE the last six story-stage numbers.
|
||||
🧪 Control in the same record: `tag_hash(name) == tag` for **11/11** named
|
||||
fields, so those six genuinely carry no name.
|
||||
🧪 **Cross-check from a different file** — grouping the Arsenal pak's 168
|
||||
stage-scoped entries by which `_Player` craft their loadout mounts gives
|
||||
**f001 = 6, f002 = 15, f004 = 1, tutorials 5+1 = 6**, and every number closes
|
||||
against `ResourceID`: 6 = `Unit_St1_6`; 15 = `Unit_St7_16` (10) + tags
|
||||
24,25,26,27,29 (5); 1 = tag 28; 6+15+1+6 = **28**.
|
||||
🔑 **The player flies the DeltaSaber A in exactly one mission, S28**, and the
|
||||
DeltaSaber T only for the first six.
|
||||
🟡 Not settled: the Arsenal entry FILENAMES, so which of the 168 is S24 vs S25
|
||||
is constrained but not pinned (`name_hash` probing over 8 templates x 40
|
||||
indices x 6 languages resolved 0; controls `<lang>\weapon.tbl`,
|
||||
`<lang>\strings.tbl`, `<lang>\GP_HANGAR_ARSENAL_3D.tbl` all resolve).
|
||||
|
||||
* ✅ **(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**.
|
||||
|
||||
@@ -99,6 +99,7 @@ files, which is how the same ground got covered twice.
|
||||
| [`structures/sound-cue-table.md`](structures/sound-cue-table.md) | The cue index in `tables.pak` — message id -> cue -> sound id -> `.slb` bank | ✅ CONFIRMED, 1 326/1 338 script message ids bind to a bank; SOUNDS and FILES agree on the same 12 absentees, 0 orphan files |
|
||||
| [`structures/cutscene-message-table.md`](structures/cutscene-message-table.md) | Cutscene dialogue — speaker, portrait, on-screen seconds, audio cue per page | ✅ CONFIRMED, field count = 9·PageCount+2 for all 7 PageCounts, 1 252/1 252 caption keys match, 138 ids close both ways |
|
||||
| [`structures/isl-mission-timer.md`](structures/isl-mission-timer.md) | The mission timer — two sequential countdowns, not a limit and a warning | ✅ CONFIRMED — read from `sub_822639B8`; the threshold reading is |
|
||||
| [`structures/stage-numbering-and-player-craft.md`](structures/stage-numbering-and-player-craft.md) | The disc's stage numbering (S01–S16, S18–S29) and the player's craft per stage | ✅✅ CONFIRMED — two independent files close on 6 + 15 + 1 + 6 = 28 |
|
||||
| [`structures/hangar-loadout-system.md`](structures/hangar-loadout-system.md) | The Hangar loadout system — loadout → per-slot allow-list → arsenal item | ✅ CONFIRMED — 60/60 slot refs, 70/88 item refs (18 = the `No_Equipment` sentinel) |
|
||||
| [`structures/arsenal-item-weapon-chain.md`](structures/arsenal-item-weapon-chain.md) | The 59-of-131 arsenal question — an item names a hardpoint slot, not a weapon | ✅✅ CONFIRMED — 0/59 `PlayerWeapon` are a `Weapon.ID`, 59/59 are a `Turret_NNN` slot |
|
||||
| [`structures/unit-substructure-records.md`](structures/unit-substructure-records.md) | The destructible-subsystem model — a unit's `Turret`/`Bridge`/`Hatch`/`ShieldGenerator`/`Thruster` sub-records | ✅ CONFIRMED — 835 turrets, 835/835 `WeaponID` resolve, `StructureCount` control 667/684 |
|
||||
|
||||
28
docs/re/data/stage-numbering.txt
Normal file
28
docs/re/data/stage-numbering.txt
Normal file
@@ -0,0 +1,28 @@
|
||||
# The disc's stage numbering, and the player's craft per stage
|
||||
# Regenerate: python3 tools/re-capture/stage_numbering.py
|
||||
# See docs/re/structures/stage-numbering-and-player-craft.md
|
||||
|
||||
## which stage numbers actually ship
|
||||
stage\UnitGroup_SNN.tbl N = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29]
|
||||
count 28 ; gap inside the range: [17]
|
||||
stage\AIParams_SNN.tbl N = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 24, 25, 26, 27, 28, 29] (count 22)
|
||||
stages with NO AIParams (the tutorials): [18, 19, 20, 21, 22, 23]
|
||||
|
||||
## ResourceID -- the player craft, keyed by stage
|
||||
tag 24 name <none> rou_f002
|
||||
tag 25 name <none> rou_f002
|
||||
tag 26 name <none> rou_f002
|
||||
tag 27 name <none> rou_f002
|
||||
tag 28 name <none> rou_f004
|
||||
tag 29 name <none> rou_f002
|
||||
tag 2354614773 name Unit_St1_6 rou_f001
|
||||
tag 3281124436 name Unit_St7_16 rou_f002
|
||||
CONTROL tag_hash(name) == tag for the named fields: 11/11
|
||||
|
||||
## CROSS-CHECK the same thing from the Arsenal entries (168 = 28 stages x 6 languages)
|
||||
90 entries = 15 stages UN_f002_TCAF_DeltaSaber_W_Player
|
||||
36 entries = 6 stages UN_f001_TCAF_DeltaSaber_T_Player
|
||||
30 entries = 5 stages UN_f001_TCAF_DeltaSaber_T_Player_Ttrl1
|
||||
6 entries = 1 stages UN_f001_TCAF_DeltaSaber_T_Player_Ttrl2
|
||||
6 entries = 1 stages UN_f004_TCAF_DeltaSaber_A_Player
|
||||
total 28 stages
|
||||
74
docs/re/structures/stage-numbering-and-player-craft.md
Normal file
74
docs/re/structures/stage-numbering-and-player-craft.md
Normal file
@@ -0,0 +1,74 @@
|
||||
# ✅✅ The disc's stage NUMBERING, and which craft the player flies in each
|
||||
|
||||
The corpus has known "the disc has 28 stages" and "the six tutorials have no
|
||||
`AIParams`" for a while. **It did not know the stage NUMBERS.** They are not
|
||||
1…28:
|
||||
|
||||
```
|
||||
S01 … S16 story (16)
|
||||
S17 ABSENT
|
||||
S18 … S23 the tutorials (6) -- exactly the six with no AIParams_SNN.tbl
|
||||
S24 … S29 story (6)
|
||||
== 28 shipped, 22 of them story
|
||||
```
|
||||
|
||||
Measured by hashing `stage\UnitGroup_S%02d.tbl` and `stage\AIParams_S%02d.tbl`
|
||||
for N = 0…39 against `GP_MAIN_GAME_E.pak`. Artefact `../data/stage-numbering.txt`,
|
||||
regenerator `tools/re-capture/stage_numbering.py`.
|
||||
|
||||
## The player's craft, per stage
|
||||
|
||||
`eng\GP_HANGAR_ARSENAL_3D.tbl` holds a `ResourceID` record whose `Unit_St*`
|
||||
fields key a player-craft mesh by stage — and whose **remaining entries are
|
||||
tagged with the raw stage NUMBER instead of a hashed name**:
|
||||
|
||||
| tag | field name | mesh |
|
||||
|---|---|---|
|
||||
| `tag_hash("Unit_St1_6")` | `Unit_St1_6` | `rou_f001` — DeltaSaber **T** |
|
||||
| `tag_hash("Unit_St7_16")` | `Unit_St7_16` | `rou_f002` — DeltaSaber **W** |
|
||||
| **24** | — | `rou_f002` |
|
||||
| **25** | — | `rou_f002` |
|
||||
| **26** | — | `rou_f002` |
|
||||
| **27** | — | `rou_f002` |
|
||||
| **28** | — | **`rou_f004` — DeltaSaber A** |
|
||||
| **29** | — | `rou_f002` |
|
||||
|
||||
🔑 The six bare tags are `24 … 29`, and the disc's last six story stages are
|
||||
**S24 … S29**. That is not a coincidence available to a guess: the numbering was
|
||||
recovered independently, from the shipped filenames. **Control in the same
|
||||
record: `tag_hash(name) == tag` for 11/11 of the named fields**, so the reader is
|
||||
right and these six genuinely carry no name.
|
||||
|
||||
## 🧪 The cross-check — the Arsenal entries say the same thing
|
||||
|
||||
[The Arsenal pak is stage-scoped](hangar-loadout-system.md): 168 entries =
|
||||
28 stages × 6 languages. Grouping them by which `_Player` craft their loadout
|
||||
mounts:
|
||||
|
||||
| stages | player craft |
|
||||
|---:|---|
|
||||
| **6** | `UN_f001_TCAF_DeltaSaber_T_Player` |
|
||||
| **15** | `UN_f002_TCAF_DeltaSaber_W_Player` |
|
||||
| **1** | `UN_f004_TCAF_DeltaSaber_A_Player` |
|
||||
| 5 + 1 | `…_T_Player_Ttrl1` / `_Ttrl2` — **6 tutorials** |
|
||||
|
||||
Every number closes against `ResourceID`, from a different file:
|
||||
|
||||
* **f001 = 6** ⇔ `Unit_St1_6`.
|
||||
* **f002 = 15** ⇔ `Unit_St7_16` (10 stages) **+** tags 24, 25, 26, 27, 29 (5) = 15.
|
||||
* **f004 = 1** ⇔ tag 28 alone.
|
||||
* **tutorials = 5 + 1 = 6** ⇔ the six stages with no `AIParams`.
|
||||
* 6 + 15 + 1 + 6 = **28**.
|
||||
|
||||
🔑 So **the player flies the DeltaSaber A in exactly one mission — S28** — and
|
||||
the DeltaSaber T only for the first six.
|
||||
|
||||
## 🟡 Not settled
|
||||
|
||||
* The Arsenal entry FILENAMES are still unrecovered, so *which* of the 168
|
||||
entries is S24 vs S25 is not directly readable — the craft grouping constrains
|
||||
it but does not pin it. `name_hash` probing over 8 templates × 40 indices × 6
|
||||
languages resolved 0 (controls `<lang>\weapon.tbl`, `<lang>\strings.tbl`,
|
||||
`<lang>\GP_HANGAR_ARSENAL_3D.tbl` all resolve, so the method works).
|
||||
* Why the numbering skips S17 and parks the tutorials at S18–S23 is a production
|
||||
fact this cannot reach.
|
||||
57
tools/re-capture/stage_numbering.py
Normal file
57
tools/re-capture/stage_numbering.py
Normal file
@@ -0,0 +1,57 @@
|
||||
#!/usr/bin/env python3
|
||||
"""The disc's stage NUMBERING, and which craft the player flies in each.
|
||||
|
||||
`eng\\GP_HANGAR_ARSENAL_3D.tbl`'s `ResourceID` record maps stage ranges to a
|
||||
player-craft mesh; the Arsenal pak's 168 stage-scoped entries say the same thing
|
||||
from the other side. Regenerates docs/re/data/stage-numbering.txt.
|
||||
"""
|
||||
import sys, os, glob, collections
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.insert(0, HERE)
|
||||
import unitgroup as U
|
||||
from unit_substructures import pak_entries
|
||||
|
||||
def main():
|
||||
mg = glob.glob('/work/sylph_extract/**/GP_MAIN_GAME_E.pak', recursive=True)[0]
|
||||
ars = glob.glob('/work/sylph_extract/**/GP_HANGAR_ARSENAL.pak', recursive=True)[0]
|
||||
H = {h for h, b in pak_entries(mg)}
|
||||
E = {h: b for h, b in pak_entries(ars)}
|
||||
|
||||
print("# The disc's stage numbering, and the player's craft per stage")
|
||||
print("# Regenerate: python3 tools/re-capture/stage_numbering.py")
|
||||
print("# See docs/re/structures/stage-numbering-and-player-craft.md")
|
||||
|
||||
ug = [i for i in range(0, 40) if U.name_hash('stage\\UnitGroup_S%02d.tbl' % i) in H]
|
||||
ai = [i for i in range(0, 40) if U.name_hash('stage\\AIParams_S%02d.tbl' % i) in H]
|
||||
print("\n## which stage numbers actually ship")
|
||||
print(" stage\\UnitGroup_SNN.tbl N = %s" % ug)
|
||||
print(" count %d ; gap inside the range: %s"
|
||||
% (len(ug), [i for i in range(min(ug), max(ug) + 1) if i not in ug]))
|
||||
print(" stage\\AIParams_SNN.tbl N = %s (count %d)" % (ai, len(ai)))
|
||||
print(" stages with NO AIParams (the tutorials): %s" % [i for i in ug if i not in ai])
|
||||
|
||||
recs = U.parse(E[U.name_hash('eng\\GP_HANGAR_ARSENAL_3D.tbl')])
|
||||
rid = [r for r in recs if r['squadron'] == 'ResourceID'][0]
|
||||
print("\n## ResourceID -- the player craft, keyed by stage")
|
||||
for t, n, v in rid['fields']:
|
||||
if n and not n.startswith('Unit_St'): continue
|
||||
print(" tag %-10s name %-14s %s" % (t, n if n else '<none>', v))
|
||||
print(" CONTROL tag_hash(name) == tag for the named fields: %d/%d"
|
||||
% (sum(1 for t, n, v in rid['fields'] if n and U.tag_hash(n) == t),
|
||||
sum(1 for t, n, v in rid['fields'] if n)))
|
||||
|
||||
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)]
|
||||
pc = collections.Counter()
|
||||
for h, rs in withu:
|
||||
ids = {U.named(r).get('UnitID', '') for r in rs if 'PlayerUnit' in U.named(r)}
|
||||
pc[tuple(sorted(ids))] += 1
|
||||
print("\n## CROSS-CHECK the same thing from the Arsenal entries (%d = 28 stages x 6 languages)"
|
||||
% len(withu))
|
||||
for t, c in pc.most_common():
|
||||
print(" %3d entries = %2d stages %s" % (c, c // 6, ", ".join(t)))
|
||||
print(" total %d stages" % sum(c // 6 for c in pc.values()))
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user