The chapters below preserve the route that led here, including the failed
models and apparently encouraging dashboards. A later trace audit proved that
every pre-v10 trace-derived checkpoint is invalid as evidence of Alan
fidelity: replay state was shifted eight frames relative to its label, and a
post-frame observation was trained against the action that had already changed
it. The clean contract is now s_i → a_(i+1)..a_(i+16). The v10 run reached
decoder initialization but stopped before its first epoch checkpoint. A later
developer-menu audit corrected +$80 from false attack strength to a
move-property row code, so the schema-v8 state cache is now invalid and must be
rebuilt under schema v9. No training process, closed-loop result, or
blinded-human result is active or complete. Nothing on this page should be read
as a claim that the current bot passes for Alan.
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, with 7,554 replay records now catalogued. 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 evolving story of building something so the community can still spar against his style. Not a "good MvC1 AI," and not a claim of completion. The failed versions matter because they show how easily an imitation project can produce plausible metrics from a broken observation/action contract.
The Turing test isn't "can it win" — it's "does this feel like Alan."
evidence funnel
What entered the frozen v10 candidate
A replay record is not automatically a faithful trace.
- Alan catalogue7,554 replay records97.06% accepted7,332
The 222-record gap remains explicit; the candidate does not silently treat discovery as usable training data.
Chapter 1 — The data is the player
The first model's vocabulary was never keypresses. It was 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:
input piano roll
One Alan phrase, exactly 50 frames long
Width is time held. The launcher and assist are decisions a strategy-guide script would miss.
- super_jump_combo_4hit50 frames24
The long back hold is part of the recorded phrase, not padding added by the model.
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. Any bot can contain every move; what makes it feel like him is how often and in what situations it chooses each one. An early version of this article compressed held directional channels into loose words such as “block” and “jump.” That was too flattering and too imprecise. The historical behavior chart in Chapter 4 now keeps the artifact's actual field meanings. The community enforces this distinction 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 268-dimension state vector with real hitbox geometry — the bot judging reach the way the engine does, not by eyeballing sprite distance.
- First motion clone · schema v3153ordered features
movement and coarse combat state, before the full collision and partner model
- Causal candidate · schema v9268ordered features
sanitized collision geometry, partners, projectiles, camera, reactions, pushbox modes, and correctly decoded move-property row context
flowchart LR
A["7,554 replay records<br/>catalogued"] --> B["7,332 frozen causal traces<br/>alignment v2; target offset +1"]
B --> C["18,046,213 action chunks<br/>16 contiguous future frames"]
C --> D["Residual VQ-VAE<br/>8 token slots"]
D --> E["Autoregressive VQ-BeT decoder<br/>s_i → a_(i+1)..a_(i+16)"]
E --> F["Manifest-bound local runtime<br/>closed-loop evaluation pending"]
B -.-> G["268 ordered state features<br/>offline/live parity"]
G -.-> EChapter 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 state and action never belonged to the same moment. The old replay
runner began consuming recorded inputs, then spent eight frames "settling"
before emitting row zero. A cross-correlation audit measured exact controller
agreement at lag +8, not zero. Worse, row i is post-frame state s_i,
after input a_i has already executed, yet the old target began with a_i.
The replacement trace builder uses zero post-start settle frames, records both
observed controllers, rejects any mismatch, and supervises only the action a
live policy could still choose: a_(i+1) onward.
causal contract
The label moved from the past to the future
Pre-v10 · rejected
future-state / past-action leakageRow zero was emitted only after the replay runner had already consumed 8 inputs.
v10 · live-compatible
target offset +1A live policy sees si only after ai changed the game, so ai+1 is the first action it can still choose.
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.
- Predicted motor chunk16frames
266.7 ms at 60 Hz if executed open-loop
- Receding execution budget8frames
133.3 ms before the policy observes again
- Tight end of a jump-in block window3frames
the measured decision window spans roughly 3–8 frames (50–133 ms)
Those older failure modes were real, but none could be interpreted cleanly until the causal alignment defect was removed. A better decoder cannot recover Alan's timing from state/action pairs that are eight frames apart.
sequenceDiagram
participant P as Policy
participant E as Emulator
P->>E: decode 16-frame chunk
E-->>P: execute frames 1–8 only
Note over P: re-encode executed frames<br/>via VQ-VAE (train parity)
P->>E: fresh 16-frame decision
E-->>P: execute frames 1–8 …
Note over P,E: mean reaction 9f → ~3f,<br/>specials survive via commit guardYou 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
This chapter documents the pre-v10 RL experiments. Their control and reward lessons remain useful, but their Alan prior was trained from the invalid old trace contract. The v10 program returns to causally correct offline imitation first; no RL result below is a current Alan-fidelity baseline.
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).
Here is that historical target with its actual field meanings preserved. These
channels may overlap on one frame, so they do not sum to 100%. down is not a
synonym for blocking, and up is not a jump-event counter.
- Attack press2.14% of frames
between-replay standard deviation ±0.54
- Multiple attack buttons4.53% of frames
±2.01; a separate held-input channel
- Toward opponent held25% of frames
±7.03
- Away from opponent held31.94% of frames
±8.23
- Down held23.05% of frames
±8.78; crouch or crouch-block context is not distinguished here
- Up held8.73% of frames
±2.96; held direction, not jump frequency
- No direction or attack28.1% of frames
±10.62
The shape still matches the defensive, distance-controlling player people remember, but the labels now say only what the artifact measured. A 68-guide GameFAQs corpus from his own era independently names many of the same tactical ideas; that agreement is context, not a substitute for the blinded evaluation.
Chapter 5 — The honest scoreboard
The scoreboard below was honest about its original arcade-CPU protocol, but it predates the causal trace audit. It measures that historical agent's strength, not whether the v10 policy resembles Alan.
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:
- iterations 0–4950.42% win rate
121 wins / 119 losses
- 50–9949.6% win rate
125 / 127
- 100–14950.42% win rate
119 / 117
- 150–19947.3% win rate
114 / 127
- 200–26742.68% win rate
140 / 188; training longer made it worse
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 — v10: fix the contract before the model
The July experiments above ran, and some produced interesting negative results. Then a more fundamental audit invalidated the prior they all shared. That is why v10 starts by freezing causality and provenance, not by adding another loss term.
The earlier eval-harness audit remains the historical record of the July failures. This article is the single canonical home for the current Alan bot, including every later correction.
recorded action a_i -> FBNeo emulates frame i -> observed post-frame state s_i
live policy sees s_i -> earliest possible output is a_(i+1)
training sample: s_i -> [a_(i+1), a_(i+2), ... a_(i+16)]Seventeen contiguous in-fight rows are required for one sample. Idle is kept. Gaps, side changes, round boundaries, mismatched observed controls, and partial tails are rejected. Both players' inputs are replayed in lock-step. Live FBNeo controls and offline GGPO masks cross exactly one named conversion boundary; directions are mirrored from the immediately preceding observed facing.
The frozen candidate contains 7,332 validated traces from 7,554 catalogued
replays (97.06%; 222 missing) and 18,046,213 causal 16-frame chunks. Its
residual VQ-VAE completed all 30 epochs, and the causal token and 268-feature
state caches were built. The developer-menu audit then proved that two entries
in that state cache carried the wrong semantics: the byte at fighter-object
+$80 is a latched seven-bit move-property row code, not an LP/MP/HP attack
strength. Schema v9 keeps the width at 268, corrects the names and normalization,
and deliberately invalidates the v8 cache. At this August 7 snapshot, the
autoregressive VQ-BeT decoder had initialized but had not produced its first
epoch checkpoint; training is held until the schema-v9 state cache is rebuilt.
The decoder, source-bound prior, deployment bundle, and held-out behavioral
evaluation therefore remain unfinished. A low reconstruction loss is only the
motor-codebook ceiling; it is not proof of Alan-like decisions.
replay-disjoint split
18 million action chunks, split by whole replay
No replay is allowed to put neighboring windows on both sides of validation.
- decoder corpus18,046,213 chunks7,230 represented replays17,145,521
These are overlapping causal windows, not 18 million independent decisions or matches.
motor codebook
Thirty epochs compressed the action language
Reconstruction error falls quickly, then settles into a narrow validation band.
release gate
Built is not the same as proven
The candidate advances only when the artifact at each gate is present and independently verifiable.
| route | trace freeze | causal chunks | motor VQ | tokens + v9 state | decoder | bundle + eval | blind FGC |
|---|---|---|---|---|---|---|---|
| v10 candidate | verified7,332 alignment-v2 traces | verifiedtarget offset +1 | verified30 epochs complete | pendingtokens built; invalid v8 state cache must be rebuilt under schema v9 | pendinginitialized; no epoch checkpoint | pendingno deployable manifest or held-out result | pendingno blinded whole-match verdict |
Capcom left an instrument panel inside the ROM
The public starting point was Ryou's
CPS2 Debug Switches and the Games That Love Them.
It documents the shared Marvel-family menu through X-Men vs. Street Fighter,
then calls out the two game-specific last tools: MSHvSF adds STAR TEST; MvC
adds HIT TEST. The consolidated CPS-2 Character Lab is the second
witness: clean stock-ROM MAME runs, controlled input sweeps, exact glyph
decoding, ROM correlation, and live RAM probes against the Fightcade revision.
The linked article establishes the menu family and documents XvSF's controls and debug-switch effects. It does not prove that XvSF's 24-switch map can be copied bit-for-bit into MvC. Every statement below is labelled as external family evidence, locally verified MvC evidence, or unresolved. The debug menu is an oracle only where two independent witnesses agree.


