case studyUpdated Aug 26, 202619 min read$24

Magneto Was Fighting in MvC. The Debug Menu Couldn't See Him.

His fight tree and Captain America's editor tree told different stories. The missing-animation mystery, the real tile bill, and a gameplay reel that still isn't full parity.

cps2reverse-engineeringxmvsfmarvel-vs-capcommagneto
CPS-2 Reverse EngineeringPart 26 of 43
Browse all writing
On this page

The July costing was a plan that said nothing Magneto-related ran. That sentence is retired. Magneto fights in Marvel vs. Capcom from ordinary P1 controls. The costing arithmetic is still true. The eviction prediction is not. There is one Magneto article, and this is it.

This is the unified record: the 15,304-tile bill, the chair we actually took, the two trees that made HIT EDITER look empty, and the nested dump that closed the source-to-destination name table at 100%.

From a costed plan to a closed editor oracle

9 STOPS · 1 CORRECTED · 1 RETRACTIONS · 1 OPEN
  1. largest free bank  2,542
    Magneto demand     15,304
    shortfall          ~6×

    XvSF tree walk packing flush against the next character; per-bank free recount

    01 / 9measured22 July 2026

    Cost the import before building it

    15,304 tilesagainst 2,542 in the largest free bank

    Striker frames in MvC are real trivia and a useless budget lever. The fighter VM cannot reach them. Extraction was a corrupted dump, not ciphertext.

    COVERAGE 10% OF THE ANIMATION TREE

    Read this section
  2. plan:  evict $06+$26
    ship:  Cap $04 + MAG_TREE
    Hulk family → Dual Rogue

    July costing, now this page

    02 / 9correctedthe plan at publication

    Predict Hulk-family eviction or 64MB expansion

    two chairsthe prediction that did not ship

    Bank 2 after deleting Hulk $06 and Orange Hulk $26 was the only stock bank that fit. Expansion was the offline alternative. Magneto did not take either road as written.

    COVERAGE 10% OF THE ANIMATION TREE

    Retracted here
    Magneto must evict Hulk, or nothing runs
    Read this section
  3. Magneto idle in Marvel vs. Capcom against Chun-Li

    mvsc_magneto_xmvsf_direct1_identity_devmenu.zip

    03 / 9measureddirect1 identity

    Seat on Captain America instead

    id $04Cap's row, MAG_TREE in fight

    The public grid cell, the HUD word MAGNETO, and the live body use imported art. Select and team keep Cap's animation tree at $107FF0 so presentation does not inherit a relocated fighter tree.

    COVERAGE 40% OF THE ANIMATION TREE

    Read this section
  4. 7/7 named segments
    P1 writes  action/anim/pos = 0
    Hyper Grav still open

    composed ROM md5 4279ea69bb585ad92dce98319082490b; clip SHA-256 1cd6c47d…

    04 / 9measured14 August 2026

    Scoped gameplay reel

    59.53 sseven named segments, zero invalid pointers

    Ordinary P1 controls. The harness grants meter twice and stabilizes the defender. It does not write Magneto's action, animation, or position. Standing HK, Shockwave, and shared audio still mismatch the source.

    COVERAGE 55% OF THE ANIMATION TREE

    Read this section
  5. Cap CATCH depth     6
    Magneto CATCH depth 9
    wrap = zero terminator

    DIR_ANIM[$04]=$107FF0 byte-identical to vanilla Cap slot counts

    05 / 9measureddestination dumps on the identity zip

    HIT EDITER wrap is donor slot count

    Cap CATCH 6Magneto CATCH 9; extra Right taps stay on slot 5

    The name table already has CATCH 06. The overlay never reaches it because Cap's slot table hits a zero terminator first. That is not a skipped dump frame.

    COVERAGE 70% OF THE ANIMATION TREE

    Read this section
  6. HIT EDITER showing Magneto PIYO SLEEP at chain $58823C

    mvsc_magneto_magtree_editor_dump.zip md5 3300c58163e358c193680195ab969466

    06 / 9measurededitor-only zip, not shipped

    Disposable magtree dump prints the holes

    11 cellsPIYO SLEEP through FLY FRONT END on screen

    Point DIR_ANIM[$04] at $588000, skip with Up, dump. Named-row OCR can FAIL_CAPTURE (AMERICA vs MAGNETO) and the table is still the right one. Do not ship this zip: select and team need Cap's tree.

    COVERAGE 90% OF THE ANIMATION TREE

    Read this section
  7. source 27 FLY  → dest 28
    source 26 ETC  → dest 26 then 29
    slot_rate 1.0

    MAGNETO_L1_TO_MVC.json; ORACLE_SCOREBOARD.json

    07 / 9measuredsecret_menu_l1_join_v5

    Join v5 closes names and slots

    186 / 186HIT source chains onto dest cells

    Stock-shape map put XvSF FLY at MvC 30. Editors print FLY at L1 28. Join tries mapped L1, then the editor L1 that actually holds the label. Magneto dest compact n=268.

    COVERAGE 95% OF THE ANIMATION TREE

    Read this section
  8. SMOKE_STATUS PASS_BOUNDED_LIVE_IDENTITY_SMOKE_V18
    candidate SHA-256 0c6b9bd6…
    receipt SHA-256 87d6fc55…

    storm_magneto_identity_live_chain_smoke_runtime_v18_0c6b9bd6_20260817

    08 / 9measured17 August 2026

    Bounded identity smoke V18

    2 / 2P1 LP Magneto and Storm

    Both cases PASS, failure_count=0. Aggregate 42 checks. Byte-identical to the sealed V17 logs. Smoke only: not P2, not five other palette buttons, not gameplay. Release flags stay false.

    COVERAGE 98% OF THE ANIMATION TREE

    Read this section
  9. P01 editor  186/186
    P02 damage  0 lanes
    throws/tags/V-Cross open

    behaviour-gate-harness; gameplay receipt SHA-256 d81aac5e…

    09 / 9openstill open

    Full XvSF parity

    openHyper Grav, throws, tags, V-Cross, story

    A coherent reel is not a source-exact character. The behaviour gate still refuses a damage number with no source expected value. P01 editor coverage is not P02 live behaviour.

    COVERAGE 100% OF THE ANIMATION TREE

    Read this section
