From 95fa3f0095d6f383840992cab6833cac92493a60 Mon Sep 17 00:00:00 2001 From: gbc dev Date: Wed, 15 Jul 2026 16:53:28 +0200 Subject: control: video frame capture + deterministic input-movie replay Two socket features for building reproducible showcase clips: - record start [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 [reset] / input stop: drive the joypad from a synthesized TAS-style movie (text: ' [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. --- README.md | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) (limited to 'README.md') 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 ` | value-change watchpoint on a byte (no arg lists) | | `unwatch <0xaddr\|#idx\|all>`| remove a watchpoint | +| `record start [everyN]` | capture frames to `` (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 [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 ` [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 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: -- cgit v1.3.1-sl0p