Precise. Pure. Powerful.
RustyNES is a cycle-accurate Nintendo Entertainment System emulator written in
pure Rust. It targets the Mesen2 / higan / ares accuracy bar — tight, lockstep
scheduling at PPU-dot resolution on a master-clock-precise timebase — clearing
AccuracyCoin 100% (141/141) and matching the Nintendulator golden log on
nestest with zero diff. (As of v2.0.3 every assigned test passes, including the
two newest upstream PPU tests, "ALE + Read" and "Hybrid Addresses", via the promoted
2-cycle-ALE fetch model — ADR 0030.)
Beyond reference accuracy, RustyNES is a complete, modern emulation platform:
172 mapper families covering the vast majority of the commercial library (plus a
UNIF .unf cartridge loader), the full Famicom Disk System (real-BIOS boot with a
timed disk-head model), Vs. System / PlayChoice-10 arcade games in true RGB,
GGPO-style rollback netplay (native UDP and browser WebRTC, 2-4 players),
RetroAchievements, a native Libretro core for RetroArch, a scriptable TAStudio piano-roll TAS editor with .fm2 /
.bk2 / .fcm / .fmv / .vmv movie interop, editing-capable debug tools
(palette / nametable / CHR / OAM writeback, an iNES / NES 2.0 header editor, an inline
6502 assembler), save states with rewind, run-ahead latency reduction, a Mesen2-class
debugger (expression / conditional breakpoints, R/W/X watchpoints, a hex editor, RAM
search, a callstack, .dbg source maps), A/V recording, HD-pack video + audio
(with an HD-Pack Builder), a shader / filter ecosystem, and a localized
(i18n) UI — all on a strict bit-determinism contract. The frontend is pure Rust (winit + wgpu +
cpal + egui) with native binaries for Linux, macOS, and Windows, plus a WebAssembly
build that runs in the browser.
Try it in your browser — no install required.
RustyNES combines accuracy-first emulation with modern features and the safety guarantees of Rust. Whether you are a casual player, a TAS creator, a speedrunner, or a homebrew developer, RustyNES provides a comprehensive and faithful platform for NES emulation.
Key differentiators:
- Reference-grade accuracy — a from-scratch core on a
u64master clock with run-to-timestamp catch-up; region-exact 3:1 NTSC/Dendy and 3.2:1 PAL clock ratios; sub-instruction PPU events visible to subsequent CPU code. - Determinism as a hard contract — same seed, ROM, and input sequence yield a bit-identical framebuffer and audio. This is what makes save-state round-trips, regression testing, and rollback netplay correct by construction.
- Modern features — RetroAchievements, rollback netplay, a scriptable TAStudio, run-ahead, display-sync pacing, an Android app, and a Mesen2-class, editing-capable debugger (read-only by default, determinism-preserving).
- Safe, modular Rust — the chip stack is
no_std + allocwith a one-directional workspace graph, so each component (CPU, PPU, APU) is independently fuzzable and benchmarkable. The onlyunsafelives behind opt-in feature boundaries.
| Feature | Description |
|---|---|
| Cycle-Accurate | Master-clock-precise CPU / PPU / APU — AccuracyCoin 100% (141/141), nestest 0-diff |
| 172 Mapper Families | NROM through MMC5, the full VRC line, Sunsoft FME-7, Namco 163, Taito, J.Y. Company ASIC, reusable-ASIC multicarts (FK23C / COOLBOY / MINDKIDS / Sachen / Waixing / Kaiser), and Vs.-System boards — classified Core / Curated / BestEffort behind a CI accuracy-honesty gate — plus a UNIF (.unf) cartridge loader |
| Famicom Disk System | .fds games with real-BIOS boot, writable disks, side-swapping, and 2C33 wavetable audio |
| Vs. / PlayChoice-10 | Arcade ROMs in true 2C03 / 2C04 / 2C05 RGB with per-game DIP presets |
| RetroAchievements | Native rcheevos integration: achievements, leaderboards, rich presence, hardcore mode |
| Rollback Netplay | GGPO-style rollback for up to 4 players, over UDP or browser WebRTC |
| TAS Tools | Frame-perfect deterministic record / replay with save-state branching (.rnm format) |
| Run-Ahead | Latency reduction that hides a game's internal input lag |
| Video Filters (v1.1.0) | Full NES_NTSC composite / S-video, a CRT / scanline shader pass, and custom .pal palettes |
| Lua Scripting (v1.1.0) | Sandboxed Lua 5.4 — memory/state access, frame & access callbacks, HUD overlay (opt-in) |
| ROM Library (v1.2.0) | .zip loading + automatic .ips/.ups/.bps soft-patching + a per-game DB and in-app ROM-Database editor |
| Shaders & HD Packs (v1.2.0) | Live NTSC knobs, a composable shader stack + CRT preset bank, and a (default-off) Mesen-style HD-pack loader |
| TAStudio Editor (v1.6.0) | A piano-roll TAS editor — per-frame button grid with drag-paint, a save-state greenzone + lag log, markers, forkable branches, and .rnmproj projects |
| Movie Interop (v1.6.0) | FCEUX .fm2 and BizHawk .bk2 movie import / export to and from the native .rnm format, plus Lua movie driving (emu.run / emu.frameadvance) |
| Mesen2-Class Debugger (v1.6.0) | Expression / conditional breakpoints, R/W/X watchpoints, a watch window, conditional trace, a full hex editor (poke / freeze / heatmap / find), and RAM watch / search |
| A/V Recording (v1.6.0) | Synchronized video + audio capture to .mp4 / .mkv via an ffmpeg pipe (default-off av-record, output-only) |
| HD Audio (v1.6.0) | HD-pack <bgm> / <sfx> OGG tracks triggered through the $4100 register, mixed on top of the produced APU buffer (default-off hd-pack) |
| Shader Ecosystem (v1.6.0) | LMP88959 NTSC/PAL, hqNx / xBRZ upscalers, and a constrained .slangp / .cgp preset import on the composable ShaderStack |
| Writable + Programmable (v1.7.0) | Editing-capable debug tools (palette / nametable / CHR / OAM writeback, an iNES / NES 2.0 header editor, an inline 6502 assembler), a scriptable tastudio.* Lua API, host IPC automation (script-ipc), .dbg source maps, Zwinder tiered rewind, audio depth (stereo / reverb / 20-band EQ), web parity, and an i18n framework |
| Android App (v1.8.x) | A complete native Android app on the byte-identical core — a multi-touch + hardware-controller (P1–P4) UI, wgpu SurfaceView rendering, save-states, Lua, RetroAchievements, direct-IP / CGNAT-TURN netplay, and a box-art ROM library (GitHub-sideload now; Google Play at v2.3.0) |
| iOS / iPadOS App (v1.9.x) | A complete native iOS app on the byte-identical core — a native SwiftUI shell over Metal (wgpu), multi-touch + GameController support, iCloud save-state sync, room-code rollback netplay, RetroAchievements, and the full TAStudio power-user suite (TestFlight now; App Store at v2.3.0) |
| Libretro Core (v1.10.0) | A complete, cycle-accurate Libretro core (the rustynes_libretro shared library — .so / .dylib / .dll by platform) integrating RustyNES seamlessly into RetroArch with RetroAchievements, dynamic audio sync, and deterministic rollback/save-state support |
| One-Clock Timebase (v2.0.0) | A single canonical cycle counter with every CPU cycle a real bus access and a split-around-the-access PPU catch-up, replacing the five-counter dot-lockstep scheduler; the release's designated breaking-behavior change (ADR 0002 / ADR 0029) |
| Vs. DualSystem (v2.0.0 core · v2.1.2 desktop) | Core-level emulation of the two-CPU/two-PPU Vs. arcade cabinet boards (Tennis, Baseball, Wrecking Crew, Balloon Fight) via a shared-WRAM + cross-wired $4016/IRQ convergence model — now presented on desktop as a composed two-screen view (side-by-side / stacked) with both consoles cross-wired |
| Audio Filter Model (v2.1.3) | Pick the APU analog filter — nes (default, authentic front-loader), famicom (fuller low end), or clean (Mesen2-like) — a tonal-only fix for the "thin / missing bass channel" character; the default stays byte-identical |
| Game Genie Database (v2.1.3) | Per-game code nomination from a bundled catalog of ~10,800 codes across ~520 USA/World games, header-robust CRC matching, and a Game Genie encoder — shipping on every target including the browser demo |
| Generated NTSC Palette + Shaders (v2.1.2) | An in-core, byte-identical-across-targets NTSC palette synthesizer (off by default) feeding a three-rung composite-shader ladder (simplified blur → LMP88959 → Bisqwit per-dot) with live emulator-synced dot-crawl |
| NSF / NSFe, non-60 Hz (v2.1.2) | The chiptune player parses the header play-speed divider and drives non-standard rates (PAL 50 Hz and custom) off a mapper cycle-timer IRQ, plus the chunked NSFE container |
| OAM Decay (v2.1.4, opt-in) | Mesen2-modeled dynamic-RAM decay of un-refreshed OAM rows with rendering disabled; off by default (byte-identical), deterministic when on, round-trips the save-state |
| Documentation Handbook (v2.1.3) | A Material-for-MkDocs site at /docs/ rendering the subsystem specs + user guide, alongside the playable demo (/) and rustdoc (/api/) on GitHub Pages |
| Pure Rust | winit + wgpu + cpal + egui frontend; safe no_std + alloc chip stack |
A cross-section of the commercial library running pixel-accurately on RustyNES — launch classics like Donkey Kong, Excitebike, and Super Mario Bros.; the Famicom Disk System's Kid Icarus; Konami's Castlevania and Contra; the Mega Man boss-select; and Mike Tyson's Punch-Out!! — spanning NROM up through MMC3 / MMC5, FME-7, and the full VRC line, plus Vs.-arcade RGB.
The full per-mapper visual corpus lives in
screenshots/external/ (Core / Curated) and
screenshots/besteffort/ (BestEffort) — boot / title /
gameplay frames spanning the bulk of the 172 mapper families.
- Master-clock-precise scheduler. A
u64master clock drives the CPU, PPU, and APU off the fundamental NES timebase with run-to-timestamp catch-up (the TetaNES / Mesen2 model). This is the central architectural choice and the reason mid-instruction PPU events — a sprite-zero hit at a precise dot, an MMC3 IRQ at a PPU dot, a mid-scanline scroll write — work without per-quirk patches. - Cycle-accurate 6502 CPU — all 256 opcodes including the full unofficial set (incl. the unstable SH* / TAS / LAS / XAA family), per-cycle bus interleaving, cycle-exact interrupt-sample timing, and sub-instruction DMC/OAM DMA via one unified dispatch.
- Cycle-accurate 2C02 PPU — per-dot scheduling, the full cycle-resolution
sprite-evaluation FSM (including the hardware
n+moverflow increment bug), the background-fetch pipeline, thePPUMASK→dot-skip delay, and a rendering-time$2007state machine. - Cycle-accurate 2A03 APU — the non-linear lookup mixer, 256-phase × 32-tap Blackman-windowed sinc synthesis (SFDR 81.6 dB), a 3-stage analog filter chain, and the DMC byte timer on the shared master clock.
- 172 mapper families covering the bulk of the licensed library — NROM, all
MMC1-5, the full VRC1/2/4/6/7 line (incl. VRC6 and VRC7 expansion audio), Sunsoft
FME-7/1/2/3/4 (+ 5B audio), Namco 163 (+ wavetable), the Taito
TC0190/TC0690/X1-005/X1-017, J.Y. Company ASIC boards, and the
Irem/Jaleco/Bandai/Tengen and Vs.-System mappers — classified Core / Curated /
BestEffort behind a CI accuracy-honesty gate. A UNIF (
.unf) cartridge loader resolves board names to the corresponding mapper. Seedocs/mappers.md. - Famicom Disk System —
.fdsgames with a user-supplieddisksys.romBIOS; the disk drive and IRQs, writable disks (.fds.sav,F9side-swap), and 2C33 wavetable audio. Real-BIOS boot works — Zelda, Metroid, and others boot into the game. v1.6.0's FDS-proper pass adds a timed disk-head position model (a motor restart rewinds the belt-driven disk and re-seeks across a deterministic not-ready window rather than teleporting to track 0),$4032drive-status auto-insert, and a per-game CRC quirk table — closing the Kid Icarus side-B post-registration replay. - Vs. System / PlayChoice-10 — the 2C03 / 2C04 / 2C05 RGB PPUs with per-game DIP presets and exact palettes; real arcade ROMs render in true RGB.
- RetroAchievements (opt-in
retroachievementsfeature, native-only) — login, achievements, leaderboards, rich presence, and hardcore mode (which disables save-state load / rewind / cheats), via the vendored MITrcheevoslibrary. - Rollback netplay — GGPO-style rollback over UDP for up to 4 players (predict →
advance → roll back and re-simulate on the deterministic core), plus a browser
(WebRTC) mesh path with a deployable signaling / STUN bundle (
deploy/). - TAS movie recording and playback — frame-perfect deterministic record / replay
with save-state branching, in a versioned
.rnmformat. - TAStudio piano-roll editor (v1.6.0) — a Mesen2 / BizHawk-class TAS-authoring
surface: a per-frame button-grid with drag-paint editing, a save-state greenzone
for instant deterministic seeking, a lag log, named markers, forkable
branches, and
.rnmprojproject files. - Movie interop (v1.6.0) — import and export FCEUX
.fm2and BizHawk.bk2movies to and from the native.rnmformat; v1.7.0 widens import to.fcm/.fmv/.vmv(and hashes the.fm2/.bk2exports), so RustyNES interoperates with the wider TAS ecosystem. - Save state + rewind — a 600-frame rewind ring, instant save / load, and a snapshot fast path used by run-ahead, plus a thumbnail save-state manager.
- Run-ahead — hides a game's internal input lag for snappier controls, built on the existing deterministic snapshot / restore.
- Emulation-speed control — 25 %–300 % speed presets (slow-motion to fast), hold-to-fast-forward, and single-frame advance while paused.
- Display-sync pacing + lock-free audio — an
auto/display/vrr/wallclockpacing matrix ends display-beat judder; a lock-free SPSC audio ring with dynamic rate control keeps audio clean and underrun-free; master volume, per-APU-channel mutes, and a graphic equalizer (selectable 5-band or 20-band ISO third-octave) round out the audio mixer. - Video filters (v1.1.0) — a full NES_NTSC composite / S-video filter, a
CRT / scanline shader pass (curvature, scanlines, aperture mask), and
custom
.palpalette loading, layered on the existing 8:7 pixel-aspect + overscan pipeline. - NSF / NSFe music player (v1.1.0; extended v2.1.2) — drop in a
.nsfchiptune and play it through the real APU, with a track selector and the file's title / artist / copyright. v2.1.2 parses the header play-speed divider and drives non-standard rates correctly (PAL 50 Hz and custom dividers on the NTSC console, via a mapper cycle-timer IRQ), and parses the chunkedNSFEcontainer. - Lua scripting (v1.1.0, opt-in
scriptingfeature, native-only) — a sandboxed Lua 5.4 engine: read / write memory, inspect CPU state, react to per-frame / per-access events, draw an HUD overlay, and drive control actions. v1.6.0 adds movie driving (emu.run/emu.frameadvanceto step the emulator from a script) and data breadth (named memory domains, sized reads, and ajoypadtable). v1.7.0 adds atastudio.*API to drive the piano-roll editor from a script, full Lua parity (getScreenBuffer/setState/ value-modifying callbacks), and a host-mediated IPC sandbox (comm.*/client.*/userdata.*, opt-inscript-ipc). The browser build runs an experimentalpiccoloLua backend (observational, not byte-parity with nativemlua, ADR 0012). Seedocs/scripting.md. - Cheats and input devices — Game Genie codes (with a Game Genie encoder) and raw
RAM cheats. (v2.1.3) The Cheats panel now nominates the known Game Genie codes
for the loaded game from a bundled catalog of ~10,800 codes across ~520 USA/World
games (ingested from the openly-licensed libretro Game Genie database), matched
header-robustly on both the header-excluded and full-file CRC32 so a re-headered dump
still resolves — shipping on every target including the browser demo. Plus a broad
peripheral set: the standard pad, Four Score (4-player),
the Arkanoid Vaus paddle (both ports), the Zapper light gun, the Power Pad,
the SNES mouse, the Family BASIC keyboard, the Family Trainer mat, the
Konami / Bandai Hyper Shot, and the Subor keyboard. Turbo / autofire with an
input-display overlay (the consolidated all-device Input Display), a per-game
database of nametable-mirroring overrides, and USB gamepads (
gilrs) with a deadzone control and hot-plug detection. - Desktop UX — a native menu bar, recent-ROMs list, a tabbed Settings window, light / dark / system themes, 8:7 pixel-aspect correction, optional overscan cropping, integer window-size presets, a pause-dim overlay, a status bar, screenshot-to-file/clipboard, and drag-and-drop ROM loading.
- egui debugger + devtools — a read-only CPU / PPU / APU / memory / OAM / mapper
inspector by default, plus opt-in breakpoints / watchpoints, a cycle trace
logger, and an event viewer (IRQ / NMI / register-write timeline) behind the
debug-hooksfeature — all preserving the strict determinism contract when off. - Mesen2-class debugger depth (v1.6.0,
debug-hooks) — expression / conditional breakpoints, read / write / execute watchpoints, a watch window, conditional trace logging, a full hex editor (poke, freeze, write-heatmap, find), and RAM watch / search — the debugging surface a TAS or homebrew developer expects, all read-only-by-default and determinism-preserving. - A/V recording (v1.6.0, opt-in
av-recordfeature, native-only) — capture the running game to an.mp4/.mkvvia an externalffmpegpipe (H.264 + AAC). It is a read-only tap on the already-produced framebuffer and audio, so it never touches the emulator or the determinism contract, and the default build pulls no extra Rust dependencies (only the systemffmpegat run time). - HD-pack HD audio (v1.6.0, opt-in
hd-packfeature, native-only) — HD-pack<bgm>/<sfx>OGG tracks triggered through the$4100control register and mixed (pure-Rustlewtondecoder) on top of the buffer the core already produced — the audio analogue of HD tile substitution, output-only and determinism-neutral. - Shader / filter ecosystem (v1.6.0) — built-in LMP88959 composite NTSC/PAL,
hqNx and xBRZ edge-directed pixel-art upscalers, and a constrained RetroArch
.slangp/.cgppreset importer (mapping well-known shader stems onto the built-in passes, never silently dropping the unsupported ones) — all composable on the off-by-default ShaderStack, post-framebuffer and never touching the core. Seedocs/frontend.mdand ADR 0013.
v1.7.0 "Forge" is the release where the tools become writable and programmable — every item below is additive and off-by-default, so the shipped core stays byte-identical and AccuracyCoin holds 100% (139/139).
- Editing-capable debug tools (
debug-hooks) — the inspector panels become editors: palette / nametable / CHR / OAM writeback (gated likeemu.write), an iNES / NES 2.0 header editor, and an inline 6502 assembler that patches code live. - Deeper debugger (
debug-hooks) — on top of the Mesen2-class breakpoint / watchpoint / hex-editor / RAM-search surface, a CallstackManager with step-into / over / out modes, a memory-access counter with uninitialized-read detection, and ca65 / cc65.dbgsource maps (plus the existing.sym/.mlb/.nlsymbol files) for source-level debugging. - Scriptable TAStudio (
scripting) — atastudio.*Lua API drives the piano-roll editor from a script, with analysis-canvas callbacks, alongside full Lua parity (getScreenBuffer,setState, value-modifying callbacks). - Host IPC / automation (opt-in
script-ipcfeature, native-only) — a host-mediatedcomm.*/client.*/userdata.*sandbox lets an external process drive and observe the emulator over IPC for automation and CI harnesses, behind a documented security posture (ADR 0016). - Rewind, deepened — a HistoryViewer, an Export-Last-30-seconds to
.rnm, and a Zwinder-style tiered greenzone (XOR-delta + LZ4) that stretches the rewind window far beyond the classic ring without bloating memory. - Expansion-audio NSF router — the NSF / NSFe player now routes through the real VRC6 / VRC7 / FDS / MMC5 / Namco 163 / Sunsoft 5B expansion-audio synths, and MMC5's expansion audio is synthesized in-game.
- Movie import breadth — in addition to FCEUX
.fm2and BizHawk.bk2, RustyNES now imports.fcm/.fmv/.vmvmovies (and hashes.fm2/.bk2exports), widening TAS-ecosystem interop. - HD-Pack Builder (
hd-pack) — author Mesen-format HD packs from the running game (ADR 0017); the loader was also corrected to parse the authentic Mesen<tile>format (ADR 0018). - Audio depth — bypass-by-default stereo panning (per-APU-channel pan), a Schroeder reverb + headphone crossfeed, an output-device picker, the 20-band EQ mode, and per-context (game / menu) volume (ADR 0020).
- Per-game config overlay — a
<rom>.jsonoverlay (region / mapper / submapper / mirroring overrides), a DIP-switch editor, and a lag-frame counter (ADR 0019). - Internationalization (i18n) — a compile-time string catalog with a Settings language picker; English is the default and universal fallback (byte-identical strings), with Spanish shipped to prove the mechanism (ADR 0023).
- Spectator netplay — observers can join a rollback session read-only, alongside the existing 2–4-player rollback.
The browser build closes several desktop-parity gaps with web-specific implementations (these live only in the wasm build, so the native build is byte-identical):
- Lua in the browser — the experimental
piccoloLua backend runs end-to-end from a.luapicker / paste box (observational, off by default, never in the determinism oracle — ADR 0012). - File System Access API — TAS
.rnmexports save through a native "Save As" dialog on Chromium browsers, with a graceful download fallback on Firefox / Safari (ADR 0021). - Gamepad API —
navigator.getGamepads()is polled each frame and routed to player 1 at the same late-latch as touch / keyboard, so it records and replays identically. - PWA / offline — a web manifest + service worker make the demo installable and offline-capable, within a 5 MiB bundle budget.
?settings=share-links — a curated subset ofConfig(filter + knobs, overscan, theme, aspect, zoom, FPS, volume) round-trips through a compact URL-safe blob, with a "Copy share link" button (ADR 0022).
The v2.1.x "Fathom" releases deepen display fidelity, audio, and accuracy on top of the v2.0.0 core. Everything here is additive and default-off (or tonal-only on the default), so the shipped build stays byte-identical and AccuracyCoin holds 141/141 — v2.1.0 landed the accuracy-remediation work (a display-only PPU palette-backdrop-override fix, 86 mapper families promoted BestEffort → Curated, and the MMC3 R1/R2 scanline-IRQ residual closed by design), and v2.1.2–v2.1.4 build on it:
- APU audio filter model (v2.1.3) — the authentic NES front-loader filter (a 90 Hz
plus an aggressive 440 Hz high-pass plus a 14 kHz low-pass) is byte-correct but rolls off
the bass hard, reading as a "thin / missing channel". Settings → Audio → Filter model lets you
pick
nes(default, authentic — byte-identical to earlier builds),famicom(a single ~37 Hz high-pass, fuller low end), orclean(a ~10 Hz DC-block, the Mesen2-like character). Tonal only — channel content, determinism, and the audio oracle are unchanged on the default. - Generated NTSC palette (v2.1.2) — an in-core synthesizer (
generate_base_palette) produces the 64-entry base palette from a model of the 2C02's composite output (Bisqwit / ares YIQ integration), tunable via saturation / hue / contrast / brightness / gamma. Every transcendental routes throughlibm, so the output is byte-identical across all targets (x86 / aarch64 / wasm /thumbv7em) and locked by a committed golden. Off by default; enable under Settings → Palette → "Generated NTSC". - Composite-shader ladder (v2.1.2) — a three-rung display-only ladder (simplified blur
→ LMP88959 composite → Bisqwit per-dot), with live emulator-synced dot-crawl now
wired to LMP88959 as well as Bisqwit. All passes are display-only — they run entirely in
the frontend, so the
visual_regressioncorpus (which hashes the pre-shader core framebuffer,Nes::framebuffer()) is byte-identical with any filter active. - Vs.
DualSystemsecond-screen presentation (v2.1.2, desktop) — a loaded Vs.DualSystemcabinet (Balloon Fight, Wrecking Crew, Tennis, Baseball) now runs both cross-wired consoles and presents them together, side-by-side (512×240, default) or stacked (256×480). P1/P2 drive the main console, P3/P4 the sub. The single-console path stays byte-identical (ADR 0032); run-ahead / rewind / netplay / TAS are scoped out of dual mode. - NSF non-60 Hz + NSFe (v2.1.2) — the chiptune player now honors the header play-speed
divider (driving sub-60 Hz rates off a mapper cycle-timer IRQ) and parses the chunked
NSFEcontainer; the standard 60 Hz path is byte-identical. - Optional OAM decay (v2.1.4, opt-in) — the 2C02's OAM is dynamic RAM; with rendering
disabled long enough, un-refreshed rows decay to a fixed garbage pattern. RustyNES now
models this exactly like Mesen2 (a 3000-CPU-cycle refresh window per 8-byte row). It is
off by default (byte-identical output and suites), deterministic when on (driven off
the PPU's monotonic dot counter), and round-trips the save-state via an additive
PPU_SNAPSHOT_VERSIONv7 tail. Enable via Settings → Emulation → "OAM decay (accuracy)". - Mapper regression + IRQ oracles (v2.1.4) — a CI boot-smoke sweep of all 26
BestEffortmapper families (auto-derived from the tier classifier) and a shared MMC3-clone A12/IRQ timing oracle (eleven clone boards driven bit-for-bit against a referenceMmc3) add safety-net coverage without moving any tier or touching the core.
RustyNES runs as a complete native Android app on the byte-identical core (so
AccuracyCoin holds 141/141 as on desktop), built on a shared rustynes-mobile
UniFFI bridge, a rustynes-android JNI layer, and a Jetpack Compose shell:
- Rendering + audio — wgpu on a
SurfaceView, reusing the desktop WGSL CRT / scanline / NTSC shaders (shared viarustynes-gfx-shaders), plus low-latencyAudioTrack. - Input — a multi-touch on-screen NES controller (foldable-aware and resizable) and full hardware-gamepad support (players 1–4, hot-plug, per-pad remapping, turbo).
- Library + state — a SHA-256-keyed box-art ROM library with SAF import, save-states and battery-SRAM, and save-on-background / auto-resume.
- Connectivity — Lua scripting, RetroAchievements, and direct-IP / LAN plus
CGNAT / TURN room-code rollback netplay over the same
rustynes-script/rustynes-ra/rustynes-netplaycores as desktop. - Platform polish — adaptive / foldable / TV (Leanback) layouts, Material You and EN/ES i18n, screenshot / MP4 capture, Picture-in-Picture, widgets, and accessibility (high-contrast + Okabe-Ito).
The apps ship now as GitHub-Releases / sideload, full-featured; the Google Play
production launch — with an ad-supported-freemium model and the foss / play flavor
split — is deferred to the v2.3.0 joint store launch (see Roadmap).
Details in docs/android.md.
RustyNES runs as a native iOS / iPadOS app on the byte-identical core (maintaining the same 141/141 AccuracyCoin bar as desktop), built on the shared rustynes-mobile UniFFI bridge and a native SwiftUI shell:
- Rendering + audio — Metal via
wgpuwith the same full WGSL shader pipelines (CRT, NTSC, Bisqwit) and ProMotion pacing, plus a low-latency CoreAudio hot path. - Input — multi-touch on-screen pad (NES-001 style), responsive sizing, GameController framework for P1–P4 (hot-plug), and Core Haptics.
- Connectivity & Tooling — room-code netplay (CGNAT/TURN) and LAN rollback, RetroAchievements, iCloud save-state sync (CloudKit), Lua console, and power-user tooling (TAS
.rnmmovies,.palpalettes,.zipROMs, HD-pack loading). - Platform polish — ReplayKit capture, Game Center, accessibility, EN/ES i18n, 4-slot save-state manager, and the dormant StoreKit
foss/App-Store seam.
The apps are currently distributed via TestFlight; the App Store launch is deferred to the v2.3.0 joint store launch (see Roadmap). Details in docs/ios.md.
Pre-built binaries for the latest release are available on the
Releases page, built automatically
for aarch64 macOS (Apple silicon), x86_64 Linux, and x86_64 Windows. Other targets
(Intel macOS, Linux ARM64, Android) build from source using the instructions below.
# Linux / macOS
tar xf rustynes-<tag>-<target>.tar.gz && ./rustynes path/to/rom.nes
# Windows (PowerShell)
Expand-Archive rustynes-<tag>-x86_64-pc-windows-msvc.zip; .\rustynes.exe path\to\rom.nesPrerequisites:
- Rust 1.96 — pinned via
rust-toolchain.tomland auto-installed by rustup. - Linux desktop dependencies for
winit/wgpu/cpal/egui(see below). - Git.
# Clone the repository
git clone https://github.com/doublegate/RustyNES.git
cd RustyNES
# Build the workspace (release)
cargo build --release --workspace
# Run a ROM you legally own (or launch bare and use F12 / drag-and-drop)
cargo run --release -p rustynes-frontend -- path/to/rom.nes
# Optional: build with RetroAchievements (needs a C compiler for vendored rcheevos)
cargo run --release -p rustynes-frontend --features retroachievements -- path/to/rom.nes
# Maximal NATIVE build — the "cargo --full equivalent". The `full` feature
# aggregates every native feature (RetroAchievements + Lua scripting + host IPC +
# HD-pack + debugger telemetry + A/V recording). Aliases make it a one-liner:
cargo full-run path/to/rom.nes # run the most fully-featured desktop binary
cargo full-run --fullscreen rom.nes # the alias ends in `--`, so flags forward to the binary
cargo full-build # build it (= --release -p rustynes-frontend --features full)The full build is purely opt-in — the default/shipped build and the emulation
core are unchanged. The WASM-only features (script-wasm, browser-cheevos,
wasm-canvas) are deliberately excluded, since full targets a native binary.
The frontend opens a 256×240 window (scaled, with 8:7 pixel-aspect correction), starts audio via the OS default device, and runs the ROM.
The native binary ships a clap 4 CLI with styled --help, a help subcommand,
shell completions, and an interactive terminal help browser:
rustynes --help # styled usage + examples + keyboard summary
rustynes help # browse all topics (interactive TUI on a terminal)
rustynes help mappers # one topic, printed (also works piped: `… | less`)
rustynes completions fish # print a shell-completion scriptHelp topics: controls, hotkeys, gamepad, features, mappers, config,
scripting, netplay, about. The interactive browser is behind the default-on
help-tui cargo feature; piped / non-terminal output falls back to a static page.
Ubuntu / Debian:
sudo apt-get install -y libxkbcommon-dev libwayland-dev libxkbcommon-x11-dev libasound2-dev libudev-devCachyOS / Arch:
sudo pacman -S --needed libxkbcommon wayland alsa-lib systemd-libsmacOS / Windows: no extra system dependencies are required for the default build.
The optional retroachievements feature additionally needs a C compiler for the
vendored rcheevos sources.
A hosted demo is live at
doublegate.github.io/RustyNES. To build
it yourself you need trunk (cargo install trunk):
cd crates/rustynes-frontend/web
trunk serve # dev server at http://127.0.0.1:8081
trunk build --release # the full winit + wgpu + egui build in ./dist
# Or a lightweight canvas-2D embed:
trunk build --release --no-default-features --features wasm-canvasThe desktop frontend frames the NES image with an always-on menu bar (top) and
status bar (bottom); the egui debugger is a separate overlay toggled with `.
Everything has a keyboard shortcut, but nothing requires one.
- Menu bar — File (Open ROM, Open Recent, save / load state, a ten-slot (0–9) Save Slot picker, a thumbnail Save States… manager, Take Screenshot, Copy Screenshot to Clipboard), Emulation (Pause, Reset, Power Cycle, Speed 25–300 %, Run-Ahead 0–3, the region label, Vs. Insert Coin / FDS Swap Disk Side when relevant), Tools (Cheats, TAS Movies, the TAStudio piano-roll editor, Record A/V, Netplay, RetroAchievements, Performance Monitor — opened as floating windows without the debugger), View (Settings, Theme, 8:7 Pixel Aspect, Hide Overscan, Fullscreen, Window Size 1x–4x, Show FPS, Pause When Unfocused, Show Menu Bar), Debug (the debugger overlay + per-chip panels), and Help (Keyboard Shortcuts, About).
- Status bar — ROM name, region, mapper, run-ahead depth, Running / Paused / Netplay state, the current speed when not 100 %, and the FPS readout.
- Settings window — a tabbed Display / Audio / Input / Advanced dialog (View → Settings…) with a live master-volume slider + mute, per-APU-channel mutes, a gamepad deadzone slider, live theme / pixel-aspect / overscan / FPS toggles, and a Reset-to-Defaults button per section.
- Quality-of-life — 25 %–300 % emulation-speed presets, hold-to-fast-forward (audio muted) and single-frame advance while paused, a thumbnail save-state browser, integer window-size presets (1x–4x), optional overscan cropping, optional pause-when-unfocused, light / dark / system themes, a pause-dim "PAUSED" overlay, a recent-ROMs list (missing files greyed out), controller hot-plug toasts, and a first-run Welcome modal.
Every binding is TOML-rebindable (and remappable in the in-app Settings); see the
controls guide for the full schema. USB gamepads
auto-bind to player 1 (Xbox-style: South = A, West = B, plus Start, Back / Select, and
the D-pad), and you can drag-and-drop a .nes / .fds onto the window to load it any time.
| Action | Player 1 | Player 2 |
|---|---|---|
| D-Pad | Arrow keys | W / A / S / D |
| A / B | Z / X | Q / E |
| Start / Select | Enter / Right-Shift | P / L |
| Action | Key | Action | Key |
|---|---|---|---|
| Pause / Resume | Space | Save / Load state | F1 / F4 |
| Fast-forward (hold) | Tab | Rewind (hold) | F5 |
| Frame-advance (while paused) | \ (backslash) |
Reset / Power-cycle | F2 / F3 |
| Speed up / down / reset | = / - / 0 | Open ROM | F12 |
| TAS record / play / branch | F6 / F7 / F8 | Swap disk side (FDS) | F9 |
| Toggle menu bar | M | Insert coin (Vs.) | F10 |
| Toggle debugger | ` (backtick) |
Fullscreen | F11 |
| Quit / exit fullscreen | Esc | Save-state slot | 0 – 9 |
RustyNES is a Cargo workspace of focused crates. Three load-bearing decisions, detailed
in docs/architecture.md and docs/scheduler.md:
- A shared master-clock timebase. The CPU advances a
u64master clock by the region'scpu_dividerper cycle; the PPU is caught up tomaster_clock − ppu_offsetin both halves of every access (APU and DMA share the same clock). This makes the region-exact 3.2:1 PAL ratio and cycle-exact interrupt / DMA timing expressible, and makes sub-instruction PPU events work naturally. - The Bus owns everything mutable.
rustynes-core::Busholds the PPU, APU, mapper, WRAM, controllers, and open-bus latch; the CPU borrows&mut Busduringtick(). This single choice avoids the borrow-checker fight the alternative creates. - A one-directional workspace graph.
rustynes-cpuhas norustynes-ppuorrustynes-apudependency; each chip is fuzzable and benchmarkable in isolation.
| Crate | Role |
|---|---|
rustynes-cpu |
Cycle-accurate 6502 / 2A03 CPU core |
rustynes-ppu |
Dot-level 2C02 PPU |
rustynes-apu |
Hardware-accurate 2A03 APU with band-limited synthesis |
rustynes-mappers |
172 mapper families + expansion audio + UNIF loader |
rustynes-core |
Integration layer: Bus, scheduler, console, save states |
rustynes-script |
Sandboxed Lua 5.4 scripting engine (native mlua, wasm piccolo) |
rustynes-frontend |
winit + wgpu + cpal + egui app (binary: rustynes) |
rustynes-netplay |
GGPO-style rollback netcode (UDP + WebRTC) |
rustynes-cheevos |
RetroAchievements rcheevos FFI (opt-in, native-only) |
rustynes-ra |
Shared RetroAchievements session state (RaClient, native-only) |
rustynes-libretro |
Native Libretro API core wrapper (RetroArch) |
rustynes-gfx-shaders |
Shared WGSL presentation shaders (desktop + Android renderers) |
rustynes-hdpack |
HD-pack loader + compositor + HD audio (shared desktop + mobile) |
rustynes-mobile |
UniFFI bridge for the mobile platforms (Android, and v1.9.0 iOS) |
rustynes-android |
Android JNI glue over the mobile bridge |
rustynes-monetization |
AdPolicy ad-supported-freemium policy core (v2.3.0; dormant) |
rustynes-test-harness |
Integration tests and the accuracy / commercial-ROM oracles |
crates/ Cargo workspace: the crates above
docs/ Implementation specs, ADRs, the user guide, the monetization
design set, STATUS.md (single source of truth), and release notes
deploy/ Docker / compose for the browser-netplay signaling server + STUN/TURN
ref-docs/ Deep-research NES hardware reference
tests/ Integration tests + vendored CC0 / MIT / zlib test ROMs (no commercial ROMs)
screenshots/ Committed commercial-game visual corpus + showcase montages
scripts/ Regression-bisect + ROM-survey tooling
fuzz/ cargo-fuzz harnesses
RustyNES demonstrates reference-grade emulation accuracy. The single validated scheduler is the master-clock core; the RAM-direct AccuracyCoin decoder over 141 assigned tests is the authoritative source.
| Suite | Result |
|---|---|
| AccuracyCoin | 100% (141/141) — every assigned test passes, including the two newest upstream PPU tests ("ALE + Read", "Hybrid Addresses"), via the promoted 2-cycle-ALE fetch model (v2.0.3, ADR 0030) |
| nestest | 0-diff vs the Nintendulator golden log |
blargg cpu_interrupts_v2 |
5/5 strict · SH* 6/6 |
region_timing |
4/4 (PAL 3.2:1) · $2007 Stress 170/170 |
| Commercial-ROM oracle | 99 titles (60-ROM gate + 39-title survey), SHA-256-pinned, byte-identical |
The commercial-ROM oracle is a regression gate, not a correctness check — a visual
99-title survey is what catches rendering bugs. The wasm32 target shares the exact
emulator core, so the browser build runs the same scheduler. The sole strict
expected-fail is mmc3_test_2/4 sub-test #3 (a 1-PPU-clock MMC3 reload-pending
bracket that affects no AccuracyCoin score and breaks no commercial game). The full
per-suite breakdown, the mapper coverage matrix, and the version policy live in
docs/STATUS.md.
v1.6.0's off-axis accuracy pass (Workstream D) was a pin-test-first audit that
confirmed the cycle-accurate engine already models the dot/CPU-cycle-granular off-axis
cluster — the DMC/OAM-DMA ↔ $4016 / $4017 controller-read double-clock / dropped-bit
conflict, the $2007 (PPUDATA) read-during-active-rendering window with its deferred
state-machine reload and v-increment glitch, and the buggy sprite-overflow n+m
evaluation with the three-group open-bus / MDR decay timer — all verified by committed
oracles with no engine change. Those residuals were subsequently taken up by the
v2.0.0 "Timebase" one-clock scheduler rewrite (ADR 0002 / ADR 0029) and the v2.1.0
accuracy-remediation pass, which closed the MMC3 R1/R2 scanline-IRQ residual by design
(the full disposition of every remaining approximation lives in
docs/accuracy-ledger.md).
Everything added since the v1.0.0 core is additive and off-by-default — each new
workstream is a frontend tap or an opt-in feature flag, so the shipped / native /
no_std / wasm builds stay byte-identical — with two deliberate exceptions to
that byte-identity guarantee: the v2.0.0 one-clock "Timebase" scheduler and the
v2.0.3 promotion of the 2-cycle-ALE PPU fetch model (ADR 0030), which together
bring AccuracyCoin to 100% (141/141) — both newest upstream PPU tests, "ALE +
Read" and "Hybrid Addresses", now pass on the shipped default.
A note on test counts: RustyNES is validated by closed-form test ROMs (AccuracyCoin, nestest, blargg, mmc3_test, Holy Mapperel) and a commercial-ROM oracle, not by a headline unit-test number. When a doc and a passing test ROM disagree, the ROM wins — that is the project's definition of "cycle-accurate."
The screenshot below shows Super Mario Bros. at first light — correct background rendering, palette, and timing straight from the master-clock scheduler.
The headless core is comfortably real-time. On an Intel i9-10850K (rustc 1.86, release), against the 16.639 ms NTSC frame deadline:
| Workload | Frame time | Headroom |
|---|---|---|
nestest (static menu) |
3.92 ms | 4.25× realtime · 255 fps |
flowing_palette (render-heavy) |
2.49 ms | 6.69× realtime · 402 fps |
The reproducible record (methodology, all benches, and the historical A/B) is in
docs/benchmarks.md.
| Platform | Status |
|---|---|
| Windows x64 | Primary (release binary) |
| Linux x64 | Primary (release binary) |
| macOS ARM64 | Primary (release binary; Apple silicon) |
| macOS x64 | Supported (Intel; build from source) |
| WebAssembly | Primary (hosted demo + build) |
| Android (arm64) | Supported (v1.8.x; GitHub-Releases / sideload — see docs/android.md) |
| Linux ARM64 | Supported (cross-compile) |
| Libretro Core | Supported (RetroArch via rustynes-libretro) |
| iOS / iPadOS | Supported (v1.9.x TestFlight; App Store at v2.3.0) |
- Rust 1.96 stable (pinned via
rust-toolchain.toml; auto-installed byrustup). - A GPU with a
wgpu-supported backend (Vulkan / Metal / DX12, or WebGPU / WebGL2 in the browser). - The optional
retroachievementsfeature needs a C compiler for the vendored rcheevos sources; the default build does not.
| Document | Description |
|---|---|
| User guide | Install, controls, save states + rewind, debugger, config, FAQ |
| Project status matrix | Per-suite pass count, mapper coverage, feature flags, version policy |
| Architecture | System design and the load-bearing decisions |
| Scheduler | The master-clock lockstep model |
| CHANGELOG.md | Version history and release notes |
| Documentation handbook | The Material-for-MkDocs site rendering the subsystem specs + user guide (also on GitHub Pages) |
| Roadmap | The forward roadmap — deepening the project through v2.2.0 and the v2.3.0 store launch |
| Release plans | Per-release design plans (v1.0.0 → the v2.0.0 "Timebase" set and the v2.1.x "Fathom" line) + the reference-emulator research dives that fed them |
| iOS / iPadOS App | Native SwiftUI shell over Metal (wgpu) — v1.9.x TestFlight |
| Libretro Core | Libretro core architecture, snapshot determinism, and RetroArch setup |
| Component | Location |
|---|---|
| CPU (6502) | docs/cpu-6502.md |
| PPU (2C02) | docs/ppu-2c02.md |
| APU (2A03) | docs/apu-2a03.md |
| Mappers | docs/mappers.md |
| Testing | docs/testing-strategy.md |
| Netplay | docs/netplay-webrtc.md |
Architecture Decision Records live in docs/adr/ (0001–0032, including
0028–0029 the v2.0.0 "Timebase" one-clock timebase + save-state/movie-format break,
0030 the AccuracyCoin 2-cycle-ALE / octal-latch closure, 0031 the game-database
must-not-override-mapper-controlled-state gate, and 0032 the Vs. DualSystem desktop
presentation). (The deeper engine-development audit logs are kept locally, outside the
public repo.)
The hosted GitHub Pages deployment serves three sections from one artifact: the playable WebAssembly demo at doublegate.github.io/RustyNES, the workspace API docs (rustdoc) at doublegate.github.io/RustyNES/api/, and the Material-for-MkDocs documentation handbook at doublegate.github.io/RustyNES/docs/.
RustyNES's current release is v2.1.9 "Fathom" ("Aperture"), the presentation-and-signal
step of the v2.1.5 → v2.2.0 "deepen the project" run — it adds a marquee CRT shader stack
(WGSL ports of CRT-Royale, crt-guest-advanced, and Sony Megatron HDR, selectable
in Settings → Shaders with mask/scanline/curvature controls and per-game presets), a
raw NTSC composite signal-decode path (a new rustynes-ppu::raw_signal un-decoded 2C02
composite model plus a signal_decode.wgsl pass that reconstructs and demodulates the real
signal for artifact-accurate color), GIF + WAV capture (extending av-record), and a
generated-NTSC palette editor with a live preview. Every shader / signal / capture path
is opt-in and default-off, so the shipped default presentation stays byte-identical:
AccuracyCoin stays 141/141 (100.00%), nestest 0-diff, visual_regression byte-identical.
The v2.1.x line opened with v2.1.0, the accuracy-remediation release (a display-only
PPU palette-backdrop-override fix, 86 mapper families promoted BestEffort → Curated, and
the MMC3 R1/R2 scanline-IRQ residual closed by design), then v2.1.1 (the Wizards &
Warriors game-database mirroring-override freeze fixed at the root), v2.1.2 "Prism"
(a generated NTSC palette + composite-shader ladder, Vs. DualSystem desktop
second-screen presentation, and NSF non-60 Hz / NSFe), and v2.1.3 "Codex" (the APU
audio filter-model selector, the full Game Genie code database + per-game nomination, and
the Material-for-MkDocs documentation handbook). The whole line rides on the v2.0.0
"Timebase" one-clock scheduler rewrite, and the same byte-identical cycle-accurate core
powers the desktop, browser, Android, iOS, and Libretro builds.
- Download: the GitHub Releases page — desktop binaries for Linux, macOS (aarch64), and Windows.
- Full per-version history:
CHANGELOG.md. - Authoritative current state:
docs/STATUS.md— the per-suite pass-count and mapper matrix (its release-header version can lag a patch-release bump;CHANGELOG.mdand the Releases page are authoritative for the latest tag).
With the mobile apps finalized (Android across v2.0.1–v2.0.4, iOS across v2.0.5–v2.0.8) and re-based onto the improved v2.1.x "Fathom" core, the forward arc keeps deepening the project — accuracy, performance, features, and quality — ahead of the joint mobile store launch:
- v2.1.5 → v2.2.0 — continued deepening of the existing project across accuracy,
performance, features, and quality (the v2.1.5 "Regression Net & Residual" work — a
Holy Mapperel mapper bank-reachability + IRQ regression net wired into CI — is already
under way in
[Unreleased]). - v2.3.0 — the joint mobile store launch (Google Play + Apple App Store + F-Droid +
AltStore PAL), turning on the
foss/playflavor split (ADR 0025) and the ad-supported-freemium monetization (AppLovin MAX + RevenueCat, a one-time $3.99 unlock).
The exact per-release scope beyond v2.1.4 is planning, not a shipped promise — see the roadmap for the current framing.
The longer forward arc lives as research-grounded design plans in
to-dos/plans/; see to-dos/ROADMAP.md
for the full roadmap and docs/STATUS.md for the current state.
Contributions of all kinds are welcome — code, testing, documentation, and design.
Please read CONTRIBUTING.md for the quality-gate contract, the
conventional-commit format, and the chip-behavior-change rule (a chip change touches
both the code and its docs/<subsystem>.md in the same PR).
# 1. Fork and clone, then create a feature branch
git checkout -b feat/my-feature
# 2. Make changes and run the quality gates
cargo test --workspace
cargo clippy --workspace --all-targets -- -D warnings
cargo fmt --all --check
# 3. Commit using conventional commits, then push and open a PR
git commit -m "feat(cpu): implement <thing>"
git push origin feat/my-featureThe four quality gates (fmt, clippy, doc, and the test suite) all run in CI and
must be green. See GitHub Discussions
if you need guidance.
RustyNES is dual-licensed under your choice of:
- MIT License — permissive, allows commercial use.
- Apache License 2.0 — permissive with a patent grant.
Unless you state otherwise, any contribution you submit is dual-licensed as above.
Vendored third-party code: the optional crates/rustynes-cheevos crate vendors the
RetroAchievements rcheevos library
under its MIT license (retained verbatim alongside the sources).
Test ROMs under tests/roms/ are individually CC0, MIT, or zlib licensed. No
commercial Nintendo ROMs are included, and they will never be bundled — dumps for the
commercial-ROM oracle are the user's responsibility and must come from cartridges they
legally own.
RustyNES stands on the shoulders of giants:
- The Nesdev wiki community for decades of hardware documentation and forum research.
- Mesen2, higan, and ares as the accuracy reference bar and trace oracles.
- TetaNES for the Bus-owns-everything architecture postmortem and Rust patterns.
- blargg, kevtris' nestest, Tepples' Holy Mapperel, and 100thCoin's AccuracyCoin as the closed-form definition of "cycle-accurate" used by this project.
- RetroAchievements and the
rcheevoslibrary that powers the achievement integration.
If you use RustyNES in academic research, please cite:
@software{rustynes2026,
author = {RustyNES Contributors},
title = {RustyNES: A Cycle-Accurate NES Emulator in Rust},
year = {2026},
version = {2.1.9},
url = {https://github.com/doublegate/RustyNES},
note = {Cycle-accurate NES emulator on a master-clock-precise scheduler;
AccuracyCoin 100\% (141/141), nestest 0-diff; 172 mapper families,
Famicom Disk System, Vs./PlayChoice-10 RGB, rollback netplay,
RetroAchievements, a TAStudio piano-roll TAS editor with .fm2/.bk2
movie interop, and a Mesen2-class debugger; pure-Rust
winit/wgpu/cpal/egui frontend with a WebAssembly build}
}
Built with Rust. Powered by passion for retro gaming.
Preserving video game history, one frame at a time.
Get Started · Play in Browser · Contribute · Documentation · Discuss






