I have been looking at the same Marvel vs. Capcom character-select screen since 1998. Fifteen faces. Three columns. Five rows. Every time I build another fighter, the game asks the same bad question: who are you willing to erase?
I am tired of answering it.
I do not want Zero to exist only because Mega Man disappeared. I do not want Magneto to live behind a route that still looks like somebody else's slot. I do not want a new character to require a long secret sequence that a player has to memorize, look up, or accidentally break while trying to reach it. A new fighter should be allowed to be new: its own ID, its own portrait, its own box, and one obvious directional input from the screen everyone already knows.

That is the missing frame: not a prompt image, not a composition, and not the hidden-character route counted as a visible column. It is the running game drawing four columns by five rows on screen. The isolated layer below makes the count even clearer.

The screen is 4 visible columns × 5 rows. The old hidden-character route is not part of that visible count. When the hidden route is included in a system diagram, the model is 5 columns × 5 rows: one hidden logical column plus four rendered columns. Roll remains a separate side branch. The two counts describe different things and are labeled separately throughout this post.
Every game screenshot in this post is now an emulator capture. The supplied generated reference image was useful for communicating the desired composition, but it is not published here as proof. Diagrams are explicitly captioned as maps.
The replacement trap
The character work already on this site shows why another slot matters. Zero was built through Mega Man's destination. Magneto has been reachable in the select flow while the grid still presents the stock arrangement. Dark Sakura and Shuma-Gorath can own side art, names and battle identity while the persistent mug surface remains a different subsystem.




Those are valuable milestones. They also expose the architectural debt. A fighter can be present in the engine while borrowing a roster address, a mug, a name path, a hidden unlock path, or all four. The fourth column is not decoration. It is the UI layer that lets the character roster finally match the work happening underneath it.
The player-facing idea is intentionally simple: stand on one of the five characters at the current left edge, tap LEFT once, and arrive at a new fighter. No wiki. No twenty-direction ritual. No guessing whether a hidden flag is active.
First, make room without deleting anyone
The menu cannot point at new characters if the ROM has nowhere stable to hold their programs, graphics and audio. That is why the first deliverable looks boring: it takes the exact encrypted Euro 980123 parent and creates an expanded—but otherwise untouched—base.

The output adds:
| new members | capacity | role |
|---|---|---|
mvc.h13m–mvc.h20m | 8 × 4 MiB | upper graphics region for imported character art |
mvc.h11m, mvc.h12m | 2 × 4 MiB | upper QSound sample region |
mvc.exp | 1 MiB | flat 68000 expansion window at $500000–$5FFFFF |
That is 41 MiB of neutral new capacity and zero changed bytes across the original 21 members.
The custom core contract is pinned to branch cps2-sprite-expansion, commit
c025d0ecfb9187a6962dc94ac3c240a8ae258fc9. Stock MAME and ordinary Fightcade do not understand
these added members; this is a deliberate custom-core project, not a disguised stock-compatible
patch.
- 1stock encrypted program$000000–$400000 · 4 MBvanilla program remains the authority
- 2unmapped gap$400000–$500000 · 1 MBnot treated as free ROM
- 3mvc.exp$500000–$600000 · 1 MB1 MiB expansion arena in the pinned core
The live gate opens the expanded archive in the pinned core, confirms all 32 members report OK,
reads $FF at five points spanning the new program window, reaches the character-select screen,
chooses a team, and enters a fight. Storage is not inferred from file size; the emulator has to
fetch it.
Reconstruct the old menu before changing it
The stock select screen is not one columns = 3 constant. It is several cooperating tables:
| raw-program offset | stock size | responsibility |
|---|---|---|
$0E2C0A | 21 XY pairs / 84 bytes | 15 normal cells, Roll, and five secret positions |
$0E2C5E | 23 words / 46 bytes | character ID → XY token |
$0E2C8C | 23 × 4 words / 184 bytes | base UP, DOWN, LEFT, RIGHT navigation |
$0E2D44–$0E3194 | seven more 184-byte tables | six individual unlock states plus the master state |
The first proof was deliberately conservative: generate the stock blobs from a human-readable model and compare every byte with the primary raw program. If the generator could not reproduce the old screen exactly, it had no business authoring a new one. It passed for the XY table, the ID map, and all eight navigation variants.
The five visible Y rows are $0040, $0060, $0080, $00A0, $00C0. The three existing X
columns are $00D0, $0100, $0130. The measured fourth-column position is $00A0, one 48-unit
pitch before the current left edge. The live proof at the top renders that exact additional pitch.

