| 1 |
# Changelog |
| 2 |
|
| 3 |
Contract versions of the Retro Remote Debug Controller (see [SPEC.md](SPEC.md)). |
| 4 |
Semantic versioning: MAJOR = breaking change to an existing endpoint, MINOR = |
| 5 |
additive (new endpoint / optional field), PATCH = clarification. Every minor so |
| 6 |
far has been purely additive — older clients keep working, and a server |
| 7 |
advertises the highest level whose backend callbacks are all present via |
| 8 |
`/status.contract`. |
| 9 |
|
| 10 |
## 0.6.0 |
| 11 |
- `GET /perf[?windows=][&zones=][&frames=][&since=]` — **performance metrics**: |
| 12 |
how much of each frame the program uses, in guest CPU cycles. `budget` (the |
| 13 |
frame and its budget), `current` (the last completed frame: total, work, |
| 14 |
idle, IRQ and main cycles, instructions, host time, utilisation), `windows` |
| 15 |
(min, mean, p50, p95, p99 and max per window, and overruns), `zones` (the |
| 16 |
program's own breakdown: address ranges, or regions the guest marks), raw |
| 17 |
per-frame records (`frames`, or every frame `since` one, so a harness never |
| 18 |
misses one), and `platform` for the rest. Backend callback: `get_perf`. |
| 19 |
- `POST /perf` — settings as a JSON body: enabled, target FPS, idle mode, |
| 20 |
history, reset, and the zones (an address range, or a **name for a guest |
| 21 |
marker region**). Backend callback: `set_perf`. |
| 22 |
- `/status.capabilities` — the endpoint groups a server serves, whatever level |
| 23 |
it reaches, so a client can ask for `perf` (or `pointer`) directly instead of |
| 24 |
inferring it from the cumulative ladder. Absent before 0.6. |
| 25 |
- Both callbacks return allocated JSON the core frees, so the core stays |
| 26 |
platform-neutral: the backend owns the measurements and their shape. |
| 27 |
- First backend: the enhanced X16 emulator, whose Performance panel and DAP |
| 28 |
`x16/perfStats` it answers from the same counters. Its `platform` section |
| 29 |
adds VERA's bus bandwidth per scanline and the sprite renderer's time; its |
| 30 |
guest markers are writes to `$9FBC`. |
| 31 |
- Motivation: a released game that players found slow. Its port measured |
| 32 |
itself with scanline probes compiled into the game, at a cost in memory and |
| 33 |
with torn reads; the emulator already knew the answer and had no way to say |
| 34 |
it to a test. |
| 35 |
|
| 36 |
## 0.5.0 |
| 37 |
- `POST /pad?index=[&buttons=][&connected=]` — present a **virtual game |
| 38 |
controller** and set what is held. `buttons` is the canonical mask (bit0 LEFT, |
| 39 |
1 RIGHT, 2 UP, 3 DOWN, 4 A, 5 B, 6 X, 7 Y, 8 START, 9 SELECT, 10 L, 11 R — |
| 40 |
the SNES/Neo6502 native order, fixed across platforms). `connected=0` unplugs. |
| 41 |
Backend callback: `set_pad`. |
| 42 |
- `GET /pad?index=` — read back `{index, connected, buttons}`, so a test can tell |
| 43 |
"the input never arrived" from "it arrived and was ignored". Backend callback: |
| 44 |
`get_pad`. |
| 45 |
- A pad is a **level, not an event**: the held mask persists across `/step` |
| 46 |
frames until changed, unlike `/key`'s momentary tap. Conformance asserts this |
| 47 |
directly, because it is the property that makes `/pad` worth having rather |
| 48 |
than reusing `/key`. |
| 49 |
- **Virtual, not SDL.** A backend presents a device its existing read path picks |
| 50 |
up rather than driving the host's joystick API. Headless CI has no joystick, |
| 51 |
and that is precisely the case this endpoint exists for. |
| 52 |
- Motivation: a real bug that shipped. A port's pad read masked the firmware's |
| 53 |
merged controller down to its d-pad and tried to recover the buttons from the |
| 54 |
wrong controller index — movement worked, fire did nothing, and there was no |
| 55 |
way to catch it without a human holding hardware. |
| 56 |
|
| 57 |
## 0.4.0 |
| 58 |
- `POST /pointer?x=&y=[&buttons=]` (absolute) or `?dx=&dy=[&buttons=]` (relative) |
| 59 |
— inject a pointer move/click through the platform's native mouse path. |
| 60 |
`buttons` is a bitmask (bit0=primary, bit1=secondary); omit for a pure move. |
| 61 |
Backend callback: `set_pointer`. |
| 62 |
- `GET /pointer` — read back the current pointer `{x, y, buttons}`, so a test can |
| 63 |
tell "input never arrived" from "wrong output drawn". Backend callback: |
| 64 |
`get_pointer`. |
| 65 |
- First consumer: the Neo6502 fork (drives the firmware mouse), unblocking the |
| 66 |
pointer floor of the `rstar-api` RWM/RGM toolkit. |
| 67 |
|
| 68 |
## 0.3.0 |
| 69 |
- `POST /mem?addr=&bank=` — write (poke) memory; bytes in the request body, |
| 70 |
returns `{"written":n}`. Backend callback: `write_mem`. |
| 71 |
- `GET /audio` — drain synthesised PCM since the last call as a self-describing |
| 72 |
WAV (the audio analogue of `/screenshot`); `X-Rrdc-Audio-Dropped` header on |
| 73 |
ring overflow. Backend callback: `capture_audio`. |
| 74 |
- The NEC PC-FX (Mednafen) fork joins as the fourth conforming emulator, the |
| 75 |
second to vendor the C core as a git submodule. |
| 76 |
|
| 77 |
## 0.2.0 |
| 78 |
- `POST /key?text=|code=[&down=]` — inject input (press / release / tap). |
| 79 |
Backend callback: `inject_key`. |
| 80 |
- `POST /reset` — soft/cold reset. Backend callback: `reset`. |
| 81 |
- Contract gating introduced: a NULL callback returns 501 for its endpoint and |
| 82 |
lowers the advertised contract, keeping partial backends honest. |
| 83 |
|
| 84 |
## 0.1.0 |
| 85 |
- Initial contract: `GET /status`, `GET /screenshot` (PPM), `GET /mem`, |
| 86 |
`GET /regs`, `POST /step`, `POST /pause` / `POST /resume`. |
| 87 |
- Shared C core (`core/retro_control.{c,h}`) — sockets, HTTP, PPM/JSON encoding, |
| 88 |
and cooperative pause/step, driven by three loop hooks. Backend callbacks: |
| 89 |
`read_mem`, `get_regs_json`, `get_framebuffer`, `get_frame_count`. |
| 90 |
- Shared pytest client (`client/emu_control.py`) and the platform-agnostic |
| 91 |
conformance suite. Reference implementation: the Commander X16 fork. |