Files
BT411/scratchpad/i860dis.py
T
arcattackandClaude Fable 5 065c114590 Impact-FX FORENSICS wave: the i860 specialfx engine, per-round detonations, the ram economy closed
The "we're off the rails" reinvestigation -- a 5-thread evidence workflow
(logs / PFX data / RES model lists / decomp / port audit) + the i860 firmware
decode, then surgical fixes.  Every claim tiered; the wrong turns are on the
record in the KB alongside the corrections.

THE OLD-STYLE SPECIALFX ENGINE [T1, firmware-decoded from VREND.MNG]:
rebuilt scratchpad/i860dis.py (binutils opcode table); mapped the dispatch
(data+0xdd0c; sfx trigger 0xf040cda0, install 0xf040cdc0, step ~0xf0413698,
instance init 0xf04128d8); decoded the heat model EXACTLY: per 30Hz board
frame h *= cool_a; RGB_ch = h_old*(h_new*cook_ch - 0.25) + 0.25 (K=0.25 =
the ember floor @VA 0xF080; kill at h <= 1e-4 @0xF0A0); alpha fades cool_b
x RAW dt (PER-SECOND -- the x30 scaling made laser hits invisible); 7s cap;
y_off = the kill plane; "variance" is DEAD DATA (binary reads "variance",
INI authors "varience").  The 13 descriptors (PPCHit/LaserHit/MissileHit/
Chunks/Sparks/Fireball...) parse from BTDPL.INI and render via the BTPfx
layer -- heat bursts draw the FIERY sheet (brightness over fire), .PFX keeps
GRAYSCALE (authored colours: DNBOOM orange, DDAM gray).

HIT-PACKAGE CENSUS [T1, RES byte-verified]: ppchit=[8] lzrhit=[9] mghit=[7,11]
canhit=[11] mslhit=[10,12,1023] explode=[6]; all 8 mech death lists identical.
Effect routing corrected in BOTH consumers: <100 = specialfx, >=1000 = psfx.

"SILVER MIST x5" ROOT CAUSE: SHKWAVE.PFX (mslhit's 1023) authors maxIssue=5
relPeriod=0.2 rate=1 -- the ONE file where rate contradicts the window; the
emitter trusted rate -> 5 shells at exactly 1Hz.  Emission rate now always
maxIssue/releasePeriod.

PER-ROUND DETONATIONS [T1 @004bef78]: every missile round spawns its own
ExplosionModelFile at ITS impact (hull + terrain) -- a volley ripples 12
fireballs like the demos; rack-tube launch spread [T3] (GUIDED rounds only --
the slot-0 deflection sent every AFC100 shell 3.4deg left/2.5deg down: the
phantom "4th gold beam"); replicant salvos detonate too (launcher index rides
the visual push).  Missile damage bundles through the shooter's messmgr with
the launcher's subsystemID (mslhit fires at the consolidated point; the
binary's dedup CONFIRMED [T1 @0049b784] -- a workflow agent's per-record
claim REFUTED by direct decomp read).

