Skip to content
Index / $08

What Are the Absolute Hardware Limits of CPS-2 Arcade Boards?

Pushing every ROM type to the theoretical maximum of Capcom's CPS-2 hardware: 128MB graphics, 16MB audio, 4MB program - documented with in-game verification

Date
Feb 6, 2026 · upd Jul 4, 2026
Runtime
13 min · 2,899 words
Tags
hardware-limits, cps2, rom-expansion
Slot
$08
Contents
tip // Evidence Strip

Goal: Push every CPS-2 ROM type to its theoretical maximum and document the hardware limits with in-game verification.

Constraints: Hardware address bus widths are fixed (24-bit for 68000, 24-bit per GFX chip). Interleaving scheme requires all 8 GFX chips to be identical size. MAME validates ROM sizes against driver expectations.

Approach: Analyzed MAME CPS-2 driver source for address space mappings. Calculated theoretical maximums from bus width and chip count. Built automated expansion pipeline. Injected in-game verification screen.

Result: 128MB GFX (8x16MB), 16MB Audio (2x8MB), 4MB Program space (24-bit bus). Total: 148MB on disk. 8MB of MSHVSSF QSound samples byte-copied into the expanded region (XMSF+XCOTA config also verified at the byte level). See Editor's update: the padded space turned out not to be addressable under the stock emulator (GFX masked at 32MB, QSound at 8MB); real addressable expansion came later via a custom FBNeo core (64MB GFX + 16MB QSound).

