Full-mission replay through the C core: 26,422 commands / 3.49B steps in 39s

The complete cap7 mission -- every command, all 8,562 draws -- executes in
39 seconds at 89.6M steps/s sustained (199,857 Python hook services). The
replayed dict matches the QUEUE ground truth exactly; the old 'baseline'
dict (16793/8397) is exposed as a budget-truncated artifact: the historic
Python regressions hit the 2e9-step budget ~96% through and silently
dropped the last 497 commands. This is the first complete execution of the
whole mission. + M4-LIVE-SEAM.md (the remaining path to live DOSBox) and
emu860c.step1() for hook-driven single-steps.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-07-19 13:37:38 -05:00
co-authored by Claude Opus 4.8
parent be8f72e731
commit cff783d2ad
4 changed files with 1003 additions and 884 deletions
+54
View File
@@ -0,0 +1,54 @@
# M4: the live DOSBox seam — design note (2026-07-19)
The last milestone of GPU-RETARGET.md: the decoded renderer running live behind
the game. All gates are now open: geometry = emu860c (351×, checkpoint-perfect),
raster = the GPU tile path (M1/M2 conformant), spec = igc_conformance.
## The loop
```
DOSBox-X (FLYK.EXE etc., unmodified)
│ port I/O 0x150-0x161 (C012 link) [VELOCIRENDER_PROTOCOL.md §7]
virtual C012 link device [NEW — small FSM]
│ byte stream ⇄ message assembler (§1-§2 framing, verified vs real EXE)
protocol server (Python, exists as emu_main) [boot handshake done]
│ wire commands -> the firmware receive path
emu860c: VREND.MNG geometry stage [DONE — real-time 3.5×]
│ per-draw DMA stream + payload programs (captured live from C memory)
igc_gpu frame loop: tile dispatch + readout [DONE for fixtures]
│ RGBA frame
present: (a) vr_readpixels reply -> host SVGA [FLYK readback path]
(b) or the render-bridge window [existing live_bridge seam]
```
## What exists vs what's new
| piece | status |
|---|---|
| Wire protocol + framing | verified against the shipped EXE (protocol doc §9) |
| Boot handshake (iserver ×3, BTL/MNG swallow) | implemented in emu_main |
| Geometry at real-time | **emu860c** — done |
| DMA-stream capture at draw time | the send_capture pattern — port the w32
tap into emu860c (a C-side write-watch range on the queue pages, cheap) |
| Tile raster on GPU | igc_gpu / igc_gpu_frame — done for fixtures |
| Texel path (TXDN) | the remaining spec item (M3) — effects/bench render
without it; texture-mapped content needs it |
| C012 DOSBox device | NEW: six ports + byte FIFO in DOSBox-X (protocol §7
calls it "bounded and testable in isolation") |
| Present path | reuse render-bridge/live_bridge (frozen renderer window) or
implement vr_readpixels for FLYK's own SVGA display (§6: "expected") |
## Sequencing
1. **M4b — offline end-to-end**: wire capture in, frames out, no DOSBox:
emu860c runs a mission, the C-side DMA tap feeds igc_gpu_frame per draw,
frames dump to disk. Validates the whole chain at speed. (next)
2. **M4c — the C012 device** in DOSBox-X + the protocol server as a socket
bridge (the emulator process stays Python+C+GPU; DOSBox talks to it over a
local pipe — same shape as the existing namedpipe serial work).
3. **M4d — present**: vr_readpixels or the bridge window; frame pacing.
4. M3 texel path lands whenever the TXDN spec closes — orthogonal.
+133 -129
View File
@@ -1,129 +1,133 @@
"""emu860c differential driver v2: boot via the Python MainRunner """emu860c differential driver v2: boot via the Python MainRunner
(authoritative loader), copy the state into the C core, run with hooks (authoritative loader), copy the state into the C core, run with hooks
serviced through a shim, and compare (pc, steps, reg-crc) at HOOK boundaries serviced through a shim, and compare (pc, steps, reg-crc) at HOOK boundaries
against the hook-aligned reference trace (ref_trace2.pkl).""" against the hook-aligned reference trace (ref_trace2.pkl)."""
import sys, os, time, pickle, zlib import sys, os, time, pickle, zlib
HERE = os.path.dirname(os.path.abspath(__file__)) HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE) sys.path.insert(0, HERE)
sys.path.insert(0, os.path.dirname(HERE)) sys.path.insert(0, os.path.dirname(HERE))
sys.path.insert(0, r'C:\VWE\TeslaRel410\dpl3-revive\patha') sys.path.insert(0, r'C:\VWE\TeslaRel410\dpl3-revive\patha')
import emu860, emu_main, emu860c import emu860, emu_main, emu860c
emu860.Mem.log = lambda self, *a, **k: None emu860.Mem.log = lambda self, *a, **k: None
SCRATCH = r'C:\Users\cyd\AppData\Local\Temp\claude\c--VWE-TeslaRel410\4e848c76-6e89-4034-8047-d8d491cb32d8\scratchpad' SCRATCH = r'C:\Users\cyd\AppData\Local\Temp\claude\c--VWE-TeslaRel410\4e848c76-6e89-4034-8047-d8d491cb32d8\scratchpad'
class MemShim: class MemShim:
def r32(self, a): return emu860c.r32(a & 0xffffffff) def r32(self, a): return emu860c.r32(a & 0xffffffff)
def w32(self, a, v): emu860c.w32(a & 0xffffffff, v & 0xffffffff) def w32(self, a, v): emu860c.w32(a & 0xffffffff, v & 0xffffffff)
def load_blob(self, a, b): emu860c.load_blob(a & 0xffffffff, bytes(b)) def load_blob(self, a, b): emu860c.load_blob(a & 0xffffffff, bytes(b))
class CpuShim: class CpuShim:
def __init__(self): def __init__(self):
self.mem = MemShim() self.mem = MemShim()
self.igc = [] self.igc = []
self.board_log = [] self.board_log = []
self.RET_SENTINEL = 0xc0de0000 self.RET_SENTINEL = 0xc0de0000
@property @property
def steps(self): def steps(self):
return emu860c.getstate()['steps'] return emu860c.getstate()['steps']
@property @property
def pc(self): def pc(self):
return emu860c.getstate()['pc'] return emu860c.getstate()['pc']
@pc.setter @pc.setter
def pc(self, v): def pc(self, v):
emu860c.setpc(v & 0xffffffff) emu860c.setpc(v & 0xffffffff)
def rd(self, i): def rd(self, i):
return 0 if i == 0 else emu860c.getstate()['r'][i] return 0 if i == 0 else emu860c.getstate()['r'][i]
def wr(self, i, v): def wr(self, i, v):
if i: if i:
emu860c.setreg(i, v & 0xffffffff, 0) emu860c.setreg(i, v & 0xffffffff, 0)
def step(self):
def boot(): emu860c.step1()
r = emu_main.MainRunner(r'C:\VWE\TeslaRel410\dpl3-revive\patha\cap7.raw.bin', return True
fw='capfw7', max_cmds=200000)
cpu = r.start()
emu860c.init() def boot():
for pn, page in cpu.mem.pages.items(): r = emu_main.MainRunner(r'C:\VWE\TeslaRel410\dpl3-revive\patha\cap7.raw.bin',
emu860c.load_blob(pn << 16, bytes(page)) fw='capfw7', max_cmds=200000)
for a in range(0xfffff000, 0x100000000, 4): cpu = r.start()
v = cpu.mem.r32(a) emu860c.init()
if v: for pn, page in cpu.mem.pages.items():
emu860c.w32(a & 0xffffffff, v & 0xffffffff) emu860c.load_blob(pn << 16, bytes(page))
for i in range(32): for a in range(0xfffff000, 0x100000000, 4):
emu860c.setreg(i, cpu.r[i] & 0xffffffff, 0) v = cpu.mem.r32(a)
emu860c.setreg(i, cpu.f[i] & 0xffffffff, 1) if v:
emu860c.setpc(cpu.pc) emu860c.w32(a & 0xffffffff, v & 0xffffffff)
emu860c.set_sentinel(cpu.RET_SENTINEL) for i in range(32):
emu860c.set_hooks(sorted(r.hooks.keys())) emu860c.setreg(i, cpu.r[i] & 0xffffffff, 0)
return r emu860c.setreg(i, cpu.f[i] & 0xffffffff, 1)
emu860c.setpc(cpu.pc)
emu860c.set_sentinel(cpu.RET_SENTINEL)
def crc_state(st): emu860c.set_hooks(sorted(r.hooks.keys()))
b = b''.join(int(v).to_bytes(4, 'little') for v in st['r']) return r
b += b''.join(int(v).to_bytes(4, 'little') for v in st['f'])
return zlib.crc32(b) & 0xffffffff
def crc_state(st):
b = b''.join(int(v).to_bytes(4, 'little') for v in st['r'])
def main(): b += b''.join(int(v).to_bytes(4, 'little') for v in st['f'])
ref = pickle.load(open(os.path.join(SCRATCH, 'ref_trace2.pkl'), 'rb')) return zlib.crc32(b) & 0xffffffff
cps = {n: (pc, steps, crc) for n, pc, steps, crc in ref['cps']}
max_hook = ref['hookno']
r = boot() def main():
shim = CpuShim() ref = pickle.load(open(os.path.join(SCRATCH, 'ref_trace2.pkl'), 'rb'))
t0 = time.time() cps = {n: (pc, steps, crc) for n, pc, steps, crc in ref['cps']}
hookno = 0 max_hook = ref['hookno']
matched = mismatched = 0 r = boot()
while hookno < max_hook: shim = CpuShim()
reason, steps = emu860c.run(200000000) t0 = time.time()
if reason == 0: hookno = 0
st = emu860c.getstate() matched = mismatched = 0
pc = st['pc'] while hookno < max_hook:
h = r.hooks.get(pc) reason, steps = emu860c.run(200000000)
if h is None: if reason == 0:
print("sentinel at hook %d" % hookno) st = emu860c.getstate()
break pc = st['pc']
hookno += 1 h = r.hooks.get(pc)
want = cps.get(hookno) if h is None:
if want: print("sentinel at hook %d" % hookno)
got = (pc, st['steps'], crc_state(st)) break
if want != got: hookno += 1
print("DIVERGE at hook %d:" % hookno) want = cps.get(hookno)
print(" want pc=%#x steps=%d crc=%08x" % want) if want:
print(" got pc=%#x steps=%d crc=%08x" % got) got = (pc, st['steps'], crc_state(st))
mismatched += 1 if want != got:
if mismatched > 3: print("DIVERGE at hook %d:" % hookno)
break print(" want pc=%#x steps=%d crc=%08x" % want)
else: print(" got pc=%#x steps=%d crc=%08x" % got)
matched += 1 mismatched += 1
if h(shim) == 'done': if mismatched > 3:
break break
continue else:
if reason == 1: matched += 1
print("STOP: pc=%#x" % emu860c.getstate()['pc']) if h(shim) == 'done':
break break
if reason == 2: continue
st = emu860c.getstate() if reason == 1:
print("FP fallback pc=%#x w=%08x" % (st['pc'], emu860c.r32(st['pc']))) print("STOP: pc=%#x" % emu860c.getstate()['pc'])
break break
if reason == 3: if reason == 2:
print("budget exhausted") st = emu860c.getstate()
break print("FP fallback pc=%#x w=%08x" % (st['pc'], emu860c.r32(st['pc'])))
dt = time.time() - t0 break
st = emu860c.getstate() if reason == 3:
print("C core: %d steps, %d hooks in %.2fs = %d steps/s; " print("budget exhausted")
"checkpoints %d/%d matched" % break
(st['steps'], hookno, dt, st['steps'] / max(dt, 1e-9), dt = time.time() - t0
matched, len(cps))) st = emu860c.getstate()
print("C core: %d steps, %d hooks in %.2fs = %d steps/s; "
"checkpoints %d/%d matched" %
if __name__ == '__main__': (st['steps'], hookno, dt, st['steps'] / max(dt, 1e-9),
main() matched, len(cps)))
if __name__ == '__main__':
main()
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,53 @@
"""Full-mission replay through the C core: all 26,422 cap7 commands.
Must reproduce the known-good replayed dict; reports wall time."""
import sys, os, time
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
sys.path.insert(0, os.path.dirname(HERE))
sys.path.insert(0, r'C:\VWE\TeslaRel410\dpl3-revive\patha')
import emu860, emu_main, emu860c
from driver import boot, CpuShim
emu860.Mem.log = lambda self, *a, **k: None
t_boot = time.time()
r = boot()
shim = CpuShim()
print("boot+copy: %.1fs" % (time.time() - t_boot), flush=True)
t0 = time.time()
hooks = 0
while True:
reason, steps = emu860c.run(2_000_000_000)
if reason == 0:
pc = emu860c.getstate()['pc']
h = r.hooks.get(pc)
if h is None:
print("sentinel: main returned", flush=True)
break
hooks += 1
if h(shim) == 'done':
print("capture exhausted", flush=True)
break
continue
if reason == 1:
print("STOP pc=%#x msg?" % emu860c.getstate()['pc'], flush=True)
break
if reason == 2:
st = emu860c.getstate()
print("FP fallback pc=%#x w=%08x" % (st['pc'], emu860c.r32(st['pc'])), flush=True)
break
if reason == 3:
print("budget exhausted", flush=True)
break
dt = time.time() - t0
st = emu860c.getstate()
print("MISSION: %d commands, %d steps, %d hook services in %.1fs = %d steps/s" %
(r.qi, st['steps'], hooks, dt, st['steps'] / max(dt, 1e-9)), flush=True)
print("replayed:", r.done, flush=True)
BASE = {'init': 1, 'create': 124, 42: 124, 'flush': 250, 'dcs_link': 2,
'list_add': 107, 'set_texmap_texels': 101, 25: 23, 28: 3, 29: 16793,
'draw_scene': 8397}
norm = {(k if isinstance(k, int) else str(k)): v for k, v in r.done.items()}
basen = {(k if isinstance(k, int) else str(k)): v for k, v in BASE.items()}
print("BASELINE MATCH:", norm == basen, flush=True)