# gbc — a Game Boy Color emulator in C, rendered to the terminal A from-scratch Game Boy / Game Boy Color emulator written in plain C11. It renders graphics directly to the terminal using 24-bit (truecolor) ANSI escape codes and the Unicode upper-half-block trick (`▀`): each character cell packs two vertical pixels (foreground = top pixel, background = bottom pixel), giving a 160×144 GB screen in 160×72 terminal cells. To keep terminal load low, output is minimized two ways: SGR color escapes are only emitted when the fg/bg changes, and **inter-frame diffing** redraws only the cells that changed since the previous frame (jumping over unchanged runs with `ESC[row;colH`). A fully static screen writes nothing after the first frame; on the acid2 screens this cut output from ~7.1 MB to ~65 KB over 2 s (~108×). Terminal resize (`SIGWINCH`) triggers a full repaint. ## Build & run ```sh 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 ``` Requires a truecolor-capable terminal (`COLORTERM=truecolor`) at least 160 columns wide. ### Speed & frame skipping The emulator always advances every Game Boy frame at the correct rate, so games run at their intended speed. Because writing a frame to the terminal is by far the most expensive step, drawing can be *decoupled* from emulation via frame skipping — the machine keeps running in real time while the terminal is refreshed less often: ```sh ./build/gbc rom.gb --frameskip 2 # emulate 60 fps, draw every 3rd frame (20 fps) ./build/gbc rom.gb --fps 120 # run the game at 2x speed ./build/gbc rom.gb --uncapped # run as fast as possible (turbo) ``` | Flag | Meaning | |-------------------|-----------------------------------------------------| | `--frameskip N` | draw 1 of every `N+1` emulated frames (default 0) | | `--fps N` | emulation speed cap in fps (default 59.73 native) | | `--uncapped`/`--turbo` | run emulation as fast as possible | Frame skip and turbo are also adjustable live with `[` / `]` and `f`; in turbo mode terminal redraws are additionally clamped to ~60 Hz. On exit the emulator prints how many frames it emulated vs. actually drew. ## Controls | Key | Button | |----------------|---------| | arrows / WASD | D-pad | | `z` / `j` | A | | `x` / `k` | B | | enter | Start | | space | Select | | `f` | toggle fast-forward (turbo) | | `[` / `]` | decrease / increase frame skip | | `q` / Ctrl-C | Quit | Since terminals don't report key-release, buttons are held for a few frames after each keypress (relying on the terminal's key-repeat for sustained holds). ### External control channel (FIFO) Start with `--fifo [path]` (default `/tmp/gbc.fifo`) to open a named pipe that accepts button commands from any process. Unlike the terminal keyboard, this can send true key-**release** events, so it gives deterministic input for scripting and automated testing. ```sh ./build/gbc rom.gb --fifo /tmp/gbc.fifo & echo 'a' > /tmp/gbc.fifo # momentary tap of A echo 'start' > /tmp/gbc.fifo # tap Start echo '+left' > /tmp/gbc.fifo # press and HOLD Left echo 'a b' > /tmp/gbc.fifo # tap A and B together (Left still held) echo '-left' > /tmp/gbc.fifo # release Left echo 'release' > /tmp/gbc.fifo # release everything held echo 'a:30' > /tmp/gbc.fifo # tap A, held for 30 frames ``` Commands (one line, space/comma separated tokens, case-insensitive): | Token | Effect | |----------------|-----------------------------------------| | `a b start select up down left right` | momentary tap | | `name:N` | tap held for N frames | | `+name` | press and hold until released | | `-name` | release a held button | | `release` | release all held buttons | Set `GBC_INPUT_DEBUG=1` to trace resulting joypad state to stderr. ## Architecture | File | Responsibility | |-------------|-----------------------------------------------------------| | `types.h` | fixed-width integer typedefs | | `cart.c` | ROM loading, header parsing, MBC1/2/3/5, battery saves | | `gb.c` | system bus / memory map, joypad, OAM-DMA, HDMA/GDMA, reset| | `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 | | `main.c` | argument parsing, frame loop, timing, screenshot/test modes| ## Emulation status Passing: - **blargg `cpu_instrs`** — all 11 sub-tests - **blargg `instr_timing`** - **blargg `mem_timing`** / **`mem_timing-2`** — all 3 - **dmg-acid2** — PPU test renders correctly - **cgb-acid2** — CGB PPU test renders correctly Implemented: - Full SM83 CPU incl. HALT bug, EI delay, interrupt dispatch - Accurate timer edge behavior - PPU modes 0–3, STAT/LYC/VBlank interrupts, window line counter - CGB: double VRAM banks, BG/OBJ color palettes, tile attributes, master priority, WRAM banking, double-speed (KEY1), HDMA/GDMA - MBC1/2/3/5 with RAM banking and battery `.sav` files Not yet implemented: - APU (audio) — registers are stubbed - MBC3 RTC advancement - Sub-instruction PPU FIFO timing edge cases ## License For educational use. Test ROMs are downloaded separately and not included.