Skip to main content

Module vs_dualsystem

Module vs_dualsystem 

Source
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 $4016 bit-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’s DSW0/DSW1 for the sub is realized structurally: two buses, two vs_dip bytes).

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§

VsDualSystem
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. DualSystem pair.