The American arcade board for Marvel Super Heroes vs. Street Fighter has one box on the character select that holds no fighter. Bottom row, middle. It shows the game's own logo.
Last time I showed that the box is not empty — it is Norimaro, the Japan-only character, switched off by a single byte in a defaults table.
This time the box holds Strider Hiryu.

The box is not a tile, and calling it one cost three builds
The grid is built at $06CF62: index a cell-art table by col*6 + row, add the
self-relative word found there, and jsr the draw routine at $001F0A with the resulting
12-byte descriptor. Every descriptor has the same shape, and only one field differs between
cells — so I called that field the tile base and wrote tiles at it.
Nothing appeared. Worse, pointing it at the one patch of virgin space in bank 0 made the game reboot the moment Start was pressed.
I wrote that up as a graphics integrity check: the tile build also took several thousand extra frames to clear POST, which fit. It was wrong, and the one-variable control is what caught it — a build with the identical 64-tile write and the descriptor untouched runs perfectly.
Blaming the graphics ROMs compared stock against a build that changed two things. Writing CPS-2 graphics ROMs is safe: the byte diff accounts for the write exactly, the codec round-trips, and a search across 80 candidate sum rules found no graphics checksum at all. What resets the game is the pointer.
Eleven instructions of disassembly ended every open question at once:
$001F0A: move.l a0, $34(a6) ; keep the descriptor pointer
$001F0E: move.w (a0)+, $3c(a6) ; +0
$001F12: move.w (a0)+, d0 ; +2 ($8100)
$001F14: move.l (a0)+, $40(a6) ; +4 <- a 32-bit SPRITE LIST pointer
$001F18: tst.w d0
$001F1A: bpl.b $1f22
$001F1C: andi.w #$7fff, d0
$001F20: movea.l (a0), a0
$001F22: move.w d0, $3e(a6)
$001F26: move.l a0, $38(a6)
$001F2A: andi.b #$fe, ccr
$001F2E: rtsThere is no tile field. What I had been calling "the tile word at +6" is the low word of
the long at +4, and that long is the object's sprite-list pointer. Every loose end closes
on that one line: the numbered test tiles never appeared because the art lives at codes the
probe never wrote to, and the reset was a garbage list pointer killing the walker on the
select screen's exit path. Not an integrity check. Not a bank problem. A bad pointer.
The geometry question dissolved too. There is no "N tiles wide from a base" — the list's
cells carry code and attr, and attr holds the dimensions and the palette:
nx = ((attr >> 8) & 15) + 1
ny = ((attr >> 12) & 15) + 1
pal = attr & $FFThe logo's cell is code $B61C, attr $2311 — four wide, three tall, palette $11, drawn
from bank 1, row pitch 16. Rendering $1B61C as 4x3 at stride 16 reproduces the logo
exactly. Banks 0, 2 and 3 and stride 4 all produce noise. That agreement against a known
answer is the only reason to trust the rule anywhere else.
The portrait was not in the ROM I was reading
With the mechanism understood, the remaining job was Strider's actual face — and the first attempt put his bomb in the box. The art I seated was gameplay art, the paraglider, and it looked plausible enough in a grayscale tile render to survive until it was on screen.
So I went looking for Marvel vs. Capcom's real portrait table, found a clean one — sixteen
one-cell 3x2 lists at stride 14, each carrying its own palette $01 through $10 — and
could not identify which index was Strider. Four methods failed. The fourth failure is the
useful one: an exact palette-bijection match, which should be trivially exact, returned
0 of 16, because that grid is drawn as a perspective trapezoid and the portraits are
scaled. No pixel-identity method can work against that screenshot.
The answer was already written down, in a tool this project had used before and I did not
open. PalMod's MVC_A_DEF.h lists sixteen character-select icon palettes at a stride of
$20, each labelled with the character's name — Strider Hiryu CS Icon, at file offset
0x3e758, which is $1BE758 in 68k terms. Thirteenth entry. The identification problem I
had declared open was a lookup.
Then the table turned out not to drive the screen at all. Repointing all sixteen entries at numbered markers changed nothing; stamping the digits over those entries' own tiles in place changed nothing; MAME confirmed it had loaded the modified set. MvC1's select grid is a scroll layer, not sprites — solid-filling the region blanked the SELECT text and the cursor and left every portrait untouched, which is exactly the control that separates "my edit didn't land" from "that isn't where the art is".
So the art was lifted from the live screen instead: the 46x30 cell interior, requantised to fifteen colours.
That requantisation was the wrong move, and it is worth being exact about why. Median-cut
on a screenshot does not recover a palette; it invents fifteen colours that approximate what
is on screen. And what is on screen there is not Strider's colours at all — the trapezoid
scaling that defeated the bijection match also means those pixels are interpolated blends
of neighbouring source pixels. So the output approximated blends. The build printed its own
error the whole time — quantise err 13.7/255 — and I read that as a tolerance rather than
as a symptom.
The shipped build uses the labelled palette instead: Strider's sixteen real CS-icon words,
read straight out of $1BE758 and written through unchanged. Two candidates were scored
against the cell before either was used — the CS Icon entry explains 1,238 of 1,380 pixels
(89.7%) as exact matches, the CSP/Duo entry at $1D2F02 explains 795 (57.6%). The
residual 10% is the scaling blend, which is the answer you would predict if the palette is
right and the art is still coming from a scaled screenshot. It is.
Transparency on this hardware is palette entry 15, not 0 — entry 0 is white, and it is
what draws the logo's lettering. Building the mask as pen == 0 punched that lettering
through the replacement art as holes.
And the pen mapping is not "15 − stored pen"; it is a 4-bit reversal (1→8, 2→4, 3→12, 4→2, 8→1). Scored against the stock logo, the reversal mismatched 1,441 of 3,072 pixels against 2,707 for the documented rule, and the per-pen table is an exact bit reversal on all sixteen values.
The palette's ROM source resisted an exact search because the game ORs $F000 into every
runtime word. Masked to $0FFF, there is exactly one hit.

