Combat: THE AUTHENTIC DAMAGE ECONOMY -- authored per-weapon amounts through the real fire chain (task #8)
Three root causes, all fixed: 1. kDamageScale was 1.0 -- _DAT_004bafbc is an x87 float80 = 1e-7, cancelling the ctor's x1e7: damagePortion = authored DamageAmount x (charge/seekV)^2 (closed form at fire time; madcat AC=25/LRM=50/ERLL=6, bhk1 PPC=12/SRM=35). The observed "0.25" was the degenerate EC=1 fallback, never authored data. 2. CheckFireEdge NaN latch: TriggerState carries ControlsButton INTS; the release value (-65) is a negative NaN. The binary's x87 unordered compare read it as "released"; IEEE-correct float compares latched the edge detector shut after the first release -- the reason the emitter discharge chain NEVER fired in-game. Fixed with bit-pattern sign compares. 3. The weapon-side submission LIVE: Emitter::FireWeapon fills damageData (amount + damageForce=target-muzzle [the gyro directional-bounce feed] + impact) -> MechWeapon::SendDamageMessage (@004b9728 real body) -> messmgr consolidation with per-weapon records + explosion bundling. The mech4 bring-up damage block + flat kShotDamage retired to diag hooks. Bonus: LODReuseHysteresis 0.82 -> 0.33 (double misread). Zone model verified byte-exact (no change). Solo end-to-end: 5-record volleys (2 PPC + 3 ERML with authored amounts), per-weapon zone granularity, explosions, kill. Heat stays bring-up scale pending the heat-calibration audit [T3]. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
77190c93e5
commit
fd055281a8
@@ -0,0 +1,91 @@
|
||||
import struct
|
||||
data = open(r'C:/git/bt411/content/BTL4.RES','rb').read()
|
||||
n = len(data)
|
||||
|
||||
def cstr(off, maxlen=32):
|
||||
s = data[off:off+maxlen].split(b'\0')[0]
|
||||
try: return s.decode('ascii')
|
||||
except: return None
|
||||
|
||||
def f32(off): return struct.unpack_from('<f', data, off)[0]
|
||||
def i32(off): return struct.unpack_from('<i', data, off)[0]
|
||||
|
||||
DMGTYPE = {0:'Collision',1:'Ballistic',2:'Explosive',3:'Laser',4:'Energy'}
|
||||
|
||||
# find ALL subsystem records: printable name @+0, classID @+0x20 in 0xBB0..0xBE0, size @+0x24 plausible
|
||||
recs = []
|
||||
i = 0
|
||||
while i < n - 0x28:
|
||||
cid = struct.unpack_from('<I', data, i+0x20)[0]
|
||||
if 0xBB0 <= cid <= 0xBE5:
|
||||
sz = struct.unpack_from('<I', data, i+0x24)[0]
|
||||
if 0xE0 <= sz <= 0x300:
|
||||
name = cstr(i)
|
||||
if name and 2 <= len(name) <= 31 and all(32 < ord(c) < 127 for c in name):
|
||||
# name buffer should be null-padded to 32
|
||||
pad = data[i+len(name):i+0x20]
|
||||
if pad.count(0) >= len(pad) - 2:
|
||||
recs.append((i, name, cid, sz))
|
||||
i += 0x28
|
||||
continue
|
||||
i += 1
|
||||
|
||||
print(f'total subsystem records: {len(recs)}')
|
||||
|
||||
# group into chains (consecutive: next record at p+sz)
|
||||
chains = []
|
||||
cur = []
|
||||
for k,(p,name,cid,sz) in enumerate(recs):
|
||||
if cur and cur[-1][0] + cur[-1][3] == p:
|
||||
cur.append(recs[k])
|
||||
else:
|
||||
if cur: chains.append(cur)
|
||||
cur = [recs[k]]
|
||||
if cur: chains.append(cur)
|
||||
|
||||
# mech-name strings for labeling
|
||||
mechnames = [b'madcat', b'bhk', b'vulture', b'thor', b'blackhawk', b'blh', b'owens', b'strider', b'sunder', b'cauldron', b'avatar', b'mdc']
|
||||
low = data.lower()
|
||||
namepos = []
|
||||
for m in mechnames:
|
||||
j = 0
|
||||
while True:
|
||||
j = low.find(m, j)
|
||||
if j < 0: break
|
||||
namepos.append((j, m.decode()))
|
||||
j += 1
|
||||
namepos.sort()
|
||||
|
||||
def nearest_label(p):
|
||||
best = None
|
||||
for pos, name in namepos:
|
||||
if pos < p: best = (pos, name)
|
||||
else: break
|
||||
return best
|
||||
|
||||
WEAP = {0xBC8:'Emitter', 0xBD4:'PPC', 0xBCD:'MechWeapon/Projectile', 0xBCE:'GaussRifle?', 0xBD0:'MissileLauncher'}
|
||||
|
||||
for ci, ch in enumerate(chains):
|
||||
lbl = nearest_label(ch[0][0])
|
||||
has_weap = any(c[2] in WEAP for c in ch)
|
||||
print(f'--- chain {ci} @0x{ch[0][0]:x} n={len(ch)} nearest-name={lbl} roster={[c[1] for c in ch]}')
|
||||
for (p,name,cid,sz) in ch:
|
||||
if cid not in WEAP: continue
|
||||
rr, wr = f32(p+0x190), f32(p+0x194)
|
||||
dmg = f32(p+0x19C); dt = i32(p+0x1A0); heat = f32(p+0x1A4)
|
||||
pip = i32(p+0x1A8); pc = (f32(p+0x1AC), f32(p+0x1B0), f32(p+0x1B4)); per = i32(p+0x1B8)
|
||||
line = (f' {name:24s} cid=0x{cid:X}({WEAP[cid]}) sz=0x{sz:X} Recharge={rr:g} Range={wr:g} '
|
||||
f'Damage={dmg:g} Type={dt}({DMGTYPE.get(dt,"?")}) Heat={heat:g} Pip={pip} PipColor={pc} PipExt={per}')
|
||||
if cid in (0xBC8, 0xBD4) and sz >= 0x1DC:
|
||||
gl, dtm = f32(p+0x1BC), f32(p+0x1C0)
|
||||
sv = [f32(p+0x1C4+4*k) for k in range(5)]
|
||||
ri = i32(p+0x1D8)
|
||||
line += f' | GraphicLen={gl:g} DischargeTime={dtm:g} SeekV={["%g"%v for v in sv]} RecIdx={ri}'
|
||||
if cid in (0xBCD, 0xBD0) and sz >= 0x1D0:
|
||||
ti, abi = i32(p+0x1BC), i32(p+0x1C0)
|
||||
mtof, mvp, mjc = f32(p+0x1C4), f32(p+0x1C8), f32(p+0x1CC)
|
||||
line += f' | TracerInt={ti} AmmoBin={abi} MinTOF={mtof:g} MinVolt%={mvp:g} MinJam={mjc:g}'
|
||||
if cid == 0xBD0 and sz >= 0x1E0:
|
||||
mc = i32(p+0x1D0); mv = (f32(p+0x1D4), f32(p+0x1D8), f32(p+0x1DC))
|
||||
line += f' | MissileCount={mc} MuzzleVel={mv}'
|
||||
print(line)
|
||||
Reference in New Issue
Block a user