- 7,554
- recorded matches — the only human data
- 153→266
- state dims across 10 schema phases
- 256
- VQ codebook entries
- 81 pages
- community RE paper adopted
- 8 weeks
- trained on one misread byte
- ~34
- arXiv papers folded in
- 27
- bugs confirmed + fixed
- ~50%
- honest win rate vs arcade CPU
Some projects are technical exercises. This one is personal.
alanmargolies88 — Alan — was one of the most distinctive Marvel vs. Capcom 1 players on Fightcade. Morrigan/Ryu, always P2, 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 is the complete story of building something so the community can still spar against his style. Not a "good MvC1 AI." A behavioral clone — then, when imitation hit its ceiling, a reinforcement learner with his style bolted into the objective itself.
The Turing test isn't "can it win" — it's "does this feel like Alan."
Chapter 1 — The data is the player
The model's vocabulary was never keypresses. It's motions: walk_F,
super_jump, qcf_p (Soul Fist), magic_series_5hit. A frame-expander
translates each token into per-frame button masks using Alan's actual
recorded timings, mined from his replays — not hand-coded interpretations of
strategy guides.
This is what "behavioral clone" earns you that a scripted bot can't:
# Alan's literal button sequence for one of his most-used air combos,
# decoded from a real match. Note the launcher:
'super_jump_combo_4hit': [
(D, 1), # crouch
(D + HP, 3), # crouching Fierce → LAUNCHER
(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),
]Every strategy guide says the MvC1 launcher is LP+LK, or standing Fierce.
Alan presses crouching D+HP, held exactly 3 frames, then calls an assist
mid-combo. If I had built this bot from FAQs, it would feel wrong in a way
players couldn't articulate but would absolutely notice.
The training data tells you the truth; the documentation lies.
Frequency turned out to matter more than capability. Alan attacks 14.5% of frames against a passive opponent, jumps 11%, blocks 23%. Any bot can do every move; what makes it feel like him is the rate at which it chooses each one. And the community enforces this mercilessly — the first feedback on early footage wasn't "the AI is bad," it was "that's not how Alan would have played the corner."
Chapter 2 — Teaching the bot to see
The perception system went through ten schema revisions — box geometry, partner state, projectile pools, camera correction, signed positions — and the breakthrough wasn't a probe session. It was an 81-page community reverse-engineering paper for the exact ROM revision Fightcade ships, documenting 27 per-player struct offsets and 432 instructions that read the character identifier.
Cross-referencing it against the bot's RAM map found four channels the bot had never seen — including one genuine bug:
-- schema v5 had read 0xFF30BA as a one-byte "jump status" flag
p1_jump_status = read_u8(0xFF30BA)
-- the paper documents +$BA as a SIGNED WORD: the engine's own
-- per-frame X-distance to the active opponent
p1_enemy_dist_x = read_s16(0xFF30BA) Eight weeks of training runs had consumed the high byte of a signed
enemy-distance word as if it were a status enum — positive distances ≥ 0x80
sign-flipped, small positives collapsed to zero. The other finds: a 32-bit
move-class bitfield (armor active, wall-jump capable, post-super state), the
frame-exact chain-cancel mask, and the per-frame animation variant selector.
For every project that touches a community-maintained binary, assume someone has produced a structured reference and go find it before you write another probe.
By the end, perception was a 266-dimension state vector with real hitbox geometry — the bot judging reach the way the engine does, not by eyeballing sprite distance.
Chapter 3 — The ceiling of imitation
Held-out token accuracy looked great. The behavioral-fidelity metrics looked great. Live?
Live, it was a statue with good taste.
Four sessions, four honest verdicts from the person holding the P2 stick: "not Alan at all." Three dissected root causes, each a lesson:
The copycat lock. The decoder conditioned on its own previous chunk token, and the conditioning self-reinforced: token 236 predicted token 236 at 98%, forever. The literature calls it causal confusion — 7,300 replays cannot cover an exponentially growing history space, so the model latches onto the one feature that always correlates: itself.
The button-deletion bug class. Two input-bit layouts differ by a two-bit shift, and a single wrong constant silently stripped the MK and HK buttons from every emitted action. This same class of bug shipped three times in different subsystems. The bot literally could not press a third of its buttons, and no offline metric noticed — the training data and the metrics lived in one layout; the emulator lived in the other.
Open-loop chunks can't block. Committing to 16 frames of pre-planned input is a ~270ms reaction floor in a game where blocking a jump-in gives you 3–8 frames. The fix that transfers: predict 16, execute 8, re-decide — with careful bookkeeping so the conditioning still looks like training.
You can't patch your way from a passive average to a fighter. The policy itself has to change — which means reinforcement learning, with everything the literature says about how RL destroys the thing you're trying to preserve.
Chapter 4 — RL with Alan inside the objective
The naive pivot fails predictably: reward damage-and-wins and the policy drifts into whatever degenerate strategy beats the opponent, shedding every trace of the human it cloned. So the objective carries three tethers:
where is a potential over the distance between the bot's rolling 9-component behavior vector and Alan's measured profile, and is an advantage-weighted cross-entropy on Alan's own best replay moments — because the frozen prior is his passive average, but the dataset contains his wins.
This design came out of ~34 adversarially-verified arXiv papers (every ID fetch-checked; the ones that changed decisions: PostBC on coverage, FASTER on receding horizons, "KL-Regularized RL is Designed to Mode Collapse" on why the naive dial was dead on arrival, and behavior-conditioned PPO — the basis for the style term).
And here is Alan, as a vector — the target the reward pulls toward:
Retreat 32 over approach 25, idle 28, block 23: the defensive, distance-controlling player everyone remembers, expressed as numbers a loss function can see. A 68-guide GameFAQs corpus from his own era independently names almost every component — the 1998 strategy writers and the 2026 behavior vector agree on what skill looks like in this game.
Chapter 5 — The honest scoreboard
The RAM archaeology chapter first: the byte the whole match-end detector
trusted, 0xFF4008, read 0x99 in every replay-derived savestate — so it was
mapped as a "fighting" flag. A fresh capture read 0x92… then 0x93. Then a
new run fell: 0x88, 0x87, 0x86. The truth, settled by reading the byte as
a time series: it's the round timer, in binary-coded decimal, counting down
from 99. One mislabeled byte had quietly ended every arcade training match
~10 game-seconds in — run v1's "wins" were really "who's ahead on health 10
seconds in." The kicker: the game's real timeout rule awards the round to the
HP leader, so the synthesized outcomes were accidentally aligned with the real
rules. Sometimes you get lucky in the exact shape of your bug.
Run v2, with the timer bug dead, played full matches against a two-stage arcade-CPU curriculum. The honest result:
Flat, then declining. Entropy healthy, no token locks, style distance oscillating instead of converging. That plateau is evidence: more PPO on this prior will not produce a winner. The bot blocks more than it ever did, presses buttons it literally couldn't press a month ago, and loses full matches it used to "win" by phantom timeout — progress of the unglamorous kind, the kind you only get by auditing your own work adversarially (two campaigns, 27 confirmed bugs) and believing live evidence over dashboards.
Chapter 6 — What's next
- Phase-3 unified retrain: a coverage-preserving prior (so Alan's rare aggressive actions stay in the policy's support), game-event conditioning replacing the self-hypnotizing prev-token, and per-token KL reweighting that frees exploration exactly where the prior is unsure.
- Self-play once the arcade CPU is exhausted — current-policy self-play, per the verified evidence, not checkpoint pools.
- The demo is live: Spar with the Ghost. The 2.2M-parameter policy runs in your browser — a hand-written forward pass, verified bit-close against the trained model. Move, and he answers with real motor chunks from his codebook. It's an original abstract arena, not the game: no ROM, no sprites, no copyrighted assets — his motor vocabulary is his; the game's expression stays the game's. That line matters, and it's the honest way to ship this.
- The only eval with real authority: the people who knew Alan, hands on a stick, saying whether the ghost moves like the man.
Why this is AI-for-good, not AI-for-novelty
Communities like the FGC are cultural archives. They preserve game knowledge, matchup theory, and individual playstyles that exist nowhere else. When a player who shaped that culture is gone, the knowledge he embodied goes with him — unless you have the recorded games and the will to build something with them.
The FGC isn't going to get a memorial bot from a big research lab. If anyone builds 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 have the technical chops to do it. The infrastructure here is open-source and the pipeline generalizes to any fighting game with recorded matches.
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, and an unreasonable number of verified arXiv abstracts. Validated by the FGC who knew him. EOF.