> ## Documentation index
> Fetch the complete documentation index at: https://rustynes-git-release-v3-6-0-aaf8d1.docs7.io/llms.txt
> Ask it with GET https://rustynes-git-release-v3-6-0-aaf8d1.docs7.io/ask?q=<keywords or question>. Short keywords rank best.
> Use these to discover all available pages before exploring further.

# Compatibility

# Compatibility scope

**References:** `ref-docs/research-report.md` §Scope and goals → In-scope /
Out-of-scope; `ref-docs/nesdev-wiki-technical-report.md`;
`docs/nesdev-hardware-emulation-checklist.md`.

## Region targets (v1.0)

| Region | Status | Notes |
|--------|--------|-------|
| NTSC (Famicom JP, NES NA, NES AU) | First-class | Primary development target |
| PAL (NES EU) | First-class | Different timing tables, 312 scanlines, no odd-frame skip |
| Dendy (PAL famiclones) | First-class | 50 Hz refresh, NTSC-style timing semantics |

Region detection per NES 2.0 byte 12; iNES 1.0 fallback uses heuristics (file extension hints, mapper-region pairs).

## Hardware variant scope (v1.0)

| Variant | Status | Notes |
|---------|--------|-------|
| Famicom (HVC-001) | Yes | Reference NTSC |
| NES (NES-001) | Yes | Functionally identical CPU+PPU |
| NES (NES-101 / "top loader") | Yes | Same internals |
| AV Famicom (HVC-101) | Yes | RGB output ignored; we emit composite-style anyway |
| Famicom Disk System (FDS) | **Yes** | RAM adaptor (mapper 20) + user-supplied `disksys.rom` BIOS; see the FDS bullet below |
| Vs. System (arcade) | **Game-verified** | RGB PPU (2C03/2C04/2C05) + 2C05 quirks + DIP/coin inputs; **mappers 99 + 151 game-verify in-game RGB** — Vs. Excitebike / Vs. Clu Clu Land + Vs. Castlevania / Vs. Pinball (clean-byte detection) boot through the 2C03 palette. See the Vs./PC10 bullet below |
| PlayChoice-10 (arcade) | **Game-verified** | NES-game part renders through the 2C03 RGB palette — Power Blade / Rad Racer 2 / Captain Skyhawk / Mario Open Golf etc. (clean-byte-7 `0x02` detection); second-screen menu + Z80 out of scope |

### PPU variant scoping (Phase 7 / T-73-007)

The RGB PPUs — **2C03 / 2C04 / 2C05** (Vs. System, PlayChoice-10) — replace the
2C02 with a hardware RGB palette (not the composite palette this emulator
generates for stock NES). The emulator implements:

- **The RGB palette tables.** All five distinct hardware palettes are baked in
  (`crates/rustynes-ppu/src/palette.rs`): the 2C03 (= 2C05) master palette and the
  four 2C04-0001..-0004 copy-protection permutations, transcribed from nesdev
  "PPU palettes" (`C = 255 * DAC / 7`). The PPU routes through
  `palette_color_to_rgba(active_palette, ..)`; the default `Composite2C02` path
  is **byte-for-byte identical** to the legacy 2C02 output. RGB-PPU colour
  emphasis follows the hardware model (each emphasis bit forces *its* channel to
  full brightness, the opposite of the 2C02's darkening).
- **2C05 register quirks.** `$2000`/`$2001` are swapped and `$2002` returns the
  2C05 sub-variant identifier (`$3D`/`$1C`/`$1B` for 2C05-02/03/04) in its low
  bits. Gated on an `is_2c05` flag (false on every other PPU).
- **NES 2.0 byte 13** parsing (`VsPpuType`) resolves the Vs. PPU type → palette +
  2C05 flag, wired into the PPU at construction.
- **Vs. System inputs.** 8-bit DIP switches + coin/service inputs overlay the
  upper bits of `$4016`/`$4017` per the Vs. protocol (`Nes::set_vs_dip` /
  `insert_coin` / `set_vs_service`). Gated on the cart's console type.

**In-game RGB is game-verified for BOTH arcade platforms.** Two
detection signals route a cart through the 2C03 RGB PPU at parse time:

1. **Mapper-driven** (the most robust): mappers **99 (Nintendo Vs. System)** and
   **151 (Konami VRC1 on a Vs. board)** are Vs.-only — no licensed home game uses
   either — so a cart bearing one is forced to `ConsoleType::VsSystem` + the 2C03
   RGB PPU. Verified by **Vs. Excitebike / Vs. Clu Clu Land** (m99) and **Vs.
   Gradius / Vs. The Goonies** (m151) booting through the 2C03 palette.
2. **Clean-byte-7 arcade flag** (real No-Intro arcade dumps): a genuine Vs./PC10
   dump is clean iNES **1.0** with byte 7 **exactly** `0x01` (Vs.) or `0x02`
   (PC10). Both route to the 2C03 RGB PPU. This is the path that game-verifies
   **PlayChoice-10**: the NES-game half of **Power Blade / Rad Racer 2 / Captain
   Skyhawk / Mario Open Golf** (all byte-7 `0x02`) renders through the 2C03
   palette, as do the clean-byte Vs. dumps **Vs. Castlevania / Vs. Pinball**
   (byte-7 `0x01`).

   The critical guard: the notorious corruption is byte 7 == `0x0A` (console
   field 2 = PlayChoice-10 **plus** the NES-2.0 marker bits 2-3 = `10`), carried
   by many home dumps (e.g. the committed `Excitebike.nes`). Because `0x0A` is
   NES 2.0 AND is neither `0x01` nor `0x02`, it is **ignored** — the cart stays
   composite (`VsPpuType::None` → `Composite2C02`, byte-for-byte the legacy home
   path). A survey confirmed **no oracle/home ROM carries a clean `0x01`/`0x02`**,
   and the 60-ROM + 49-title oracles remain byte-identical after this change.

`crates/rustynes-test-harness/tests/vs_system_rgb.rs` asserts every rendered colour is
a 2C03-palette colour and at least one is impossible under the composite 2C02
(proving genuine RGB routing). The Vs./PC10 dumps are iNES **1.0** (no byte-13),
so the parser defaults the palette to 2C03; the **per-game database**
supplies the correct 2C04-000x / RC2C03 permutation for games that used one (see
below). Vs. games sit on an insert-coin attract loop until
a coin is latched (`Nes::insert_coin`); PC10 games render directly.

### Vs. System per-game database

`crates/rustynes-core/src/vs_db.rs` is an embedded, SHA-256-keyed table
(`no_std`-safe const data; `rustynes_core::vs_db::lookup`) that closes two gaps
for Vs. carts:

1. **Correct PPU palette.** iNES-1.0 dumps carry no NES 2.0 byte-13, so the
   parser defaults every Vs. cart to the 2C03 — wrong for the many games that
   used a 2C04-000x (different copy-protection LUT) or an RC2C03. The DB maps the
   ROM hash to its real `VsPpuType`, applied via `Nes::set_vs_ppu_type` (which
   re-runs the palette via the bus's private `reapply_vs_palette`, shared by the
   constructor + power-cycle). The DB is **authoritative for the palette**.
2. **DIP-switch presets.** Each entry carries the game's documented factory
   `DSW0` default (sourced from MAME `src/mame/nintendo/vsnes.cpp`), in this
   emulator's encoding (switch 1 = bit 0 .. switch 8 = bit 7, exactly the byte
   `Nes::set_vs_dip` consumes).