Stops are measured stages, not a time axis.

What Magneto actually costs

I walked Magneto's animation tree in X-Men vs. Street Fighter the same way the Shuma port walked his: expand each multi-tile sprite segment by the CPS-2 OBJ rule, sum unique tiles. The walk self-validates. Each character's tree packs flush against the next character's tree start, so an off-by-anything shows up as overrun.

Magneto is 15,304 distinct tiles. Among the X-Men in XvSF he sits near the top — heavier than Storm (12,377), Cyclops (13,364), Wolverine (13,417) and Rogue (14,359), just under Gambit (15,541), with only Juggernaut (21,219) clearly above him.

CPS-2's sprite word carries a 16-bit tile code. MvC's GFX region is four 64K-tile banks. The two high bits that pick the bank come from object field $47, written once at fighter init from $E67C8. Nothing during a match changes it. Every frame a character will ever draw must live in one 64K window.

Tile appetite against the bank that matters
  • Largest stock free bank2,542tiles

    bank 1; the 5,649 free-pool total is four non-fungible puddles

  • Storm12,377tiles
  • Rogue14,359tiles
  • Magneto15,304tiles

    measured, not estimated

  • Gambit15,541tiles
  • Juggernaut21,219tiles
XvSF tree walks. Magneto is six times the largest free stock bank.

Stock free by bank: 1,841 / 2,542 / 609 / 657. The 1,841 in bank 0 cannot pool with the 2,542 in bank 1. Full Magneto does not fit stock without eviction or expansion. That arithmetic did not change when he started moving.

The seductive version of this story was the Special Partner striker. Magneto is in that rotation. A read-tap on $0E4084 through $0E40B5 catches the RNG. The fighter engine still cannot reach those tiles: the only animation-tree pointer table the fighter VM uses is the 23-entry table at $0E676C. Content-matching XvSF tiles against all 262,144 MvC tiles rendered shared FX — hatching, lightning fragments — not Magneto's body. Net-new tiles come out at ~99% of the full set. Keep the striker in the narrative column.