Proof / Validation: ULTRA SETTINGS: GFX TOTAL: 128MB, CPS2 GFX: AT MAXIMUM (computed from file sizes). GOLD marker at mvc.13m:0x3FFFFB. MAME output shows WRONG LENGTH warnings but game runs (it never touches the padded region). Byte-for-byte ROM verification: mvs.11m/mvs.12m placed at intended QSound $800000/$C00000 offsets -- but bank playback of that region under the stock emulator wraps into the original 8MB (see Editor's update).

Repo / Artifacts: CHARACTER_SELECT/scripts/patch_rom_check_complete.py, patched ROM set in CHARACTER_SELECT/roms/mvscud.zip.

warning // Editor's update (July 2026)

Two claims in this post were overclaims, and honest verification later caught them:

  • "MAME maps the full expanded ROM into the hardware's address space" -- wrong. Padding the ROM files makes the bytes present, not addressable. The mvsc emulator mapper AND-masks GFX addressing past 32MB and QSound addressing past 8MB, so reads of the "expanded" region silently wrap back into the original data. The GOLD marker exists in the file; nothing in the emulated machine can fetch it. The same wrap applies to the imported MSHVSSF sample banks at $800000+ -- what the sound test actually played came from the original 8MB region.
  • "148MB at the CPS-2 ceiling" describes the ZIP, not the machine. No stock emulator build addressed a single byte past the shipped sizes.

The caps were genuinely broken later with a custom FBNeo core: extended GFX chip loading in d_cps2.cpp (appended chips, stock 0-32MB kept byte-identical), a new mapper_mvsc64, and a Cps2ExtScroll OBJ hook (sprite Y-bit12 carried into tile-code bit 18) -- reaching a pixel-proven 64MB GFX region, plus 16MB QSound via a two-line driver change. Stock MAME cannot even load the 64MB zip, and the custom core breaks standard Fightcade netplay -- a real trade-off, documented rather than hidden.

Read the rest of this post as: correct hardware ceiling analysis and a working file-expansion pipeline, paired with an addressability claim that didn't survive verification.

Every piece of hardware has theoretical limits imposed by its address bus width, chip select lines, and memory controller design. For Capcom's CPS-2 arcade platform (1993-2003), these limits were never fully utilized by any shipping game. This post documents the process of expanding Marvel vs. Capcom 1 to the absolute ceiling of what CPS-2 can address -- and proving it works.

The CPS-2 Platform

The CPS-2 (Capcom Play System 2) powered over 40 arcade games from 1993 to 2003, including the entire Street Fighter Alpha series, the Marvel VS series, and many others. Its architecture:

ComponentSpecification
CPUMotorola 68000 @ 11.8 MHz
GraphicsCustom CPS-2 GPU
Resolution384 x 224 pixels
Colors4,096 per entry (12-bit RGB444)
SpritesUp to 900 on screen
SoundQSound DSP (Z80 @ 8 MHz)
Audio16-bit stereo, 4 channels

What No Shipping Game Ever Did

Every CPS-2 game shipped with ROM sizes well within the hardware's capabilities. Here's what the hardware can actually address vs. what games used:

Loading diagram...

Why These Are the Limits

Graphics: 128MB (8 x 16MB)

CPS-2 uses 8 interleaved graphics ROMs. The hardware reads 64 bits (8 bytes) at a time, one byte from each chip. Each chip position can hold a maximum 128Mbit (16MB) mask ROM -- the largest size manufactured for this package type.

Address Bus Width: 24 bits per chip = 16MB addressable
Number of Chips:   8 (interleaved)
Total:             8 x 16MB = 128MB

The interleaving means pixel data is spread across all 8 chips:

Byte 0  → mvc.13m
Byte 1  → mvc.14m
Byte 2  → mvc.15m
Byte 3  → mvc.16m
Byte 4  → mvc.17m
Byte 5  → mvc.18m
Byte 6  → mvc.19m
Byte 7  → mvc.20m
Byte 8  → mvc.13m (next address)
...

Program: 4MB (68000 address space)

The Motorola 68000 has a 24-bit address bus, allowing 16MB of total addressable memory. CPS-2 maps program ROM to $000000-$3FFFFF -- exactly 4MB. Some games (Street Fighter Alpha 3) actually use the full 4MB.

CPU Address Bus:   24 bits (16MB total)
ROM Mapping:       $000000 - $3FFFFF
Available:         4MB
MvC1 Uses:         1MB (two 512KB chips)
Expansion Room:    3MB (75% unused)

Audio: 16MB (2 x 8MB) -- QSound Hardware Ceiling

The QSound DSP (DL-1425 / DSP16A) uses an 8-bit bank register + 16-bit address register = 24-bit addressing = 16MB maximum. This is the hard limit imposed by the DSP silicon -- unlike GFX (where the custom GPU has wider addressing), the QSound chip physically cannot address beyond 16MB of sample ROM. This means 8MB free is the absolute ceiling for audio expansion, regardless of how much ROM we attach.

QSound Address Space (24-bit, 16MB max):
  $000000-$3FFFFF  mvc.11m original (4MB) ── MvC1 instruments
  $400000-$7FFFFF  mvc.12m original (4MB) ── MvC1 instruments
  $800000-$BFFFFF  mvc.11m expanded (4MB) ── Imported CPS-2 audio
  $C00000-$FFFFFF  mvc.12m expanded (4MB) ── Imported CPS-2 audio

Current use: All 8MB of free space is filled with MSHVSSF (Marvel Super Heroes vs. Street Fighter) QSound sample data, imported directly from mshvsfu.zip. Configurable to XMSF + XCOTA instead (both verified at the ROM-byte level). Correction: under the stock emulator this region is not reachable -- the QSound mapping for mvsc masks at 8MB, so banks $80+ wrap into the original samples. The imported data only becomes addressable under the custom FBNeo core's 16MB QSound driver (see Editor's update).

The Expansion

Before and After

ROM TypeChip CountOriginal SizeExpanded SizePaddingFree Space
GFX84MB each16MB each0xFF (transparent)96MB
Audio Sample24MB each8MB each0x00 (silence)8MB
Audio Program1256KB256KBN/A0
Program2512KB each512KB eachN/A3MB addressable

Total ROM Set Size

Graphics:   8 x 16MB  = 128MB
Audio:      2 x  8MB  =  16MB
Program:    2 x 512KB =   1MB
Data:       6 x 512KB =   3MB
Audio Pgm:  1 x 256KB = 256KB
Key:                   =  20B
───────────────────────────────
Total:                 ~148MB

Why Padding Matters

GFX Padding: 0xFF

In CPS-2's tile format, 0xFF represents a transparent pixel. Filling empty graphics space with 0xFF means:

  • No visual artifacts from unused regions
  • The GPU skips transparent tiles efficiently
  • Safe to read from any expanded address

Audio Padding: 0x00

In QSound's PCM format, 0x00 represents digital silence. Filling empty audio space with 0x00 means:

  • No audio glitches if the DSP reads expanded regions
  • Silent by default, can be filled with new samples later