**Keys (v2.9.8).** Each row carries two SHA-256 keys. Its **identity key** is
`Nes::rom_sha256` of the dump, the hash of the bytes after the 16-byte header,
which is the identity saves and states use; its **image key** is the hash of the
whole file, header included (`Nes::image_sha256`), the only key before v2.9.8.
`vs_db::lookup` takes the `Nes` and matches the identity first, then the image,
so a re-headered or header-corrected dump keeps its palette and DIP row instead
of falling back to the 2C03 and DIP 0. All 19 rows were re-keyed from their
staged dumps; the image key stays on every row so that a future row added from a
dump nobody has staged is still reachable. `vs_db::lookup_by_hashes` takes the
two hashes when there is no `Nes`. Pinned by the `vs_db` unit tests (every row
reachable by each of its keys) and `tests/vs_db_identity.rs` (each staged dump
reaches its row by identity alone, and still does with a header byte rewritten).

The frontend's `apply_vs_db` (`crates/rustynes-frontend/src/app.rs`) runs on every ROM
load and applies the DIP with the precedence **explicit `[vs] dip` config >
per-game DB default > 0**. To pin an explicit DIP that overrides the DB, set
both `[vs] dip = <value>` **and** `[vs] dip_set = true` in `config.toml`
(`dip_set` is serde-default `false`, so existing configs and not-in-DB games are
unaffected). 19 rows (Excitebike, Clu Clu Land, Castlevania, Pinball,
Balloon Fight, Tennis, Mahjong, Stroke & Match Golf, Wrecking Crew, Gradius,
Goonies, Ice Climber, Duck Hunt, T.K.O. Boxing, Super Mario Bros.; Excitebike,
Goonies, Balloon Fight and Wrecking Crew each have two dumps) cover the staged
Vs. set. **PPU types** are taken from MAME `src/mame/nintendo/vsnes.cpp` —
each game's `ROM_START` block names its hardware palette ROM via the
`PALETTE_2C04_000x` / `PALETTE_STANDARD` macro (for DualSystem carts the `ppu1`
master-CPU palette), cross-checked against the fceux `src/vsuni.cpp`
"Games/PPU list. Information copied from MAME" table; both agree for every staged
game. The 2C04-0001..-0004 scramble tables in `crates/rustynes-ppu/src/palette.rs`
were verified byte-for-byte against the nesdev "PPU palettes" DAC tables
(`C = 255*DAC/7`). Re-audited 2026-06-11 against MAME `master`: all 15 staged
assignments were already correct, no change required. Per-game assignments
(MAME driver -> PPU): vsgradus/vspinbal -> 2C04-0001; cstlevna/smgolf/wrecking
-> 2C04-0002; excitebk(o)/goonies/tkoboxng/balonfgt -> 2C04-0003;
suprmrio/iceclimb/cluclu -> 2C04-0004; duckhunt/vstennis/vsmahjng ->
RC2C03B (standard). Note the Japanese sets differ (excitebkj -> 2C04-0004,
vspinbalj -> standard) but those ROMs are not staged.

