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:
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