The name plate is stored backwards
The text under the highlighted cell is one 1x1 sprite per letter: palette $01, y = 208,
twelve pixels apart, code = $5210 + (letter − 'A').
Seven attempts guessed at addresses. The eighth measured, and the first thing measuring
corrected was the record layout itself. Object RAM records are (x, y, code, attr) —
x first. Of 226 non-zero-code records in one frame, x-first puts 208 on the visible
screen; y-first puts 91. Every earlier read had the two words swapped, which is precisely
why the plate "wasn't there".
The CPU never writes those codes. A glyph-filtered write tap across all of main RAM caught
nine hits in 550 frames and all nine came from the random number generator, whose
muls #$b0 / divs #$7fed lands in the glyph range often enough to look like a lead. The
records are DMA'd, and the ROM stores each name last letter first:
+0 longword glyph count
+4 word $0000
+6 longword pointer to a per-length layout block
+10 glyphs `count` words, REVERSEDEntries are packed with no slack, so changing a name's length desynchronises every name
after it. STRIDER keeps the count at eight.
The plate's space is $54EE, not $5020. $5020 is a blank in two other fonts on the
same screen, and assuming it was this font's space was one of the seven dead ends. What
settled it was CAPTAIN AMERICA — the only name long enough to contain a space, sitting in
the table as a control nobody designed to be one.
Then the standing figure refused to change
The small full-body character beside your locked-in pick is sprites. I measured its records, mapped which screen cell drew which tile, wrote Strider's art into those tiles — and the figure kept its original shape while taking on Strider's colours.
So I measured again. Same answer. I checked the tile base by stamping seven candidate offsets at once, each in its own colour, and the figure changed for none of them. I tried to fit the multi-tile geometry: every combination of horizontal flip, vertical flip, column step ±1 and row step (±16, ±width, ±(width+1), ±height) against four block-origin conventions — 384 rules. The best reproduced 4 of 18 known cell-to-tile pairs.
I wrote it up as unreachable. The pieces clearly weren't derivable from the record, so the asset must belong to the animation path, which is the full character-port problem rather than a cosmetic swap.
That was wrong, and the reason is worth the whole post.
The asset was animated, and I was reading the wrong frame
Sampling the object list every six frames for two seconds gives eleven distinct poses across 66 sprite codes. The map I had measured belonged to whichever pose was on screen in the snapshot I read — and because the probe wrote several snapshots and I read the last one, that was a different pose almost every time.
Everything downstream inherited it. The geometry looked unfittable because I was scoring a correct rule against mismatched reference data. Nine of eighteen tiles looked like "background" because they belonged to a pose that wasn't being drawn. Two dead ends, one mistake, two costumes.
If your patched art appears somewhere on screen but the target keeps its original shape in the new colours, you are patching a frame that is not the one you are looking at. Freeze the animation before you measure anything.
Once you know it's animated, the fix stops being an import and becomes a data edit. The poses are ROM sprite lists: searching for a pose's record bytes finds nothing, because positions and palette bits are applied at runtime, but searching for its code sequence at stride 4 hits exactly once. All eleven measured poses matched lists in one region, exactly.
The pointers to those list headers then all live in a single run, sixteen bytes apart — a pointer, some constant words, a duration. That run is the animation script. Point all thirty-one rows at one pose and the figure stops moving, and a single pose's tiles become sufficient.
Re-measured on the frozen build, with every sampled frame required to agree, the map came out clean at twenty cells:
tile = code + $20000 + row * $10 + (width - 1 - column)Offset exactly $20000, row step sixteen, horizontally flipped so the code decreases as
screen x increases. It reproduces the pose's ROM list entry for entry.
That was the first rule I tried. It was never wrong. My reference data was.

