Generate interactive MTG metagame heatmaps from simulation data. Use this skill whenever the user wants to build a meta matrix, metagame visualization, matchup heatmap, deck tier analysis, or interactive matchup grid for any card game format (Legacy, Modern, Pioneer, etc.). Also triggers on requests to visualize win rates across many decks, create tournament prep tools, or build interactive HTML dashboards showing deck-vs-deck performance. Use this skill even if the user just says "build the matrix" or "make the heatmap" in the context of MTG sim work.
Generates a single-file interactive HTML heatmap showing deck-vs-deck win rates with drill-down matchup details, deck profiles, card-level tracking, and meta-weighted win rates.
The output is a single self-contained HTML file (~800-900KB) with all data embedded as inline JSON and all interactivity in vanilla JS. No external dependencies, no build step, no server needed.
| Constant | Source | Contents |
|---|---|---|
D | meta_N.json | Win matrix, averages, weighted WR, per-matchup stats |
DA | deck_agg.json | Deck profiles: MVPs, finishers, matchup spreads, plan text |
I | interact_v3.json | Interaction events: locks, removal, counters, pivots |
C | card_trimmed.json | Per-matchup card stats: top casts, attackers, damage engines |
ARCH | inline | Deck-to-archetype mapping |
Read references/data_pipeline.md for the full extraction pipeline. Summary:
run_batch.py): Run N games per matchup pair, collect win rates + game statsextract_interactions.py): Parse game logs for strategic eventsextract_cards.py): Parse logs for casts, attacks, damage, finishersRead references/ui_components.md for implementation details. Summary:
"deck1|deck2" stringsThe forEach brace bug: When generating JS with if(condition){...forEach(...)}), the }) closes the forEach but NOT the if block. Always end with });} not })}.
Weighted WR formula:
weighted_wr[deck] = Σ(wr_vs_opp × opp_avg_wr) / Σ(opp_avg_wr)
Matchup data array format (M[key]):
[win_rate, kill_turn, game_length, otp_wr, otd_wr, otp_n, otd_n,
p1_final_life, p2_final_life, p1_mulls, p2_mulls, win_reasons]
mtg-meta-matrix/
├── SKILL.md (this file)
└── references/
├── data_pipeline.md — How to extract data from any sim engine
├── ui_components.md — HTML/CSS/JS patterns for each component
└── build_template.py — Python script that assembles the final HTML
The pills(cards, color) function renders card pill badges in the matchup detail panel.
It is the most commonly forgotten function when rebuilding the HTML.
Always verify after any rebuild:
grep 'function pills' output.html
If missing, inject before closeDet():
function pills(cards,color){
return '<div style="display:flex;flex-wrap:wrap;gap:3px;margin:2px 0">'
+ cards.map(([c,n]) => '<span style="display:inline-block;background:'
+ color+'15;color:'+color+';border:1px solid '+color
+ '30;border-radius:10px;padding:1px 7px;font-size:10px;font-family:JetBrains Mono,monospace">'
+ c+' <span style="opacity:.5">×'+n+'</span></span>').join('')
+ '</div>';
}
Never rebuild from scratch. Use the template and swap data:
# 1. Load template
with open('templates/reference_meta_matrix.html') as f: html = f.read()
# 2. Replace each data constant
for name, data in [('D',meta),('DA',agg),('C',cards),('I',interact),('ARCH',arch)]:
html = replace_const(html, name, json.dumps(data))
# 3. Verify pills() exists
if 'function pills' not in html:
# inject it
# 4. Verify all 9 functions
for fn in ['pills','wc','tc','muc','getCT','tierOf','tierTag','getWR','closeDet']:
assert f'function {fn}' in html, f'MISSING: {fn}'