Files
Sylpheed/tools/re-capture/arsenal_chain.py
sim e909c7c133 chore: retire the last dead paths and names from the consolidation
Nothing here changes what a tool computes; it changes where tools look.

- tools/re-capture: 33 censuses globbed /work/sylph_extract, a path that has
  existed nowhere since /work became a clone, so they matched nothing and
  printed empty results. They now resolve the disc through a new disc.py
  from $SYLPHEED_DISC and exit loudly without it (the #44 fix, generalised).
  Nine scripts that imported siblings from the retired Reborn checkout or an
  old session scratchpad now import from their own directory. unitgroup.py
  only needs the variable when --pak is not given.
- sylpheed-xex: the loader only ever uses the XEX2 retail key. The dead
  devkit key and a doc comment claiming a devkit fallback that does not
  exist are gone; Project Sylpheed is a retail XEX2, so no XEX1 key either.
- sylpheed-viewer: real_font_rasterizes looked for /tmp/sylph_extract and so
  always skipped. It reads $SYLPHEED_DISC now, and passes against the disc.
- Comments and docs that named xenia-rs, the Reborn repository or /work/*.pe
  as places to look now name sylpheed.db, Canary's ppc_context.h and the
  flat .pe; docs/re/README.md no longer says the native Canary build does not
  run.

Historical records keep their original paths: findings that were measured
against /work/xenia-rs/sylpheed.db still say so.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 22:30:28 +02:00

95 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
from disc import disc_root
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(disc_root() + '/**/GP_HANGAR_ARSENAL.pak', recursive=True)[0]
mg = glob.glob(disc_root() + '/**/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()