From 6aaceaa3125a35ff76af73281b7cd9b5825bc94f Mon Sep 17 00:00:00 2001 From: Cyd Date: Thu, 16 Jul 2026 14:03:45 -0500 Subject: [PATCH] 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 --- emulator/firmware-decomp/capture_wireframe.py | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 emulator/firmware-decomp/capture_wireframe.py diff --git a/emulator/firmware-decomp/capture_wireframe.py b/emulator/firmware-decomp/capture_wireframe.py new file mode 100644 index 0000000..bd937af --- /dev/null +++ b/emulator/firmware-decomp/capture_wireframe.py @@ -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('=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")