From a7a59af9b4b1ea24fced7d20e7aa6c304df86eb9 Mon Sep 17 00:00:00 2001 From: Cyd Date: Thu, 16 Jul 2026 20:50:43 -0500 Subject: [PATCH] Emit-stream capture + first span read-out from a live scene (trek) capemit.py records every emitter-range write plus the DMA stream during trek draws; analyze_emit.py decodes it. Findings: TILE ids = a 13x4 grid (row<<5|col, 832/64 x 512/128); the famous SENDE block at 0x8015020 is byte-identical across captures = the fixed BACKGROUND-CLEAR program (not scene coeffs); trek's scene content lives in further payload blocks (0x8015800..0x80168c8+, many primitives) with a readable per-scanline span structure: row ids (0x39f,0x3a0,... = rows 415..422 observed), place-value indices, and edge constants in x2 chains. A first-guess parser already extracts 8 consecutive spans forming a leaning polygon edge -- rasterized geometry read directly from the compiled micro-code. Full multi-block decode + scene assembly = next session (see memory). Co-Authored-By: Claude Opus 4.8 --- emulator/firmware-decomp/analyze_emit.py | 52 ++++++++++++++++++++ emulator/firmware-decomp/capemit.py | 62 ++++++++++++++++++++++++ 2 files changed, 114 insertions(+) create mode 100644 emulator/firmware-decomp/analyze_emit.py create mode 100644 emulator/firmware-decomp/capemit.py diff --git a/emulator/firmware-decomp/analyze_emit.py b/emulator/firmware-decomp/analyze_emit.py new file mode 100644 index 0000000..3f182b1 --- /dev/null +++ b/emulator/firmware-decomp/analyze_emit.py @@ -0,0 +1,52 @@ +"""Offline decode of trek_emit.pkl: (1) DMA pairs -> per-page chains + TILE ids; +(2) emitter writes grouped by page, in address order, each word interpreted as +float / .8 fixed-point / byte fields; (3) hunt the payload structure (0x100 headers, +edge groups, z-sweeps) and candidate tile-local coordinate pairs.""" +import pickle, struct, collections +S = r'C:\Users\cyd\AppData\Local\Temp\claude\c--VWE-TeslaRel410\4e848c76-6e89-4034-8047-d8d491cb32d8\scratchpad' +d = pickle.load(open(S + r'\trek_emit.pkl', 'rb')) +emits = d['emits']; dma = d['dma'] + +def asf(w): + return struct.unpack('> 28) & 0xf + name = OPS.get(code, '?') + if name == 'TILE': + tiles.append(a) + if i < 80: + print(" %08x %s(%d)" % (a, name, op & 0x7f)) +tile_all = [dma[i] for i in range(0, len(dma) - 1, 2) + if ((dma[i + 1] >> 28) & 0xf) == 0x2] +print("TILE ids in stream: %d distinct: %s" % + (len(tile_all), sorted(set(tile_all))[:30])) + +# ---- emitter writes grouped by page ---- +bypage = collections.defaultdict(list) +for qi, pc, a, w in emits: + bypage[a >> 12].append((a, w, pc)) +print("\n=== pages: %s ===" % {hex(k << 12): len(v) for k, v in bypage.items()}) + +# dump the densest page in address order +page, rows = max(bypage.items(), key=lambda kv: len(kv[1])) +rows = sorted(set((a, w) for a, w, pc in rows)) +print("\n=== densest page %#x: %d words ===" % (page << 12, len(rows))) +for a, w in rows[:90]: + f = asf(w) + e = (w >> 23) & 0xff + notes = [] + if 1e-5 < abs(f) < 1e5 and 1 < e < 254: + notes.append("f=%.5g" % f) + if 0 < w < 0x10000: + notes.append("/256=%.2f" % (w / 256.0)) + b = [(w >> s) & 0xff for s in (24, 16, 8, 0)] + if all(x < 0x80 for x in b) and any(x for x in b): + notes.append("bytes=%s" % b) + print(" %08x: %08x %s" % (a, w, " ".join(notes))) diff --git a/emulator/firmware-decomp/capemit.py b/emulator/firmware-decomp/capemit.py new file mode 100644 index 0000000..7f66bbb --- /dev/null +++ b/emulator/firmware-decomp/capemit.py @@ -0,0 +1,62 @@ +"""THE capture: during 2 trek draws record (a) every w32 issued from the EMITTER range +(0xf041d000-0xf041e000): (pc, addr, word) -- the compiled micro-code with tile-local +coords packed in 8-bit fields; (b) every DMA-list pair from the coeff-copy +(0xf0411cd4): the TILE(id)/GOTO(link) entries that map queue pages -> tile ids. +Save both streams to trek_emit.pkl for offline decode + scene reconstruction.""" +import sys, time, struct, pickle +sys.path.insert(0, r'C:\VWE\TeslaRel410\emulator\firmware-decomp') +sys.path.insert(0, r'C:\VWE\TeslaRel410\dpl3-revive\patha') +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'\snap_trek0.pkl', 'rb')) +r = emu_main.MainRunner(r'C:\VWE\TeslaRel410\dpl3-revive\patha\trek.raw.bin', + fw='capfw7', max_cmds=30000) +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']) + +ELO, EHI = 0xf041d000, 0xf041e000 +COPY = 0xf0411cd4 +emits = [] # (qi, pc, addr, word) +dma = [] # copy words in order (pairs) +orig = emu860.Mem.w32 +def w32(self, addr, val): + pc = cpu.pc + if ELO <= pc < EHI: + emits.append((r.qi, pc, addr & 0xffffffff, val & 0xffffffff)) + elif pc == COPY: + dma.append(val & 0xffffffff) + return orig(self, addr, val) +emu860.Mem.w32 = w32 +t0 = time.time(); startq = r.qi +while time.time() - t0 < 300: + if r.qi >= startq + 4: break + h = r.hooks.get(cpu.pc) + if h: + if h(cpu) == 'done': break + continue + if not cpu.step(): break +emu860.Mem.w32 = orig + +print("emitter writes: %d dma words: %d (cmds %d..%d)" % + (len(emits), len(dma), startq, r.qi)) +pickle.dump({'emits': emits, 'dma': dma}, open(S + r'\trek_emit.pkl', 'wb')) +print("saved trek_emit.pkl") + +# quick preview: field spectrum of the packed words +import collections +b0 = collections.Counter(); b1 = collections.Counter() +pages = collections.Counter() +for qi, pc, a, w in emits: + b0[w & 0xff] += 1 + b1[(w >> 8) & 0xff] += 1 + pages[a >> 12] += 1 +print("distinct target pages:", len(pages)) +print("byte0 spectrum (top 12):", dict(b0.most_common(12))) +print("byte1 spectrum (top 12):", dict(b1.most_common(12)))