firmware-decomp: capture_wireframe.py -- reconstruct the projected object as a

VSTRIP triangle-strip wireframe from the emulated i860's transform output

Vertex node layout (capfw7): model xyz @+0x00, normal @+0x10, next @+0x2c (linked
list = triangle strip), screen Y @+0x28, screen X @+0x40. Hook the transform loop
tail (0xf041614c), read node=r22-0x40 + f13/f16 = screen (x,y), group strips by
node-address gap. cap7 death-cam object = 4 strips / 45 verts -> a real wireframe.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-07-16 14:03:45 -05:00
co-authored by Claude Opus 4.8
parent 9f6cd44333
commit 6aaceaa312
@@ -0,0 +1,46 @@
import sys, time, struct, pickle
sys.path.insert(0, r'C:\VWE\TeslaRel410\emulator\firmware-decomp')
import emu860, dis860, emu_main
emu860.Mem.log = lambda self, *a, **k: None
S = r'C:\Users\cyd\AppData\Local\Temp\claude\c--VWE-TeslaRel410\4e848c76-6e89-4034-8047-d8d491cb32d8\scratchpad'
snap = pickle.load(open(S + r'\snapv2.pkl','rb'))
r = emu_main.MainRunner(r'C:\VWE\TeslaRel410\dpl3-revive\patha\cap7.raw.bin', fw='capfw7', max_cmds=6000)
cpu = r.cpu
cpu.mem.pages = {k: bytearray(v) for k,v in snap['pages'].items()}
cpu.ctrl.clear(); cpu.ctrl.update(snap['ctrl'])
cpu.r=list(snap['r']); cpu.f=list(snap['f']); cpu.cr=dict(snap['cr']); cpu.pc=snap['pc']
cpu._apipe=list(snap['apipe']); cpu._mpipe=list(snap['mpipe']); cpu._fp_pipes()
cpu._lpipe=list(snap['lpipe']); cpu._gpipe=list(snap['gpipe'])
cpu._kr,cpu._ki,cpu._t=snap['kr'],snap['ki'],snap['t']
cpu.lcc=snap['lcc']; r.qi=snap['qi']; r.heap=list(snap['heap'])
def fs(i): return struct.unpack('<f', struct.pack('<I', cpu.frd(i)))[0]
# (node, sx, sy) in emission order at 0xf041614c; node = r22 - 0x40
seq=[]; t0=time.time()
while time.time()-t0 < 90:
pc=cpu.pc
if pc==0xf041614c:
seq.append((cpu.rd(22)-0x40, round(fs(13),2), round(fs(16),2)))
if len(seq)>=400: break
h=r.hooks.get(pc)
if h:
if h(cpu)=='done': break
continue
if not cpu.step(): break
# take the FIRST full pass (until nodes repeat) = one frame of objects
firstpass=[]; seen=set()
for node,sx,sy in seq:
if node in seen: break
seen.add(node); firstpass.append((node,sx,sy))
# group into objects by address gap
objs=[]; cur=[]; prev=None
for node,sx,sy in firstpass:
if prev is not None and abs(node-prev) > 0x200:
if cur: objs.append(cur); cur=[]
cur.append((sx,sy)); prev=node
if cur: objs.append(cur)
print(f"first pass: {len(firstpass)} verts in {len(objs)} objects: {[len(o) for o in objs]}")
for i,o in enumerate(objs):
X=[v[0] for v in o]; Y=[v[1] for v in o]
print(f" obj{i}: {len(o)}v x[{min(X):.0f},{max(X):.0f}] y[{min(Y):.0f},{max(Y):.0f}]")
pickle.dump({'objs':objs,'firstpass':firstpass}, open(S+r'\strips2.pkl','wb'))
print("saved strips2.pkl")