Files
Sylpheed/tools/re-capture/turret_coverarea.py
sim ac0ad579fd 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

111 lines
4.6 KiB
Python

#!/usr/bin/env python3
"""`Turret_NNN.CoverArea`, and the three other turret leftovers.
Regenerates docs/re/data/turret-coverarea.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
TUR = re.compile(r'Turret_\d{3}')
VER = re.compile(r'Versatile_\d{3}')
def units_of(pak):
out = []
for h, b in pak_entries(pak):
if b[:4] != b'IDXD': continue
try: recs = U.parse(b)
except Exception: continue
g = [r for r in recs if r['squadron'] == 'Generic']
if g and 'HP' in U.named(g[0]):
out.append((recs, U.named(g[0])))
return out
def wclass(w):
for k in ('AAGun', 'ASGun', 'Missile', 'Beam', 'Laser', 'Cannon', 'Gun', 'NULL'):
if k.lower() in w.lower(): return k
return w or '<empty>'
def main():
mg = glob.glob(disc_root() + '/**/GP_MAIN_GAME_E.pak', recursive=True)[0]
units = units_of(mg)
rows = [(gn.get('ID'), gn.get('Type'), U.named(r))
for recs, gn in units for r in recs if TUR.fullmatch(r['squadron'])]
m = lambda n: int(n.get('CoverArea', '0x0'), 16)
print("# Turret CoverArea, and the turret leftovers")
print("# Regenerate: python3 tools/re-capture/turret_coverarea.py")
print("# See docs/re/structures/unit-substructure-records.md")
print("\n## CoverArea is a 6-BIT mask")
print(" turrets %d ; distinct values %d ; max 0x%02x ; any bit >= 6 set: %s"
% (len(rows), len({m(n) for _, _, n in rows}), max(m(n) for _, _, n in rows),
any(m(n) >> 6 for _, _, n in rows)))
print(" per-bit set count:")
for b in range(8):
print(" bit %d %4d" % (b, sum(1 for _, _, n in rows if m(n) >> b & 1)))
pc = collections.Counter(bin(m(n)).count('1') for _, _, n in rows)
print(" popcount: %s" % dict(sorted(pc.items())))
print("\n## it tracks the MOUNT, not the weapon")
agg = collections.defaultdict(collections.Counter)
for _, _, n in rows:
agg[m(n)][wclass(n.get('WeaponID', ''))] += 1
print(" mask n weapon classes")
for k in sorted(agg, key=lambda k: -sum(agg[k].values())):
t = sum(agg[k].values())
if t < 10: continue
print(" 0x%02x %4d %s" % (k, t, dict(agg[k].most_common(4))))
print(" worked example -- one hull, one weapon, masks differ by mount:")
for uid in ('UN_e107_ADAN_AAFrigate', 'UN_f104_TCAF_Battleship'):
for recs, gn in units:
if gn.get('ID') != uid: continue
print(" %s" % uid)
for r in recs:
if not TUR.fullmatch(r['squadron']): continue
n = U.named(r)
print(" mask 0x%02x Frame=%-20s Yaw=%-7s %s"
% (m(n), n.get('Frame'), n.get('YawLimit'), n.get('WeaponID', '')))
wid, mounted, vers = set(), set(), collections.Counter()
for pk in sorted(glob.glob(disc_root() + '/**/*.pak', recursive=True)):
for h, b in pak_entries(pk):
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: wid.add(i)
if VER.fullmatch(r['squadron']): vers[os.path.basename(pk)] += 1
g = [r for r in recs if r['squadron'] == 'Generic']
if not (g and 'HP' in U.named(g[0])): continue
for r in recs:
if TUR.fullmatch(r['squadron']):
w = U.named(r).get('WeaponID')
if w: mounted.add(w)
print("\n## the weapons no turret mounts")
print(" Weapon.ID %d ; mounted %d ; never %d" % (len(wid), len(wid & mounted), len(wid - mounted)))
for w in sorted(wid - mounted):
print(" %s" % w)
print("\n## Versatile_NNN across all 41 archives")
print(" populated records: %s" % (dict(vers) or 'NONE — only the `???` template row'))
print("\n## the two units whose turret count is one short")
for recs, gn in units:
uid = gn.get('ID')
if uid not in ('UN_e001_ADAN_Elan_EX4', 'UN_e107_ADAN_AAFrigate_EX4'): continue
sc = U.named([r for r in recs if r['squadron'] == 'StructureCount'][0])
ts = [U.named(r) for r in recs if TUR.fullmatch(r['squadron'])]
print(" %s TurretCount=%s records=%d" % (uid, sc.get('TurretCount'), len(ts)))
for n in ts:
print(" mask 0x%02x Frame=%-20r %s" % (m(n), n.get('Frame'), n.get('WeaponID')))
if __name__ == "__main__":
main()