diff options
| author | gbc dev <gbc@localhost> | 2026-07-14 23:26:49 +0200 |
|---|---|---|
| committer | gbc dev <gbc@localhost> | 2026-07-14 23:26:49 +0200 |
| commit | c879adc43b91a53eb7e76f232a39abb56d8144c1 (patch) | |
| tree | 3bd8cd77fed46ceb62504d2fb067f349bad2cf0e /README.md | |
| parent | speed control: frame skipping to decouple terminal draw from emulation (diff) | |
| download | sl0pboy-c879adc43b91a53eb7e76f232a39abb56d8144c1.tar.gz sl0pboy-c879adc43b91a53eb7e76f232a39abb56d8144c1.tar.xz sl0pboy-c879adc43b91a53eb7e76f232a39abb56d8144c1.zip | |
control: socket-based debug channel + gbctl CLI driver
Replace the input-only FIFO's limitations with a Unix-domain socket control
channel (--sock) that is a superset of the button vocabulary plus emulator
introspection: read/write bus memory, get/set CPU context, single-step, PC
breakpoints, value-change watchpoints, run/pause. Being a socket it replies to
each command and broadcasts async 'event stop ...' lines; a stored last-stop
record (stopinfo) lets per-request clients recover a missed event.
Add gbctl: a stdlib-python CLI that spawns a tmux pane running the emulator on
a ROM, auto-resolves the socket, and drives it with terse verbs (cpu/read/write/
reg/step/break/watch/continue/pause/press/hold/screen/stop). The FIFO stays as
legacy input-only.
Diffstat (limited to 'README.md')
| -rw-r--r-- | README.md | 113 |
1 files changed, 112 insertions, 1 deletions
@@ -20,11 +20,31 @@ make ./build/gbc path/to/rom.gb # interactive ./build/gbc rom.gb --test 30 # headless: run 30s, log serial (test roms) ./build/gbc rom.gb --shot 60 out.ppm # run 60 frames, dump a PPM screenshot +./build/gbc rom.gb --sixel 3 # sixel graphics output at 3x zoom +./build/gbc rom.gb --sock # open a control/debug socket (/tmp/gbc.sock) ``` Requires a truecolor-capable terminal (`COLORTERM=truecolor`) at least 160 columns wide. +### Sixel output + +On terminals that support **sixel** graphics (xterm `-ti vt340`, foot, WezTerm, +mlterm, Contour, recent iTerm2, etc.) pass `--sixel [scale]` to render true +pixels instead of half-blocks: + +```sh +./build/gbc rom.gb --sixel # default 2x zoom (320×288 pixels) +./build/gbc rom.gb --sixel 4 # 4x zoom +``` + +Each frame builds a per-frame palette from the framebuffer (≤256 entries — 4 +shades on DMG, a small set on CGB; colors are quantized more coarsely only if a +frame ever overflows), then emits standard sixel bands with per-column +run-length encoding. `scale` is an integer pixel zoom (1–6). Sixel frames are +sent in full (no inter-frame diffing), so combine with `--frameskip` on slower +terminals. + ### Speed & frame skipping The emulator always advances every Game Boy frame at the correct rate, so @@ -95,6 +115,96 @@ Commands (one line, space/comma separated tokens, case-insensitive): Set `GBC_INPUT_DEBUG=1` to trace resulting joypad state to stderr. +The FIFO is one-way (input only) and can't reply. For introspection/debugging +use the socket control channel below, which is a strict superset. + +### Socket control & debug channel + +Start with `--sock [path]` (default `/tmp/gbc.sock`) to open a Unix-domain +stream socket. It accepts everything the FIFO does **plus** emulator +introspection commands, and — being a socket — it *replies* to each command and +pushes asynchronous events when execution stops. Connect any line-oriented +client: + +```sh +./build/gbc rom.gb --sock /tmp/gbc.sock & +socat - UNIX-CONNECT:/tmp/gbc.sock # interactive +# or: nc -U /tmp/gbc.sock +``` + +The protocol is line based (one command per line). Replies start with `ok` or +`err`; asynchronous notifications start with `event`. Numbers accept `0x` hex +or decimal; **memory bytes are hex** (so `read` output feeds straight back into +`write`). + +| Command | Effect | +|-----------------------------|----------------------------------------------------| +| `ping` | `pong` liveness check | +| `help` | one-line command summary | +| `state` | `ok state running\|paused` | +| `cpu` (`regs`) | dump CPU context (af/bc/de/hl/sp/pc/ime/halted/cycles) | +| `reg <name> <val>` | set a register/flag (a..l, af..hl, sp, pc, ime) | +| `read <addr> [len]` | read `len` bytes (bus read), returned as hex | +| `write <addr> <bytes…>` | write bytes: `de ad be ef` or a `deadbeef` string | +| `step [n]` | single-step n instructions, replies with new context | +| `continue` (`c`) | resume free-running emulation | +| `pause` | halt CPU advancement at the next instruction | +| `break <addr>` | add a PC breakpoint (no arg lists them) | +| `delete <0xaddr\|#idx\|all>` | remove a breakpoint | +| `watch <addr>` | value-change watchpoint on a byte (no arg lists) | +| `unwatch <0xaddr\|#idx\|all>`| remove a watchpoint | +| `quit` | shut the emulator down | +| *(any button tokens)* | same vocabulary as the FIFO (tap/hold/release) | + +When free-running (`continue`) hits a breakpoint or watchpoint, every connected +client receives an async line and the machine pauses: + +``` +event stop breakpoint af=0x0840 bc=0x0800 … pc=0x0048 … cycles=72654560 +event stop watch 0xFF44 af=0x90C0 … pc=0x4812 … +``` + +There is also a `stopinfo` command (alias `why`/`laststop`) that reports the +last reason execution halted, so a client that wasn't connected when an async +`event` fired can still recover it (`ok stop breakpoint 0x0150 …`). + +`step` runs synchronously and replies immediately with the stop reason and the +resulting context (`ok stop step …` / `ok stop breakpoint …` / `ok stop watch …`). +Example session: + +``` +> pause +ok paused af=0x9040 … pc=0x021D … +> break 0x0150 +ok break #0 0x0150 +> read 0xFF44 1 +ok read 0xFF44 1 90 +> write 0xC000 de ad be ef +ok write 0xC000 4 +> reg pc 0x0150 +ok reg pc=0x0150 +> continue +ok running +event stop breakpoint … pc=0x0150 … +``` + +#### `gbctl` — CLI driver (agent/tmux friendly) + +`./gbctl` wraps the socket in a terse CLI that also spawns/stops a tmux pane +running the emulator, so an LLM agent (or you) can drive it by tool-calling one +command at a time. Socket auto-resolves to the single live instance. + +```sh +./gbctl spawn roms/game.gb --uncapped # tmux pane + emulator --sock; waits ready +./gbctl cpu ; ./gbctl read 0xC000 16 ; ./gbctl write 0xC000 deadbeef +./gbctl break 0x0150 ; ./gbctl continue 5 # blocks up to 5s for the stop event +./gbctl press a b ; ./gbctl hold left ; ./gbctl release +./gbctl screen ; ./gbctl stop # view the pane / tear down +``` + +Run `./gbctl` with no args for the full command list. This is driven end-to-end +by the `gbc-driver` skill. + ## Architecture | File | Responsibility | @@ -105,7 +215,8 @@ Set `GBC_INPUT_DEBUG=1` to trace resulting joypad state to stderr. | `cpu.c` | Sharp SM83 core (full opcode set, cycle-accurate accesses)| | `timer.c` | DIV/TIMA/TMA/TAC with falling-edge accuracy | | `ppu.c` | LCD controller, scanline renderer (BG/window/sprites), CGB| -| `render.c` | terminal truecolor output + raw-mode keyboard input | +| `render.c` | terminal truecolor output + raw-mode keyboard/FIFO input | +| `control.c` | socket control/debug channel: memory, CPU, step, breakpoints| | `main.c` | argument parsing, frame loop, timing, screenshot/test modes| ## Emulation status |
