A deterministic GameBoy (DMG-01) emulator with M-cycle precision and lockstep netplay support, built in Rust.
- Deterministic Emulation: Precise M-cycle timing (70224 cycles per frame at 60 Hz)
- Complete CPU: All 512 opcodes (256 base + 256 CB-prefixed) of the Sharp LR35902
- PPU with Mode Tracking: Accurate timing with temporal memory access restrictions
- Lockstep Netplay: Frame-perfect synchronization via FF01/FF02 serial protocol
- Dual Network Transport: TCP (reliable) and UDP (low-latency with custom reliability)
- Desync Detection: Automatic checksum validation with freeze-on-desync
- TUI Interface: Beautiful terminal UI with Ratatui for monitoring and control
-
gameboy_core: Synchronous 60Hz emulation loop
cpu.rs: Sharp LR35902 CPU with all opcodesmmu.rs: Memory orchestration with PPU mode-based access controlppu.rs: Picture Processing Unit with mode state machinetimer.rs: Timer subsystem (DIV, TIMA, TMA, TAC)checksum.rs: Deterministic state hashing for netplay
-
gameboy_netplay: Async networking and lockstep
bridge.rs: Event types and channel definitionsemulator_task.rs: Async wrapper with try_recv() anti-gravity patternlockstep.rs: Frame synchronization and input bufferingtransport/: TCP and UDP implementations
-
gameboy_tui: Terminal User Interface
app.rs: Application state managementui.rs: Three-panel layout (control, display, status)main.rs: Event loop and integration
# Build all crates
cargo build --release
# Run the TUI
cargo run --release --bin gameboy_tui
# Run tests
cargo testPlace your GameBoy .gb ROM files in the roms/ directory:
gameboy_rust/
└── roms/
├── tetris.gb
├── dr_mario.gb
└── your_game.gb
The emulator will automatically load the first ROM it finds. See LOADING_ROMS.md for detailed instructions.
Note: Currently only MBC0 cartridges (≤32KB) are supported.
Menu Mode:
Tab: Cycle through input fields (IP, Port, Buffer)T: Toggle transport (TCP/UDP)H: Host a gameJ: Join a gameQ: Quit
Emulator Mode:
Esc: Return to menu- Arrow keys, Z, X, A, S: Joypad inputs
- IP Address: Remote host IP (for joining)
- Port: Network port (default: 8888)
- Buffer (B): Frame buffer size for jitter absorption (default: 3)
- Transport: TCP (reliable) or UDP (low-latency)
The lockstep protocol uses the GameBoy serial port (FF01/FF02):
- Each frame, if FF02 is written with 0x81, a serial transfer is triggered
- The emulator sends:
{frame_number, serial_data (FF01), checksum} - Remote input is buffered until B frames are available
- Checksums are validated each frame; mismatch triggers desync alert
# Run unit tests
cargo test --all
# Run specific test
cargo test test_deterministic_execution
# Run with output
cargo test -- --nocapturePlace test ROMs in tests/blargg/ directory:
cpu_instrs.gbmem_timing.gbinstr_timing.gb
Every operation returns exact M-cycle count. The frame loop accumulates exactly 70224 M-cycles per frame (4.194304 MHz / 59.73 Hz).
- VRAM (0x8000-0x9FFF): Blocked during PPU Mode 3 (Drawing)
- OAM (0xFE00-0xFE9F): Blocked during PPU Modes 2 (OAM Search) and 3
The emulator task uses try_recv() to consume input events without blocking, preserving timing precision:
while let Ok(event) = input_rx.try_recv() {
// Process input
}
// Continue with frame execution- Target: 60 FPS (16.67ms per frame)
- CPU Usage: ~5-10% on modern hardware (single-threaded)
- Network: < 1ms latency on LAN, 10-50ms typical WAN
- MBC0 only (no cartridge mappers)
- No audio (APU not implemented)
- No boot ROM (starts at 0x0100)
- Simplified sprite rendering
- No link cable features beyond netplay
MIT
Based on GameBoy hardware specifications from:
- Pan Docs (pandocs.gbdev.io)
- The Cycle-Accurate Game Boy Docs
- Blargg's test ROMs