System Architecture
Fork. Modernize. Wrap. Differentiate.
The strategy is NOT to write a new N64 emulator core from scratch. It's to take the best existing MIT-licensed core (N64Wasm) and surround it with modern web architecture that nobody else has built.
High-Level Architecture
Thread Model
The N64 has 3 processors that operate in parallel on real hardware. We mirror this with Web Workers:
| Thread | Responsibility | Communication |
|---|---|---|
| Main | React UI, input polling, ROM loading | postMessage + SharedArrayBuffer |
| Emulation Worker | CPU + RSP + RDP emulation loop | SharedArrayBuffer for frame/audio |
| GFX Worker | OffscreenCanvas rendering + shaders | transferControlToOffscreen() |
| Audio Worklet | Sample output, frame pacing | SharedArrayBuffer ring buffer |
Why This Matters
Current browser emulators run everything on the main thread:
- Input handling competes with emulation for CPU time
- Rendering blocks the next emulation step
- Audio processing causes frame drops
- UI becomes unresponsive during heavy scenes
Our architecture ensures:
- Input is never blocked — main thread is free to poll at 1000Hz
- Audio never crackles — dedicated thread with deterministic timing
- Rendering is decoupled — can drop frames gracefully without affecting emulation
- UI stays responsive — React state updates never compete with MIPS instructions
Memory Architecture
All threads share a single SharedArrayBuffer backing the WASM linear memory. Synchronization uses Atomics.wait() / Atomics.notify() for producer-consumer patterns.
Build Pipeline
Key Design Decisions
1. Fork N64Wasm, Not EmulatorJS
| N64Wasm | EmulatorJS | |
|---|---|---|
| License | MIT | GPL-3.0 |
| Focus | N64-only (optimized) | Multi-system (bloated) |
| Architecture | Standalone WASM | RetroArch wrapper |
| Integration | Simple, hackable | iframe-mandatory |
| Graphics | ParaLLEl core + Glide | Same cores, more overhead |
2. AudioWorklet Over ScriptProcessorNode
ScriptProcessorNode runs on the main thread with unpredictable timing — the #1 cause of audio issues in every browser emulator. AudioWorklet runs on a dedicated OS-level audio thread with sample-accurate timing.
3. OffscreenCanvas for Rendering
Moving WebGL to a Web Worker via transferControlToOffscreen() means:
- Main thread never touches pixel data
- Frame delivery is 4x more consistent
- GPU operations don't block input handling
4. OPFS Over IndexedDB for Saves
Origin Private File System is 3-4x faster than IndexedDB for binary data (90ms vs 850ms for 100MB). Critical for save states which can be several MB.