aboutsummaryrefslogtreecommitdiffstats
path: root/README.md
diff options
context:
space:
mode:
authorgbc dev <gbc@localhost>2026-07-14 23:26:49 +0200
committergbc dev <gbc@localhost>2026-07-14 23:26:49 +0200
commitc879adc43b91a53eb7e76f232a39abb56d8144c1 (patch)
tree3bd8cd77fed46ceb62504d2fb067f349bad2cf0e /README.md
parentspeed control: frame skipping to decouple terminal draw from emulation (diff)
downloadsl0pboy-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.md113
1 files changed, 112 insertions, 1 deletions
diff --git a/README.md b/README.md
index 0412f4c..80151c4 100644
--- a/README.md
+++ b/README.md
@@ -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