HTTP Contract
The normative spec is SPEC.md in the repo; this page is the readable tour.
Contract version 0.6.0 (semver — clients assert on the MAJOR). Every minor
has been purely additive:
| Level | Added |
|---|---|
| 0.1 | /status, /screenshot, GET /mem, /regs, /step, /pause, /resume |
| 0.2 | /key, /reset |
| 0.3 | POST /mem (write), /audio |
| 0.4 | /pointer |
| 0.5 | /pad |
| 0.6 | /perf (performance metrics), /status.capabilities |
A server advertises the highest level whose backend callbacks are all present
(contract is cumulative), and from 0.6 lists each group it serves in
capabilities, so a client can ask for perf directly.
Principles
- Opt-in. The server is OFF by default; it starts only with a control-port flag. Zero cost to normal use.
- Localhost only. It MUST bind
127.0.0.1./memand input injection are a memory-read / remote-control surface and never leave the machine. - The contract is the HTTP surface, not the flag. Each emulator names its
flag idiomatically (x16emu:
-controlport N; FAB:--control-port N). Only the endpoints are normative. - Determinism is first-class. Every state read observes a consistent machine captured at an instruction/frame boundary — never a torn read from the HTTP thread.
Endpoints
GET /status
{ "contract":"0.6.0", "emulator":"x16emu-enhanced", "platform":"x16",
"frame":12345, "paused":true, "running":true,
"capabilities":["mem","regs","screenshot","step","key","reset",
"mem-write","audio","pointer","pad","perf"] }
frame is the monotonic completed-frame counter — the determinism anchor.
Clients MUST reject a mismatched contract MAJOR. capabilities (0.6) is
absent on older servers: read contract there.
GET /screenshot
Binary PPM P6, 8-bit RGB, image/x-portable-pixmap. The most recent
complete frame (never a half-scanned one). Read width/height from the PPM
header — don't hardcode.
GET /mem?addr=&len=[&bank=]
Raw bytes (application/octet-stream). addr decimal or 0x-hex, in the
platform's address space. bank is an optional selector for banked platforms
(X16); omitted → current bank; ignored on flat-memory platforms. Reads use the
debug path (no side effects).
GET /regs
Registers as JSON; keys are platform-specific (see Per-Platform Notes).
POST /step?frames=N
Advance exactly N complete frames (default 1), then halt. Returns
{ "frame": <new counter> }. Meaningful when paused; on a free-running machine
the server pauses, runs N, and stays paused.
POST /pause / POST /resume
Halt / free-run. Returns { "paused": true|false }.
POST /key?text=|code=[&down=] / POST /reset (0.2)
Inject a key: text= a character, or code= a raw platform key code; down=1
holds it, down=0 releases, neither taps (press and release). /reset resets
the machine (the one time frame may go backwards).
POST /mem?addr=[&bank=] (0.3)
The body's bytes written at addr (a debug poke). Returns {"written": n}.
GET /audio (0.3)
The sound synthesised since the last call, as a PCM WAV; read the rate and
channels from its header. Under pause + step it is exactly the stepped frames'
sound. X-Rrdc-Audio-Dropped counts samples lost to overflow.
POST /pointer?x=&y=|dx=&dy=[&buttons=] / GET /pointer (0.4)
Move and click through the platform's own pointer path; read back
{x, y, buttons}. bit0 primary, bit1 secondary.
POST /pad?index=[&buttons=][&connected=] / GET /pad?index= (0.5)
A virtual game controller, in one canonical layout everywhere (bit0 LEFT, 1
RIGHT, 2 UP, 3 DOWN, 4 A, 5 B, 6 X, 7 Y, 8 START, 9 SELECT, 10 L, 11 R). A pad is
a level: what it holds persists across /step until changed.
connected=0 unplugs it.
GET /perf / POST /perf (0.6)
How much of each frame the program uses, in guest CPU cycles: the frame's
budget, the last completed frame (total, work, idle, IRQ, main), statistics
windows (min, mean, p50, p95, p99, max, overruns), the program's own breakdown
by zone, raw per-frame records (since= the last frame seen, so none is
missed), and a platform section. POST /perf takes a JSON body of settings.
The whole of it, with examples and graphs, is
Performance Metrics.
Conventions
- JSON is
application/json. Errors: 4xx/5xx with{"error":"..."}. - Unknown path → 404; wrong method → 405; unimplemented callback → 501.
- A CORS
Access-Control-Allow-Origin: *header is sent so browser tools work.
Consistency (normative)
/mem,/regs,/screenshotare serviced at an instruction/frame boundary on the emulator thread — the HTTP thread never reads CPU/RAM mid-instruction./screenshotserves a complete-frame snapshot./perfis answered on the emulator thread and describes completed frames only.frameincrements once per completed video frame and never goes backwards (except across/reset).