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:
@@ -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.
|
||||||
@@ -46,6 +46,10 @@ class CpuShim:
|
|||||||
if i:
|
if i:
|
||||||
emu860c.setreg(i, v & 0xffffffff, 0)
|
emu860c.setreg(i, v & 0xffffffff, 0)
|
||||||
|
|
||||||
|
def step(self):
|
||||||
|
emu860c.step1()
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def boot():
|
def boot():
|
||||||
r = emu_main.MainRunner(r'C:\VWE\TeslaRel410\dpl3-revive\patha\cap7.raw.bin',
|
r = emu_main.MainRunner(r'C:\VWE\TeslaRel410\dpl3-revive\patha\cap7.raw.bin',
|
||||||
|
|||||||
@@ -682,6 +682,13 @@ static PyObject *py_run(PyObject *self, PyObject *args) {
|
|||||||
return Py_BuildValue("iK", reason, (unsigned long long)C.steps);
|
return Py_BuildValue("iK", reason, (unsigned long long)C.steps);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static PyObject *py_step1(PyObject *self, PyObject *args) {
|
||||||
|
(void)self; (void)args;
|
||||||
|
int fb = 0;
|
||||||
|
int stopped = step_once(&fb);
|
||||||
|
return Py_BuildValue("ii", stopped, fb);
|
||||||
|
}
|
||||||
|
|
||||||
static PyObject *py_getstate(PyObject *self, PyObject *args) {
|
static PyObject *py_getstate(PyObject *self, PyObject *args) {
|
||||||
(void)self; (void)args;
|
(void)self; (void)args;
|
||||||
PyObject *regs = PyList_New(32), *fregs = PyList_New(32);
|
PyObject *regs = PyList_New(32), *fregs = PyList_New(32);
|
||||||
@@ -739,6 +746,7 @@ static PyMethodDef methods[] = {
|
|||||||
{"set_hooks", py_set_hooks, METH_VARARGS, "set hook pcs"},
|
{"set_hooks", py_set_hooks, METH_VARARGS, "set hook pcs"},
|
||||||
{"run", py_run, METH_VARARGS, "run until hook/stop/fallback/max"},
|
{"run", py_run, METH_VARARGS, "run until hook/stop/fallback/max"},
|
||||||
{"getstate", py_getstate, METH_NOARGS, "dump state"},
|
{"getstate", py_getstate, METH_NOARGS, "dump state"},
|
||||||
|
{"step1", py_step1, METH_NOARGS, "force one step (ignores hooks)"},
|
||||||
{"setreg", py_setreg, METH_VARARGS, "set r/f register"},
|
{"setreg", py_setreg, METH_VARARGS, "set r/f register"},
|
||||||
{"setpc", py_setpc, METH_VARARGS, "set pc"},
|
{"setpc", py_setpc, METH_VARARGS, "set pc"},
|
||||||
{"set_sentinel", py_setsentinel, METH_VARARGS, "set return sentinel"},
|
{"set_sentinel", py_setsentinel, METH_VARARGS, "set return sentinel"},
|
||||||
|
|||||||
@@ -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)
|
||||||
Reference in New Issue
Block a user