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.)
Development note — AI-assisted: RustyNES is heavily AI-assisted software, built with LLM tooling under a human-directed, test-driven workflow (public test ROMs as the oracle, a
no_stdcore, and continuous CI). Seedocs/originality-and-provenance.mdfor what that means for originality and licensing, and the Acknowledgments for the references and components it builds on. Accuracy claims are meant to be checked by running the public suites, not taken on faith; comparisons to other emulators are comparisons, not a claim of being "better."
Beyond reference accuracy, RustyNES is a complete, modern emulation platform:
174 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 |
| One-Clock Timebase | A single canonical cycle counter, every CPU cycle a real bus access, with a split-around-the-access PPU catch-up |
| 174 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) loader |
| Famicom Disk System | .fds games with real-BIOS boot, writable disks, side-swapping, a timed disk-head model, and 2C33 wavetable audio |
| Vs. / PlayChoice-10 | Arcade ROMs in true 2C03 / 2C04 / 2C05 RGB with per-game DIP presets; Vs. DualSystem two-screen presentation on desktop |
| 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 — room-code / TURN traversal, matchmaking / lobby, and spectators |
| TAStudio + Movie Interop | A piano-roll TAS editor (drag-paint grid, save-state greenzone, lag log, markers, forkable branches) with .fm2 / .bk2 / .fcm / .fmv / .vmv import and the native .rnm format |
| Run-Ahead & Rewind | Input-lag-hiding run-ahead and a tiered (Zwinder) rewind window, on the deterministic snapshot path |
| Mesen2-Class Debugger | Expression / conditional breakpoints, R/W/X watchpoints, a hex editor, RAM search, a callstack, and .dbg source maps — editing-capable (palette / nametable / CHR / OAM writeback, header editor, inline 6502 assembler), read-only by default |
| Lua Scripting | Sandboxed Lua 5.4 — memory / state access, frame & access callbacks, a tastudio.* API, HUD overlay, and host-IPC automation (opt-in) |
| Shaders & HD Packs | An NES-NTSC composite / S-video filter, a composable CRT / scanline shader stack (CRT-Royale / guest-advanced / Megatron look), a generated NTSC palette, custom .pal palettes, and a Mesen-style HD-pack loader + builder (video + OGG audio) |
| Cheats & Peripherals | A ~10,800-code Game Genie database with per-game nomination + encoder, raw RAM cheats, and a broad peripheral set (Four Score, Zapper, Arkanoid, Power Pad, keyboards, mouse) |
| A/V Recording | Synchronized video + audio capture to .mp4 / .mkv via an ffmpeg pipe (opt-in, output-only) |
| NSF / NSFe Player | Chiptune playback through the real APU + expansion synths, honoring non-60 Hz play-speed dividers |
| Android & iOS Apps | Complete native apps on the byte-identical core — touch + hardware controllers, save-states, netplay, RetroAchievements, and the shader stack (sideload / TestFlight; free store listing possible later) |
| Libretro Core | A cycle-accurate rustynes_libretro core for RetroArch (RetroAchievements, dynamic audio sync, deterministic rollback / save-state, region-correct NTSC / PAL / Dendy pacing, FDS multi-disk swapping, Game Genie cheats, and the NES Zapper on ports 1-2) |
| 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 174 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.
- 174 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), 2C33 wavetable audio, a timed disk-head position / not-ready model,$4032drive-status auto-insert, and a per-game CRC quirk table. Real-BIOS boot works — Zelda, Metroid, and others boot into the game. - 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, native-only) — login, achievements, leaderboards, rich
presence, and hardcore mode, via the vendored MIT
rcheevoslibrary. - Rollback netplay — GGPO-style rollback over UDP for up to 4 players (predict →
advance → roll back on the deterministic core), plus a browser WebRTC mesh with a
deployable signaling / STUN bundle (
deploy/), room-code / TURN traversal, matchmaking / lobby, and read-only spectators. - TAS + TAStudio — frame-perfect deterministic record / replay in the versioned
.rnmformat, plus a Mesen2 / BizHawk-class piano-roll editor: a drag-paint button grid, a save-state greenzone for instant seeking, a lag log, markers, forkable branches, and.rnmprojprojects. Imports FCEUX.fm2/ BizHawk.bk2/.fcm/.fmv/.vmv. - Save state, rewind, run-ahead — instant save / load, a thumbnail manager, a tiered (Zwinder) rewind window, and input-lag-hiding run-ahead — all on the deterministic snapshot path.
- Speed, pacing, audio — 25 %–300 % speed presets, hold-to-fast-forward, frame advance;
an
auto/display/vrr/wallclockdisplay-sync matrix; and a lock-free audio ring with dynamic rate control, per-channel mutes, and a 5- / 20-band equalizer. - Lua scripting (opt-in, native-only) — a sandboxed Lua 5.4 engine: read / write
memory, inspect state, react to per-frame / per-access events, draw an HUD, and drive
movies (
emu.run/emu.frameadvance) and the piano-roll (tastudio.*), with a host-mediated IPC sandbox. The browser build runs an experimentalpiccolobackend (observational, never in the determinism oracle). Seedocs/scripting.md. - Cheats + peripherals — a Game Genie encoder plus a bundled ~10,800-code database with
per-game nomination (header-robust CRC matching), raw RAM cheats, and a broad peripheral
set (standard pad, Four Score, Arkanoid Vaus, Zapper, Power Pad, SNES mouse, Family BASIC
and Subor keyboards, Family Trainer, Hyper Shot). Turbo / autofire, an all-device
input-display overlay, and USB gamepads (
gilrs) with deadzone + hot-plug. - Debugger + devtools (opt-in
debug-hooks) — a read-only CPU / PPU / APU / memory / OAM / mapper inspector by default; opt-in expression / conditional breakpoints, R/W/X watchpoints, a watch window, conditional + cycle trace, an event viewer, a full hex editor (poke / freeze / heatmap / find), RAM search, and a callstack with step in / over / out — all determinism-preserving when off. - A/V recording (opt-in
av-record, native-only) — capture to.mp4/.mkvvia an externalffmpegpipe; a read-only tap on the produced framebuffer / audio, so it never touches the core.
- Editing-capable debug tools — the inspectors become editors: palette / nametable /
CHR / OAM writeback, an iNES / NES 2.0 header editor, and an inline 6502 assembler; plus
ca65/cc65.dbgsource maps (and.sym/.mlb/.nl) for source-level debugging. - Host IPC / automation — a host-mediated
comm.*/client.*/userdata.*sandbox lets an external process drive and observe the emulator over IPC for CI harnesses, behind a documented security posture. - HD packs — an HD-Pack Builder authors Mesen-format packs from the running game, and
the loader mixes HD-pack
<bgm>/<sfx>OGG audio through$4100. - Audio depth — stereo panning, Schroeder reverb + crossfeed, an output-device picker, and per-context (game / menu) volume.
- Per-game config + i18n — a
<rom>.jsonoverlay (region / mapper / mirroring overrides), a DIP-switch editor, a lag-frame counter, and a compile-time i18n catalog (English default + universal fallback; Spanish shipped).
- Video filters + shaders — a full NES-NTSC composite / S-video filter and a composable
CRT / scanline shader stack (curvature, scanlines, aperture mask; LMP88959 composite,
hqNx / xBRZ upscalers, and a constrained RetroArch
.slangp/.cgpimporter), plus a three-rung composite-shader ladder (blur → LMP88959 → Bisqwit per-dot) with live emulator-synced dot-crawl and custom.palpalettes — all display-only and off by default, so the pre-shader framebuffer stays byte-identical. Seedocs/frontend.md. - Generated NTSC palette (opt-in) — an in-core synthesizer builds the 64-entry palette
from a 2C02 composite model (tunable saturation / hue / contrast / brightness / gamma),
byte-identical across all targets via
libmand locked by a committed golden. - APU filter model — pick the analog filter:
nes(default, authentic front-loader),famicom(fuller low end), orclean(Mesen2-like) — tonal-only, byte-identical on the default. - NSF / NSFe player — chiptune playback through the real APU and expansion synths, with a
track selector and metadata, honoring non-60 Hz play-speed dividers and the chunked
NSFEcontainer. - OAM decay (opt-in) — Mesen2-modeled dynamic-RAM decay of un-refreshed OAM rows; off by default (byte-identical), deterministic when on, and round-trips the save-state.
The browser build runs the same core with web-specific glue (native builds are byte-identical):
- Lua in the browser — the experimental
piccolobackend runs from a.luapicker / paste box (observational, off by default, never in the determinism oracle). - File System Access API — TAS
.rnmexports use a native "Save As" on Chromium, with a download fallback on Firefox / Safari. - Gamepad API —
navigator.getGamepads()is polled each frame at the same late-latch as touch / keyboard, so it records and replays identically. - PWA + share-links — an installable, offline-capable manifest + service worker (within a
5 MiB budget), plus
?settings=URL share-links for a curatedConfigsubset.
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; a possible
free Google Play / F-Droid listing — a free app with the foss / play flavor
split distinguishing pure-AOSP builds from optional free Google Play services
(achievements, Cast, Integrity, in-app update, cloud save) — is a later step with
no fixed version (see Roadmap). RustyNES is permanently open-source and
income-free (ADR 0035): no ads, no tracking, no paid unlock. 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, and a 4-slot save-state manager. (No monetization — the app is free; see ADR 0035.)
The apps are currently distributed via TestFlight; a future free App Store listing (no ads, no purchase) is possible but has no fixed version. 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, the Audio Mixer, Record A/V, Netplay, RetroAchievements, a read-only ROM Info browser, and the Performance Monitor — opened as floating panels; on native, every tool panel also offers a Detach button that pops it out into a real, separate OS window you can move to another monitor), 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 |
174 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-test-harness |
Integration tests and the accuracy / commercial-ROM oracles |
crates/ Cargo workspace: the crates above
docs/ Implementation specs, ADRs, the user guide,
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 |
blargg blargg_apu_2005 |
11/11 NTSC — length counters + table, frame-IRQ flag and timing, clock jitter, length timing in both frame-counter modes, reset timing, length halt/reload ordering |
blargg pal_apu_tests |
10/10 PAL — the region-calibrated rebuild of the same corpus, forced to PAL timing |
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."
RustyNES's accuracy claims are meant to be checked, not taken on faith: run the
public suites yourself (AccuracyCoin, nestest, blargg, Holy Mapperel — see
Compatibility & Accuracy). Any comparison to
another emulator is exactly that — a comparison against a reference RustyNES was
measured against (e.g. Mesen2 / higan / ares — see the Acknowledgments) —
and is not a claim that RustyNES is "better." For an honest
account of where the project advances, diverges from, or independently re-derives
NES emulation technique (and its license posture), see
docs/originality-and-provenance.md.
The screenshot below is an early-milestone image — Super Mario Bros. at "first light," among the first commercial titles to render during development. It predates much of the current accuracy work and is kept as a representative gameplay shot, not a claim about any particular sub-system.
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; free App Store listing possible later) |
- 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 — currently the v2.5.1 → v2.7.0 MiSTer core line (the v2.2.6 → v2.3.0 de-monetization + NESdev-remediation line is complete) |
| Release plans | Per-release design plans (v1.0.0 → the v2.0.0 "Timebase" set, the v2.1.x "Fathom" line, and the current v2.7.0 MiSTer core plan) + 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 |
| MiSTer co-simulation | The v2.5.1 → v2.7.0 programme: a NEW NES core in SystemVerilog, written from public documentation in a sibling repository, with this emulator as its verification oracle. The rung ladder, the compare surfaces, and what each rung cannot verify |
| Accuracy ledger | Known residuals in this emulator, including the ones the co-simulation ladder found in the oracle rather than the DUT — the first being NROM's PRG-RAM window, which this emulator provides and the board does not |
| 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–0038, 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, 0032 the Vs. DualSystem desktop
presentation, 0035 RustyNES is permanently non-commercial, 0036 the relicense to
GPL-3.0-or-later as a derivative work, 0037 the provenance firewall extended to
HDL for the MiSTer co-simulation programme, and 0038 the co-simulation
interrupt-injection API). (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.6.13 "Slack" — the cartridge outgrows the die, and three consumers want the same bus. An SDR SDRAM controller, a behavioural part model, a four-way arbiter and a console bridge, all written from the AS4C32M16SB-7 datasheet revision 1.4 -- no third-party controller read, ADR 0037 applying. The budget the previous step worked to was a single figure read off the fetch structure and never measured; nes_top's CHR_LAT sweep asks the console directly, and there are THREE answers: a background or sprite fetch tolerates 28 cycles and uses 17, the PPUDATA data port tolerates 8, and the CPU sampling at mc7 has 24 -- four cycles of every budget going to the console-domain crossing, which is not optional, because publishing a runtime modulo combinationally into an 11.64 ns domain costs -24.769 ns of setup. The PPUDATA port could never fit, half its budget being the crossing, so it leaves the shared bus entirely: ppu2c02 gains BUFFER_HANDSHAKE and fills its read buffer through a port of its own on the arbiter, affordable because the CPU does not read that buffer until its next PPUDATA access. THAT FIX SHIPPED A DEFECT ONLY A BANKED CARTRIDGE COULD SEE -- the request carried the RAW fourteen bits the PPU presents, which is right by coincidence on NROM because the mapper's translation is the identity there, and reads BANK ZERO on everything else; ppu-misc-2007-stress passed off-die throughout while the two CNROM gates read a zero byte where the oracle reads one, one and two. The fix REMOVES the address rather than correcting it: cart.sv already publishes the translation for the fetch path, so the request carries none and there is one source of truth for where CHR lives instead of two that agree only on NROM. A DELETION WAS THEN REFUTED BY ONE CYCLE: with the address fixed, a control issuing at a flat +7 passed both gates, which read as the anti-contention deferral buying nothing, so it was removed -- and the deployed code issued at +6, and both gates failed again. The control and the code differed by a single cycle, which is the measurement of how thin the CPU's off-die deadline is and the concrete argument for scheduling the bus rather than arbitrating it. Two INFERRED LATCHES that Verilator cannot see: ppu2c02 assigned two signals only under BUFFER_HANDSHAKE, so in the shipped on-die build the only assignment either reached was the reset branch, and a variable holding its previous value on every live path is a latch -- Quartus said so twice while the lint gate stayed green, and the comment beside them asserted the defect as a virtue ("outside BUFFER_HANDSHAKE neither signal ever moves", which is true and is exactly the condition). And a defect in the HARNESS: USE_SDRAM reaches Verilator as -G rather than as a file, so make ran ONE binary under both configurations' names and a log labelled on-die was the off-die build, reproducing the off-die failures exactly -- closed with a stamp-file prerequisite, demonstrated by mutation. THE OPEN ROW: accesses no longer auto-precharge, a hit costs 6 cycles against 10 for a miss, tRAS's 120 us MAXIMUM is respected by an early close in idle, and two defects came out of the rewrite -- re-entering idle with the request still asserted issued every access TWICE, and subtracting one from CAS the way the other waits are computed broke every read to all zeroes, CAS being "data appears at cycle N" rather than a command-to-command gap. Two MiSTer tickets close: sdram_sz is consumed VALIDITY BIT FIRST, so absent is deliberately not !present and a power-on all zeroes cannot read as "no board" (gated exhaustively over all 65,536 values), and status_menumask is computed rather than tied off, greying Reset whenever the console is already held in reset. OFF THE DIE THE CONSOLE PASSES 142 OF 142, every gate the on-die build passes, with better timing margin and 384 fewer M10K blocks -- and it still SHIPS ON the die, because an off-die core cannot run at all without the SDRAM add-on while rung 7's five mapper families fit on the die at 468 of 553 blocks. The emulation core is unchanged, so AccuracyCoin 141/141 and nestest 0-diff hold by construction. Built on v2.6.12 "Groundwork" — the bitstream was an NROM-only console. Rung 7 landed five mapper families and 142 co-simulation gates verify them, and the layer that turns that RTL into a bitstream was never told: rtl/emu.sv left cart_mapper, cart_prg_16k_banks and cart_chr_8k_banks unconnected, so Quartus tied all three to GND -- mapper 0 for EVERY cartridge, prg_8k_count = 0 collapsing PRG to an 8 KiB window, and CHR forced to RAM. The declared 256 KiB PRG and 128 KiB CHR were implemented as 8 KiB each; connecting three wires takes block memory from 666,061 to 3,680,717 bits and timing still closes at all four corners. NOTHING COULD HAVE CAUGHT IT: simulation cannot, because emu.sv is not in the testbench file list and the harness drives those ports itself, so all 142 gates exercised a correctly-configured cartridge; and Quartus DID say so three times, in messages that cite an INSTANCE path rather than a file and are absent from the "0 errors, N warnings" tally, so the existing checker read 0 of 125. Two gates close that -- one fails on an unconnected pin of any module this repository declares, the other pins the warning SET rather than its count -- and both are demonstrated to fail by mutation. The hps_io tie-off audit that followed raised nine T-MISTER-* tickets, and annotating each with a blocker turned "none of these landed" into a measurement: not one is blocked on EFFORT, so the list is the rung-6 agenda rather than a backlog. T-MISTER-SAVE was attempted and refuted -- every save route terminates in hps_io, which no gate here instantiates. The emulation core is unchanged, so AccuracyCoin 141/141 and nestest 0-diff hold by construction. Built on v2.6.11 "Exposure" — a picture is a gate the ladder did not have. All 141 co-simulation gates THEN IN THE SUITE were green (it ends this release at 142, the one it added) and TWO OF SIX commercial games rendered wrong -- a CHR-RAM write was taking the shared-pin composite address built for FETCHES instead of v, so the layout was right and the tiles were scrambled. The split is exactly CHR-ROM against CHR-RAM, which named the mechanism before any tracing, and a CONTROL says it is not v2.6.10's regression: the pre-M10K-fix RTL differs by the IDENTICAL 16,565 pixels, so the defect dates from the cartridge landing in v2.6.9. It was not UNREACHED -- the DUT asserts chr_wr 9,600 times in the Battletoads run -- it was UNCOMPARED: only THREE of the 141 gates compare a framebuffer, all three ship CHR-ROM, every other gate is CPU-side, and AccuracyCoin, the widest gate in the suite, is CHR-ROM too. The rung-7 gates' own comment says what they are for -- "these gates are about BANKING and nothing else" -- and it was accurate, and it was the whole coverage. Six commercial titles now render byte-identically to the oracle over all 61,440 pixels, published as a montage built by a script that REFUSES to publish a tile that differs from the oracle. The v2.6.10 bitstream carries the defect and a published version is immutable, so the corrected .rbf ships here. The same release finds EIGHT release leads describing v2.6.10 with v2.6.9's summary, and v2.6.9 gone from the lineage entirely, with docs/STATUS.md naming v2.6.10 under the codename "Abeyance" -- every existing check passing CORRECTLY, because they pin the version TOKEN and the token was right. Prose cannot be audited; an ORDERING can, so two gates are added and both are demonstrated to fail by mutation. The emulation core is unchanged, so AccuracyCoin 141/141 and nestest 0-diff hold by construction. Rung 6 does NOT close -- no DE10-Nano and no SuperStation One are attached to this machine, confirmed by checking rather than assumed. Built on v2.6.10 "Inference" — the cartridge meets the synthesiser. Five cartridge boards verified across 141 co-simulation gates had never been through Quartus, and Analysis & Synthesis refused the design: chr was written from TWO always_ff blocks, which cannot infer as one M10K, so 128 KB of CHR stayed in flip-flops -- 1,048,576 registers against roughly 166,000. Simulation cannot ask this question: Verilator accepts both forms without complaint. It is v2.6.6's finding one layer out -- that release established that an M10K read is REGISTERED, this one that a correctly registered memory still will not infer with two writers. The fitter was also throttling itself under Auto Fit while the .qsf carried no optimisation assignments at all: at full effort all six seeds close where two had failed, so the effort settings move the whole distribution across zero and the seed only picks where in it you land -- and the project had been pinned to seed 4, the WORST of the six. Pinned at seed 3, +0.531 ns setup and +0.099 ns hold, byte-identical across two independent compiles. The bitstream v2.6.9 could not produce ships here. Built on v2.6.9 "Abeyance" — an exclusion hides improvement as well as regression. Both remaining denied co-simulation streams close. One had been carried for seven releases as a "declared diagnostic, nine divergences by design" and was a defect in the harness, not the console; it is now identical on all 357,361 cycles and all 88 checkpoints. The other differs on exactly one attributed cycle, carried by a new per-cycle nine-field comparator whose scoped allowance fails both ways — so a DUT that improves cannot leave a stale allowance quietly hiding coverage.
The emulation core is unchanged in this release, so its accuracy numbers hold by construction.
| AccuracyCoin | 141/141 (100.00%), RAM decoder |
| nestest | 0-diff against the reference log |
| Mapper families | 174 — 51 Core, 95 Curated, 28 BestEffort |
| Co-simulation suite | 142 gates green, 0 failed, 0 skipped |
| MiSTer bitstream | published for Cyclone V, timing closed at every corner |
| MiSTer commercial rendering | six titles, one per supported board (NROM from rung 5, plus rung 7's MMC1, UxROM, CNROM, MMC3 and AxROM), each byte-identical to this emulator over all 61,440 pixels — the montage |
The MiSTer core has not run on hardware. No DE10-Nano or SuperStation One has been attached; a booting core, a synced display, audible sound and a working pad are not claimed. The palette, the video timing constants and the audio's absolute level are unverified by construction, because every gate in the co-simulation ladder compares something upstream of them.
- Download: GitHub Releases — desktop binaries for Linux, macOS (aarch64) and Windows, plus the MiSTer
.rbf. - Try it in a browser: https://doublegate.github.io/RustyNES/
- Per-release detail:
CHANGELOG.md— every release back through v2.0.0 "Timebase" and the v1.x line. - Authoritative current state:
docs/STATUS.md— per-suite pass counts and the mapper matrix. - Version policy and the forward table:
VERSION-PLAN.md.
The active line is v2.5.1 → v2.7.0 — the MiSTer core. It builds a new NES
core in SystemVerilog, written from public hardware documentation in the sibling
RustyNES_MiSTer repository, with this emulator as its verification oracle.
RustyNES is not being ported to FPGA and cannot be — a MiSTer core is
SystemVerilog compiled by Quartus into a Cyclone V bitstream, and high-level
synthesis of a cycle-accurate emulator's control flow does not produce usable
hardware. What is buildable is a new implementation verified against this one,
and crates/rustynes-cosim is the boundary: a narrow C ABI a Verilator
testbench links, plus a nes_golden_export CLI emitting golden traces.
Progress is a rung ladder, and a rung may not open until the one below is
green: rung 0 the compare surface, rung 1 the 6502, rung 2 the bus and
interrupts, rung 3 the 2C02, rung 4 the 2A03, rung 5 AccuracyCoin parity, rung 6
hardware bring-up, rung 7 mappers. Rungs 0-5 are closed — the 6502, the
bus and interrupts, the 2C02, the 2A03, and AccuracyCoin parity, with the status
vector identical entry for entry across all 146 entries and 128 gates green,
0 failed, 0 skipped. The console compiles to a Cyclone V bitstream with timing
closed at every corner, and that bitstream is published. Rung 6 is open and
blocked on hardware: no DE10-Nano or SuperStation One is attached, so nothing
about a booting core is claimed. Every rung is labelled in
docs/mister.md by whether it has an independent oracle —
because 141/141 on AccuracyCoin is not the same as "matches silicon", and a rung
verified only against this emulator inherits whatever this emulator has wrong.
Rung 5 has already shown why that caveat is not decorative. A cpu_bus
module written from the wiki disagreed with this emulator at $6000-$7FFF: an
NROM board decodes nothing there and reads open bus, while RustyNES allocates
8 KiB of PRG-RAM unconditionally. The wiki names that emulator default as a
problem and lists games that crash on it. The DUT is the more accurate of the
two, and the finding is recorded in
docs/accuracy-ledger.md rather than fixed inside a
co-simulation step — it changes shipped behaviour on every iNES NROM cartridge.
The emulation core is unchanged by this line. Releases in it touch the co-simulation apparatus and the DUT, not the shipped emulator, so AccuracyCoin 141/141 and nestest 0-diff hold throughout — verified rather than assumed on any release that does touch a chip crate.
Two risks are accepted in writing rather than discovered later: NES_MiSTer
already scores 121/125 on AccuracyCoin and real Famicom AV hardware scores about
the same, so there is no published accuracy headroom and the core may be
declined as a duplicate (Retro Remake / openFPGA are planned routes, not
contingencies); and the oracle can be wrong.
A free mobile store listing (Google Play / F-Droid / App Store) remains a possible later, unversioned step with no monetization attached (ADR 0035). Per-release scope beyond the current step is planning, not a shipped promise.
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.
The same console, in SystemVerilog. Six commercial cartridges, one per supported board, rendered by the
MiSTer core's RTL under Verilator — each one byte-identical to this emulator over all
61,440 pixels. Pre-palette output, coloured by the renderer. No hardware has run it.
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 licensed GPL-3.0-or-later.
Why GPLv3, and provenance. RustyNES is a derivative work of GPL-licensed NES
emulators: it incorporates code derived from Mesen2 (GPL-3.0-or-later) and, for
several mappers and the FDS drive model, from puNES, FCEUX, and Nestopia UE
(GPL-2.0-or-later). An earlier version of this project incorrectly described that code
as "oracle cross-checks" and licensed it MIT/Apache-2.0; that was wrong. Following a
NESdev community review, the project is relicensed GPL-3.0-or-later and the derivation
is credited per subsystem in docs/originality-and-provenance.md
and NOTICE (see also ADR 0036). Contributions are accepted under
GPL-3.0-or-later.
AI-assistance disclosure. RustyNES is heavily AI-assisted software. That does not change the above: code an LLM reproduces from GPL sources is still GPL-derived, and the maintainer is responsible for what lands in the tree — which is why the provenance is now stated plainly rather than scrubbed.
Reference firewall (so it does not recur). The failure that led to the relicense —
an AI reproducing reference-emulator source despite a black-box instruction, then later
scrubbing the honest "ported from" comments — is documented as a forensic post-mortem
(docs/provenance-failure-postmortem.md) and
distilled into a preventive, console-agnostic ruleset,
docs/ai-emulator-provenance-guardrails.md
(themed PDFs of both in ref-docs/). It is the project's top development
rule, ingested into AGENTS.md: reference emulators are black-box oracles whose
output may be observed but whose source is never read or reproduced; the local
ref-proj/ reference-emulator clone has been removed from the repo and stays
gitignored so that source is out of reach by design; hardware behavior is implemented
from public documentation and test ROMs; and any genuine derivation is attributed and
license-checked rather than laundered. The guardrails are shared as community
best-guidance for other AI-assisted emulator projects.
Incorporated permissive components (all GPL-compatible, notices in NOTICE):
emu2413 (MIT), TriCNES (MIT), the optional crates/rustynes-cheevos crate's vendored
RetroAchievements rcheevos (MIT),
blip_buf (LGPL-2.1-or-later), and the bundled fonts.
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 (GPL-3.0-or-later) — the primary derivation source. RustyNES is a derivative work and incorporates code derived from it (CPU unstable-store opcodes, the PPU sprite-evaluation / OAM model, ~15 mapper boards, the Bisqwit NTSC tables, and EEPROM / UNIF / debug-symbol / PGO code). higan and ares set the accuracy bar and serve as behavioral / trace oracles.
- puNES, FCEUX, and Nestopia UE (GPL-2.0-or-later) — derivation for specific subsystems: the puNES FDS drive-timing table, the FCEUX / puNES JV001 / mapper-147 code, and the Nestopia FME-7 model.
- 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. - emu2413 (Mitsutaka Okazaki, MIT) — the YM2413 / OPLL model behind VRC7 audio — and TriCNES (Chris Siebert, MIT), the cycle-accurate C# emulator (a detailed sub-cycle CPU/PPU/APU/DMA state machine) whose PPU / DMA models RustyNES ports (MIT-licensed, its source vendored in-repo with attribution) and also uses as a golden oracle. GeraNES (GPL-3.0-only) served as a behavioral oracle — consulted, not incorporated.
- The community CRT shaders and NTSC filters whose looks RustyNES independently
reimplements — CRT-Royale (TroggleMonkey), crt-guest-advanced (guest.r),
Sony Megatron (MajorPainInTheCactus),
NTSC-CRT (EMMIR), and Bisqwit's
NES composite model — plus the Press Start 2P (OFL) and Font Awesome
fonts. Full attribution and the complete license posture are in
NOTICE.
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.3.0},
url = {https://github.com/doublegate/RustyNES},
note = {Cycle-accurate NES emulator on a master-clock-precise scheduler;
AccuracyCoin 100\% (141/141), nestest 0-diff; 174 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





