README.md
retro-remote-debug-controller
A tiny, portable HTTP control API for retro-platform emulators, and the shared tooling that speaks it. One contract → one test harness across every Doomsday One port.
An emulator launched with a control-port flag exposes a localhost HTTP server:
| Endpoint | Returns |
|---|---|
GET /status | contract version, platform, frame counter, paused state |
GET /screenshot | PPM image of the live screen |
GET /mem?addr=&len=&bank= | raw memory bytes |
GET /regs | CPU registers (JSON) |
POST /step?frames= | advance N frames deterministically |
POST /key, /reset | (0.2) inject input |
Why
Every one of these ports (Pac-Man on X16, Neo6502, Agon; and whatever's next) is chasing frame-exact arcade fidelity, and every one had its own fragile, platform-specific way to check state headless (write to the SD card, dump 64 KB of RAM on a magic jump, grep stdout — which lies). This replaces all of that with one contract:
GET /mem→ assert game state (sprite table, actor positions).GET /screenshot→ Pillow → assert pixels, or just catch "nothing rendered" (the exact class of bug that costs an afternoon otherwise).POST /step→ deterministic, frame-addressable capture, so CI diffs don't flake against a free-running 60 Hz loop.
The payoff is uniformity: the same emu_control.py and the same pytest patterns
drive the X16, Neo6502, and Agon emulators.
Layout
SPEC.md the normative HTTP contract (start here)
core/retro_control.h portable backend vtable each emulator implements
core/retro_control.c shared server: sockets, HTTP, PPM/JSON, marshalling [next]
client/emu_control.py shared pytest client
conformance/ tests an emulator runs to prove it conforms [next]
Adopting it in an emulator (the shape)
Write the server once, adapt per fork. Each fork vendors core/ and implements
~5 callbacks, then calls the server:
#include "retro_control.h"
static retro_control_backend_t backend = {
.platform = "x16",
.read_mem = x16_read_mem, /* debug-read, no side effects */
.get_regs_json = x16_get_regs_json,
.get_framebuffer= x16_get_framebuffer,
.step_frames = x16_step_frames,
.get_frame_count= x16_get_frame_count,
};
if (control_port) /* set by -controlport / --control-port */
retro_control_start(control_port, &backend);
The socket loop, HTTP parsing, PPM encoding, JSON framing, and the enqueue-to-emulator-thread marshalling live in the shared core — not in three different codebases.
Status
Early. SPEC.md (0.1.0) and the interfaces below are the stable target the
forks build against; the server .c and conformance suite are landing next.
Reference implementation: the Commander X16 fork (x16-emulator).