An older note called the XvSF dump "opcode-only, data ciphertext." It was a corrupted xmvsfu.zip. Intact xmvsfj.zip (md5 3d424538843cfce0e03b96d73277f60b) plus the decrypted opcode view of the US set is the working pair. Magneto is row 7, internal id $0E. An earlier revision called him slot 17 / id $22 because it attached names to an inferred roster order. The pointer walk superseded that label.

The July prediction was wrong in a useful way

The plan said: evict Hulk $06 and Orange Hulk $26 into bank 2 (~17,037 tiles after the family, ~1,700 of slack), or take the 64MB expansion and lose Fightcade netplay. Captain Commando's bank 3 was short by ~1,150. Orange Hulk reuses 100% of Hulk's tiles, so deleting Hulk alone frees literally zero.

What shipped: Magneto on Captain America $04, fight art in the expansion band, DIR_ANIM left on Cap so select and team stay native. The Hulk family is now Dual Rogue's chair — the same two-slot fact, spent on the character I actually wanted there. Dual Rogue’s fight 68K lives in CLEAN $399816. Hulk’s window $02F6FC is parked for versus init. Magneto's fight 68K lives at MAG_TREE $588000. Those are two different answers to "where does the guest's brain go." Dual Rogue does not take Magneto's expansion path.

eviction prediction vs seating

The bank-2 family the plan reserved for Magneto

MagnetoslackRogue new slotsarmor pin leftover
  1. July plan (not shipped)17,037 tiles
  2. August Dual Rogue (shipped on Hulk)16,427 tiles
Hulk+OH exclusive bank 2 is 16,427 tiles. Magneto did not consume it. Rogue did, with 2,276 leftover pinned as Shadow Rogue armor. Dual Rogue zip md5 a6c411ce0e9c0f35ac41a1b03bdb1fe7. Playtest still crashes, glitches, and can become Ryu.

The prediction was useful because it named the constraint: one fighter, one bank, and a palette-pair that only opens when both ids leave. It was wrong about who spends that opening.

Two trees, one id

DIR_ANIM at $0E676C is a 23-entry table. Direct1 forces the Cap row back after relocating Magneto's fight data, because select, VS, and partner bench read that table and Cap's presentation is the native contract we kept.

Magneto's two trees
AB$0E0000$590000
  • 1
    DIR_ANIM
    $0E676C$0E679A · 46 B
    23 fighter tree pointers
  • 2
    Cap editor/select
    $107FF0$10E000 · 24.0 KB
    DIR_ANIM[$04]; HIT/CATCH EDITER; vanilla Cap slot counts
  • 3
    MAG_TREE fight
    $588000$590000 · 32 KB
    relocated XvSF Magneto; PIYO 2, CATCH 9, PLAYER 7, FLY 18
  • A
    Cap $107FF0
    $107FF0 · marker
  • B
    MAG_TREE $588000
    $588000 · marker
HIT EDITER dereferences DIR_ANIM. Fight code runs MAG_TREE. Extra Right taps on Cap CATCH stay on the last live slot ($0010CB02).

Source XvSF Magneto tree $0C6D74: PIYO 2, CATCH 9, PLAYER 7, FLY at L1 27 (Char Gp $36) depth 18. Cap editor window: PIYO 1, CATCH 6, PLAYER 6, FLY at L1 28 (Char Gp $38) depth 6. Vanilla scale: Jin CATCH is 21 deep (Storm dest can show CATCH 06); Ryu FLY L1 28 is 18; Cap FLY is 6.

Fight code already remaps flight $36 → $38. HIT EDITER does not. Pointing a disposable editor zip at MAG_TREE and walking L1 27 prints TWIN names. Sharing Magneto's 18-slot flight table onto L1 28 is what makes Char Gp $38 print FLY.

The MvC name table at $0E1308 already contains the hole strings (CATCH 06 $08E, FLY DOWN $12E, …). Overlay is family_start(Char Gp) + tree_sub. Wrap is a zero terminator in the slot table, not a skipped dump frame.

