Files
Sylpheed/tools/re-capture/arsenal_chain.py
Sylpheed RE agent 21e710a531 re: the 59-of-131 arsenal question is closed -- an item names a hardpoint
An Arsenal item does not reference a Weapon record.  It references a
Turret_NNN HARDPOINT SLOT on the player craft's own unit table, and the
slot is what carries the WeaponID.  Three hops:

  Arbalest_155KG.PlayerWeapon -> Turret_050  (a slot on
  UN_f001_TCAF_DeltaSaber_T_Player) -> .WeaponID ->
  Weapon_DSaber_P_wep_50_Cannon

Controls, both in the same loop: 0/59 distinct PlayerWeapon values are a
Weapon.ID; 59/59 are a Turret_NNN slot id; the full chain lands on a
Weapon.ID 59/59.  WingmanWeapon resolves identically.  The WEAPONS
roster's 59 = 55 item names + 4 empty-slot sentinels.

Wingmen fly a cheaper gun: following the same 59 slots across craft
variants, the _Player tables give each item its own weapon record (59
distinct) while the AI tables collapse all 59 onto 10 generic classes.
That is most of the 131.

Upgrades yesterday's 'hardpoint catalogue' reading from 21 to adopted,
proved from an independent file, and corrects its '10 distinct WeaponID'
figure -- that was the AI variant, not the player's.

Also adds an __main__ guard to unit_substructures.py so importing
pak_entries from it does not run its report; its artefact is unchanged
and still byte-identical.
2026-08-27 12:49:22 +00:00

94 lines
4.2 KiB
Python

#!/usr/bin/env python3
"""Resolve the arsenal item -> hardpoint slot -> weapon record chain.
An Arsenal item in GP_HANGAR_ARSENAL.pak names a HARDPOINT SLOT on the player
craft (`PlayerWeapon = Turret_050`), not a weapon. The slot's `WeaponID` is
what points into the 131-record `Weapon` datasheet. Regenerates
docs/re/data/arsenal-chain.txt.
"""
import sys, os, glob, re, collections
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
import unitgroup as U
from unit_substructures import pak_entries
ITEMF = {'Dependency', 'MissionObjective', 'Model', 'Package',
'PlayerWeapon', 'Points', 'Power', 'Range'}
def main():
ars = glob.glob('/work/sylph_extract/**/GP_HANGAR_ARSENAL.pak', recursive=True)[0]
mg = glob.glob('/work/sylph_extract/**/GP_MAIN_GAME_E.pak', recursive=True)[0]
slots = collections.defaultdict(dict); wids = set()
for h, b in pak_entries(mg):
if b[:4] != b'IDXD': continue
try: recs = U.parse(b)
except Exception: continue
for r in recs:
if r['squadron'] == 'Weapon':
i = U.named(r).get('ID')
if i: wids.add(i)
g = [r for r in recs if r['squadron'] == 'Generic']
if not (g and 'HP' in U.named(g[0])): continue
uid = U.named(g[0]).get('ID')
for r in recs:
if re.fullmatch(r'Turret_\d{3}', r['squadron']):
slots[uid][r['squadron']] = U.named(r).get('WeaponID')
items = {}; roster = None
for h, b in pak_entries(ars):
if b[:4] != b'IDXD': continue
try: recs = U.parse(b)
except Exception: continue
for r in recs:
if r['squadron'] == 'WEAPONS' and roster is None: roster = r
if ITEMF <= set(U.named(r)): items.setdefault(r['squadron'], r)
print("# The Arsenal item -> hardpoint slot -> Weapon record chain")
print("# Regenerate: python3 tools/re-capture/arsenal_chain.py")
print("# See docs/re/structures/arsenal-item-weapon-chain.md")
rset = {v for _, _, v in roster['fields']}
print("\n## the roster and the item records")
print(" WEAPONS roster values %3d" % len(rset))
print(" arsenal ITEM records %3d" % len(items))
print(" item names that are roster values %3d" % len(set(items) & rset))
print(" roster values with no item record: %s" % sorted(rset - set(items)))
print(" Weapon.ID values in the datasheet %3d" % len(wids))
print("\n## CONTROL item.PlayerWeapon is NOT a Weapon.ID")
pw = {U.named(r).get('PlayerWeapon', '').strip() for r in items.values()}
print(" distinct PlayerWeapon values %3d ; how many are a Weapon.ID: %d" % (len(pw), len(pw & wids)))
print("\n## CONTROL item.PlayerWeapon IS a Turret_NNN slot id")
for craft in sorted(slots):
if 'Saber' not in craft: continue
s = slots[craft]
hit = sum(1 for r in items.values() if U.named(r).get('PlayerWeapon', '').strip() in s)
wing = sum(1 for r in items.values() if U.named(r).get('WingmanWeapon', '').strip() in s)
print(" %-40s slots %2d Player %2d/%d Wingman %2d/%d" %
(craft, len(s), hit, len(items), wing, len(items)))
print("\n## THE CHAIN, per craft variant: item -> slot -> WeaponID -> Weapon.ID")
for craft in sorted(slots):
if 'Saber' not in craft: continue
s = slots[craft]
reach = [s.get(U.named(r).get('PlayerWeapon', '').strip()) for r in items.values()]
land = [w for w in reach if w in wids]
print(" %-40s %3d/%3d land on a Weapon.ID, %2d distinct" %
(craft, len(land), len(items), len(set(land))))
vs = [v for v in s.values() if v and v != 'Weapon_NULL']
print(" slot WeaponIDs: %d non-NULL, %d distinct, e.g. %s" %
(len(vs), len(set(vs)), sorted(set(vs))[:2]))
s = slots['UN_f001_TCAF_DeltaSaber_T_Player']
used = {U.named(r).get('PlayerWeapon', '').strip() for r in items.values()}
print("\n player craft slots not referenced by any item: %s" % sorted(set(s) - used))
k = sorted(items)[0]
print("\n## a full item record -- %s" % k)
for kk, vv in sorted(U.named(items[k]).items()):
print(" %-22s %s" % (kk, vv))
main()