Expand description
Vs. DualSystem — two complete NES systems in one arcade cabinet
(v2.0.0 beta.5, Workstream C of the “Timebase” plan).
The DualSystem boards (Vs. Tennis, Vs. Mahjong, Vs. Wrecking Crew,
Vs. Balloon Fight) carry two CPUs, two PPUs, and two work RAMs,
sharing a small inter-CPU communication signal, a 2 KiB work RAM, and
the coin/DIP panel — each half drives its own screen. RustyNES models
this as a wrapper over two byte-identical Nes instances:
VsDualSystem
├── main: Nes (the primary cabinet half; $4016 bit 7 reads 0)
├── sub: Nes (the secondary half; $4016 bit 7 reads 0x80)
└── the comms latch + shared-WRAM ownership (wrapper-owned)The wrapper owns ALL cross-wiring (the design rule from
docs/audit/vs-dualsystem-design-2026-06-11.md): the two buses never
hold references to each other. Each bus only records its $4016
bit-1 (main/sub comms signal) levels and accepts an external-IRQ
level; the wrapper polls the levels after every stepped instruction
and applies the protocol (IRQ wiring per Mesen2
Core/NES/Mappers/VsSystem/VsControlManager.cpp; memory model per
MAME src/mame/nintendo/vsnes.cpp, where the four DualSystem games
verifiably run):
- a
$4016bit-1 write going LOW asserts the PARTNER console’s external/IRQ; going high clears it (UpdateMainSubBit; MAME:cpu.set_input_line(0, (data & 2) ? CLEAR_LINE : ASSERT_LINE)); - the shared 2 KiB WRAM at
$6000-$67FF(mirrored ×4 across the 8 KiB window) is simultaneously visible to both CPUs — MAME maps ONE RAM.share("nvram")into both address spaces with no access mux. (nesdev/Mesen2 document a$4016-bit-1 access mux instead, but Balloon Fight’s boot handshake polls a mailbox the partner writes while the mux would deny it access — under exclusive routing the boot provably deadlocks, so the MAME model is adopted.) Realized as two per-console copies converged by draining each mapper’s write log into the partner after every stepped instruction; - coins 1/2 + service drive main; coins 3/4 drive sub;
- each console owns its own DIP bank (Mesen2’s
dipSwitches >> 8/ MAME’sDSW0/DSW1for the sub is realized structurally: two buses, twovs_dipbytes).
Stepping mirrors Mesen2 NesConsole::RunFrame +
RunVsSubConsole: the main console steps one instruction, then the
sub runs until it is within a 5-CPU-cycle gap of the main (or has
caught up to the main’s frame count) — a soft lockstep. Both
consoles run the same deterministic one-clock core, so the interleave
is reproducible run-to-run (the determinism contract holds per
console; the 5-cycle tolerance is the documented coupling knob).
Out of scope by design (stated in the plan + the design doc):
netplay (rollback assumes one state blob) and RetroAchievements (one
memory map) do not support the dual path. Audio: the frontend drains
the MAIN console’s mixer (each cabinet half has its own speaker; the
sub’s audio is synthesized but undrained until a frontend feature
surfaces it).
Structs§
- VsDual
System - Two complete NES systems + the cabinet’s cross-wiring. See the module docs for the architecture and protocol.
Enums§
- Emu
- The top-level emulator: one standard console, or a Vs.
DualSystempair.