You are an expert in Rust, WebAssembly, and Next.js. Help me build a multi-system emulator that runs in the browser, supporting both Game Boy (DMG) and NES.
- Emulator core: Rust compiled to WebAssembly via wasm-pack + wasm-bindgen
- Frontend: Next.js 14 (App Router) + React + TypeScript
- Styling: Tailwind CSS
- Build: wasm-pack for Rust → WASM, Next.js for the frontend
rustboy/ ├── core/ # Rust crate (all emulator backends) │ ├── src/ │ │ ├── lib.rs # wasm-bindgen exports (SystemEmulator enum) │ │ ├── gb/ # Game Boy (DMG) backend │ │ │ ├── mod.rs │ │ │ ├── cpu.rs # Sharp LR35902 CPU │ │ │ ├── ppu.rs # Pixel processing unit │ │ │ ├── apu.rs # Audio processing unit │ │ │ ├── mmu.rs # Memory map / bus │ │ │ └── timer.rs # Timer registers │ │ └── nes/ # NES backend │ │ ├── mod.rs │ │ ├── cpu.rs # Ricoh 2A03 (6502) CPU │ │ ├── ppu.rs # NES PPU (256×240) │ │ ├── apu.rs # NES APU (pulse×2, triangle, noise, DMC) │ │ ├── bus.rs # Memory map / bus │ │ └── cartridge.rs # iNES parser + mapper 0/1/2/3 │ └── Cargo.toml └── web/ # Next.js app ├── app/ ├── components/ │ ├── Emulator.tsx # Main emulator shell (detects system from ROM) │ ├── Screen.tsx # Canvas renderer (variable resolution) │ └── Controls.tsx # Button UI + keyboard input └── hooks/ └── useEmulator.ts # WASM lifecycle hook
The Rust core exposes a single Emulator struct that wraps either backend:
Emulator::new(rom: &[u8]) -> Emulator— auto-detects system from ROM headeremulator.step_frame()— runs one full frameemulator.frame_buffer() -> *const u8— RGBA pixels (size depends on system)emulator.frame_width() -> u32emulator.frame_height() -> u32emulator.set_joypad(button: u8, pressed: bool)emulator.audio_buffer() -> Vec<f32>— samples since last frame (44100 Hz)
.gb/.gbc: Game Boy — header at 0x0104–0x014F.nes: NES — iNES magic bytes4E 45 53 1Aat offset 0
- Clock: 4.194304 MHz → 70224 cycles per frame at 59.7 fps
- Screen: 160×144 pixels, 4 shades of green
- CPU: Z80-ish, 8-bit registers (A, B, C, D, E, H, L, F), 16-bit PC/SP
- Memory map:
- 0x0000–0x7FFF ROM (cartridge)
- 0x8000–0x9FFF VRAM
- 0xA000–0xBFFF External RAM
- 0xC000–0xDFFF Work RAM
- 0xFE00–0xFE9F OAM (sprites)
- 0xFF00–0xFF7F I/O registers
- 0xFF80–0xFFFE High RAM
- Interrupts: VBlank, LCD STAT, Timer, Serial, Joypad
- MBC support: ROM-only, MBC1, MBC2, MBC3, MBC5
- Test ROMs to target: blargg's cpu_instrs, then dmg-acid2 for PPU
- Clock: 1.789773 MHz (NTSC) → 29780 CPU cycles per frame at 60.1 fps
- Screen: 256×240 pixels, 64-color palette (typically 25 on screen at once)
- CPU: Ricoh 2A03 (6502 without BCD), 8-bit registers (A, X, Y, P, SP), 16-bit PC
- CPU memory map:
- 0x0000–0x07FF Internal RAM (mirrored ×4 to 0x1FFF)
- 0x2000–0x3FFF PPU registers (mirrored)
- 0x4000–0x401F APU / I/O registers
- 0x4020–0xFFFF Cartridge space (PRG ROM/RAM)
- PPU memory map:
- 0x0000–0x1FFF CHR ROM/RAM (pattern tables)
- 0x2000–0x2FFF Nametables (mirrored)
- 0x3F00–0x3FFF Palette RAM
- APU channels: Pulse 1, Pulse 2, Triangle, Noise, DMC
- Interrupts: NMI (VBlank), IRQ (APU/mapper), RESET
- Mappers to support: 0 (NROM), 1 (MMC1), 2 (UxROM), 3 (CNROM)
- Test ROMs to target: nestest.nes, then blargg's nes_instr_test
- Rust: no unsafe except where strictly needed for WASM memory
- TypeScript: strict mode, no
any - Each hardware component in its own file/struct
- Prefer accuracy over speed; optimize only after passing test ROMs
- Each backend (gb/, nes/) is self-contained — no cross-dependencies
When I ask for a specific component, implement it fully with no placeholders.