Files
BT411/scratchpad/dump_dzones.py
arcattackandClaude Fable 5 fd055281a8 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>
2026-07-11 15:32:24 -05:00

71 lines
2.7 KiB
Python

import struct, sys
data = open(r'C:\git\bt411\content\BTL4.RES', 'rb').read()
labOnly, maxID = struct.unpack_from('<ii', data, 4)
offs = struct.unpack_from('<%dI' % maxID, data, 12)
# ResourcePhysicalFormat: id i32, type i32, name[32], prio i32, flags i32, offset i32, length i32
recs = []
for o in offs:
if o == 0: continue
rid, rt = struct.unpack_from('<ii', data, o)
nm = data[o+8:o+40].split(b'\0')[0].decode('ascii','replace')
prio, flags, roff, rlen = struct.unpack_from('<iiII', data, o+40)
recs.append((rid, rt, nm, roff, rlen))
DZ = [r for r in recs if r[1] == 20]
print('type-0x14 DamageZoneStream resources:')
for r in DZ:
print(' id=%d name=%r off=0x%x len=%d' % (r[0], r[2], r[3], r[4]))
def parse_zone_stream(buf, tag):
p = 0
def i32():
nonlocal p
v = struct.unpack_from('<i', buf, p)[0]; p += 4; return v
def f32():
nonlocal p
v = struct.unpack_from('<f', buf, p)[0]; p += 4; return v
def cstr():
nonlocal p
n = i32()
s = buf[p:p+n].decode('ascii','replace'); p += n + 1
return s
count = i32()
print('\n===== %s: zoneCount=%d streamLen=%d =====' % (tag, count, len(buf)))
for z in range(count):
name = cstr()
sites = []
for j in range(5):
c = i32()
sites.append([i32() for _ in range(c)])
dap = f32()
scales = [f32() for _ in range(5)]
sklcount = i32()
mats = []
for s in range(sklcount):
styp = i32()
mc = i32()
names = [cstr() for _ in range(mc)]
mats.append((styp, names))
descend = i32(); sibs = i32(); segidx = i32()
lleg = i32(); rleg = i32(); vital = i32(); ncrit = i32()
crits = []
for c in range(ncrit):
w = f32(); pct = f32(); sidx = i32()
crits.append((w, pct, sidx))
nred = i32()
redirects = [i32() for _ in range(nred)]
# implied per-type armor points: scale = 1/points -> points = 1/scale
pts = ['%g' % (1.0/s if s else 0) for s in scales]
print('zone %2d %-24s armor(default)=%g scale=[%s] -> points=[%s]' %
(z, name, dap, ' '.join('%.6g' % s for s in scales), ' '.join(pts)))
print(' seg=%d vital=%d legs(L%d,R%d) descend=%d sibs=%d mats=%d crits=%s redirects=%s' %
(segidx, vital, lleg, rleg, descend, sibs, sklcount,
[(round(w,3), round(pc,3), si) for (w,pc,si) in crits], redirects))
print('consumed %d of %d bytes' % (p, len(buf)))
want = [n.lower() for n in (sys.argv[1:] or ['madcat','bhk1'])]
for (rid, rt, nm, roff, rlen) in DZ:
if any(w in nm.lower() for w in want):
parse_zone_stream(data[roff:roff+rlen], 'id=%d %s' % (rid, nm))