Asking the screen a question only the code can answer
The large bust came out desaturated, borrowing the donor's olive and grey instead of Strider's reds. To find out whether its palettes were safe to patch, I gave each of the five candidate palettes a saturated marker colour and looked at what each one painted. Two painted nothing inside the bust; one straddled its edge. I concluded none was exclusive to it and the route was closed.
Then I write-tapped palette RAM instead. One instruction does all 560 writes, and the routine around it says everything:
movea.l #$001FEDAE, a0 ; source table
muls.w #$0050, d0 ; 80 bytes per character
adda.w d0, a0
move.w #$0027, d0 ; 40 longwords = 160 bytes = five palettes
.loop
move.l (a1), d1
andi.l #$F000F000, d1 ; keep the brightness nibbles
or.l (a0)+, d1
move.l d1, (a1)+
dbra d0, .loopIt copies 160 bytes out of a table whose stride is 80. Entries overlap by design. The block was correct all along; only the copy length made writing all five unsafe, because the back half belongs to the next characters. The first eighty bytes — palettes zero and one — are exclusively this character's.

The marker probe answered what does this palette paint, which is a fact about the screen. Whether a ROM block is safe to write is a fact about the loader's arithmetic. The right question, the wrong oracle.
What it costs to be sure
Every claim here is pinned to a change on screen, and the ones that only ever looked right in a render are the ones that wasted the most time. Rendering the standing figure's tiles at one candidate base produced a perfectly coherent picture of the donor, which I took as confirmation. It was coincidence: that art exists at that ROM location, but the hardware draws from elsewhere. Stamping the same tiles and running the game changed nothing, and the game is the only witness that counts.
Two harness defects nearly ate the whole project. The first: an input control hard-coded a
port name that does not exist on CPS-2, and the resulting error fired inside the frame
notifier where MAME swallows it silently — so every select-screen run produced no frames and
looked like a failed patch. The second: cpu.state['PC'] is not a valid key on this driver,
only CURPC, and it failed the same silent way.
And every "select screen" in my notes before a certain point was the attract-mode demo grid, because the coin never registered. CPS-2 wants a long coin hold: an 8-frame pulse does nothing, 120 to 150 frames gives credits.
In order, because the first two are cheap and protect everything after them.
- Is the object list I am reading live? CPS-2 renders to an end-of-table marker, not to the end of the buffer. On one frame here the terminator sat at record 204 of 1,024 — about eighty percent of what I had been scanning was stale.
- Is the asset animated? If yes, freeze it before measuring anything.
- Where is each pose's sprite list? Search the code sequence at stride 4, not the records.
- What sequences them? Find the pointer run; that is the script.
- Only now measure the cell-to-tile map, and patch.
The instrument I should have used first
Everything above about the portrait works from a screenshot, and a screenshot of that grid is the worst available view of it: scaled, interpolated, and drawn by a scroll layer whose tile numbering is offset from the sprite numbering by an amount nobody here has measured.
Capcom shipped a better instrument in the ROM. CPS-2 development boards carried three extra DIP banks and a secret test menu — hitbox editor, throw editor, AI tester, and a raw ROM tile viewer that draws graphics 1:1 with a page index. Production boards read those switches as all-off and the menu is unreachable, which emulators reproduce faithfully.
Two things gate it, and both are needed. MAME 0.259 and newer expose the switches as a
machine configuration — :HW_TYPE set to "Development (Enable Debug DIPs)", plus :DSWA,
:DSWB, :DSWC. But the switches alone do nothing, because the game also tests a master
debug flag held in ROM data; with it clear, the code zeroes the switches and never reads the
bus. That flag sits in an unassigned 68000 exception vector slot repurposed as a config
block, outside the encrypted opcode span — so turning it on is a two-byte data patch and
needs no CPS-2 cipher at all.
emu.add_machine_frame_notifierreturns a subscription. Drop the reference and Lua garbage-collects it, and the notifier silently stops firing after about one frame. It looks exactly like a patch that did nothing.- Errors thrown inside that notifier are swallowed by the emulator. Everything goes in a
pcallwith a log, or a typo reads as a failed hypothesis. - P1 Button 1 is both the secret-menu gate and the menu's select button. Held across the Service Mode flip it lands on the ordinary menu root; held in a window after the flip it opens the secret menu; held a moment too long once a menu is up, it selects item 1.
The same flag address, the same window and the same switch configuration open the menu on both games measured here — the one the art comes from and the one it is going into. That is the control that says the mechanism was understood rather than fitted to one ROM.
Where it stands

Name plate, grid box, bust and standing figure are all Strider. Boot checksum self-located and unchanged on every build. The one-word portrait change diffs against stock in exactly one 55x43 region on all 21 frames and nowhere else.
Still borrowed: half the bust's tiles, because the other three palettes in that 160-byte window are not this character's to take. Closing that means following the second source table the loader can select, which is a fresh thread.
Still open, and this is the honest list. The art in that cell still comes from a screenshot — the palette is now exact, the pixels are not, and closing that means reading the icon's tiles out of the ROM through the tile viewer rather than off the screen. The scroll-layer tile-number offset is the same question wearing a different hat. And everything past the face — tree, animations, hitboxes, move properties, code — is untouched, which is the actual port.
He is not playable. Selecting that slot still gives you the character whose record lives there, wearing Strider's face. He is standing in the grid, named, in a game he was never in, and the box that held the logo does not hold it any more. That is all this is, and it is worth saying plainly, because a screenshot of a select screen is very good at implying otherwise.