Use when modifying the card data pipeline — adding new fields to card exports, changing Oracle parser output shape, updating card database loading, modifying the coverage report, adding synthesis functions, or debugging why a card's data looks wrong at runtime.
Instalação
Instalar com Codex ou Claude Copie este prompt, cole no Codex, Claude ou outro assistente e deixe que ele revise a página da skill e instale para você.
Use when modifying the card data pipeline — adding new fields to card exports, changing Oracle parser output shape, updating card database loading, modifying the coverage report, adding synthesis functions, or debugging why a card's data looks wrong at runtime.
Card Data Pipeline
Hard rules — all pipeline work must respect these (see CLAUDE.md § Design Principles):
CR-correctness is non-negotiable. The pipeline is the bridge between Oracle text and engine types. Parser output must faithfully represent the Comprehensive Rules semantics of each card. If a synthesis function or parser pattern produces an incorrect typed representation, the engine will enforce wrong rules. Verify every new pattern against the relevant CR section and annotate the code.
Build for the class, not the card. Every parser pattern, synthesis function, and export field must handle a category of cards. A synthesis function that works for one card but mishandles the next card with the same keyword pattern is a defect. Test with multiple representative cards from the class.
Test the building block. Every new parser pattern needs a unit test. Every new synthesis function needs a test verifying its output. Run cargo coverage after changes to verify coverage improvements. Run cargo insta review if snapshot tests are affected.
The card data pipeline converts raw MTGJSON card data into the typed card-data.json consumed by the WASM frontend and multiplayer server. Understanding this pipeline is essential when adding new ability types, fields, or synthesis steps — a change to the export format must propagate through all loading paths.
Before you start: Run the pipeline end-to-end once to see what it produces: cargo run --release --bin oracle-gen -- data --stats > /tmp/test-export.json 2>/tmp/stats.txt && head -1 /tmp/stats.txt
CR Verification Rule: Every CR number in annotations MUST be verified by grepping docs/MagicCompRules.txt before writing. Do NOT rely on memory — 701.x and 702.x numbers are arbitrary sequential assignments that LLMs consistently hallucinate. Run grep -n "^701.21" docs/MagicCompRules.txt (etc.) for every number. If you cannot find it, do not write the annotation.
Pipeline Flow
MTGJSON (AtomicCards.json, ~30k cards)
↓ download: scripts/gen-card-data.sh
data/mtgjson/AtomicCards.json
↓ parse: crates/engine/src/database/mtgjson.rs
HashMap<String, Vec<AtomicCard>>
↓ process: crates/engine/src/database/synthesis.rs (build_oracle_face)
│ For each card face:
│ ├─ build_card_type() → CardType
│ ├─ parse_oracle_text() → ParsedAbilities { abilities, triggers, statics, replacements }
│ └─ synthesize_all() — runs all synthesis functions (see function for current list)
↓ export: crates/engine/src/bin/oracle_gen.rs
client/public/card-data.json (~49 MB in the current export)
↓ consume (three loading paths):
├─ WASM: CardDatabase::from_json_str() [browser, via engine-wasm]
├─ Server: CardDatabase::from_export() [phase-server, multiplayer]
└─ CI: CardDatabase::from_export() [coverage-report binary]
Quick Commands
Regenerate card data:
cargo run --release --bin oracle-gen -- data --stats > client/public/card-data.json
Look up a specific card's exported data:
jq '.["card name in lowercase"]' client/public/card-data.json
# Example:
jq '.["lightning bolt"]' client/public/card-data.json
cargo coverage # Standard-legal cards
cargo run --bin coverage-report -- data --all # All cards
cargo run --bin coverage-report -- data --ci # CI mode (exits 1 on gaps)
Key Files
Data Source — MTGJSON
crates/engine/src/database/mtgjson.rs — Deserialization types for MTGJSON format.
synthesize_all() — runs all keyword-implied synthesis (see synthesize_all() for the current registry). Each synthesize_* function takes &mut CardFace and adds abilities/triggers/statics that the keyword implies but Oracle text doesn't make explicit.
Handles MTGJSON-specific loading concerns: card layout detection, face splitting for DFCs/split cards, and legality normalization. Delegates to synthesis.rs for the per-face build pipeline.
coverage-report loads card-data.json via CardDatabase::from_export(), not raw MTGJSON
Without --all, it filters to standard-cards.txt
It strips known-benign MTGJSON keyword mismatches (bare parameterized keywords like Keyword:Ward and action-keyword noise like Keyword:Scry) before computing final manifest coverage
Checks a game object at runtime for unsupported mechanics. Used by the frontend to show amber "!" badges on cards with partial support.
Checklist — Modifying the Pipeline
Adding a New Field to Card Export
When a new feature needs data that isn't currently exported:
crates/engine/src/types/card.rs — CardFace struct
Add the field. Use #[serde(default)] for backward compatibility — existing card-data.json files must still deserialize.
crates/engine/src/database/synthesis.rs — build_oracle_face()
Populate the new field during card processing.
crates/engine/src/bin/oracle_gen.rs — Usually no changes needed (serializes CardFace automatically via serde).
Regenerate card-data.json: cargo run --release --bin oracle-gen -- data --stats > client/public/card-data.json
client/src/adapter/types.ts — TypeScript types (if frontend needs the field)
Add the optional field to the card type definition.
Adding a New Synthesis Function
When a keyword or ability implies game mechanics that Oracle text doesn't make explicit:
crates/engine/src/database/synthesis.rs — new synthesize_*() function
Pattern: takes &mut CardFace, checks for the triggering condition (keyword, type, etc.), adds abilities/triggers/statics to the face.
crates/engine/src/database/synthesis.rs — call from synthesize_all()
Add the call in synthesize_all() alongside existing synthesis calls. This is automatically invoked by build_oracle_face().
Test: Add a test in synthesis.rs verifying the synthesis produces the expected output.
Modifying the Oracle Parser Output Shape
When a parser change affects the structure of ParsedAbilities:
Update all three loading paths — from_mtgjson, from_export, and from_json_str all need to understand the serialized CardFace shape. If you add/rename fields, verify both raw-MTGJSON loading and flattened export loading.
Audit serialized consumers — If the parser output shape changes an enum or serialized ability field, verify WASM JSON loading, server from_export, coverage-report loading, and any AI/community scenario fixtures that store the changed enum. Add serde defaults, fixture migrations, or compatibility only when deliberately needed.
Regenerate card-data.json — Always regenerate after parser changes.
Update coverage report — If the parser now recognizes previously-unimplemented patterns, coverage numbers will change. Run the report to verify improvements, and update any benign-keyword filtering in coverage_report.rs if the mismatch profile changed.
Coverage honesty gate — Accepting full Oracle text while dropping a rules-bearing rider, continuation, restriction, granted ability, replacement, or sentence tail is a hard failure unless an Effect::unimplemented, TriggerMode::Unknown, equivalent strict-failure marker, or unchanged unsupported coverage keeps the card red. Coverage improvements must list shipped semantics separately from residual unsupported riders.
Update snapshot tests — crates/engine/tests/oracle_parser.rs has insta snapshots that must be updated: cargo insta review.
CI Coverage Gate
The coverage report checks card support across format-based legality data. There is no separate manifest file — coverage is computed from the cards' legality fields in card-data.json.
Usage: initialize_game() resolves deck card names via face_index lookup
Important: The WASM bridge receives the JSON as a string from JavaScript, not as a file path. Any changes to the JSON structure must round-trip through serde correctly.
Common Mistakes
Mistake
Consequence
Fix
Missing #[serde(default)] on new CardFace fields
Existing card-data.json fails to deserialize
Always default new optional fields
Changing field types without regenerating card-data.json
Runtime deserialization panic
Always regenerate after type changes
Adding synthesis but not calling from build_oracle_face()
Synthesis exists but never runs — abilities missing from export
Add the call
Not updating TypeScript types
Frontend can't access new fields, or gets wrong types
Update adapter/types.ts
Modifying CardFace but only testing from_mtgjson path
from_export and from_json_str may fail differently
Test all three paths
Parser accepts a full sentence but drops a semantic rider
Coverage claims support while the engine enforces incomplete rules
Preserve an explicit Unimplemented/Unknown/strict-failure marker or implement the whole rules-bearing clause
Parser enum shape changes but AI/community fixtures are stale
Serialized scenario loading fails even though parser/card-data checks pass
Migrate fixtures or provide an intentional compatibility path
Coverage looks wrong after parser improvement
Manifest filtering or benign-keyword stripping hides real result
Check coverage_report.rs filtering logic
Forgetting to regenerate after parser changes
Card-data.json contains stale parsed data
Run oracle-gen after any parser modification
Parser produces new support but coverage still looks wrong
Manifest filtering or benign MTGJSON keyword mismatches hide the real result
Check coverage_report.rs filtering before assuming the parser regressed
Self-Maintenance
After completing work using this skill:
Verify references with the check below
Update the synthesis function list if you added a new one