# Emulator Architecture

> Prime context for architectural discussions about SvelteBoy's design patterns, component interactions, timing model, and trade-offs vs. general emulator approaches

- Skill: `vizigr0u/emulator-architecture` (Agent Skill)
- Install (CLI): `npx skillmds@latest add vizigr0u/emulator-architecture`
- Raw SKILL.md: https://api.skillmd.com/api/skills/vizigr0u/emulator-architecture/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: vizigr0u (https://skillmd.com/u/vizigr0u)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/vizigr0u/emulator-architecture

---


## Main loop (`assembly/emulator.ts`)

`Emulator.Tick()`: CPU-driven catch-up per instruction.
```
cycles = Cpu.Tick()
Timer.Tick(cycles)
Ppu.Tick() × cycles   (1 dot/T-cycle)
Dma.Tick() if active
```
`Run(timeMs)` / `RunFrames(n)` loop on `Tick()` until `GetStopReason() != None`.

## Component sync strategies

**CPU/PPU/Timer/DMA**: instruction-boundary catch-up; tightly coupled via returned cycle count.

**APU**: decoupled event-queue. CPU writes to audio registers → `AudioRender.EnqueueEvent(type, val)` with timestamp `(CycleCount - init) * SAMPLE_RATE / CYCLES_PER_SECOND`. End-of-frame `AudioRender.Render()` replays queue, synthesizing between event timestamps. Decouples synthesis from emulation speed.

## PPU (`assembly/io/video/ppu.ts`)

Full pixel FIFO pipeline, not scanline-at-end. State machine: OAMScan (80 dots) → Transfer (172–289 dots, variable due to sprite/window stalls) → HBlank → VBlank (lines 144–153). Variable Mode 3 duration captures mid-scanline effects correctly.

## Timer (`assembly/io/timer.ts`)

16-bit `internalDiv` increments every T-cycle. TIMA increments on watched-bit 1→0 transition:

| TAC | bit | freq |
|-----|-----|------|
| 00 | 9 | 4 kHz |
| 01 | 3 | 256 kHz |
| 10 | 5 | 64 kHz |
| 11 | 7 | 16 kHz |

Writing DIV resets `internalDiv` to 0 — can spuriously increment TIMA if watched bit was 1.

## Memory bus (`assembly/memory/memoryMap.ts`)

- Raw `load`/`store` for VRAM/WRAM/OAM/HRAM (precomputed WASM offsets, no dispatch)
- `GBload`/`GBstore` dispatched gate for IO/cartridge/bootrom → `io.ts` routes by address
- MBC1/2/3 in `assembly/memory/mbc*.ts` behind common interface

## Component model

All components (`Cpu`, `Ppu`, `Apu`, `Timer`, `MemoryMap`, `AudioRender`) are `@final` static classes — no instantiation. Circular references (CPU↔MMU↔PPU) are direct static calls. Static fields at known WASM offsets; cache-friendly.

## Timing accuracy

- PPU ticked 4×/M-cycle (required for STAT interrupt timing)
- Conditional branches return actual cycles taken
- EI delay: `Cpu.isEnablingIME` — IME takes effect after next instruction
- Interrupts: all components call `Interrupt.Request(IntType.X)`; CPU polls `Interrupt.HandleInterrupts()` each tick
- Register writes land immediately; Transfer reads current values — raster effects work correctly

## Per-frame call counts (~69,905 T-cycles, ~17,476 instructions)

`Ppu.Tick()` dominates (~69,905 calls). Most OAMScan/HBlank calls are no-ops (dot increment only).

$ARGUMENTS

