aboutsummaryrefslogtreecommitdiffstats
path: root/README.md
diff options
context:
space:
mode:
authorgbc dev <gbc@localhost>2026-07-15 16:53:28 +0200
committergbc dev <gbc@localhost>2026-07-15 16:53:28 +0200
commit95fa3f0095d6f383840992cab6833cac92493a60 (patch)
treeb0ce39e89f15bdffbe38c3f1896750e702644952 /README.md
parenttools: stitch_overworld.py - DFS EXPLORE's connection graph into one overworl... (diff)
downloadsl0pboy-95fa3f0095d6f383840992cab6833cac92493a60.tar.gz
sl0pboy-95fa3f0095d6f383840992cab6833cac92493a60.tar.xz
sl0pboy-95fa3f0095d6f383840992cab6833cac92493a60.zip
control: video frame capture + deterministic input-movie replay
Two socket features for building reproducible showcase clips: - record start <path> [everyN] / record stop: append the RGB888 framebuffer of each produced frame to a flat 'GBCV' capture file (downsample with everyN). tools/gbgif.py turns it into a GIF (or PNG frames). Capture is decoupled from wall-clock/turbo/frameskip, so timing is always correct. - input play <path> [reset] / input stop: drive the joypad from a synthesized TAS-style movie (text: '<frames> [buttons...]' per line), frame-locked so replay is deterministic. 'reset' does an exact power-on (preserve cart ROM+SRAM, zero all other state) so a movie replays byte-identically -- verified by hashing two runs. tools/gbmovie.py is a Python builder for movies.
Diffstat (limited to 'README.md')
-rw-r--r--README.md48
1 files changed, 48 insertions, 0 deletions
diff --git a/README.md b/README.md
index 80151c4..b93446d 100644
--- a/README.md
+++ b/README.md
@@ -153,9 +153,57 @@ or decimal; **memory bytes are hex** (so `read` output feeds straight back into
| `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 |
+| `record start <path> [everyN]` | capture frames to `<path>` (1 of every N); build a GIF with `tools/gbgif.py` |
+| `record stop` | finish the capture (replies with the frame count) |
+| `record` (`status`) | report whether a capture is active |
+| `input play <path> [reset]` | replay a synthesized input movie (frame-locked); `reset` = replay from a clean power-on |
+| `input stop` | stop movie playback (releases all buttons) |
+| `input` (`status`) | report movie playback state |
| `quit` | shut the emulator down |
| *(any button tokens)* | same vocabulary as the FIFO (tap/hold/release) |
+### Video capture & input replay (demo GIFs)
+
+Capture the RGB framebuffer to a flat file and turn it into a GIF:
+
+```
+./gbctl record start /abs/out.gbv 2 # capture 1 of every 2 frames (~30fps)
+...
+./gbctl record stop
+python3 tools/gbgif.py out.gbv out.gif --scale 3
+```
+
+Drive the ROM from a **synthesized input movie** instead of live input, for
+reproducible demos. A movie is a text file of `<frames> [buttons...]` lines
+(`a b start select up down left right`; none/`-` = released); the emulator
+applies one line's worth of joypad state per emulated frame, so replay is
+deterministic. Build one by hand or with `tools/gbmovie.py`:
+
+```python
+from gbmovie import Movie
+m = Movie()
+m.wait(340) # boot splash + fastboot -> overworld
+m.hold("select", 8) # open the SL0P menu
+m.tap("down", repeat=13) # cursor to the last item
+m.hold("a", 6); m.wait(600)
+m.save("demo.gbmv")
+```
+
+`input play <path> reset` gives a clean, repeatable power-on (keeps cart
+ROM+SRAM, zeroes all other state), so a movie replays byte-identically every
+time. For a frame-exact demo GIF, arm both while paused so they start on the
+same frame:
+
+```
+./gbctl pause
+./gbctl input play /abs/demo.gbmv reset
+./gbctl record start /abs/demo.gbv 2
+./gbctl continue
+... # let the movie play
+./gbctl record stop
+python3 tools/gbgif.py demo.gbv demo.gif --scale 3 --start 172
+```
+
When free-running (`continue`) hits a breakpoint or watchpoint, every connected
client receives an async line and the machine pauses: