Goal: Import MSHvSF's two-tier New York street -- the one where the ground gives way and the fight drops into the subway -- into MvC1 as a playable stage, on the same custom-ROM lineage that carries the native secret characters.
Constraints: No source. Base ROMs untouched; everything is a build-script artifact. MvC's scroll-GFX space is nearly full -- the stage's art appetite versus MvC's free space is the central conflict of this post.
Approach: Live RAM archaeology in MSHvSF to prove what the tier flip actually is (it is not what any of us guessed); a donor-stage + 64MB-expansion strategy for the art budget; differential and poison-tile testing before seating any graphics.
Result: The tier-flip mechanism is fully RE'd and was live-verified in MSHvSF on 2026-07-02 (counter $FFFF30, gate $06A70A, a +0D` now loads live in the current lineage, with a live layer probe confirming the stage id and the layer structs in a running match. What remains open is the art: the ~400KB scroll-art shortfall is still real -- 74 tiles had to be dropped -- so the stage renders incomplete. Both states are described honestly below.
Proof / Validation: Live-verified tier flip in the source game; pixel-level differential runs against pristine stock for every merge; a GFX poison test that caught art being seated on cells the attract mode secretly used.
See also: the Saturn MSHvSF recompile -- a plaintext, byte-identical mirror of the same character data -- and the 64MB ROM expansion the art budget leans on.
Everyone Wants This Stage
If you ask MvC1 players what the game is missing, the answers cluster fast: more characters (we're working on it), and that stage. Marvel Super Heroes vs. Street Fighter's New York street is the most theatrical backdrop in the whole Capcom Versus line: you fight on the pavement, someone gets bounced off a parked police van a few too many times, and the street collapses -- a scripted camera plunge into a subway tunnel where the rest of the match plays out underground, emergency lightbars strobing above.
MvC1 shipped one year later on the same CPS-2 hardware, same engine family, and has nothing like it. So the port question looks simple: same hardware, adjacent engine, move the data. That framing survived about one afternoon of actual reverse engineering.
This post is the story of what the stage turned out to be, why the real wall was never the code, and the first piece of it now rendering live in the wrong game.
Update, July 28: Both Tiers Live in MvC — and the "Broken Stage" Was Two Other Bugs
The user report was "the NYC stage is broken." The diagnosis found something better than a stage bug.


These are the first screenshots of stage $0D that have ever existed in the ported game — every earlier gate declared PASS the instant the fight went live and never soaked past it. Soaking found two defects, neither of which was the stage's known art shortfall:
1. A character bug wearing a stage costume. Every fight on the NYC stage stalled ~15 seconds in: sprites dropped, the HUD flickered, both fighters froze. The stage was exonerated by controls — a stock team on $0D played a full match; Shuma on a stock stage played clean. The real fault: Shuma-Gorath's hit-reaction animation (group 14, sub 13) ends in a terminal record that is byte-faithful to its MSHvSF original, but MvC's frame-advance runs off it into non-record bytes and parks both fighters in a permanent wait — a pair deadlock, no crash, no exception. The stage's only role was that its geometry steered the CPU's deterministic attack script into the triggering hit at the same frame every run. Fixed by aliasing the reaction chain's start — chain starts go through the lookup and are redirectable; chain interiors are not, a lesson this project has now paid for twice.
2. A genuine stage regression, from a fork nobody gated. The character build chain had silently forked onto an untested branch of the tier-flip code that added an extra +512-pixel scroll shift on top of the one the flip already does — double-shifting the post-flip window clean past the underground into empty skyline. Six bytes reverted to the proven in-place flip body, and the teal tunnel came back.
Current tip: mvsc_nycfix.zip — boot-gated at every link, full-gate PASSED with hit-reactions recovering mid-soak (the exact class that used to wedge), tier flip firing, and the underground rendering with the fight still alive twenty seconds later.
What has not changed: the ~400KB art shortfall below is still real (the stage renders with gaps), the lightbar animation VM is still unported, and the tier flip still needs its counter poked rather than a faithful van-bounce trigger. Those are the honest open items, same as they were.
Two more residuals, spotted in the proof screenshots by the person who knows this stage best: fighters keep their street-tier floor height in the underground (the source game reshuffles the floor-Y band with the flip; the port shifts the scroll planes but not the floor), and the layer ordering/parallax is partially wrong on the street (the tilemaps are seated; the per-layer priority and scroll-rate config was never carried). Both are now pinned to the exact tables that need porting.
What the Underground Actually Is
Before touching MvC, I needed ground truth from the source game. There were three plausible theories for how the two-tier gimmick works, and the community holds all three:


- Two stages -- the collapse triggers a stage load, underground is a separate stage id.
- Object scenery -- the underground is drawn by the OBJ (sprite) system over a static backdrop, like Onslaught's transformation set-pieces.
- One tall tilemap -- both tiers live in a single oversized scroll map, and the "collapse" is a camera trick.
Theory 3 is the truth, and it's the most elegant of the three. The underground is the same stage id $06, and the flip is nothing more than a +512-pixel (+$200) shift of the scroll base offset into the same tall tilemap. There is no second stage. There is no load. The subway was under the street the whole time, in the most literal sense a tile engine allows: the tilemap is simply twice as tall as what the camera shows, and the trigger slides the window down.
The trigger chain is small enough to state exactly, and every piece of it was live-verified in MSHvSF on 2026-07-02. The receipts are inline on the state machine below:
flowchart TD
A["STREET TIER<br/>camera window at scroll base"] -->|"opponent bounced off police van"| B["counter $FFFF30 += 1"]
B -->|"count ≤ 5"| A
B -->|"count > 5<br/>gate $06A70A fires"| C["scripted camera pan<br/>the collapse"]
C --> D["scroll base += $200<br/>same tall tilemap, window slides down 512px"]
D --> E["palette swap<br/>51 of 64 pages"]
E --> F["UNDERGROUND TIER<br/>same stage id $06 — no load, no second stage"]This finding matters for the port in a very practical way. If the underground had been a second stage, the port doubles. If it had been OBJ scenery, it lands in a completely different subsystem with its own budget. Because it's one tall tilemap plus a scroll delta and a palette swap, the mechanism ports almost for free -- the flip logic is a counter, a compare, and an add. The mechanism was never going to be the problem.
How the Stage Is Wired
A CPS-2 stage is not one image. It is three independent scroll layers composited with the OBJ (sprite) layer, all read through a camera window, and the NYC stage uses every one of them. SCROLL3 is the far parallax background, SCROLL2 carries the bulk of the street-and-tunnel tiles (this is the tall tilemap that holds both tiers), SCROLL1 is the near text/detail layer, and the police van lives entirely in the OBJ layer as a bounce prop. The tier flip acts on exactly two things in this picture: the camera window's vertical offset and 51 of the 64 palette pages the layers index into.
architecture-beta
group stage(server)[NYC stage id 06]
service tilemap(disk)[Tall tilemap street over subway] in stage
service pal(disk)[64 palette pages 51 swap on flip] in stage
group scroll(server)[CPS2 scroll layers]
service s3(server)[SCROLL3 far parallax] in scroll
service s2(server)[SCROLL2 street and tunnel tiles] in scroll
service s1(server)[SCROLL1 near detail] in scroll
group obj(server)[OBJ sprite layer]
service van(server)[Police van bounce prop] in obj
group cam(server)[Camera]
service window(server)[Window plus 200 offset on flip] in cam
tilemap:R --> L:s2
pal:R --> L:s2
pal:R --> L:s3
s3:R --> L:window
s2:R --> L:window
s1:R --> L:window
van:T --> B:windowThe Real Wall Is the Art Budget
Here's the number that reframed the whole project. The NYC stage's scroll art -- both tiers of that tall tilemap, the props, the animation cells -- needs roughly 400KB of scroll-GFX space. I mapped MvC's scroll graphics region for free space, and the largest contiguous free run is about 51KB.
- What the NYC stage needs400KB
- Largest contiguous free run in MvC51KB
roughly an eighth of it, and the shortfall is still open
Not 400 versus 300. Not "compress harder and squeak in." An 8-to-1 shortfall against the largest hole in the map. No amount of cleverness inside the stock address space closes that; the tile data for MvC's own stages is packed nearly wall to wall.
Two moves together make the budget real:
- Donate a stage. One of MvC's existing stages gives up its scroll allocation; NYC takes over the donor's space and stage slot. (In the current build lineage the target is stage slot
$0D.) - The custom 64MB GFX expansion. The same expanded FBNeo core that carries the native characters' tiles -- extra graphics chips plus a mapper above the stock 32MB ceiling -- absorbs what the donor slot alone can't. The standing caveat applies here too: the 64MB core is an offline build and breaks Fightcade netplay compatibility.
This is the same shape of solution as the character work, and that's not a coincidence: on CPS-2, code is almost never the scarce resource. Space is.
A Police Van, Rendering in the Wrong Game
The stage's soul is the police van -- it's the trigger object, the thing players remember, and mechanically it's an OBJ-system prop with its own bounce animation frames rather than part of the scroll tilemap. So it made the right first milestone: small enough to seat honestly, load-bearing enough to prove the pipeline.


That milestone is shipped: the van renders and animates live in MvC. Bounce frames cycle, and it tracks the camera correctly -- which sounds cosmetic but is the actual test, because camera-relative OBJ placement is where a half-ported prop falls apart first.
Two bugs from this phase are worth writing down, because both are the kind that silently eat weeks.
The silent canvas trim. For several builds, the underground tier came out of the pipeline with garbled yellow blocks across the top of the tunnel. The art source was provably right; the seated bytes were provably wrong; everything in between claimed success. The culprit was an image-processing step that silently trimmed the working canvas to 1024 pixels -- and since the underground lives in the bottom half of a taller-than-1024 canvas, the trim quietly dropped the tunnel's top rows and shifted everything above them into garbage. v26 of the stage build fixed it. The lesson generalizes: any pipeline stage that can resize without erroring is a pipeline stage that will eventually lie to you.
The attract-mode font. An early merge of the stage's first scroll layer corrupted MvC's attract mode -- four of the GFX cells I'd seated art into were doing double duty as glyphs in an attract-screen font. Those cells read as free by every static test I'd run: nothing in the stage tables referenced them, and they were full of $FF. The fix is deployed and the attract screen is now pixel-identical to stock, but the cautionary tale is the point: all-$FF is not sufficient evidence that a tile is unowned. The rule that came out of it is the GFX poison test, now standard for every seating operation: fill the candidate cells with a loud sentinel pattern, sweep attract mode, the select screen, and full matches, and prove the sentinel renders nowhere before a single byte of real art goes in. Prove cells unreferenced by observation, not by absence of evidence.
Where It Stands: Hang Fixed, Art Short
Honesty section, updated with good news. The stage work has been merged onto the current native-character build lineage, and the character regression suite passes -- Dark Sakura and Shuma-Gorath are unharmed by the merge. As first published, this section reported that forcing a match onto stage $0D hung at load, suspected to be a relocated-pointer integration bug. That hang has since been root-caused and fixed, and stage $0D now loads live in the current lineage -- a live layer probe confirms the stage id and the layer structs in a running match. What the load fix does not buy is art: the ~400KB shortfall is still real, 74 tiles had to be dropped, and the stage renders incomplete. The van milestone was real; the load milestone is now real too; the visual-completeness milestone is not yet.
Also still open on the art side: eight more OBJ props are extracted but not seated (cops, medics, the dog, civilians), and the flashing emergency lightbars turn out to be the most interesting remaining piece -- they are not OBJ sprites at all but scroll-tilemap animation driven by a small VM in the stage script, a mechanism MvC's stages use differently. That's a genuine porting sub-project, not a copy job.
Open Items
Stated plainly, because the project rule is no false success claims:
- The art shortfall. The load hang is fixed and stage
$0Dloads live, but the ~400KB scroll-art budget gap remains -- 74 tiles dropped -- so the stage renders incomplete in the deployed lineage. - Eight OBJ props (cops, medics, dog, civilians) extracted but not seated.
- The lightbar VM. The flashing emergency lights are scroll-tilemap animation driven by a small VM, not sprites -- not yet ported, and genuinely nontrivial.
- The 64MB expansion the art budget depends on remains an offline-only core -- it breaks Fightcade netplay compatibility.
Proven, with receipts: the tier-flip mechanism (live-verified in MSHvSF), the van rendering and animating in MvC, the attract-mode fix (pixel-identical to stock), and -- since publication -- the load-hang fix (stage $0D live-probed in the current lineage). Promised, and only promised: the stage visually complete in MvC. The ~400KB art shortfall and its 74 dropped tiles are the remaining wall; when that falls, that's a follow-up post.
The other half of this investigation -- what MSHvSF's own Sega Saturn port turned out to be, and whether MvC content could go the other direction onto Saturn hardware -- got long enough to earn its own writeup: Byte-Identical on a CD: What the Saturn MSHvSF Port Actually Is.