Skip to main content

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:

ThreadResponsibilityCommunication
MainReact UI, input polling, ROM loadingpostMessage + SharedArrayBuffer
Emulation WorkerCPU + RSP + RDP emulation loopSharedArrayBuffer for frame/audio
GFX WorkerOffscreenCanvas rendering + shaderstransferControlToOffscreen()
Audio WorkletSample output, frame pacingSharedArrayBuffer 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

N64WasmEmulatorJS
LicenseMITGPL-3.0
FocusN64-only (optimized)Multi-system (bloated)
ArchitectureStandalone WASMRetroArch wrapper
IntegrationSimple, hackableiframe-mandatory
GraphicsParaLLEl core + GlideSame 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.