Part 1 ended with 133 clean Ken frames on disk and a promise: nothing was in a ROM yet. This part puts one of them in.
Ken's 3rd Strike idle stance now renders inside Marvel vs. Capcom, in Gambit's slot, on a real CPS-2 build running on the expanded core. He is standing there. He does not yet do anything else — and I'll be precise about that at the end, because the gap between "renders" and "is a character" is most of the remaining work.

Five writes
The whole build is five writes, and that is deliberate. The version of this project I inherited scrambled the game partly because it wrote things it had guessed — including 4-byte pointers into a name table whose entry width the builder's own comment admitted it did not know.
Everything here is anchored. Each address was read back out of the stock ROM and compared before a single byte went in:
$0E676C[5] -> $1195EE Gambit's animation tree (verified)
$0E67C8[5] == $0006 graphics bank word (verified)
$1C7268[5] -> $1C84C4 live palette blob (verified)
$119846 idle chain, first record (expect-checked)That last number is the real cost of the format change. CPS-3 gives 8bpp art; CPS-2 sprites get fifteen colours plus transparency. Ken's idle uses more than fifteen, so 18% of his pixels land on a near neighbour rather than their own colour. That's not a bug, it's the tax, and it should be reported rather than hidden.
One CPS-2 quirk worth carrying if you ever touch this hardware: sprites treat pen 15 as transparent, not pen 0 like almost every other format. Get it backwards and your character becomes a solid silhouette.
The palette bug that deletes red
The first build came out of the verifier looking like this — correct geometry, correct transparency, every tile in the right place, and completely the wrong colours:
The packing function is three shifts:
return ((r >> 4) << 8) | ((g >> 4) << 4) | (b >> 4)The colours arrive from the quantizer as a numpy uint8 array. So r >> 4 is a uint8,
and uint8 << 8 doesn't widen — it overflows to zero. Every red nibble was being silently
discarded, in a function with no error, no warning, and a perfectly reasonable-looking
implementation. Ken came out green because his palette had R=0 in all fifteen entries.
I found this before booting anything, because the builder renders the sprite back out of the zip it just wrote — decoding the seated tiles, walking the parked list, and reading the palette from ROM. That render is the gate. If it doesn't show Ken, the emulator is not going to either, and debugging it in an emulator would have meant a five-minute round trip per attempt instead of five seconds.
The second palette bug: right transparency, wrong everything
With red restored, the offline verifier showed a perfect Ken. In game he came out like this — legs and belt roughly right, torso a mess:
It would have been easy to blame the 15-colour budget, or to guess that a second sprite was drawing on top of him. Instead: flatten Ken's entire palette row to one impossible colour, rebuild, and count.
Every pixel of that silhouette is drawn by my tiles through my palette row. Nothing is overlapping him, and the row is reaching the hardware. So the geometry was right, the palette was live, and the colours were still wrong — which leaves exactly one thing: the mapping from pen index to palette slot.
CPS-2 sprite hardware addresses the row with the 4-bit-reversed pen index relative to the codec's decode domain:
0 8 4 12 2 10 6 14 1 9 5 13 3 11 7 15That permutation is an involution, and 15 maps to itself — which is why transparency looked
perfect the whole time while every visible colour was scrambled. Writing each colour to
PEN_PERM[d] instead of d fixed it in one pass.
Three plausible stories fit the symptom — quantization loss, an overlapping sprite, a dead palette copy. A flat diagnostic palette distinguishes all three in one run, because it turns a question about colour into a question about pixel count, which is not a matter of opinion.
The bank redirect I had to give up on
Gambit's tiles come from graphics bank 3. The project's allocation ledger had a plan for Ken: point Gambit's bank word at bank 6, out in the expanded graphics ROM, and seat Ken there with room to grow.
I tried it. To avoid blanking every non-idle animation, I first copied the whole of Gambit's stock bank 3 into bank 6, then overwrote only the tiles Ken's idle needed. Careful, and wrong:

So I dropped it. Ken's tiles now go into Gambit's own bank 3, near the top, and the bank word is never touched. That removes an unknown instead of building on top of it. Working out the real expansion bank mapping is its own job, and it belongs to the part of this project where Ken needs more than 32 tiles — not to the part where he needs to stand up.
The harness refused two wrong screenshots
To capture him I need the emulator to reach a match with Gambit selected. Every shot asserts
P1's live fighter id at $FF3053 is $0A, and voids the trial otherwise.
It voided twice, and both were real.
First: the identity check reported P1 id=08 not 0A. The select-screen timer had run out and
auto-started the match on whatever cell the cursor happened to be on. Without that assertion I'd
have had a screenshot of a character that wasn't Gambit, on a build I would then have believed
was working.
Second: the cursor genuinely could not reach Gambit in time. Logging every cell the cursor
visited gave $12, $1A, $20, $18, $06, $08 — Gambit's $0A is in a row the search never got to
before the timer fired.
Writing the id once didn't help either: the game recomputes the hovered id from the cursor
position on the next frame. What works is poking the committed slot at $FF40B0/$FF40B1
every frame through the select screen and the transition into the match. Then:
cursor id $0A (f=523)
committed $0A
=== LIVE, settling 210 frames ===
SHOT 0 id=0A anim=119846 list=507E00anim=119846 is the idle chain this build repointed. list=507E00 is the sprite list it parked.
The character on screen is being drawn by exactly the bytes the builder wrote — that's what makes
the screenshot evidence rather than decoration.

What he still cannot do
This is one animation chain. Being honest about the size of that:
- Only the idle is Ken. Walk, jump, crouch, every attack, every reaction — all still draw
Gambit's art from Gambit's chains. Walking him away from the corner for a better screenshot
put him straight back into Gambit frames, and the capture log shows it (
anim=11B6AA, a chain this build never touched). - No identity. The lifebar says GAMBIT. Worth knowing why that isn't a one-line fix: the
name tables at
$006D9C/$006E5Chold no ASCII at all. Dumping the region shows pointer words into further tables — MvC1 draws lifebar names as tile graphics, so "KEN" means authoring nameplate tiles in the HUD font and seating them, not editing a string. The previous build wrote 4-byte pointers there on a guess, which is part of why it scrambled. - No sound, no behavior, no hitboxes.
- He's a little small. 3rd Strike characters are drawn at a slightly different scale than MvC1's cast. Whether that gets a fixed ratio is a decision for later, once there's enough of him on screen to judge it against Ryu.
Next
The order from here, roughly in the sequence a character becomes real:
- Basic movement — walk forward and back, crouch, jump up, jump forward and back.
- Dashes — forward and back, dash-jump in both directions, and the dash-into-crouch cancels.
- Specials — hadouken and shoryuken.
- Sound — his 3rd Strike voice and SFX.
Plus the nameplate, now that its format is known.
Each of those is a different kind of problem. Movement is more of what this post did: more frames, more lists, more chains. Dashes and cancels are where the animation chain stops being a list of pictures and starts being a state machine with entry conditions. And sound is a whole separate bus.
He's standing. That's one chain out of 245.



