Files
TeslaRel410/emulator/firmware-decomp/emu_replay.py
T
CydandClaude Fable 5 20dbd90026 Tier 0 emulator: wire-command replay harness (emu_replay.py)
Feeds the real captured VelociRender wire (dpl3-revive/patha/*.raw.bin, from the
soft-renderer work) to the firmware's own command handlers in emu860 -- the path
to building a scene and rendering a frame with the board's own code.

- Parses captures via vrboard's Assembler (skips the .BTL boot, frames
  action+payload). cap7 = a clean small scene (17 zones / 22 instances / 24
  geometry / 1 view).
- Calls each command's handler directly with the payload pointer in r16 --
  verified valid: create's prologue reads [r16+0]=type, [r16+4]=handle, i.e. the
  raw wire layout (no CCB wrapping needed). Bypasses the un-emulated transputer.
- Confirmed WIRE-action -> handler map by function identity (create/flush/
  dcs_link/list_add/draw_scene/statistics/set_texmap_texels).

Two blockers identified for a full frame (documented in the memory + README):
  1. The dispatch jump table is indexed by an INTERNAL command code, not the
     wire action (velocirender_input remaps wire->internal first, e.g. draw=9
     ->12, flush=3->30); the high-frequency per-frame commands (0x09, 0x1d
     artics, 0x2a) still need that remap reversed.
  2. init(0) must run first to set up the allocator/name-table/scene-root --
     cold create() faults without it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 07:01:52 -05:00

105 lines
4.4 KiB
Python

"""Replay a captured VelociRender wire through the REAL firmware in emu860.
Feeds each captured (action, payload) command to the firmware's own handler
(payload pointer in r16, per the PGI calling convention), building the scene up
in the emulated i860's memory, then runs draw_scene -- capturing the IGC
coefficient stream the firmware emits (the Tier-1 handoff).
Bypasses the transputer/CCB/interrupt path (see HARDWARE-ARCHITECTURE.md): we
call the handlers directly, which is valid because they read the raw wire payload.
python emu_replay.py [capture.raw.bin] [--verbose]
"""
import sys, os, struct
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
sys.path.insert(0, r'C:\VWE\TeslaRel410\dpl3-revive\patha')
import emu860
from vrboard import Assembler, A, BOOT_ACTIONS
MNG = r'C:\VWE\TeslaRel410\sda4\RPLIVE\VREND.MNG'
def s26(v): return v - 0x4000000 if v & 0x2000000 else v
# Confirmed WIRE-action -> handler map (vaddr). IMPORTANT: the firmware's
# dispatch jump table (file 0x47cb8) is indexed by an INTERNAL command code, NOT
# the wire action -- velocirender_input remaps wire->internal first (e.g. wire
# draw_scene=9 -> internal index 12; flush=3 -> 30; statistics=15 -> 31; while
# create/list_add/dcs_link are identity). So we can't index that table by the
# capture's action. These are matched by function identity instead (create's
# prologue verified reads type/handle; statistics verified returns; draw_scene,
# flush, list_add, dcs_link identified in the dispatch analysis).
# TODO: reverse the wire->internal remap in velocirender_input to complete the
# map for the high-frequency per-frame commands (0x09 draw, 0x1d artics, 0x2a).
WIRE_HANDLERS = {
A.create: 0xf040c180,
A.flush: 0xf040cfd8,
A.dcs_link: 0xf040dc70,
A.list_add: 0xf040d4d0,
A.draw_scene: 0xf040e340,
A.statistics: 0xf040e940,
A.set_texmap_texels: 0xf040a030,
# A.init handler + the 0x1d/0x2a artic handlers: need the remap (see TODO).
}
def extract_handlers(cpu):
return dict(WIRE_HANDLERS)
def parse_capture(path):
data = open(path, 'rb').read()
start = None
for i in range(0, len(data) - 8):
w = struct.unpack_from('<I', data, i)[0]
if (w & 0xffff0000) == 0x40ff0000 and 8 <= (w & 0xffff) <= 4096:
act = struct.unpack_from('<I', data, i + 4)[0]
if act in (A.init, A.code860, A.args860, A.data860, A.bss860):
start = i; break
if start is None:
raise SystemExit("no framed region found")
asm = Assembler(); asm.feed(data[start:])
return [(m.action, m.payload) for m in asm if not m.iserver]
def main():
args = [a for a in sys.argv[1:] if not a.startswith('--')]
cap = args[0] if args else r'C:\VWE\TeslaRel410\dpl3-revive\patha\cap7.raw.bin'
verbose = '--verbose' in sys.argv
cpu = emu860.I860(trace=0)
cpu.load_mng(MNG)
cpu.map_control(); cpu.map_board()
handlers = extract_handlers(cpu)
print(f"handlers extracted: {len(handlers)} "
f"(draw_scene={handlers.get(A.draw_scene):#x} create={handlers.get(A.create):#x})")
cmds = parse_capture(cap)
print(f"{os.path.basename(cap)}: {len(cmds)} wire commands")
ANAME = {int(a): a.name for a in A}
pbuf = 0x00200000 # fresh payload buffer per command (advances)
done = {}
for idx, (act, pl) in enumerate(cmds):
if act in BOOT_ACTIONS or act == A.init:
continue # skip firmware download; init handled below
h = handlers.get(act)
if h is None:
done[act] = done.get(act, 0) + 1
continue
cpu.mem.load_blob(pbuf, pl or b'\0')
r = cpu.call(h, args=(pbuf,), maxsteps=5_000_000)
pbuf += (len(pl) + 15) & ~15
done[ANAME.get(act, act)] = done.get(ANAME.get(act, act), 0) + 1
if cpu.stopmsg:
print(f"\n[cmd {idx}] action {ANAME.get(act,act)} FAULTED after building "
f"{sum(v for k,v in done.items())} cmds:\n {cpu.stopmsg}")
import dis860
for pc, w in cpu.tail[-6:]:
m, ops = dis860.decode(w, pc); print(f" {pc:#010x}: {w:08x} {m} {ops}")
break
if verbose and act == A.draw_scene:
print(f"[cmd {idx}] draw_scene ok IGC writes so far: {len(cpu.igc)}")
print("\nreplayed:", done)
print(f"IGC/board writes captured: {len(cpu.board_log)}")
if __name__ == '__main__':
main()