The editor cannot see the fight tree

Destination dumps on the identity zip — the one that must remain playable — walk Cap's window. Extra Right taps after CATCH 05 stay on the last live slot. Compact named tables skip mode: unknown, so PIYO SLEEP and some FLY rows lived only in all_screens until we merged them.

Named-row FAIL_CAPTURE is a label check (accept_label MAGNETO vs OCR AMERICA on the character-select line of HIT EDITER). It does not mean the table is bad. Bind still runs if receipt.json exists. Magneto's dest id $04 was correct on those FAIL_CAPTURE runs.

This is why a 100% source walk and a dest dump on the ship zip disagreed. The disagreement was geometry, not a missing import.

How the holes got onto the screen

Disposable zip mvsc_magneto_magtree_editor_dump.zip (md5 3300c58163e358c193680195ab969466):

  • DIR_ANIM[$04] = $588000
  • L1 28 root word pointed at Magneto's 18-slot flight table so Char Gp $38 prints FLY
  • Group axis Up (Down from $00 is a no-op on this dest)
  • GROUP_SKIP plus SUB_STEPS; skip to the group, do not sweep many groups after a Right-sub walk

Live tree_base verified $00588000. Do not ship magtree editor zips. Select and team need Cap's tree.

PIYO SLEEP is a MAG_TREE cell, not a Cap cellDisposable DIR_ANIM retarget. Identity zip still points this group at Cap.CONFIRMED IN GAME
HIT EDITER on the magtree dump zip: Magneto in PIYO SLEEP, CG_Ptr $58823C
editor label02 MAGNETO
statePIYO SLEEP
CG_Ptr$0058823C
tree_base$00588000
identity zip DIR_ANIM[$04]$00107FF0 (Cap)

Named-row OCR on this dump is FAIL_CAPTURE (AMERICA vs MAGNETO). The HIT labels and the chain pointer are the proof. This zip is a dump instrument.

destination_magtree_holes/g000_s001.png · md5 3300c58163e358c193680195ab969466

FLY DOWN at Char Gp $38 after sharing the 18-slot tableFight code already remaps $36 to $38. HIT EDITER needed the table pointed here.CONFIRMED IN GAME
HIT EDITER magtree fly38 dump: Magneto hovering, FLY DOWN at CG_Ptr $58C89C
CG_Ptr$0058C89C
Char Gp on screen$36 TWIN until L1 28 shares the table
FLY DOWN chain$58C89C
source FLY depth18 slots
Cap FLY depth6 slots

L1 27 on MAG_TREE is flight geometry labelled TWIN by MvC's name family. L1 28 is the FLY vocabulary. Join v5 prefers dest 28 for source 27.

destination_magtree_fly38 · MAGNETO_DEST_WRAP_HOLES.json v2

Example MAG_TREE chains now in MAGNETO_DEST_NAME_TO_CHAIN.json (n=268): PIYO SLEEP $58823C, CATCH 06 $58E6A4, PLAYER 06 $58CB9C, FLY DOWN $58C89C.

Storm has the sibling pattern: dest DIR_ANIM Jin row already is STORM_TREE $508000. A disposable mvsc_storm_fly38_editor_dump.zip shared L1 27's 18-slot table onto L1 28 to print FLY FRONT DOWN END at $38/$1E. Same rule: skip to the group; Jin/Storm leave HIT EDIT if you Up across many groups after a Right-sub walk.

Join v5 and the scoreboard

The harness post is the tool log. The Magneto-specific result is:

ANIMATION_GROUP_MAP places XvSF 26 ETC at MvC 29 and XvSF 27 FLY/TWIN at MvC 30 because those are the last two live stock groups. Editors actually show ETC at 26, TWIN at 27, FLY at 28. Join v5 (dest_slot_candidates) tries mapped L1, then the editor L1s. For source 27 it prefers dest 28 before 27.

That closed Magneto FLY slots without pretending dest L1 30 exists. GETUP can family-match across names (GETUP BACK vs GETUP FRONT MIDDLE at the same mapped slot). CATCH 06 is not CATCH 00.