DAMAGE-BAND SEMANTICS [T1]: MechDeathHandler fires the CURRENT band effect on
any damage rise (the binary's changed-flag coalescing) -- a mauled mech under
fire smokes/burns per hit; bands 3/4 are authored fire plumes.

THE RAM ECONOMY -- CLOSED (3 layers, measured live):
 1. armor = POINTS (every zone: damageScale = 1/armorPoints, armor 50-140;
    the [zone-armor] BT_DMG_LOG dump);
 2. StaticBounce prices rams with authored moverMass ~1.3e6 -> ~59,000 pts
    @10m/s; the binary dispatches it RAW but pod MP dropped it on the local
    replicant (MECH.CPP:986 warns) -- ram damage was NETWORK-INERT; our
    task-#47 replicant forwarding surfaced it as a one-shot.  Port
    normalizes x1e-3 to the point economy [T3];
 3. contact EDGE (ramLastVictim/ramContactLinger): the binary's bounce made
    separation implicit; our gait-derived velocity re-priced full rams at
    60Hz (the respawn explode-loop).  One bump = one hit; pressed = BLOCK.

PLUS: sfx size x0.5 + 1.25m visibility floor + hot-phase occlusion [T3];
particle pool 2048->8192 (missile traffic starved laser bursts -- the
"intermittent effects"); replicant beam aging (a lost beam-END record pinned
a stale beam on forever); UV-variant noise stamps (mask-safe mirror/swap);
no scroll on particle stamps (material-path only); BT_FX_TEST/fxshot.py
self-verification harness ([beam-draw]/[zone-armor]/[collide-tx] telemetry).

KB: rendering.md (specialfx engine + census + closeout), combat-damage.md
(ram economy + band semantics).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 08:14:12 -05:00

100 lines
3.7 KiB
Python

"""Minimal i860 XR/XP disassembler driven by the binutils opcode table.
Usage: python i860dis.py <va_hex> [len_hex]
Disassembles VREND.MNG code (loads at 0xf0400000; file header 0x1C).
Rebuilt 2026-07-12 (the task-#55 original didn't survive); opcode truth =
scratchpad/i860_opcodes.h (binutils include/opcode/i860.h, haiku legacy
mirror). Operand syntax per the header comment.
"""
import re, struct, sys, os
HERE = os.path.dirname(os.path.abspath(__file__))
MNG = os.path.join(HERE, '..', 'content', 'VREND.MNG')
HDR = 0x1C
CODEBASE = 0xf0400000
CODESZ = 0x3ac80
DATASZ = 0x1e9c0
def load_ops():
src = open(os.path.join(HERE, 'i860_opcodes.h')).read()
ops = []
for m in re.finditer(r'\{\s*"([^"]+)",\s*(0x[0-9a-fA-F]+),\s*(0x[0-9a-fA-F]+),\s*"([^"]*)",\s*(\w+)\s*\}', src):
name, match, lose, args, expand = m.groups()
ops.append((name, int(match, 16), int(lose, 16), args))
return ops
OPS = load_ops()
def fields(w):
return {
'src2': (w >> 21) & 0x1F,
'dest': (w >> 16) & 0x1F,
'src1': (w >> 11) & 0x1F,
'imm16': w & 0xFFFF,
'imm5': (w >> 11) & 0x1F,
'lbroff': w & 0x03FFFFFF,
'sbroff': ((w >> 5) & 0xF800) | (w & 0x07FF), # split 16-bit branch offset
'split16': ((w >> 5) & 0xF800) | (w & 0x07FF), # split 16-bit (st) immediate
'creg': (w >> 21) & 0x1F,
}
def sext(v, bits):
return v - (1 << bits) if v & (1 << (bits - 1)) else v
def decode(w, pc):
for name, match, lose, args in OPS:
if (w & match) == match and (w & lose) == 0:
# binutils order guarantees no false positive only if we check
# match as EXACT on the fixed bits: (w & ~varmask) == match.
# Reconstruct: fixed bits = match | lose; variable = operands.
f = fields(w)
out = []
for a in args:
if a in '#,()': continue
if a == '1': out.append('r%d' % f['src1'])
elif a == '2': out.append('r%d' % f['src2'])
elif a == 'd': out.append('r%d' % f['dest'])
elif a in 'iIJKLM': out.append('0x%x' % f['imm16'])
elif a == '5': out.append('%d' % f['imm5'])
elif a == 'l':
tgt = pc + 4 + (sext(f['lbroff'], 26) << 2)
out.append('0x%08x' % tgt)
elif a == 'r':
tgt = pc + 4 + (sext(f['sbroff'], 16) << 2)
out.append('0x%08x' % tgt)
elif a in 'sSTU': out.append('0x%x' % f['split16'])
elif a == 'e': out.append('f%d' % f['src1'])
elif a == 'f': out.append('f%d' % f['src2'])
elif a == 'g': out.append('f%d' % f['dest'])
elif a == 'c': out.append('creg%d' % f['creg'])
else: out.append('?%s' % a)
return name, out
return None, None
def candidates(w):
"""All table rows consistent with word w (the table has overlaps; the
first (most specific) is printed, rest shown when ambiguous)."""
res = []
for name, match, lose, args in OPS:
if (w & match) == match and (w & lose) == 0:
res.append(name)
return res
def main():
data = open(MNG, 'rb').read()
code = data[HDR:HDR + CODESZ]
va = int(sys.argv[1], 16)
ln = int(sys.argv[2], 16) if len(sys.argv) > 2 else 0x100
off = va - CODEBASE
for pc in range(va, va + ln, 4):
w = struct.unpack_from('<I', code, pc - CODEBASE)[0]
name, ops = decode(w, pc)
if name is None:
print('0x%08x: %08x ???' % (pc, w))
else:
print('0x%08x: %08x %-10s %s' % (pc, w, name, ','.join(ops)))
if __name__ == '__main__':
main()