re: CoverArea is a 6-bit MOUNT mask; the three other turret leftovers closed
Six bits, never more. Over all 835 turrets: 41 distinct values, max 0x3f, bits 6/7 set on none; per-bit 709/117/274/274/260/237. 0x00 (34) = the empty Weapon_NULL hardpoints with YawLimit 0; 0x01 alone (439) = craft hardpoints with YawLimit 2.5/1.0/0; 2-6 bits (362) = warship mounts with YawLimit 45-180. It tracks the MOUNT, not the weapon. UN_e107_ADAN_AAFrigate has eight identical AAFrigate_AAGun turrets: GN_GunXS_01..04 are 0x1d and 05..08 are 0x2d -- same gun, same YawLimit 120, different mask. The Battleship spreads five masks over GN_TGunL_01..05 / GN_TGunM_02..03, all firing the same CAF_Ship_ASGun. Which sector each bit denotes is NOT determined; six bits and the name invite +-X/+-Y/+-Z or six hull faces, but nothing static fixes the convention. Not adopted. Refuted on the way: bits 2 and 3 are not a mutually-exclusive pair -- 188 turrets set both. The 26 weapons no turret mounts are a coherent set: 11 _P player variants, the nose/twin mounts, two _Child sub-munitions, the three S16Boss_*, three Weapon_Test_*, and ADAN_Attacker_S_GunTurret. Versatile_NNN ships nowhere -- 0 populated records across all 41 archives, only the ??? template row. The two units one turret short: both extras are missile mounts with no Frame. Elan_EX4 = NoseGun + Missile, both mask 0x01, both frameless; AAFrigate_EX4 = eight framed guns plus one Ship_AAMissile at 0x0c with no Frame. n=2, stated as the observed pattern, not a rule. New artefact and regenerator; the other nine regenerate byte-identical.
This commit is contained in:
109
tools/re-capture/turret_coverarea.py
Normal file
109
tools/re-capture/turret_coverarea.py
Normal file
@@ -0,0 +1,109 @@
|
||||
#!/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
|
||||
|
||||
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('/work/sylph_extract/**/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('/work/sylph_extract/**/*.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()
|
||||
Reference in New Issue
Block a user