secret-menu join v5

Five fighters, names and slots closed

verified means the dest cell exists at the resolved (L1, sub) and the name agrees, families, or is found elsewhere. This is P01 editor geometry, not live damage.

nativeverifiedbridgeddonorpending
routeHIT sourcedest cellsnamesslots
Magnetoverified186verified268verifiedverified
Stormverified183verified307verifiedverified
Rogueverified196verified197verifiedverified
Shumaverified169verified169verifiedverified
Dark Sakuraverified223verified223verifiedverified
ORACLE_SCOREBOARD.json. import_rate = name_agree + family + elsewhere + diverge. slot_rate = dest cell at resolved coordinate. Behaviour lanes are a different gate.

Scoped gameplay, not parity

clip · loops · audio available
COMPOSED ROM md5 4279ea69bb585ad92dce98319082490b // Magneto's seven named segments share this reel with Storm. Harness grants meter twice and stabilizes the defender. Not full XvSF parity.
The live body is Magneto. The remaining matrix is not.Identity path on id $04. Standing HK, Shockwave, and shared action $0122 still mismatch the source.PARTIAL
Magneto idle in Marvel vs. Capcom, HUD word MAGNETO, Cable as partner, Chun-Li jumping in
composed ROM md54279ea69bb585ad92dce98319082490b
Magneto clip SHA-2561cd6c47d43a77dac1b2e0109273cdd02d44efe8982ebeda616addb449786f1ed
receipt SHA-256d81aac5e8d11029becfd948001c0200ef50588d9906d6c60adeb225d7b863551
P1 writesaction / animation / position = 0
openHyper Grav, throws, tags, V-Cross, story/AI

MAG-16 audio pack passes 8/8 exact source sequence and PCM comparisons plus reconstructed 68000 and Z80 transport. The gameplay clip is not by itself a heard-audio oracle for every shared action.

August 14 composed candidate · same artifact as the Storm capture

Foundation that did not move: bounded source walk of 1,801 animation records, 978 framedata blocks, 837 tile rows, 15,304 distinct tiles, zero unresolved references. Box-shape, hitbox, move-attribute, and palette roots located. Eighteen normal routes resolve live imported collision rows with 2,256 exact comparisons and zero mismatches — data-path proof, not proof that all 18 attacks behave like XvSF.

The August 16 identity smoke printed PASS identity/MAGNETO/P1/LP with failure_count = 0 from boot through natural selection, VS, HUD, meter, and hyper cut-in, and then failed Storm on two expectations the instrument had learned from Magneto's route. V18, sealed 17 August on candidate SHA-256 0c6b9bd65001201c3ba61d92992dac8308e10ee31bf35842e8c3a6d4f137b0b1, is the first end-to-end pass: Magneto and Storm P1/LP both PASS (failure_count = 0), the aggregate scorer ran 42 checks, and both acceptance logs are byte-identical to the sealed V17 run. That is two natural identity paths, one palette button, no P2, no gameplay. The candidate is not accepted and not release-eligible. Receipt SHA-256 87d6fc55d5c7aa619fc3f5d1e04fc34dd32315acf91dec2e42feee4a16f31416.

Select: source-derived pose, native Marvel Super Heroes Magneto wordmark, lifebar MAGNETO. The bench body is a private 73-record animation chain, not the stock mechanism the first harness assumed. Details of that measurement stay in the August 15 section of the git history of this file; the live contract is the latch frames already published (bench, entrance zoom).

What complete still means

Every move must show Magneto's source animation length, reach the intended state, carry its own hurt/attack boxes and move properties, produce the correct child objects and sound, work facing both directions, and survive a normal match. Capcom's secret editor is an instrument. Gameplay remains the authority. The behaviour gate still fails closed when a damage number has no source-derived expected value.

The July costing is the first half of this page. The old /blog/magneto-xsf-import-prep URL redirects here so inbound links do not 404. Read Dual Rogue for where Hulk's family actually went: public $06, secret $26 with Shadow Lady navy, CLEAN 68K at $399816, Power Drain live.