The new visible IDs are $30, $32, $34, $36, and $38. Existing $2E work is reserved for
Iron Spider compatibility rather than being silently reused. The names NEW A through NEW E are
temporary seats, not claims about which five ports will occupy them.
Table delta
The new table model grows only where the fourth column needs it
Generated binary sizes; proposed values are not yet injected into the encrypted program.
- visible XY cells+5 / +33.3%stock screen154×5 model20
- XY table bytes+20 / +23.8%21 coordinate pairs8426 coordinate pairs104
- ID-map bytes+12 / +26.1%through id $2C46through id $3858
- bytes per navigation state+48 / +26.1%23 IDs18429 IDs232
Add a column before the old ones—not on top of them
Appending a fourth column on the right would collide with Roll's stock secret route and the visual space already used by the screen composition. The useful opening is on the left. More importantly, the left insertion has a compatibility property the secret-code audit can prove.
The base rule for each row is:
new cell LEFT -> itself old left-edge LEFT -> new cell
new cell RIGHT -> old left-edge new cell UP/DOWN -> new column neighborSo the top row becomes $30 ↔ $16 ↔ $12 ↔ $1A, the second becomes
$32 ↔ $1E ↔ $14 ↔ $20, and so on. Top and bottom new cells clamp vertically just as the stock
edge cells do. Every other stock link remains unchanged.
This detail matters because cursor movement and selection are different proofs. A box can draw at the right coordinate while committing the wrong fighter ID. The planned one-cell engine test must therefore show the new mug, accept P1 and P2 cursor movement, commit the new ID, survive team selection, and reach gameplay before the remaining four seats are enabled.
Preserve every old secret door
MvC does not recognize its six secret codes as abstract arrows. The program stores the character-ID sequence visited by each code. That was the compatibility break hiding behind the simple grid edit: change navigation carelessly and the same physical input string walks through different IDs, so the matcher never unlocks its flag.
The raw matcher arrays were decoded directly from the program. Then each sequence was replayed through every navigation state before and after the proposed insertion: six codes × eight tables × two models = 96 replays.

The six stock unlock edges stay exactly where players expect them:
| unlock | stock edge changed when active |
|---|---|
| Roll | Mega Man RIGHT → Roll |
| Red Venom | Chun-Li UP → Red Venom |
| Orange Hulk | Ryu UP → Orange Hulk |
| Gold War Machine | Zangief UP → Gold War Machine |
| Shadow Lady | Gambit DOWN → Shadow Lady |
| Lilith | War Machine DOWN → Lilith |
The base table, six individual-secret tables, and the all-secrets table all receive the same new left-column routing. Patching only the default table would create exactly the kind of “works until a secret flag is active” bug this project is trying to eliminate.

Finding the real portrait renderer
The repository contains an older, confident story: fifteen explicit calls near $005ECE–$005F06
looked like one per portrait, and a splice at $024AB4 tried to append a sixteenth OBJ entry.
Runtime testing did not validate that story. The splice never produced the intended cell, later
ran during gameplay, and emitted garbage tiles and numbers. The build script now leaves it off.
A synchronized OBJ capture made the contradiction sharper. The active object list contained the cursor, side art, title and UI objects—but no regular fifteen-mug pattern. The decisive test was to turn the CPS layers off one at a time in the running game. Sprites left the side presentation. Scroll 2 left the Earth. Scroll 3 left the star field. Scroll 1 left the complete portrait grid and nothing else.


