I want 3rd Strike Ken in Marvel vs. Capcom.
Not Ken-colored Gambit. Not a sprite parked on a select screen. Ken, on the six-button game I grew up with, carrying his own art and eventually his own behavior, standing in Gambit's slot.
This is part one of that road, and I'll be straight about where it ends: Ken is not in Marvel vs. Capcom yet. No ROM in this post boots him. What this post has is the thing that has to exist first, and that I did not actually have when I started — his real pixels, provably correct, in a form I can convert.
If you just want the picture, skip to the contact sheet. If you want to do this yourself on a different character, the last section is the recipe.
The build I inherited was making things up
This didn't start from zero. It started from a working directory full of scaffolding, a 245-row animation map, four 91 MB ROM zips, and a document titled "Ken piece → pixel gate — Status: PASSED."
The character in those ROMs was garbled and scrambled in game.
So the first job wasn't building. It was working out which claims in front of me were load-bearing. Four were false, and they compound in a way worth showing, because every one of them produces an artifact that looks like progress.
One: the sprite format was invented. The gate document described a clean-sounding layout — a
12-byte header, then W eight-byte tile descriptors, then W eight-byte place records, with a
tag word that was "always $0200." That format does not exist. It has no counterpart in the
CPS-3 hardware. It had been pattern-matched out of ROM bytes by staring until structure
appeared, which is a thing bytes will always eventually do for you.
Two: the evidence didn't support the conclusion. The gate claimed PASSED on a mean normalized cross-correlation of 0.563 across 16 of 27 tiles, with no control. The contact strip offered as proof looks like this:

That staircase is what you get when tile contents are roughly right and tile placement is entirely wrong.
Three: the capture was never mid-match. The harness that produced the source data drove the
game to the character select screen, then entered its "fight" phase on a frame counter and
dumped memory blindly. Every sprite entry in that capture is a tile=0x1 stub. There was no
character art in it at all. It was a dump of a menu.
Four — and this is the one that reached the ROM. The builder that seated Ken's tiles didn't read any of that. It read a PNG:

A screenshot. With the background in it. Quantized to 15 colors, chopped on a raster grid, and written into graphics ROM as Ken's sprite tiles. That is the complete explanation for the garbled character. It was never Ken's data. It was a photograph of Ken, sliced up.
Each failure produced something that looks like forward motion — a format spec, a passing gate, a capture file, a built ROM. None were tested against anything that could have said no. A similarity score with no control is the tell: 0.563 out of 1.0 sounds like most of the way there, right up until you ask what a random answer scores.
What I decided would count as proof
I picked the standard before writing anything, because picking it afterward is how you end up with 0.563.
The emulator already renders these sprites correctly. So the bar isn't "does my output look like Ken." The bar is exact RGB equality with MAME's own frame, over thousands of pixels, with a deliberately-wrong alignment scored alongside. Plausible-but-wrong decodes don't survive that.
Reading the hardware instead of the bytes
The CPS-3 sprite format isn't a mystery. It's in MAME's driver, src/mame/capcom/cps3.cpp, and
it looks nothing like the gate document. It's a two-level list.
The outer level is objects, four 32-bit words each, walked until the top bit of the first word is set: how many sub-entries, where they start, screen position, global flip and palette flags, and which of eight scroll registers it uses. The inner level is sub-entries, three used words out of every four:
v1: tileno = (v1 & 0xfffe0000) >> 17
flipx b12 | flipy b11 | bpp b9 | pal 8..0
v2: xpos2 (25..16) | ypos2 (9..0)
v3: ysizedraw (30..24)+1 | xsizedraw (22..16)+1
ysize (3..2) | xsize (1..0) -> tilestable = {8,1,2,4}The single most useful line in the driver: when xsize == 0, the entry isn't a sprite at all —
it's a command to draw a background tilemap. Skip those and you get every character and
effect on screen with no background, for free. No rotoscoping, no chroma keying, no cleanup.
The hardware hands you the separation.
Four bugs between there and a picture
Implementing the list was the easy part. Three of the next four bugs are invisible — they produce output that looks almost right, which is the worst kind.
Tiles are 8bpp, 256 bytes each. The old pipeline decoded 6bpp.
Colour RAM is 0x40000 bytes, not 0x20000. I dumped half of it, clamped every index past
the midpoint, and got wrong colors on exactly the sprites using high palette banks. This one bit
me twice — a stale half-size dump survived on disk and quietly produced a 58.93% gate score
hours later, until I re-captured and got 98.35% back. Delete your intermediates.
The scroll registers are write-only. 0x040c0000-0x040c001f is mapped writeonly(), so a
Lua capture reads back zeros. The fix was to stop trying to read them: render into the full
0x400 coordinate space and recover each object group's offset from the picture afterward.
And the one that took longest. MAME stores char RAM as 32-bit words but the graphics decoder
reads it through a byte pointer. On a little-endian host those disagree — bytes inside every
word come out reversed relative to what a Lua read_u32 hands you. The result:
Look at the left one. Blonde hair, white gi, black belt, brown gloves — it's obviously Ken. Every structural thing is right. If you were eyeballing your way to success you'd call that a win and move on, building everything downstream on a decoder that gets every pixel wrong.
The gate
With those fixed, here's the renderer against MAME's own output from the same frame, on an unoccluded character:

