| 1 |
# retro-remote-debug-controller |
| 2 |
|
| 3 |
A tiny, portable **HTTP control API for retro-platform emulators**, and the |
| 4 |
shared tooling that speaks it. One contract → one test harness across every |
| 5 |
Doomsday One port. |
| 6 |
|
| 7 |
An emulator launched with a control-port flag exposes a localhost HTTP server: |
| 8 |
|
| 9 |
|
| 10 |
|
| 11 |
| `GET /status` | contract version, platform, frame counter, paused state | |
| 12 |
| `GET /screenshot` | PPM image of the live screen | |
| 13 |
| `GET /mem?addr=&len=&bank=` | raw memory bytes | |
| 14 |
| `GET /regs` | CPU registers (JSON) | |
| 15 |
| `POST /step?frames=` | advance N frames deterministically | |
| 16 |
| `POST /key`, `/reset` | *(0.2)* inject input | |
| 17 |
|
| 18 |
## Why |
| 19 |
|
| 20 |
Every one of these ports (Pac-Man on X16, Neo6502, Agon; and whatever's next) is |
| 21 |
chasing **frame-exact** arcade fidelity, and every one had its own fragile, |
| 22 |
platform-specific way to check state headless (write to the SD card, dump 64 KB |
| 23 |
of RAM on a magic jump, grep stdout — which lies). This replaces all of that |
| 24 |
with one contract: |
| 25 |
|
| 26 |
- **`GET /mem`** → assert game state (sprite table, actor positions). |
| 27 |
- **`GET /screenshot`** → Pillow → assert pixels, or just catch "nothing |
| 28 |
rendered" (the exact class of bug that costs an afternoon otherwise). |
| 29 |
- **`POST /step`** → deterministic, frame-addressable capture, so CI diffs don't |
| 30 |
flake against a free-running 60 Hz loop. |
| 31 |
|
| 32 |
The payoff is uniformity: the same `emu_control.py` and the same pytest patterns |
| 33 |
drive the X16, Neo6502, and Agon emulators. |
| 34 |
|
| 35 |
## Layout |
| 36 |
|
| 37 |
``` |
| 38 |
SPEC.md the normative HTTP contract (start here) |
| 39 |
core/retro_control.h portable backend vtable each emulator implements |
| 40 |
core/retro_control.c shared server: sockets, HTTP, PPM/JSON, marshalling [next] |
| 41 |
client/emu_control.py shared pytest client |
| 42 |
conformance/ tests an emulator runs to prove it conforms [next] |
| 43 |
``` |
| 44 |
|
| 45 |
## Adopting it in an emulator (the shape) |
| 46 |
|
| 47 |
Write the server once, adapt per fork. Each fork vendors `core/` and implements |
| 48 |
~5 callbacks, then calls the server: |
| 49 |
|
| 50 |
```c |
| 51 |
#include "retro_control.h" |
| 52 |
|
| 53 |
static retro_control_backend_t backend = { |
| 54 |
.platform = "x16", |
| 55 |
.read_mem = x16_read_mem, /* debug-read, no side effects */ |
| 56 |
.get_regs_json = x16_get_regs_json, |
| 57 |
.get_framebuffer= x16_get_framebuffer, |
| 58 |
.step_frames = x16_step_frames, |
| 59 |
.get_frame_count= x16_get_frame_count, |
| 60 |
}; |
| 61 |
|
| 62 |
if (control_port) /* set by -controlport / --control-port */ |
| 63 |
retro_control_start(control_port, &backend); |
| 64 |
``` |
| 65 |
|
| 66 |
The socket loop, HTTP parsing, PPM encoding, JSON framing, and the |
| 67 |
enqueue-to-emulator-thread marshalling live in the shared core — not in three |
| 68 |
different codebases. |
| 69 |
|
| 70 |
## Status |
| 71 |
|
| 72 |
Early. `SPEC.md` (0.1.0) and the interfaces below are the stable target the |
| 73 |
forks build against; the server `.c` and conformance suite are landing next. |
| 74 |
Reference implementation: the Commander X16 fork (`x16-emulator`). |