Files
BT412/scratchpad/scan_heatsub.py
T
arcattackandClaude Fable 5 4ed2bbc293 Heat: authentic 1e7-unit heat + the ambient radiator land (task #9)
- Emitters: heatPortion closed form = heatCostToFire x 1e7 x (charge/seekV)^2
  (PPC 1.1e8 -> +632K on its own sink); the missing projectile/missile heat
  adds (raw -- resources author pre-scaled 1e7 units).
- The bank's AMBIENT RADIATOR @4ae73c reconstructed (the system's ONLY heat
  exit; conductance x 0.1 x HeatSinkCount -- _DAT_004ae974 float80 = 0.1) +
  the link-attach guard corrected (skip = the 0xBBE bank, not Condenser; the
  inversion blocked condenser->bank links and closed the system).
- Constant corrections (all byte-verified float80s): coolant epsilons
  0.0025/0.003/1e-4 (was a single 1e-4 serving three sites);
  CoolantCapacityScale 0.05 (was 1.738).
- Verified: heat flows + exits; max-rate autofire overheats weapons into the
  authentic heatLoad range-cutout (thermal spam unsustainable by design).
- KNOWN REMAINING: linked-sink routing scrambled (heat pools in Condenser1;
  authored map says PPC->C4/C6) -- the heat-stream offset audit, filed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 16:21:37 -05:00

87 lines
3.0 KiB
Python

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]
# find ALL subsystem records (same heuristic as scan_weapsub.py)
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):
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
# 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)
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
HEAT = {0xBBD:'Condenser', 0xBBE:'AggregateHeatSink', 0xBC0:'Reservoir', 0xBBB:'HeatSink?', 0xBBC:'?'}
WEAP = {0xBC8:'Emitter', 0xBD4:'PPC', 0xBCD:'ProjWeapon', 0xBCE:'GaussRifle', 0xBD0:'MissileLauncher'}
for ci, ch in enumerate(chains):
lbl = nearest_label(ch[0][0])
names = [c[1] for c in ch]
print(f'--- chain {ci} @0x{ch[0][0]:x} n={len(ch)} nearest-name={lbl}')
for idx,(p,name,cid,sz) in enumerate(ch):
tag = HEAT.get(cid) or WEAP.get(cid)
if cid in HEAT:
st, dg, ft = f32(p+0xE4), f32(p+0xE8), f32(p+0xEC)
tc, tm = f32(p+0xF0), f32(p+0xF4)
hsi = i32(p+0xF8)
line = (f' [{idx:2d}] {name:20s} cid=0x{cid:X}({tag}) sz=0x{sz:X} '
f'StartT={st:g} DegT={dg:g} FailT={ft:g} Conduct={tc:g} ThermMass={tm:g} HeatSinkIdx={hsi}')
if cid == 0xBBD:
line += f' RefrigFactor={f32(p+0xFC):g}'
elif cid == 0xBBE:
line += f' HeatSinkCount={i32(p+0xFC)}'
elif cid == 0xBC0:
line += f' CoolantCap={f32(p+0xFC):g} SquirtMass={f32(p+0x100):g}'
print(line)
elif cid in WEAP:
heat = f32(p+0x1A4)
print(f' [{idx:2d}] {name:20s} cid=0x{cid:X}({tag}) Heat={heat:g}')
else:
print(f' [{idx:2d}] {name:20s} cid=0x{cid:X} sz=0x{sz:X}')