Goal: Train an AI bot that plays Marvel vs. Capcom 1 as a specific human player — alanmargolies88, a beloved Fightcade competitor — closely enough that the FGC community can spar against his style for the first time since he passed. Behavioral cloning, not "a good AI." The bot has to feel like him.
Constraints: Bot lives inside a real CPS-2 emulator (vendor FBNeo), driven from MLX/PyTorch at decision-frame granularity. No retraining loop on live play — pure offline behavioral cloning from his recorded matches. Must run on Apple Silicon at 59.63 fps without dropping audio.
Approach: Mine Alan's 7,554 Fightcade replays into per-frame state→action traces. Train a motion-sequence transformer that predicts what motion he starts next given game state + token history. Layer policy biases on top: passive-pressure trigger (he attacks idle opponents at 14.5% rate), per-matchup tactical overlays (FAQ-derived), Alan's mined combo phrases (his actual button sequences, not hand-coded macros).
Progress so far: 92 traces ingested (1000+ in flight). State-aware multihead model on schema v3 (153 dims: positions, hitstun, hitboxes, super meter, match phase, wall distance). 18 matchup tactical bias dicts covering the full playable cast + alt palettes. Mined combo expansions: super_jump_combo_4hit, dash_combo_7hit, magic_series_5hit — all his actual button timings. Live perception diagnostic to verify the bot reads the world the way the human does.
Open work: State-perception verification (bot sometimes super-jumps when opponent is grounded — the kind of glitch that means a RAM-slot read is off somewhere). Sound restoration after a savestate-load DSP wipe regression. Scaling the trace corpus from 92 to 1000+ replays for variance.
Repo / Artifacts: fightcade4/src/training/motion_policy.py, fightcade4/data/alan_combo_expansions_morrigan.py (his mined button sequences), fightcade4/data/character_knowledge/ (per-character FAQ knowledge, 19 characters), fightcade4/src/training/matchup_tactics.py (per-opponent bias overlays).
Some projects are technical exercises. This one is personal.
alanmargolies88 — Alan — was one of the most distinctive Marvel vs. Capcom 1 players on Fightcade. His main was Morrigan / Ryu, he played P2, he had 7,554 matches on record. Every regular knew his style: the patient air-throw setups, the hit-confirm into magic series, the tag-to-heal habit at low HP that most players never bothered to learn. He didn't rushdown. He punished.
Alan isn't around to play anymore.
This post is about what I'm building so the community can still spar against his style. Not a "good MvC1 AI." Not a "hard CPU." A behavioral clone. The Turing test isn't "can it win" — it's "does this feel like Alan."
Why this is AI-for-good, not AI-for-novelty
There's a category of AI work that gets lumped under "AI for good" — climate models, medical imaging, accessibility. The fighting game community doesn't usually show up on that list. But here's the thing: communities like FGC are cultural archives. They preserve game knowledge, matchup theory, and individual playstyles that don't exist anywhere else. When a player who shaped that culture is gone, the knowledge they embodied goes with them — unless you have the recorded games and the willingness to build something with them.
Behavioral cloning of a beloved player is preservation work. It's the same impulse that drives the Internet Archive, that drives ROM preservation, that drives the people who run private Wayback mirrors of old GeoCities pages. The medium is different — game replays instead of HTML — but the goal is the same: don't let this disappear.
That's the lens I think about this in. AI for cultural preservation. AI for community continuity. AI for the specific people who would otherwise have nobody left to play against in their corner of the world.
What "behavioral clone" actually means here
The model is a motion-sequence transformer. Input: a window of recent game-state vectors (153 dimensions — positions, HP, hitstun frames, hitbox geometry, super meter, character IDs, match phase, wall distance, recent hit deltas) and a window of recent motion tokens. Output: the next motion token Alan would start.
The vocabulary isn't keypresses. It's motions: walk_F, dash_F, super_jump, qcf_p (Soul Fist), dp_pp (super-cancel), magic_series_5hit, super_jump_combo_4hit. A frame-expander then translates each token into the per-frame button mask sequence — using Alan's actual recorded timings, mined from his replays, not hand-coded interpretations of FAQ guides.
# From data/alan_combo_expansions_morrigan.py — Alan's literal button
# sequence for one of his most-used air combos, decoded from a real match.
'super_jump_combo_4hit': [
(D, 1), # crouch
(D + HP, 3), # crouching Fierce → LAUNCHER (this is what he presses)
(D, 1),
(0, 5),
(MP + MK, 2), # call assist mid-combo
(0, 2),
(back, 24),
(HP, 4),
(D, 2),
(D + forward, 3),
(forward + LP, 3),
# ... his actual frame timing, his actual button hold durations
]This is what "behavioral clone" earns you that a hand-coded bot can't. The launcher isn't LP+LK (which is the canonical MvC1 launcher input but which Alan doesn't use). It isn't Standing HP (which the FAQ I just mined says is "the most common launcher"). It's D+HP (crouching fierce punch), held for exactly 3 frames, followed by a 5-frame pause, followed by an assist call. Because that's what he does.
You can't get that from any move list. You have to mine it out of his recorded play.
The progress so far
This has been months of work. The honest summary:
What's working:
- The data pipeline. Fightcade replays → per-frame state→action parquet → trace-native enriched builder (the join cap on quarkids that was capping the corpus at 122 frames per replay is fixed). 92 replays fully ingested. Scaling to 1000+ in the background.
- The motion-sequence model. Trained on schema v3 (the multihead with match-phase one-hot and wall-distance — these matter way more than I initially thought). Per-character training pipeline with opponent conditioning. The model has learned most of Alan's neutral game.
- Combo phrase extraction. Alan's actual combos, mined from his replays, replayable as atomic frame sequences. The bot can perform his exact 4-hit, 7-hit, 8-hit combos with his exact timing.
- Stochastic trigger overrides. Whiff-punish at 35% rate, passive-pressure (when opponent is idle in range) at 18% rate, anti-air DP, tag-to-heal at critical HP. These aren't always-on — they fire at Alan-derived frequencies.
- Per-matchup tactical bias. Mined from 138 MvC1 GameFAQs (re-extracted via a Chrome connector because GameFAQs blocks direct URL access). 18 matchup entries covering every playable character + alt palettes. The bot blocks more vs Wolverine, walks back + fireball-wars vs Mega Man, super-jumps over Onslaught's commit attacks, plays the Morrigan mirror with DP anti-air on Shell Kick dives.
- Live HUD. A custom React/Tailwind in-game overlay showing the bot's current token, top-3 sampled candidates, entropy, and the active gate. So I can watch decisions in real time and debug why the bot did what it did.
What's not yet working:
- State perception in some matches. The bot occasionally super-jumps when the human opponent is grounded — a tell that somewhere in the RAM-slot mapping (or the bot-side flip logic), the position read is misaligned. Added a per-decision diagnostic that prints the bot's perceived
(self_xy, opp_xy, dx, dy, opp_hitstun, opp_hit_active)every ~10 seconds so we can verify the bot sees the world the way the user does. - Audio after savestate load. A regression: when the bot match boots from a canonical savestate, the QSound DSP scratch buffers get wiped by
QscForceNormalState(), leading to silence (or garble on older builds). TheFBN_OPGG_QSND=timeronlymode bypasses the wipe but the SDLcoreaudiodriver needs explicit selection on macOS — just landed this fix. - Engaging idle opponents at long range. The model knows to attack when in range, but at mid-screen distance with the human standing still, it sometimes oscillates between
walk_Fandcrouchwithout committing. Working theory: state-feature dx threshold is too aggressive. Diagnostic will confirm.
What's been disproven along the way:
- The QSound audio garble was not corrupted savestate data. It was the harness single-stepping FBNeo so QSound never generated at wall-clock rate. Realtime mode fixed it.
- The bot's "random attacks at the air" was not the model being broken. It was the policy not suppressing raw normal tokens at distances where they couldn't possibly land. Fixed with a range-aware dampener.
- The launcher was not
LP+LK. The MvC1 manual says that combo "is a launcher input" — but in practice that's the Assist-1 call, and Alan doesn't use it. He usesD+HP. The behavioral clone has to mirror the human, not the manual.
What this teaches about AI imitation
A few things I didn't expect going in:
1. The training data tells you the truth; the documentation lies. I read every MvC1 strategy guide I could find. They all say LP+LK is the launcher. Alan never presses LP+LK as a launcher. He presses D+HP. If I had built the bot from FAQs instead of from his replays, the bot would feel wrong in a way the user might not be able to articulate but would absolutely notice. The data is the source of truth.
2. Frequency matters more than capability. Alan can do every move in the game. So can the bot. What makes the bot feel like him is the rate at which it does each thing. Alan attacks 14.5% of frames vs a passive opponent. He jumps 11%. He blocks 23%. The model needs to match those frequencies, not just the move set. Per-state frequency calibration is the actual lever.
3. Behavioral cloning is a humility exercise. Every time I think "the bot should also do X because that's obviously correct," I have to check his replays. Half the time he doesn't do X. Sometimes he does the opposite. The discipline is to write what the data says, not what I think the player should do.
4. The community knows immediately. I showed early bot footage to some FGC people. The first comment was "that's not how Alan would have played the corner." Not "the AI is bad." That's not how Alan would have played the corner. The Turing test is real and the testers are merciless.
The path forward
The remaining work, ranked:
- Verify state perception. The per-decision diagnostic ships in the next match. If the bot's view of opponent position doesn't match the human's actual on-screen position, that's the root cause of the air-swinging behavior and fixing it unblocks everything downstream.
- Scale the corpus. 92 → 1000+ replays. More variance in the training data → better generalization across matchups Alan didn't play frequently.
- Engage-on-idle calibration. The passive-pressure trigger needs its dx threshold loosened so the bot commits to closing distance when the human is standing still. Currently the bot reads
dx > 0.40as "too far to commit" and Alan would be in range to dash-combo by then. - Eval harness. A standardized rubric — "does this match feel like Alan?" — with 20 questions a human judge answers per match. Until I can quantify the Turing-test gap, every improvement is anecdotal.
On AI for good
I want to close with the thing that motivated me to write this up.
A lot of AI for good is framed around large-scale impact: predicting wildfires, screening for diseases, optimizing food distribution. Those matter. But there's also a smaller scale of good that I think gets underweighted in the discourse: making something a specific community of people actually wants, that nobody else is going to make for them, that honors someone who can't be honored any other way.
The FGC isn't going to get a memorial bot from OpenAI. It's not going to come out of an Anthropic research lab. If anyone is going to build a behavioral clone of alanmargolies88 — or of the other players the community has lost — it's the people inside the community who knew them and who have the technical chops to do it.
That's a kind of AI work I think is worth doing. And I think there should be more of it.
If you're a developer who plays — or played — a competitive game where someone you cared about is no longer at the controls, and you want to talk through how this kind of project gets built: my DMs are open. The infrastructure I'm building is open-source. The training pipeline generalizes to any fighting game with recorded matches. None of this is proprietary.
The goal isn't to commercialize a tool. The goal is for nobody who was important to a community to have to disappear from it completely just because they're gone.
That's the project.
— Built with FBNeo, MLX, PyTorch, Next.js. Tested against Fightcade replay corpus. Validated by the FGC who knew him.