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>
95 lines
4.2 KiB
Python
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()
|