aboutsummaryrefslogtreecommitdiffstats
path: root/README.md
blob: 0412f4c3426260f9c42dd230594658bcc7ae4504 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# 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.