Current state: The expanded audio space is filled (not merely padded). 8MB of MSHVSSF (Marvel Super Heroes vs. Street Fighter) QSound sample data has been imported directly from the source ROM, with zero conversion needed (same CPS-2 QSound format). A Z80 dispatch table hook routes sound codes $0900-$0901 to a custom handler for QSound channel 15 playback with multi-bank streaming. Alternatively, XMSF + XCOTA can fill the same space ($0900-$0903). The build script supports multiple configurations.

Correction (July 2026): playing those banks under the stock emulator does not reach the imported data -- QSound addressing for mvsc wraps at 8MB, so banks $80+ alias the original samples. The imported region is genuinely playable only under the custom FBNeo core's 16MB QSound driver (see Editor's update).

Why NOT 0x00 for Graphics

Using 0x00 for graphics padding would mean black pixels, not transparent. Sprites rendered from the expanded region would show as solid black rectangles instead of being invisible.

The Build Pipeline

A single Python script handles the entire expansion:

Loading diagram...

Key Code: Expansion Logic

# GFX: 4MB → 16MB per chip (0xFF = transparent)
for name in GFX_ROMS:
    data = bytearray(self.clean_files[name])
    expanded = bytearray(16 * 1024 * 1024)  # 16MB
    expanded[:len(data)] = data
    for i in range(len(data), len(expanded)):
        expanded[i] = 0xFF
    self.clean_files[name] = bytes(expanded)
 
# Audio: 4MB → 8MB per chip (0x00 = silence)
for name in AUDIO_SAMPLE_ROMS:
    data = bytearray(self.clean_files[name])
    expanded = bytearray(8 * 1024 * 1024)   # 8MB
    expanded[:len(data)] = data
    # 0x00 is default for bytearray - already silence
    self.clean_files[name] = bytes(expanded)

GOLD Marker

To verify the expansion is real (not just MAME mapping tricks), we plant a 5-byte marker at offset 0x3FFFFB in mvc.13m -- the last 5 bytes of the original 4MB region:

marker = b'GOLD\x01'
expanded[0x3FFFFB:0x400000] = marker

The in-game ULTRA SETTINGS screen can read this marker to confirm the expansion was applied.

MAME Behavior

MAME handles oversized ROMs gracefully. It generates warnings but loads the data:

mvc.13m WRONG LENGTH (expected: 00400000 found: 01000000)
mvc.13m WRONG CHECKSUMS:
    EXPECTED: CRC(fa5f74bc)
    FOUND:    CRC(xxxxxxxx)
mvc.11m WRONG LENGTH (expected: 00400000 found: 00800000)
...
WARNING: the machine might not run correctly.

The game runs despite all warnings -- but not because the expansion is live. MAME loads the oversized files into the region, then the mvsc mapper AND-masks GFX addressing at 32MB and QSound at 8MB, so the game simply never sees the padded space. "Runs perfectly" proved the padding was harmless; it did not prove the space was mapped. (It wasn't -- see Editor's update.)

In-Game Verification

The ULTRA SETTINGS screen (injected into the F2 Test Menu) confirms the expansion in real time:

  GFX TOTAL: 0128MB    <- Computed from actual chip sizes
  GFX USED:   032MB        <- File size > original 4MB
  GFX FREE:  0096MB    <- TOTAL - 32MB original data
  SND TOTAL:   16MB
  SND USED:    08MB
  SND FREE:    08MB
  CPS2 GFX: AT MAXIMUM
  CPS-2 MAX: 0128MB    <- Hardware theoretical limit

When GFX TOTAL equals CPS-2 MAX (both 128MB), you've reached the hardware ceiling. There is nowhere else to expand.

Content Capacity

With 96MB Free Graphics

ContentSize EachCountNotes
Average Characters1.5-2 MB48-64Full sprite set + animations
Large Characters2.5-3 MB32-38Hulk, Sentinel-class
Stages0.5-1 MB96-192Scrolling backgrounds
Helpers/Assists0.2-0.4 MB240-480Short animation sequences

With 8MB Free Audio

ContentSize EachCountNotes
Character Voice Set200-400 KB20-40Attacks, wins, intros
Stage Music500KB-1MB8-16Full BGM loops
Sound Effects100-300 KB27-80Hits, projectiles, UI

Realistic "Dream Expansion" Mix

CategoryCountSpace Used
New characters30~60 MB
New stages15~15 MB
New helpers30~12 MB
Effects + UI--~9 MB
Total75+ items~96 MB