**Vs. DualSystem games** (Balloon Fight / Tennis / Mahjong / Wrecking Crew):
these run two CPUs/PPUs. As of v2.0.0 "Timebase" (beta.5) the **DualSystem core
is modelled** — `crates/rustynes-core/src/vs_dualsystem.rs` runs two cross-wired
`Nes` instances (`Emu::Dual`: inter-CPU `$4016`-bit-1 IRQ signalling, shared
`$6000-$67FF` WRAM, coin/DIP routing, soft-lockstep step) and exposes **both**
screens via `main_framebuffer()` and `sub_framebuffer()`. As of v2.1.2 "Fathom"
(F2.1) the **desktop frontend presents both screens** — side-by-side (512×240,
default) or stacked (256×480) via `[graphics] dual_screen_layout` — with P1/P2 →
main, P3/P4 → sub, coin (F10) → main acceptor, and the main console's audio (ADR
0032). Netplay, TAS, the debugger, and HD-pack are **scoped out in dual mode**;
save states work there since v2.9.7, and rewind and run-ahead since v3.1.0, on
the whole cabinet. Libretro presents both screens since v2.1.10 (512×240,
`docs/libretro/advanced_features.md`), the browser since v2.9.7, and the mobile
bridge carries the cabinet since v2.9.7 (its device rows are T1-T12 of
`docs/mobile-v2.9.3-run-sheet.md`; the Swift half has not been compiled).
Real-cabinet boot stays fixture-limited (maincpu-half dumps). The non-DualSystem games (Excitebike, Clu
Clu Land, Castlevania, Pinball, Gradius, Goonies, Ice Climber, Golf, Super Mario
Bros.) boot and render with their correct 2C04 palette.
**PlayChoice-10's second-screen instruction menu and its Z80 coprocessor are out
of scope** — only the NES-game half runs (with the 2C03 palette). All of the above
is gated on `ConsoleType::VsSystem`/`Playchoice10`; a stock `Nes` cart is byte-for-
byte unchanged (AccuracyCoin 100.00% -- 141/141 at the time, 144/144 from v2.6.18, both overstated by the older ROM's masked `Misaligned OAM behavior` failure, and 146/146 since v3.1.0 -- plus both ROM oracles byte-identical).
Region timing (PAL/Dendy)
is validated by automated gates (`ppu_region_constants_match_hardware` in
`rustynes-ppu`; `region_timing.rs` in `rustynes-test-harness`). The R1
master-clock scheduler runs the **region-exact CPU:PPU clock ratio** —
3:1 NTSC/Dendy and the hardware-true **3.2:1 PAL** (the `region_timing` PAL gate
asserts 33,247 CPU cyc/frame, not the legacy 3:1 approximation's 35,464).

## Mapper scope (v1.0)

See `docs/mappers.md` §Mapper coverage matrix. **51 mapper families**
(developed across the engine's v2.0–v2.6 lineage and all shipping in RustyNES
v1.0.0: the top-25 by ROM count first; then 16/159, 18, 64, 65, 67, 68,
70, 73, 78, 88/206, 118, 210; then 119 TQROM — Pin\*Bot, High Speed; then 33
TC0190, 93 Sunsoft-3R, 99 Vs. System, 152 Bandai-74161, then a second batch of
32 Irem G-101, 48 Taito TC0690 (A12 IRQ), 87 Jaleco CNROM-style, 89 Sunsoft-2,
184 Sunsoft-1, then a third batch of 80 Taito X1-005, 82 Taito X1-017, 151 Konami
VS / VRC1-on-Vs.), covering the bulk of the licensed library. These are
spec-implemented from the nesdev
wiki + unit-tested + boot-smoked (commercial-ROM visual survey via the
`coverage_smoke` bin). Pirate, multicart, and ultra-niche mappers remain gated by
the long-tail policy below.

**v1.2.0** extends coverage to **87 families**: a curated (Tier-1) batch of 9
discrete-logic boards and an aggressive best-effort (Tier-2) sweep of 27
reference-ported multicart / Sachen / discrete boards. Subsequent best-effort
sweeps continued the climb — **101** (v1.3.0 "Bedrock"), **113** (v1.4.0
"Fidelity"), **123** (v1.5.0 "Lens"), **150** (v1.6.0 "Studio"), and **168**
(v1.7.0 "Forge", the current count). The best-effort tier is register-decode
unit-tested but **not** accuracy-gated; see `docs/mappers.md` §Mapper accuracy
tiering and `docs/adr/0011-mapper-tiering.md`.

**Resolved in v1.2.0 — mapper 89 (Sunsoft-2) bus conflict.** *Tenka no
Goikenban: Mito Koumon* (the only iNES mapper-89 dump on hand) previously left its
background nametable empty, degrading to the backdrop colour after ~400 frames.
Root cause: the Sunsoft-2 board has **bus conflicts** (nesdev `INES_Mapper_089`
documents them; GeraNES models them) — a register write is driven onto the bus
ANDed with the PRG-ROM byte already at that address. RustyNES applied the raw
write, so a CHR/PRG/mirroring select that the game expects to be masked resolved
to the wrong bank. The fix masks the written value with `read_prg(addr)` (the
existing bus-conflict idiom, e.g. `m066_gxrom.rs`); the 4-bit CHR decode (bit 7 = A16)
is kept — correct for this 128 KiB-CHR title. Verified on the real cartridge:
the title screen renders and stays rendered past frame 600. A bus-conflict unit
test guards it.

**Resolved — SMB3 sprite flashing in World 1-1 (was *not* an MMC3 issue).**
*Super Mario Bros. 3* (mapper 4 / MMC3) flickered Mario in/out continuously once
in-game (the player's report; reproduced by replaying a recorded `.rnm` into
1-1, ~26% of frames dropping Mario from OAM). The cause was **not** the mapper:
it was the PPU **OAM-row-corruption model**, which keyed the corrupted OAM row
off the raw PPU dot (`dot >> 1`). SMB3 disables rendering mid-scanline (`$2001`)
to split its status bar, and NMI/DMA timing jitter shifted the disable dot so
the flagged row intermittently landed on Mario's OAM row, wiping his sprite at
re-enable. Mesen2 ships this corruption model OFF by default (calls it
unfinished); the fix is a faithful port of TriCNES's eval-pointer model — the
corrupted index is the live secondary-OAM evaluation pointer (`OAM2Address`),
captured at the disable edge during dots 1-64 and committed on re-enable. Default
builds stay byte-identical for games without such a split; AccuracyCoin
OAM-Corruption (0x047B) + 139/141 at the time (the two newest upstream PPU tests were then known gaps; 146/146 at upstream `f5f41dc2` since v3.1.0, `docs/STATUS.md`), nestest 0-diff, and blargg/kevtris remain
green; `repro_smb3 --movie` idle drop count went 63-80/240 → 0. See `ppu-2c02.md`
edge-case 2 and `crates/rustynes-test-harness/src/bin/{repro_smb3,smb3_dma_trace}.rs`.

## Audio expansion scope

| Mapper audio | Status | Notes |
|--------------|--------|-------|
| MMC5 (2 pulse + raw PCM) | **Landed** (`mapper-audio`, Track C2 / Phase 2.3) | Castlevania III JP, Just Breed, Laser Invasion |
| VRC6 (3 channels) | **Landed** (`m024_vrc6.rs`, `Mapper::mix_audio`) | Akumajou Densetsu, Madara, Esper Dream 2 |
| VRC7 (FM, 6 channels) | **Landed** — clean-room `emu2413` port (`crates/rustynes-apu/src/opll.rs`, MIT); ADR 0006 supersedes ADR 0004 | Lagrange Point (JP) plays with in-game audio. |
| Sunsoft 5B (3 channels) | **Landed** (`m069_sunsoft_fme7.rs`, `Mapper::mix_audio`) | Gimmick! |
| Namco 163 (1-8 channels) | **Landed** (`m019_namco163.rs`, `Mapper::mix_audio`) | Several Japanese RPGs |
| FDS (wavetable + envelope) | **Landed** | 2C33 — 64-entry wavetable + 32-step modulation + envelopes + master volume; behind `mapper-audio` |

## Game compatibility goals (v1.0)

- The 100 best-selling NES titles boot, render correctly, audio plays correctly.
- All blargg + kevtris test ROMs targeted in `docs/testing-strategy.md` pass.
- Holy Mapperel detects and exercises every implemented mapper.
- AccuracyCoin pass rate ≥ 90%.
- Manual smoke test of Mesen2's "compatibility-difficulty" set: *Battletoads*, *Megaman III*, *Punch-Out!!*, *Castlevania III*, *Cobra Triangle*, *Mig 29 Soviet Fighter*.

## Out-of-scope (v1.0) — HISTORICAL

> Several items below have since shipped (WebAssembly, TAS, VRC7 FM). See
> "Platform scope" immediately below for the authoritative current list; it
> supersedes this one where they disagree. All listed capabilities ship in
> RustyNES v1.0.0.

- Famicom Disk System (FDS).
- Vs. System and PlayChoice-10 arcade variants.
- Network play. **(Shipped — GGPO-style rollback netcode over UDP, native; see `docs/release-notes/v2.3.0.md`.)**
- WebAssembly target.
- Mobile (iOS/Android). **(Android shipped across the v1.8.x line — a Jetpack
  Compose app over the shared `rustynes-mobile` UniFFI bridge + `rustynes-android`
  JNI glue; sideload now, Google Play production at v2.1.0. iOS/iPadOS is the
  still-future v1.9.0 milestone, reusing the same bridge.)**
- TAS (tool-assisted speedrun) movie recording / playback. Architecture supports it (deterministic core); UI is post-v1.0.
- Cheats / Game Genie codes.
- Non-standard input devices beyond standard pads unless explicitly listed in
  release notes. NES 2.0 default-device metadata is parsed but not yet a full
  device-selection system.

## Platform scope (Phase 7)

This section supersedes the early "Out-of-scope" list above where they disagree
(several items ship in v1.0.0). Decisions from Phase 7 Sprint 4:

- **Shipped in v1.0.0:** WebAssembly target, TAS movie recording /
  playback, VRC7 FM audio. These are no longer out of scope.
- **FDS (Famicom Disk System) — SUPPORTED.** `.fds` images play. The
  `.fds`/fwNES parser (`rustynes_mappers::parse_fds`); the FDS RAM-adapter device
  (`rustynes_mappers::Fds`, iNES mapper 20, a `Box<dyn Mapper>`) owning 32 KiB PRG-RAM
  (`$6000-$DFFF`), 8 KiB CHR-RAM, and the user-supplied 8 KiB BIOS
  (`$E000-$FFFF`); the register map (`$4020-$4026`/`$4030-$4033`) + 16-bit
  per-CPU-cycle timer IRQ; the disk **read + write** drive (`$4025`/`$4030`/
  `$4031`/`$4024`, ~149-cycle cadence, byte-transfer IRQ) with **multi-side
  eject/insert** + writable-disk persistence; and the **2C33 wavetable audio**
  (`$4040-$4092`: 64-entry wavetable + 32-step modulation + envelopes + master
  volume, behind `mapper-audio`, via `mix_audio`→`tick_with_external`). Construct
  via `Nes::from_disk(disk_bytes, bios_bytes)`. Frontend: `.fds` open/drag-drop,
  a one-time BIOS prompt (new `[fds]` config), an `F9` side-swap key + a disk
  indicator, `.fds.sav` persistence under `<data_dir>/fds-saves/` (keyed, like
  the game's save-state slots and cheat file since v2.9.9, on the disk image as
  loaded before any write, so a disk save does not move them; since the core
  half of that fix the console booted from the `.fds.sav` reports the pristine
  image as its `Nes::rom_sha256` (`Nes::set_rom_identity`), so movies,
  netplay, the per-game and HD-pack keys and RA progress keep it too; checked once a
  second and written off the emulation lock, and released for a movie,
  TAStudio or netplay session as the battery is, so a session's disk never
  reaches the player's file); wasm-winit has
  an in-browser BIOS upload (session-only). **The BIOS is user-supplied (Nintendo
  copyright — NEVER committed); real-BIOS boot is unverified in CI by design** —
  the device + audio are unit-tested (56 FDS unit tests) + an env-gated
  (`RUSTYNES_FDS_BIOS`) `fdsirqtests.fds` path. Simplified: no CRC/gaps-on-write,
  no analog seek timing. nesdev `FDS*` is the primary source; Mesen2
  `Core/NES/Mappers/FDS/` is a structural-only (GPL-3.0) reference.
- **Input devices — standard pad + Four Score + Zapper + Vaus + Power Pad + SNES
  mouse + Family BASIC keyboard + Family Trainer + Subor keyboard + Konami /
  Bandai Hyper Shot.** The **Four Score** 4-player adapter is
  supported. Also supported: the **Arkanoid Vaus paddle** (`Nes::set_paddle`,
  game-ROM-verified against `vaus-test`; selectable on either port), the
  **Zapper** light gun (`Nes::set_zapper`, framebuffer-luma light-detect,
  unit-verified only), the **Power Pad / Family Fun Fitness mat**
  (`Nes::set_power_pad`, the 12-button dual-shift-register serial protocol,
  unit-verified against the `NESdev` / Mesen bit layout), the **SNES-style serial
  mouse** (`Nes::set_snes_mouse`, the 32-bit MSb-first D0 report — signature
  nibble + sensitivity + buttons + signed 7-bit X/Y movement, unit-verified
  against the `NESdev` "Mouse" layout), and the **Famicom Family BASIC keyboard**
  (`Nes::set_family_keyboard`, the 9-row x 2-column-half matrix scan on
  `$4016` write / `$4017` read, active-low, unit-verified) as opt-in per-port
  `InputDevice` overlays — when no device is attached the standard-controller +
  Four Score read paths are byte-identical. (v1.1.0 beta.1: the Power Pad is
  playable — selectable as the player-2 device with a 12-key default mapping;
  rebindable mat keys are a follow-up. v1.2.0 Workstream D: SNES mouse + Family
  BASIC keyboard added, both selectable as the player-2 device; the Family BASIC
  keyboard uses a direct host-key passthrough rather than a faithful positional
  layout — a per-key positional map is a follow-up. v1.3.0 Workstream F1 added
  the niche peripheral aliases + simple Hyper Shots: the **Family Trainer**
  (`Nes::set_family_trainer`) — layout-equivalent to the Power Pad, reusing its
  12-button scan; the **Subor keyboard** (`Nes::set_subor_keyboard`) — a Family
  BASIC keyboard work-alike reusing the 9×8 matrix scan; the **Konami Hyper
  Shot** (`Nes::set_konami_hyper_shot`, the 4-button 2-player Run/Jump parallel
  read with `$4016` per-player enable, unit-verified against the `NESdev`
  "Konami Hyper Shot" page); and the **Bandai Hyper Shot** / Exciting Boxing
  punching bag (`Nes::set_bandai_hyper_shot`, the 8-sensor `$4016`-bit-1-
  multiplexed read, unit-verified against the `NESdev` "Exciting Boxing Punching
  Bag" page). All are additive, default-off `InputDevice` overlays, so
  `ExpansionDevice::None` keeps every read byte-identical.) The Famicom
  microphone shipped in v2.2.0 (`Nes::set_microphone`, `$4016` D2;
  `docs/accuracy-ledger.md`). (DMC-DMA controller-bit corruption is **modelled** as of
  v1.4.0 — see `Bus::dmc_dma_read`, gated by `dmc_dma_during_read4/dma_4016_read`,
  `sprdma_and_dmc_dma`, and `read_joy3/count_errors`.)
- **Vs. System / PlayChoice-10 (2C03/04/05 RGB PPUs) — game-verified.**
  RGB palette tables + 2C05 register quirks + NES 2.0 byte-13 parsing + Vs.
  DIP/coin inputs; **mappers 99 + 151 game-verify in-game RGB** (Vs.
  Excitebike / Vs. Clu Clu Land / Vs. Gradius / Vs. The Goonies), and the
  **clean-byte-7 arcade flag** (exact `0x01`/`0x02` on a non-NES-2.0 header)
  game-verifies **PlayChoice-10** (Power Blade / Rad Racer 2 / Captain Skyhawk /
  Mario Open Golf) plus the clean-byte Vs. dumps (Vs. Castlevania / Vs. Pinball).
  The corrupted `0x0A` home-dump trap is explicitly ignored (it is NES 2.0 and is
  neither `0x01` nor `0x02`), so the oracles stay byte-identical. The dumps are
  iNES 1.0, so 2C04-000x games would need byte-13 / a game-DB. PC10 second screen
  - Z80 remain out of scope (see "PPU variant scoping").
- **Long-tail mapper policy.** A pirate / multicart / homebrew-only mapper is
  accepted only when: (a) there is concrete user demand or a notable title that
  needs it, **and** (b) a redistributable test fixture or a well-specified
  nesdev page exists, **and** (c) it carries NES 2.0 metadata for unambiguous
  detection — weighed against maintenance cost. (MMC5's >8 KiB multi-chip
  PRG-RAM configs were named here as falling under this policy; v2.7.2
  implemented them from `MMC5.xhtml` as the wiki's 64 KiB superset,
  `docs/audits/core-disposition.md` §5.3.)

## Open questions

Checked at v3.1.0 against the code, and recorded together as backlog item
FE-11, which the v3.1 → v4.0 line plan does not schedule:

- the **CRC32 question is half-answered**: the per-game correction database
  (`crates/rustynes-gamedb`, vendored from TetaNES) is CRC32-keyed, while the
  Vs. System database (`crates/rustynes-core/src/vs_db.rs`) and save-state
  naming key on SHA-256;
- the **region override is still open**: no desktop, mobile or libretro
  frontend overrides the region the header declares.

- **CRC32 vs. SHA-256 for ROM identification.** We use SHA-256 for save state directory naming (lower collision risk). For ROM compatibility databases, CRC32 is the community standard; we may add it as a secondary key.
- **Region override.** Some users want to play PAL versions of NTSC games at NTSC speed. Plan: expose a region override in the settings UI; warn that this may break timing-sensitive ROMs.

## Curated evidence from local dumps (D23)

A mapper family whose only evidence is a dump that cannot be redistributed may
still be **Curated** (ADR 0011) when that dump boots to a correct picture and
this table records it (maintainer decision D23, v3.2.0 `T-CURATED-EVIDENCE`):
the title, the SHA-256 of the image after its 16-byte header (the key
`Nes::rom_sha256` uses), the frames the capture ran, and a committed screenshot.
The capture is the byte-identity `external_coverage` boot (1,141 frames:
START tapped five times from frame 240, every 150 frames, then 300 free) unless
the row names an `external_extended` test; the rows for the families new in
v3.2.0 (6, 8, 17, 114, 116, 158, 165, 172, 173, 182, 197, 207, 215, 235,
252) give the frame count of their
`external_extended` boot test, whose script the title cell notes where it is
not plain idle. The dumps live in the gitignored
`tests/roms/external/`; only the hash, the snapshot and the screenshot are
committed.

| mapper | title | SHA-256 after the header | frames | screenshot |
| --- | --- | --- | --- | --- |
| 47 | Super Spike V'Ball + Nintendo World Cup (U) | `8951dccb6640b48c857f321ef01c06110c5b8870f5d901dcc538ab1b8db7e2a1` | 1,141 | `screenshots/external/mapper-047-SpikeVBall-NWC/Super Spike V'Ball + Nintendo World Cup (U).png` (the first game's menu) |
| 47 | the same dump, second game | same | 1,000 (`extended_m47_nintendo_world_cup_block_1`: DOWN at 240, START at 260, 480, 700) | `screenshots/external/mapper-047-SpikeVBall-NWC/Super Spike V'Ball + Nintendo World Cup (U) - Nintendo World Cup.png` (its team select, from the second 128 KiB block) |
| 121 | Ultimate Mortal Kombat 3 (Unl) | `040c7b6497754415172735a6dc9e3d6812cb9caef58a8f49ae48b72bfcc435e6` | 1,141 | `screenshots/external/mapper-121-KashengA9711/Ultimate Mortal Kombat 3 (Unl).png` (a fight) |
| 154 | Devil Man (Japan) | `d2140fc2e6354a9f4d0154dabac757e5559890edba4885799c1c979d8b7a8b20` | 1,141 | `screenshots/external/mapper-154-Namco3453/Devil Man (Japan).png` (the opening cutscene) |
| 191 | Mighty Final Fight (J) [T+Chi] | `671f372fa92819d02147cae9072139f1193ebcc22030db3d09c61a768fdaec17` | 1,141 | `screenshots/external/mapper-191-Waixing191/Mighty Final Fight (J) [T+Chi_madcell].png` (a cutscene in its CHR-RAM font) |
| 243 | Honey Peach (Asia) (Ja) (Unl) | `4342d7b1bbd3b263dfc47e3e3cd51abdf0d98f826a89ec9bc67db35a1c2ad871` | 1,141 | `screenshots/external/mapper-243-SachenSA020A/Honey Peach (Asia) (Ja) (Unl).png` (its play screen: both players, the hand symbols) |
| 157 | Datach - Battle Rush - Build Up Robot Tournament (Japan) | `cbc404d57879beca0b65b855b19fda9e017e7d8cc716941b52bbea8f8a6ee859` | 1,141 | `screenshots/external/mapper-157-Datach/Datach - Battle Rush - Build Up Robot Tournament (Japan).png` |
| 157 | Datach - Dragon Ball Z - Gekitou Tenkaichi Budoukai (Japan) | `a1370e7410a98a4a2de19f4872281a065b1d7d7cd7b5f2ba0da70504b4a1d52d` | 1,141 | `screenshots/external/mapper-157-Datach/Datach - Dragon Ball Z - Gekitou Tenkaichi Budoukai (Japan).png` |
| 157 | Datach - J.League Super Top Players (Japan) | `a7b1e4693093c8a155747f9023399ebb2537064e788970ecdcccee467108b62e` | 1,141 | `screenshots/external/mapper-157-Datach/Datach - J.League Super Top Players (Japan).png` |
| 157 | Datach - SD Gundam - Gundam Wars (Japan) | `a6a909d829e3d9ef1a14d002f8ac3c59173420530d467c09fe338b28143f4308` | 1,141 | `screenshots/external/mapper-157-Datach/Datach - SD Gundam - Gundam Wars (Japan).png` |
| 157 | Datach - Ultraman Club - Supokon Fight! (Japan) | `85f0ab77ae56df26e18bf55caafb795ec24712c168912f0963756fe4daa2ef65` | 1,141 | `screenshots/external/mapper-157-Datach/Datach - Ultraman Club - Supokon Fight! (Japan).png` |
| 157 | Datach - Yu Yu Hakusho - Bakutou Ankoku Bujutsukai (Japan) | `acc54496853ce6306589790857b0d56145d8382bd00f5d0f40ecdfa2906ad349` | 1,141 | `screenshots/external/mapper-157-Datach/Datach - Yu Yu Hakusho - Bakutou Ankoku Bujutsukai (Japan).png` |
| 114 | Aladdin (SuperGame) (Mapper 114) [!] | `5489244538c94653f766bd80f07b2aa684ad139bddea68f40b60a0473c9286e2` | 600 | `screenshots/external/mapper-114-SuperGame/Aladdin (SuperGame) (Mapper 114) [!].png` |
| 114.1 | Boogerman (Sugar Softec) (Unl) [!] (GoodNES labels it 215; staged with a NES 2.0 114.1 header) | `ba2d65f240d8f97c7b77973042d8141cf15116ffc3d73caed450c21e08fd54a2` | 600 | `screenshots/external/mapper-114-SuperGame/Boogerman (Sugar Softec) (Unl) [!] (NES 2.0 114.1).png` |
| 182 (as 114.0) | Super Donkey Kong (Unl) [!] | `67d06173c4b8239d46c84335591216b1adfa38344107405ec88c58327217a361` | 600 | `screenshots/external/mapper-182-Hosenkan/Super Donkey Kong (Unl) [!].png` |
| 116 | Somari (SOMARI-W) (Unl) [!] (START at 400; screenshot = the f1000 checkpoint, Green Hill Zone act 1 in play) | `f5b2998c853ddb5c495e96ac31e4abfff8f1da8367b9ae074efbcb7fdfee452c` | 1200 | `screenshots/external/mapper-116-SomariW/Somari (SOMARI-W) (Unl) [!].png` |
| 116 | Kart Fighter (SOMARI-W) (Unl) [!] | `8fd609f49be61a9a82233a175a0a32a3d43c722d31a7a4115380f4b7f1e1615f` | 600 | `screenshots/external/mapper-116-SomariW/Kart Fighter (SOMARI-W) (Unl) [!].png` |
| 165 | Fire Emblem - Ankoku Ryuu to Hikari no Tsurugi (Ch) (START at 700 and 1101; screenshot = the f2100 checkpoint, the intro text over the sword) | `0d396a7615b2499edd0997628b733236d88b002e364833edaf166e0a1c96ecf9` | 2202 | `screenshots/external/mapper-165-FireEmblemCh/Fire Emblem - Ankoku Ryuu to Hikari no Tsurugi (Ch).png` |
| 165 | Fire Emblem Gaiden (Ch) (same script; it holds its title) | `621a4d4ee54524e9e62cce97adcba5b332c06bd1e4174a37455f474e747cd35e` | 2202 | `screenshots/external/mapper-165-FireEmblemCh/Fire Emblem Gaiden (Ch).png` |
| 197 | Super Fighter III (Unl) [!] (submapper 0, PRG CRC32 `C333F621`) | `dfb4771a36dbcb05a2753a95810a11164665282ccac4cf7ee52d26d2ed6e5e4f` | 600 | `screenshots/external/mapper-197-SuperFighter3/Super Fighter III (Unl) [!].png` |
| 215 | Mortal Kombat 3 Extra 60 (Unl) [!] (scramble pattern 4) | `19ccf8d3a3fc37b82d403d0668b00f05125bfcf01ad510dfff73e3ac30dad827` | 600 | `screenshots/external/mapper-215-SugarSoftec/Mortal Kombat 3 Extra 60 (Unl) [!].png` |
| 215 | `2-in-1 Super 808 (Unl) [p1][!]` (scramble pattern 1, menu) | `4211468e61181f0956f10259ba6091ea3fead297e61ba7f22d7daa227463414a` | 600 | `screenshots/external/mapper-215-SugarSoftec/2-in-1 Super 808 (Unl) [p1][!].png` |
| 172 | 1991 Du Ma Racing (Ch) [!] | `4a2ec0f51526ad9659b0ede758216ea8f7acae7d49faaed5db46b6acc7be6915` | 600 | `screenshots/external/mapper-172-TXC-JV001/1991 Du Ma Racing (Ch) [!].png` |
| 173 | Xiao Ma Li (Ch) [!] | `103e27a9b2e2c029fee9a221e2ffe35b5b0b4a3e529a4df88fd6d71f21cf56a4` | 600 | `screenshots/external/mapper-173-TXC-05-00002-010/Xiao Ma Li (Ch) [!].png` |
| 158 | Alien Syndrome (USA) (Unl) | `d676ef6f7aa3b042ae5a0c95a7f07fabb4c3a5fccb3767ae1950713e51189a47` | 2400 (attract demo, in play) | `screenshots/external/mapper-158-Tengen800037/Alien Syndrome (USA) (Unl).png` |
| 207 | Fudou Myouou Den (Japan) (En) (1.0) (translation, 207 header) | `e33482a375923d692bb5ddd3be71dab63de6a786ec311e8dfc32417a19e48fb3` | 3000 (START held at 1900 and 2100; stage 1 in play) | `screenshots/external/mapper-207-TaitoX1-005-207/Fudou Myouou Den (Japan) (En) (1.0).png` |
| 252 | San Guo Zhi (Ch) | `659837c9b4ffbc6089aa191e6fdc238f50c1c2116a04b993381a5022444cb652` | 3800 (START held at 1500-3300, A at 3600; first personality question on the map) | `screenshots/external/mapper-252-WaixingVRC4/San Guo Zhi (Ch).png` |
| 235 | `260-in-1 [p1][!]` (staged with a NES 2.0 header; body unchanged) | `0c62f12da81c712a8a9d56e808ff8825d824169cfff05eee2a15c7456f3ceb91` | 1600 (RIGHT, A, START; Mr. Mario I phase 1) | `screenshots/external/mapper-235-GoldenGame/260-in-1 [p1][!] - Mr. Mario I.png` |
| 6 | Ganbare Goemon! - Karakuri Douchuu (J) [hFFE] (trainer) | `d390d04843f7ea5b72203ac001956df1fd57ec62febccdba1bba43f88377c064` | 2600 (START/START/A/START; in play) | `screenshots/external/mapper-006-FFE-MagicCard/Ganbare Goemon! - Karakuri Douchuu (J) [hFFE].png` |
| 6 | Cosmo Police Galivan (J) `[hFFE][p1]` | `4d90a01e16203511877166c804282316cac65105d687724fe6d3714f1e4dda9c` | 2600 (same script; status screen of area 1) | `screenshots/external/mapper-006-FFE-MagicCard/Cosmo Police Galivan (J) [hFFE][p1].png` |
| 6 | Wai Wai World (J) [hFFE] (trainer) | `eccd72962bb3da25541dc818757d71afcd9b9b5530326597187d30a9fca61f25` | 3800 (menu answered, RIGHT held; first stage in play) | `screenshots/external/mapper-006-FFE-MagicCard/Wai Wai World (J) [hFFE].png` |
| 8 | Mermaids of Atlantis (AVE) `[T+Rus_Guyver]` | `909158c6d116ffcff01ac7fb616bc2f46d48a4230af970ffff95631ca0ee5bbb` | 2600 (START/START/A/START; in play, paused) | `screenshots/external/mapper-008-FFE-GNROM/Mermaids of Atlantis - The Riddle of the Magic Bubble (AVE) [T+Rus_Guyver].png` |
| 17 | Kaiketsu Yanchamaru 2 - Karakuri Land (J) [hFFE] | `1ea190b0e2ac856d6caeca343812d3553621c5120689224e1650463eb8cf764b` | 2600 (START/START/A/START; in play) | `screenshots/external/mapper-017-FFE-SuperMagicCard/Kaiketsu Yanchamaru 2 - Karakuri Land (J) [hFFE].png` |
| 17 | Juvei Quest (J) [hFFE] (trainer) | `1915b1b02ae9fbd27962065b7482c744ccc5f938198b4fefbed71588b1207e9c` | 2600 (same script; name entry) | `screenshots/external/mapper-017-FFE-SuperMagicCard/Juvei Quest (J) [hFFE].png` |
| 17 | Dragon Ball Z III - Ressen Jinzou Ningen (J) `[hFFE][p1]` | `1d5e6d3a998d3828659da2cfe4bbba1c664dad6a337314cf65c4125afdf473e6` | 2600 (same script; story text box) | `screenshots/external/mapper-017-FFE-SuperMagicCard/Dragon Ball Z III - Ressen Jinzou Ningen (J) [hFFE][p1].png` |

Mapper 157 notes: the six Hearto 1G1R NES 2.0 dumps (the GoodNES copies carry
a mapper-16 header). The screenshots are `external_coverage`'s final frame
(1141 frames of START taps): *Battle Rush*'s factory, *Dragon Ball Z*'s
tournament desk, *J.League*'s title menu, *SD Gundam Wars*' file menu (four
empty files), *Ultraman Club*'s first event and *Yu Yu Hakusho*'s tournament
opening. *Dragon Ball Z* and *SD Gundam Wars* also read scanned barcodes
(`extended_m157_dbz_karin_room_reads_a_scanned_card`,
`extended_m157_gundam_wars_barcode_lab_reads_a_scanned_card`; what each code
became is in `docs/mappers.md`, mapper 157).

**Not evidence, and why:**

- **230** (*22-in-1 (Reset Based)*) boots correctly, *Contra* at power-on and
  the menu after a console RESET, but stays **BestEffort**. Its page is
  Disch's notes and does not say which mode the board powers on in or what a
  reset does to its register, so the model chooses both; a correct boot cannot
  tell a right choice from a lucky one (the maintainer's ruling for v3.2.0,
  2026-10-09). The boot stays pinned by its `external_coverage` snapshot (the
  reset-to-*Sky Destroyer* run; ADR 0011 keeps BestEffort out of the
  byte-identity oracle files), with screenshots under
  `screenshots/besteffort/`, and a page or a hardware report that states the two
  behaviours would make it Curated. The notes-only families whose models needed
  no unstated behaviour, or whose choices a boot visibly settled (47, 121, 191,
  165), are Curated on the rows above (ADR 0011, v3.2.0 amendment).
- **The Sachen titles GoodNES headers as mapper 243** (*Strategist*, *Poker
  III*, *Chinese Checkers*, *Magic Cubes* and others) are SA-015 games, mapper
  150 (`nesdev_wiki/INES_Mapper_150.xhtml` lists them; 243's page names *Honey
  Peach* alone). The two board layouts read the same ASIC's CHR bits at
  different significance, so a 150 game run as 243 renders correctly only when
  its CHR fits the bits the two share, and *Poker III* and *Strategist* render
  garbled. The vendored game database also routes most of these titles to 243.
  *Poker III* renders garbled under both decodes, so it is an open finding
  recorded in the v3.2.0 plan, not a routing fix.
- **290** (*Asder 20-in-1*) booted to a uniform grey frame until v3.2.0 fixed its
  32 KiB PRG mode; it now starts a game, but the capture shows a top band of
  scrambled tiles that nobody has compared with hardware (NESdev notes one game
  on the cart glitches in attract mode on a real console). It stays BestEffort.