capability census
Same twelve-slot shell, one decisive game-specific swap
The categories describe safe use in this project—not how important the tools may have been inside Capcom.
- Marvel vs. Capcom12 menu entriesHIT TEST353
Candidates: HIT EDITER, CATCH EDITER, HIT TEST. Diagnostics: ENEMY SENSEI plus the four SCRL tools. None becomes supervision without a verified join.
- Marvel Super Heroes vs. Street Fighter12 menu entriesSTAR TEST253
STAR TEST replaces MvC's HIT TEST. Both the public research and the local reproduction reach a black, input-inert screen that can still return to the menu.
flowchart TD
G["HIT / CATCH: pointer + frame join<br/><br/>MvC HIT TEST: bounded row-code join"] --> R["causal replay state"]
R --> P["268-d schema-v9 policy input"]| Tool | Safe role here | What it can contribute | Boundary that stays visible |
|---|---|---|---|
HIT EDITER | semantic oracle / training candidate | Capcom's animation name, Char Gp, position, frame counters, live CG_Ptr, flip state, and HIT_0…HIT_7 box IDs; useful for move phase, startup/active/recovery, and named-action validation | The name index is not yet proven to equal the animation tree's group index; the hit slots are IDs, not box coordinates |
CATCH EDITER | semantic oracle / training candidate | Synchronized CATCH nn and HOLD nn programs, relative positions, and frame progression for thrower and victim; useful for throw-state and tech-window validation | It does not expose the separate live throw-range hook, and a menu pair is not evidence Alan attempted that throw |
ENEMY SENSEI | engine diagnostic | Runs TAIKI, OKIAGARI, and LIBRARY table checks and carries explicit malformed-command / bad-parameter errors; potentially a validator for altered fighter and CPU data | Its exact implementation is still unresolved. It describes Capcom's CPU/fight-engine tables, not Alan's decisions |
ENDING TEST | asset-only | Character-id and ending-variant routing, useful for catching roster and identity-table drift | Story playback supplies no combat supervision |
MESSAGE TEST | asset-only | Message-bank ids, option paths, and language/string integrity; useful for ROM-build regression tests | Text content supplies no policy signal |
SCRL MOVE TEST | diagnostic, unresolved detail | A possible live camera/scroll-motion probe for super-jump tracking and world-to-screen transforms | The linked XvSF investigation reaches a black screen; local MvC navigation reaches the tool, but its fields are not fully decoded |
SCRL 1 BLOCK | stage diagnostic | Scroll-plane 1 bank/image browsing; candidate reference for foreground/background origin and wrap checks | The public XvSF result is partly broken and cannot be treated as an MvC address map |
SCRL 2 BLOCK | stage diagnostic | Scroll-plane 2 bank/image browsing; useful for camera-parallax and stage-boundary tests | Visual/asset evidence, not player intent |
SCRL 3 BLOCK | stage diagnostic | Scroll-plane 3 bank/image browsing; useful for independently checking layered camera transforms | Visual/asset evidence, not player intent |
KAO TEST | asset / identity diagnostic | kao means face: the portrait browser can verify character-id, face tile, and palette joins—the same identity needed to select the right property table | Portrait correctness does not prove gameplay-state correctness |
HIT TEST — MvC only | property oracle / training candidate | A 128-selector UI page per stock character; the underlying $20-byte property records now have eight exact field offsets | It is not a hitbox-coordinate viewer, physical table lengths vary, tail selectors can expose adjacent ROM data, and row id is not the animation-name index |
STAR TEST — MSHvSF only | unresolved | Establishes a real version difference in the shared menu | Public and local runs show a recoverable black screen; no training or diagnostic claim is justified yet |
EXIT | navigation | Returns from the secret menu to the surrounding service flow | No data product |
HIT TEST is a move-property table, not a box drawing
The public source carefully described HIT TEST as a grid “possibly related
to hit boxes.” The full local sweep resolves the ambiguity. MvC renders 23
character pages × 128 selector cells = 2,944 captured UI cells. That is the
screen's addressable selector envelope—not 2,944 valid move records. Physical
property tables vary by character; alternate forms may share pointers, and
unsafe tail selectors can run into adjacent tables or trailer ROM data.
Correlating 125 independently decoded, in-range records against the program ROM
produced one exact layout: a $20-byte stride with all eight displayed fields
at fixed offsets.
- HIT TEST UI selector envelope128records / selector cells
capture ceiling on every character page; not a validity guarantee
- Ryu physical property table127records / selector cells
Alan's partner; nearly fills the selector space
- Spider-Man physical property table103records / selector cells
- Morrigan physical property table84records / selector cells
Alan's point character; selectors 84–127 are not valid Morrigan move rows
- War Machine physical property table71records / selector cells
- Dachi template physical table30records / selector cells
the shortest verified example; the visible page still offers all 128 selectors
- 1Dmg · +$00$000000–$000001 · 1 BHP damage.
- 2Piy · +$06$000006–$000007 · 1 BDizzy contribution.
- 3Sto · +$07$000007–$000008 · 1 BHitstop.
- 4Snd · +$08$000008–$000009 · 1 BStanding hit-stun duration; despite the label, this is not a sound id.
- 5Slp · +$09$000009–$00000A · 1 BProne/knockdown recovery seed.
- 6Shc · +$10$000010–$000011 · 1 BSuper-meter charge/gain.
- 7Grd · +$18$000018–$000019 · 1 BGuard-stun override; zero asks the engine to derive its normal value.
- 8Mut · +$1A word$00001A–$00001C · 2 BLow six bits seed the multi-hit rehit-vulnerability window. It is not post-hit invulnerability.
- A+$12 attack-box selector$000012 · markerHidden from the HIT TEST columns; geometry lives in the separate box tables.
- B+$14 attack-box selector$000014 · markerA selector, not coordinates.
The row number lives in its own move-property id space. Two independent
tests rejected the tempting shortcut “row id = animation-name or pattern
index,” so the data does not get move names pasted onto it. The safe join is a
stock character's table pointer and verified physical length plus the seven-bit
code the fight engine latches at fighter object +$80; any out-of-range code
must fail closed.
- P1 values greater than 281.87% of sampled rows
108 distinct codes in the first 16 full-candidate row groups
- P2 values greater than 285.15% of sampled rows
95 distinct codes in the same sample
- Nonzero codes outside canonical strike frames96% of sampled rows
approximately 96%; evidence that the byte is latched move context, not attack-box liveness
The live and offline builders now expose indices 210/211 as
self_move_property_code_norm and opp_move_property_code_norm, computed as
(raw & 0x7F) / 127. They are deliberately not gated on an active strike:
the code persists through move context, while attack liveness comes from the
separate canonical collision gates. Historical trace/parquet wire fields keep
their old p*_atk_strength names for compatibility, but the model-facing
meaning is corrected. Schema v9 carries the normalized selector only; it does
not silently add the eight decoded static fields. A bounds-checked static
lookup is the next controlled A/B experiment after provenance and live/offline
parity gates. The existing schema-v8 state cache is invalid; it must be rebuilt
before decoder training resumes.
The two editors can name behavior—after a join
HIT EDITER exposes Capcom's animation/group label, group value, position,
frame counters, live CG_Ptr, flip flags, and eight hit slots. CATCH EDITER
exposes synchronized CATCH nn / HOLD nn pairs for thrower and victim.
ENEMY SENSEI exposes named idle, wake-up, and library-table passes plus error
messages that make it promising as a validator, not a replay-label source.
The automated stock-MvC sweep's main roster rows contain 30 distinct
(label, CG_Ptr) HIT observations and 31 CATCH observations for Morrigan, and
30 HIT / 12 CATCH for Ryu. Those are deduplicated observations from an
oversampled editor sweep—not proof that either character has exactly that many
complete moves.
- Morrigan · HIT EDITER30deduplicated observations
- Morrigan · CATCH EDITER31deduplicated observations
- Ryu · HIT EDITER30deduplicated observations
- Ryu · CATCH EDITER12deduplicated observations
The live collision representation is more immediately actionable:
- 1+$06…+$07 · hurt/reaction code$000006–$000008 · 2 BObserved values 0/4/8/12 are preserved; only code 8 has a documented grabbed meaning.
- 2+$6C…+$6F · box-table pointer$00006C–$000070 · 4 BResolves box ids into signed center offsets and unsigned X/Y radii.
- 3+$70…+$73 · two attack ids$000070–$000074 · 4 B
- 4+$74…+$7B · four vulnerability ids$000074–$00007C · 8 B
- 5+$7C…+$7D · defender throwable region$00007C–$00007E · 2 BVulnerability to throws, not an active grab-range box.
- 6+$82…+$83 · viewer gates$000082–$000084 · 2 BNecessary inputs to canonical classification, never sufficient on their own.
- 7+$B4 · pushbox mode$0000B4–$0000B5 · 1 BByte values 0 ground, 2 super-jump air, 4 normal-jump air, 6 knockdown/hit.
- !ROM throw-check hook$0D75E8 · out of bounds (past $0000B8)The canonical viewer creates active throw range here, outside the fighter object and current raw trace.
Attack ids at +$70/+$72, four vulnerability ids at +$74…+$7A, and
the pointer at +$6C are one system. An id is resolved through the table
into signed center offsets plus unsigned X/Y radii.
+$7C belongs to the defender and means throwable vulnerability. The
active throw box comes from a separate ROM hook at $0D75E8, which the raw
replay trace does not yet capture.
A damaging strike requires an open +$82 gate, a nonzero low-bit attack
id, and sanitized nondegenerate geometry. High-bit marked geometry is kept
in a separate potential-throw-box channel and never drives reach or overlap.
+$B4 is one byte of pushbox mode, not stance or facing: 0 ground, 2
super-jump air, 4 normal-jump air, 6 knockdown/hit.
That last correction matters: +$B4 is the engine's pushbox selector, not
stance or facing. Its observed modes are 0 ground, 2 super-jump air, 4
normal-jump air, and 6 knockdown/hit. Together the eight collision roles are
push, four vulnerable slots, throwable, and two attack slots. Nonzero bytes at
+$82/+$83 are not, by themselves, evidence that an attack is live. Starting
with schema v8, a slot is a damaging strike only when its raw ID is nonzero
with bit 0x8000 clear, its sanitized geometry is nonzero, and the +$82 gate
is open. Only that strike mask drives attack presence, reach, overlap,
pressure, and the tactical governor; schema v9 retains this contract.
Crucially, +$7C describes whether the defender can be thrown; it is not
an active grab-range box. Fightcade's canonical training-mode script creates
the active-throw box separately at a ROM throw-check hook, and that signal is
not yet resolved in the raw trace. That collision revision grew the state to
268 ordered features; schema v9 keeps the same width while correcting the
separate +$80 move-property semantics. A potential-throw-box signal requires
attack-ID bit
0x8000, sanitized nonzero geometry, the +$82 gate, and +$06 == 0, matching
the canonical viewer's hurt suppression. It is box/type presence—not intent,
an attempt, or a connection. The same schema
preserves all observed +$06 hurt codes (0/4/8/12) and exposes the documented
code-8 grabbed state without inventing a meaning for code 4.
- self · potential-throw-marked box present59.1% of fight rows
- opponent · potential-throw-marked box present58.77% of fight rows
- self · damaging strike box present5.38% of fight rows
- opponent · damaging strike box present7.19% of fight rows
- self strike · geometric overlap2.2% of fight rows
- opponent strike · geometric overlap2.32% of fight rows
This lets v10 derive box presence, overlap margins, contact approach, throw-vulnerability windows, marked attack-slot states, and corner/push spacing from the same replay frame the policy sees without pretending to measure grab reach.
The editor names are not training labels yet. Char Gp has not been proven
to equal the animation tree's L1 index, and the name-index-to-tree mapping is
still unresolved. They become safe replay labels only after a stock-ROM-hash-
specific join from CG_Ptr and frame position into the live traces. Until
then, replay RAM geometry is supervision; the editor is a reference oracle.
What the newer arXiv sweep changed
Behavior Transformers and VQ-BeT support modeling multiple action modes instead of averaging turtle, movement, pressure, assist, and super play into one median action. Keyframe-Focused Imitation and the 2026 revision of Balanced Behavior Cloning justify measuring and reweighting rare decisive states—but only after causal traces exist. OCAtari is the closest architectural analogy for extracting object-centric game state rather than learning from pixels; MvC's native fighter, projectile, and box structs let this project do that without a vision detector.
BeTAIL motivates a future bounded residual for states outside the replay distribution, while Adaptive Q-Chunking makes a particularly relevant prediction: near contact, shorter commitments can be better; in free space, longer chunks can preserve coherent plans. Neither is implemented in the current candidate. They are closed-loop experiments to attempt only after the imitation policy passes its offline and local gates.
Finally, fighting-game style needs its own evaluation. The Universal Fighting Engine study evaluates human/AI style and diversity, while the Navigation Turing Test and Navigates Like Me show why an automated discriminator is only a diagnostic. The release gate here is randomized, blinded whole-match comparison against held-out Alan footage, with confidence, judge rationales, uncertainty, and controls—not a cherry-picked clip.
The replay collector is now part of the evidence chain
The Chrome extension was rebuilt as Mission Control v8.0.0. SQLite is the authority for durable jobs, receipts, events, and controls; one same-origin Web Lock owns each scope; Cloudflare cooldowns are explicit; completion requires two clean receipt-bound terminal sweeps; and discovery, queue, download, and faithful-trace counts are no longer collapsed into one flattering number. At the August 7 snapshot, the MvC frontier held 253,234 known rows across 19,097 players and the SFIII database held 2,512 rows across 1,748 players. Frontier rows are discovery evidence, not automatically playable or faithful traces.
flowchart LR
T["Fightcade tab<br/>rate-limited executor"] -->|"page batch + stable chunk id"| I["Durable ingest receipt"]
I --> D[("SQLite<br/>jobs · events · controls")]
D --> M["Mission Control<br/>resume · diagnose · repair"]
M -->|"scoped pause / retry / reset"| D
D -->|"explicit authority<br/>and last checkpoint"| T
D --> P["Two clean terminal sweeps<br/>bound to receipt ledger"]
P --> Q["Queue next scope"]The health HUD now has an explicit delivery contract
The August 7 UI work hardened how live state reaches Mission Control. A view binds to the owning run id instead of whichever output happens to look current; its JSONL follower can attach after a file appears and continue across rotation; and the SSE path sends keepalives and reconnects without silently abandoning the run. In the browser, a monotonic-revision reducer rejects older snapshots so a delayed event cannot roll HP backward, while a 2.5-second freshness window exposes a visible stale signal instead of leaving an old health bar looking live. Pushbox-mode labels now use the verified engine meanings—ground, super-jump air, normal-jump air, and knockdown/hit. These are delivery and observability guarantees, not live-emulator accuracy proof: end-to-end emulator validation is still pending, and the underlying RAM mapping remains the boundary on whether the displayed health and state are actually correct.
sequenceDiagram
participant R as Match recorder
participant J as Rotation-aware JSONL follower
participant S as Run-scoped SSE
participant U as Monotonic UI reducer
participant H as Health HUD
R->>J: telemetry_seq + health_revision + HP
J->>S: follow late file, rotation, truncation
S-->>U: state event / keepalive / reconnect
U->>U: reject duplicate or older revision
U->>H: newest HP + fresh/stale age
Note over S,H: Owning run id prevents cross-run log attachmentThe rebuild order, and the stop condition
The v10 runner is content-addressed and resumable, but the old one-line resume
command is intentionally withdrawn at this snapshot. The schema-v8 state cache
was built before the +$80 correction; resuming the decoder against it would
either fail the manifest contract or, worse, train against the collapsed
feature. The safe order is verify no duplicate process → rebuild only the
frozen candidate's schema-v9 state cache → verify version, dimension, ordered-
name hash, and offline/live parity → resume the existing decoder in a
persistent session. Do not launch the top-level pipeline, which would create
a different candidate rather than repair this one.
The production path still refuses to train until every catalogued replay validates. The existing Spar with the Ghost browser arena remains a historical sandbox built from the older policy; it is not the v10 candidate and is not evidence that the memorial bot is finished.
The real stop condition is stricter: manifest verification, replay-held-out metrics, deterministic local-match probes, stratified closed-loop evaluation, then blinded judgments from people who knew Alan's play. Only after those gates pass can this project say 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. Final FGC validation is still a release gate. EOF.