Editor's update — August 12, 2026: the Ryu voices were a numbering collision, and I can prove it

Live play of the current candidate produced a precise complaint: standing HP and crouching HP shout Ryu's Shoryuken, standing HK and crouching HK play Ryu's Hadouken, and the punch super layers a Hadouken underneath Magneto's own audio. That pattern — heavy normals and one super, nothing else — turned out to be the whole diagnosis.

Every CPS-2 fighter dispatches character audio the same way. The routine at $0D5EF0 reads the character id, indexes a 23-entry offset table at $0D5F30, and lands on that character's sound row: a fixed 128-byte block of 32 four-byte slots mapping animation slot to sound-command id. X-Men vs. Street Fighter has the identical structure at $079462. And both games hand every character a contiguous block of 32 ids.

XvSF Magneto's block is $260–$27F. In Marvel vs. Capcom, that is Ryu's block.

Two lanes of sound-id blocks; Magneto's XvSF block occupies exactly the same id range as Ryu's MvC block, with five leaked ids marked
Magneto's id block in XvSF is Ryu's id block in MvC. Fourteen ids were translated; the five that were not went straight to Ryu.

The expanded build translated fourteen of Magneto's ids into private aliases. Cross-referencing his row against MvC's ownership map shows exactly which ones were left behind:

untranslated idwhat it plays in MvC
$269 $26A $26E $27B $27CRyu
$133Captain Commando / Chun-Li
$147Venom / Spider-Man

Five untranslated Ryu ids, and the reported symptoms are the heavy rows and the super. Nothing was mixing wrong; the ids were simply pointing at their rightful owner.

The stock-layout build removes the failure mode instead of patching it. Because each character owns a block and a fixed-size row, Magneto's sequences go into Cap's own block ($340–$35F: 32 slots, 12 used by Cap, 20 unused) and Cap's row is rewritten in place at $0D608C. The row is fixed size and lives in the code region, so it costs zero allocation — and nothing Magneto posts can leave his own block. No private aliases, no Z80 hook, no tagged packets.

That converts the acceptance gate from "play every move and listen" into three assertions on the emitted ROM: every id in Cap's row lies in $340–$35F or in an audited translation list; the same for Jin's row; and no id in $260–$27F is reachable from either. The five reported rows stay as live regression cases, and Ryu's own audio must remain byte-identical.

Sizing it: Magneto needs 124,719 bytes of sample data, Storm 97,516. Removing both donors frees 435,199 bytes of exclusive PCM plus 66,126 bytes of trailing padding. The extraction was checked against this post's own earlier build — the 14 pinned actions reproduce 14/14 on sequence addresses, sequence hashes and PCM hashes — which took finding two places where the two games' sound drivers genuinely differ: XvSF's instrument pointer table starts at header+4 (the header's third word is the first entry, not the table), and XvSF note records are 8 bytes wide where MvC's are 4.

Three other things came out of the same measurement pass:

  • A boot ROM checksum. $000236 samples one byte every $FFF across all eight banks; the expected sums are stored at $0000D0 in the unused vector area and match the canonical image exactly. 63 sample points fall inside the free space this build spends, so recomputing that table is now a required build step.
  • 59 roster-indexed tables. Found by an alias fingerprint — the alternate characters always point at their base character's row — every one has a row 2 (Cap) and row 12 (Jin) the port must own. A row left untouched renders the donor's identity, which is precisely the still-Captain-America cut-in, the VS portrait, the missing cape and the roster icon.
  • The palette surface is still not located. It is not among those 59 tables; the ones that looked palette-shaped are (id, x, y, flags) spawn records. A whole-image scan found 162 palette-shaped groups in 15 runs, far short of a 23-character roster, so the bulk is reached another way. That is the weakest link in the current measurement pass and it is being hunted with a live probe rather than guessed at.

Magneto's graphics line remains the one thing holding the allocation ledger open: he needs 14,789 tile slots in his bank against 13,294 freed, with 2,075 candidate slots identified and one reference-universe proof outstanding. Storm, by contrast, fits outright.

Written by Daniel Plas Rivera · 4,265 words · $24

ShareXLinkedIn