One request comes up whenever stage mods do: take a Marvel vs. Capcom 3 stage — Kattelox Island, the Daily Bugle — and put it in Dreamcast MvC2. Not a texture swap. The stage.
Short answer: it's real work but it's not research anymore — every link in the chain already exists. The honest version of the answer is a budget problem, and the budget is brutal. This post walks the actual numbers from my own dump, because "just port it" changes meaning when you see what an MvC2 stage physically is.
What an MvC2 stage actually is
Crack open the US Dreamcast disc (redump 3-track bin/cue; high-density data track is 1,185,760,800 bytes like every GD-ROM) and the stage inventory is plain in the root directory: 17 stages, exactly two files each.
STG00POL.BIN 173,976 STG00TEX.BIN 1,671,168
STG01POL.BIN 116,384 STG01TEX.BIN 1,671,168
STG02POL.BIN 168,592 STG02TEX.BIN 1,654,784
...
STG10POL.BIN 170,720 STG10TEX.BIN 1,441,792POL is geometry, TEX is textures. That's the whole stage. No shader
packages, no lighting rigs, no streaming — two files.
The observed ceilings across all 17 stages: geometry tops out at 194,008
bytes (STG03POL.BIN) and textures at 1,671,168 bytes = 1,632 KB
(several stages sit exactly there — it's the cap the community's stage
tutorial documents, and the dump agrees). Whatever MvC3 stage you love, it
has to become ≤190-ish KB of geometry and ≤1,632 KB of 16-bit textures.
POL: pointer tables aimed at a fixed address
First bytes of STG00POL.BIN:
0000 10 00 ea 0c 0f 00 00 00 50 00 ea 0c 20 01 ea 0c
0010 20 01 ea 0c 68 89 eb 0c e0 f0 eb 0c e8 2a ec 0cRead those as little-endian words: 0CEA0010, count 0000000F, then a run
of pointers — 0CEA0050, 0CEA0120, 0CEB8968… Every one is an absolute
SH-4 RAM address. The file is built to be loaded at 0x0CEA0000 and used
in place: a section-count header and a table of absolute pointers into
itself (15 sections for STG00, 25 for STG05 — it varies per stage). There is
no relocation. That matches how the community builds custom stages: geometry
is assembled, literally, with an SH-4 assembler, so the addresses are baked
at build time.
TEX: raw twiddled PVR banks
STGxxTEX.BIN has no header at all — it's concatenated PowerVR texture
data, 16 bits per pixel, in the Dreamcast's Morton-order "twiddled" layout.
Read it linearly and you get colored noise. Untwiddle it and the stage art
falls out:

Here's the untwiddler core, small enough to gut-check — this is the whole trick, interleaving x/y bits:
def expand(v): # spread bits: abc -> a0b0c
v = (v | (v << 8)) & 0x00FF00FF
v = (v | (v << 4)) & 0x0F0F0F0F
v = (v | (v << 2)) & 0x33333333
v = (v | (v << 1)) & 0x55555555
return v
idx = (expand(x) << 1) | expand(y) # Morton index into the texture
px = u16le(tex[2*idx:]) # RGB565 (opaque) / ARGB1555 (cutout)Formats are the stock PVR pair the tutorial names: RGB565 for opaque, ARGB1555 for anything with cutout transparency, power-of-two sizes from 2 up to 1024.

What an MvC3 stage is, by contrast
MvC3/UMvC3 is MT Framework: stages are .arc archives of .mod meshes with
.tex textures, real-time lighting, shader materials, animated set pieces,
and texture budgets per object that dwarf an entire MvC2 stage. The
extraction half is solved by the community: ARC unpackers and MT Framework
model tools get stage geometry and textures into Blender today.
So the port is never a translation. It's a demake with a fixed exchange rate:
| MvC3 has | MvC2 accepts | conversion |
|---|---|---|
| shader materials, real-time lights | vertex color + baked texture light | bake in Blender; flatten every material to one textured quad-mesh |
| meshes in the millions of tris | ~190 KB of assembled geometry | decimate hard; rebuild as quads (the format wants 4-vertex faces); split into floor / midground / background layers like the stock stages |
| hundreds of MB of textures | 1,632 KB total, 16bpp, pow2 ≤1024 | bake+atlas to a handful of sheets; RGB565/ARGB1555; twiddled PVR |
| animated set pieces, crowd systems | static geometry (plus whatever palette tricks you earn) | accept the loss, or fake motion with UV/palette cycling later |
| its own camera | MvC2's fixed stage camera, floor plane, and bounds | model to MvC2's world scale; the stock stages are the reference |
The working pipeline, end to end
Nothing here is hypothetical — each step is an existing tool:
- Extract the MvC3 stage: ARC unpack → MT Framework model import → Blender (the UMVC3 importer docs cover the model side).
- Demake in Blender: decimate, quad-ify, split into layers, bake all lighting to textures/vertex color, atlas UVs onto pow2 sheets.
- Textures out: PVR Tool (RGB565/ARGB1555, vertical flip), then the
community's
buildTEX.pyto pack the bank — stay under 1,632 KB. - Geometry out: Blender-to-
.asmexport, headers describing mesh/texture assignment, thensh4_asmto aPOL.BIN— same path the from-scratch custom stages use. - Inject + rebuild: drop the pair over a stage slot (
STG05*etc.), rebuild with GDROM Explorer +mkisofs/cdi4dc, boot it in Flycast before ever burning anything. - Verify like we verify CPS-2 work: hash the two files, screenshot the stage from fixed camera states, and keep the stock pair for A/B.
Where the bodies are buried
- The texture wall is the fight. 1,632 KB at 16bpp is about 800K pixels — one 1024×512 sheet plus change, for an entire stage. MvC3's look survives on baked lighting quality, so the atlas/bake step decides whether the port reads as "MvC3 in MvC2" or as a blurry postcard.
- Absolute pointers mean assembled output, not patched output. POL files
carry baked
0x0CEAxxxxaddresses; you generate a fresh file with the assembler toolchain rather than hex-editing geometry in place. - Slot discipline: replacing a stage in place keeps the filesystem simple; going bigger than the stock extent means a full disc rebuild — which the CDI pipeline does anyway, so the real limit is the Dreamcast's texture memory, not the ISO layout.
- NAOMI is a separate release target. Everything above is the Dreamcast disc. The arcade/NAOMI packaging is different containers around close cousins of these formats — worth doing after a DC build proves the art.
The next post in this thread should be receipts, not maps: one MvC3 stage actually crushed through this pipeline into a booting Flycast capture. The decoder above already proves the read side; the write side is the same trip in reverse.