The stock box boundaries are 48 pixels wide and 32 pixels high. In Scroll 1 video RAM, the old left portrait column occupies six tile columns across twenty-two tile rows, including its border. The new destination is the six tile columns immediately before it. Copying that measured surface produced the two live captures above: 20 actual boxes, not an image laid over the game.
For this proof, the emulator instrumentation copies the old left edge into the new destination, which is why Chun-Li, Morrigan, Strider, Captain America and Gambit appear twice. That is deliberate: it proves geometry and rendering without pretending five character portraits are finished. The production patch still has to move this copy into a ROM-resident writer, connect five new IDs, and replace the duplicated art with each fighter's real mug.
A player installer, not a folder of patch instructions
The installer follows the hardened pattern used by the other MvC releases. The player supplies
their own clean mvsc.zip. The command validates the archive SHA-256, exact case-sensitive member
names, member count, sizes and per-member hashes. It rejects duplicates, extras, mislabeled sets,
wrong regions, modified parents and existing expanded sets.
flowchart LR
V["clean Euro mvsc.zip"] --> C{"exact parent?"}
C -->|"no"| R["refuse without writing"]
C -->|"yes"| P["copy 21 members byte-for-byte"]
P --> E["create 11 expansion members"]
E --> T["write .part archive"]
T --> Q{"reopen + verify all 32"}
Q -->|"fail"| R
Q -->|"pass"| O["publish ZIP + JSON receipt"]The Python authority and the no-dependency Windows CMD produce decompressed members that are byte-identical and identically ordered. Their ZIP container hashes differ because their deflate implementations differ; the verifier compares the payload that the emulator actually loads. The installer never edits or overwrites the clean input and contains no Capcom ROM data.
Current Windows installer SHA-256:
4bd7b0d32ca105e0a30b9708c049352f458c92b9a3aca30af4a7eacf12fe923eThe scorecard keeps the ending honest
The fighter-port scorecards elsewhere on this site taught a useful lesson: “present,” “modeled,” and “verified” are different states. Character expansion needs the same discipline.

Release boundary
What each expansion layer owns today
Verified means a named artifact and replay or runtime gate exists. Modeled means generated bytes exist but are not in the live ROM.
| route | data model | written ROM | live runtime | player release |
|---|---|---|---|---|
| neutral ROM capacity | verified | verified | verified | verified |
| stock select preservation | verified | verified | verified | verified |
| 4×5 XY + ID + navigation | verified | pendingrelocation and reader repoints open | pending | pending |
| six secret-code routes | verified96 model replays | pendingexpanded tables not injected | pending | pending |
| Scroll 1 portrait surface | verified | bridged | verifiedlayer-isolated live capture | pending |
| four visible columns | verified | pendinginstrumented, not ROM-resident | verified20 live rendered boxes | pending |
| five new selection commits | bridged | pending | pending | pending |
| five playable fighters | pending | pending | pending | pending |
How this moves forward
The next milestones are deliberately narrow:
- Move the proven Scroll 1 copy from emulator instrumentation into a guarded ROM-resident writer.
- Relocate the expanded XY, ID and all eight navigation tables into a safe arena.
- Repoint every reader and verify the old fifteen cells remain pixel-identical.
- Enable
$30only, draw its real left-column mug, move P1 and P2 onto it, and commit the ID. - Replay the six secret codes, Roll, the all-secret state, continue, attract and team-select paths.
- Run at least ten minutes of gameplay after selection before enabling
$32–$38.
After that, the character ports stop competing for chairs. Magneto, Dark Sakura, Shuma-Gorath, Zero, Cable, Ken—or whichever five pass their own behavior gates—can be linked to visible boxes without rewriting the stock fifteen. The menu expansion is the seating plan; it does not waive the inspection for the people sitting in it.
The real screen at the end of this pass
This is where the work stands in the running game: twenty visible boxes, four columns, five rows, and the added column physically before the original fifteen. It is shown again here because the article should end on the evidence it actually produced—not on generated target art.

We have been looking at the old screen for twenty-eight years. Capcom never came back to give it five more doors. That does not mean those doors cannot exist.
The point is not to erase 1998. It is to preserve every character and every secret route from 1998, then make room beside them for everything we wished had happened next. Twenty-something years later, the ROM has room, the real menu can render the extra column, and the installer lets another person reproduce the foundation without becoming a reverse engineer first.
Next, those five new doors need their own names, portraits, IDs and fighters.