Two orders of magnitude of separation, on exact equality rather than similarity.
The remaining 1.65% is the lifebar — and I checked that rather than assuming it. When the same measurement ran on Ken, who was standing under a "ROUND 2" banner, it dropped to 67.63%, and only 28% of the misses were under the banner. The rest turned out to be a second sprite object overdrawing him. Worth chasing, because "it's probably occlusion" is exactly the kind of guess this post is about not making.
Scaling up: 133 poses
One correct frame is a proof; a character needs hundreds. So: a harness that drives Ken through a scripted move list — idle, walks, crouch, jumps, all six normals, crouching attacks, hadouken, shoryuken, tatsu — sampling every fourth frame.
Dumping 8 MB of char RAM per frame is a non-starter. Instead each sample is a frame package: the object list, the sub-entry ranges those objects point at, and the 256-byte tile for every tile actually drawn that frame. Nothing else.

Finding Ken was harder than decoding him
The most transferable lesson here. Two obvious ways to identify which object is your character both fail:
Object index doesn't work. Indices shift frame to frame. My first sweep pinned Ken to object 8; the resulting set quietly contained Yun frames and a shadow ellipse.
The palette bank doesn't work either. gpal looks like a character ID — Ken 8, Yun 16. It
isn't. It's per-round, per-state. Ken moves from gpal 8 to gpal 0 across a round transition,
so filtering on 8 silently lost every move after the heavy punch. When I tried gpal 0 instead I
got shadow ellipses, because shadows use it too. I nearly concluded "0 is the shadow bank" and
moved on — then found Ken also rendering under gpal 0, on the floor, knocked down.
What works is his palette's exact colour set, taken from a frame the gate already confirmed:
One wrinkle: the nameplate and portrait legitimately use Ken's colours and score a perfect 1.00. So crops repeating in more than 30% of frames get excluded as HUD first. Three did.
P1 kicks are on MAME port :EXTRA, not :INPUTS. Resolving field names against :INPUTS
alone returns nil silently — no error, no warning, the button simply never presses. Every
kick-based move captured Ken standing still. The harness now resolves across all ports and
prints MISSING INPUT if a name doesn't bind.
Order your script so the character isn't cornered. With walk_fwd first, all 18 of its
frames deduplicated to idle poses — Ken was against the wall and holding forward did nothing.
Walking backward first took that from 0 to 16 distinct poses.

What this is not
- Nothing is in a ROM. Not one byte. The CPS-2 side — quantizing to 4bpp, encoding tiles, seating them in expanded graphics ROM, rewiring Gambit's animation chains — is all ahead.
- These counts are a floor, not a census. They're the frames one scripted match against a CPU
opponent happened to produce.
hadoukenyields two poses because the motion often didn't come out. Several knockdown frames are there because Yun kept hitting me. - The whole-frame composite is unproven. The gate measures per-object agreement. Compositing a full frame needs sprite z-order, alpha blending, and background tilemaps — none of which are implemented, because none are needed to extract a character.
The extractor deduplicates on frame hash, so merging repeated sweeps is safe and purely additive. Full coverage is a matter of running it more, ideally against a passive opponent.
The recipe, if you want to do this yourself
This generalizes to any CPS-3 character, and most of it to any system MAME supports. The order matters more than the specifics.
1. Find the hardware's format, not the data's apparent format. Read the emulator driver.
draw_sprites in the platform's .cpp is the actual specification, and it's free. Don't
pattern-match structure out of ROM bytes — bytes will always eventually look structured.
2. Decide what would prove you wrong, before you start. Exact equality against the emulator's own output over a large pixel count, always with a deliberately-wrong control alongside. If your metric can't distinguish your answer from a wrong answer, it isn't a metric.
3. Capture from a real gameplay moment, and make the harness verify that itself. Don't trust a frame counter. Mine dumps only when the live sprite list is genuinely rich — ≥150 tiles across ≥12 distinct tile numbers. A harness that can't tell a menu from a match will hand you a menu.
4. Expect the byte-order trap. Any time an emulator stores memory at one width and reads it at another, a host-endianness mismatch is waiting. It produces recognizable, entirely wrong output. Test both orders early; it costs one line.
5. Skip the tilemap draw commands. On CPS-3 that's xsize == 0. Most sprite hardware has an
equivalent. This is what gives you clean, background-free art with no image processing at all.
6. Identify your character by palette content, not by index or palette bank. Take the exact colour set from one confirmed frame and score candidates against it. Exclude static overlays first — the HUD legitimately shares your character's colours.
7. Capture sparse. Only what each frame references: object list, sub-entry ranges, referenced tiles. 276 frames fit in 9 MB. Full char RAM dumps would have been over two gigabytes.
There's a full decompilation of the PS2 Anniversary build of 3rd Strike, and it's genuinely
useful — it gives Capcom's own names for the character data model: ten typed animation tables
(nmca normal, dmca damage, atca attack, saca super arts, …), six hitbox tables, and a
125-opcode animation VM.
It contributed nothing to the work above, and it's worth saying why. It has no pixel data — the character tables are filled from data files at runtime. And its structures don't transfer to the arcade binary: I tried three ways (a byte-signature search, an animation-table scan, and a function-pointer-table fingerprint) and all three came back empty, or came back with degenerate matches that collapsed the moment I required the entries to be distinct. The decomp is authoritative for shapes and names, never for bytes or addresses. It'll matter in part two, when there are live traces to label.
Next
Part two is the conversion: 8bpp CPS-3 art down to CPS-2's 4bpp, 16-color rows, into expanded graphics ROM — with the same standard applied. A round-trip that reassembles pixel-for-pixel, or it doesn't ship. Then the harder half, which is that a fighting game character isn't a pile of sprites. It's animation chains, hitboxes, and state, running on someone else's engine.
Ken's still not in Marvel vs. Capcom. But for the first time on this project, what's on disk is actually his.