That's enough to add the entire MSHvSF roster, half of XMvSF, plus original characters and stages.

Cross-Game Comparison

How does our expanded MvC1 compare to other CPS-2 games?

GameYearGFX SizeProgramAudioTotal
X-Men COTA199432 MB1 MB8 MB~41 MB
XMvsSF199632 MB1 MB8 MB~41 MB
MSHvsSF199732 MB1 MB8 MB~41 MB
SFA3199832 MB4 MB8 MB~44 MB
MvC1 (original)199832 MB1 MB8 MB~41 MB
MvC1 Phoenix2026128 MB1 MB16 MB~148 MB

Our Phoenix Edition ROM set is 3.6x larger than any shipping CPS-2 game.

Using the Free GFX Space: Sprite Data Copies

The 96MB of free GFX space isn't just theoretical headroom -- we have a concrete use case. To add Armored Spider-Man (character ID 0x2E) with actual modified sprites (not just a palette swap), we need to copy Spider-Man's tile data to the expanded region and modify it.

CPS-2 Sprite Tile Format

PropertyValue
Tile dimensions16x16 pixels
Color depth4bpp planar (4 bitplanes per tile)
Bytes per tile128 bytes
Palette16 colors per tile, indexed into Palette RAM
Color formatRGB444 big-endian (0x0RGB) -- 2 bytes per color, 32 bytes per palette
ROM interleavingTiles split across all 8 GFX ROM chips

The Copy Plan

Spider-Man's complete sprite set (all animation frames) occupies approximately 1.5-2 MB across the 8 interleaved chips. To copy and modify:

  1. Extract -- De-interleave Spider-Man's tile data from the 8-chip layout
  2. Copy -- Place copies at the start of the free region (offset 4MB in each chip)
  3. Modify -- Add armor details: thicker outlines for plating, visor glow, metallic highlight tiles
  4. Update pointer -- Change the sprite bank pointer in the character table entry for ID 0x2E at $065CCA

Since the CPU can't directly read GFX ROMs (it only writes tile numbers to Object RAM), the character table at $065CCA contains a sprite bank index that tells the CPS-2 graphics hardware which tile range to use. Pointing ID 0x2E's bank to the copied tiles is a simple table patch.

Space Usage

AssetSize% of Free Space
Spider-Man sprite copy~2 MB2.1%
Remaining free~94 MB97.9%

We could theoretically copy ALL existing characters' sprites and still have 60+ MB for completely new content.

Program ROM: The Next Frontier

The one area we haven't expanded yet is program ROM. MvC1 uses 1MB but the hardware supports 4MB. Street Fighter Alpha 3 proves 4MB program ROMs work on CPS-2.

Expanding program ROM would give us 3MB of additional code space -- enough for:

  • Complete AI opponent logic
  • New character movesets and combo systems (including wiring Armored Spider-Man's load function)
  • Enhanced training mode features
  • Additional game modes
  • Position check patches for new secret character boxes

This requires more careful MAME driver analysis since the program ROM file names and sizes are validated differently than GFX/Audio ROMs.

Summary

MetricOriginalFile Size After ExpansionAddressable (stock emulator)Addressable (custom FBNeo core)
GFX ROM32 MB128 MB32 MB (mapper masks past 32 MB)64 MB (pixel-proven)
Audio ROM8 MB16 MB8 MB (QSound masks past 8 MB)16 MB
Program ROM1 MB1 MB1 MB1 MB (4 MB hardware max)
Build PipelineManualAutomatedPython + vasmPython + vasm
VerificationNoneULTRA SETTINGS (file-size based)22 diagnostics+ pixel-proof from upper GFX region

The hardware ceiling analysis in this post stands: 128MB GFX / 16MB audio / 4MB program is what the CPS-2 platform can physically address. What this post's original conclusion got wrong is who was doing the addressing -- padded files alone changed nothing, and the real expansion (64MB GFX + 16MB QSound) required a custom emulator core, at the cost of standard Fightcade netplay compatibility.

References

  • Capcom CPS-2 hardware specifications
  • MAME CPS-2 driver source (cps2.cpp, cps_draw.cpp)
  • 68000 Programmer's Reference Manual (Motorola)
  • CPS-2 ROM interleaving analysis
  • MAME ROM loading and validation code
EOF · $08 · 2,899 words · Daniel Plas Rivera
Share[X][LinkedIn]