Add MTG Card
Implement the card specified in $ARGUMENTS.
Parse the arguments: the card name is the main argument, and --set <set-code> is optional (defaults to por for Portal).
Phase 1: Fetch Card Data from Scryfall
REQUIRED: Always fetch the exact card data from Scryfall API before implementing.
- •
Use WebFetch to get card data:
- •URL:
https://api.scryfall.com/cards/named?exact=<card-name-url-encoded>&set=<set-code> - •ALWAYS use the
&set=<set-code>parameter - each printing has different rarity, collector_number, artist, flavor_text, and image_uris - •Extract: name, mana_cost, type_line, oracle_text, power, toughness, colors, rarity, collector_number, artist, flavor_text, image_uris.normal
Common set codes:
ons(Onslaught),por(Portal),lea(Alpha),leb(Beta),2ed(Unlimited) - •URL:
- •
CRITICAL - Image URI:
- •ALWAYS use the exact
image_uris.normalURL from the Scryfall API response - •NEVER generate, guess, or hallucinate an image URI - they have specific hash-based paths
- •Format:
https://cards.scryfall.io/normal/front/X/X/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX.jpg?XXXXXXXXXX - •Validate that the URL really exists with a head request, if it doesn't exist find it on scryfall (always get the one with the correct series)
- •Include the query parameter (e.g.,
?1562911270) - it's part of the URL
- •ALWAYS use the exact
- •
Check for Oracle Errata:
- •The
oracle_textfrom Scryfall contains the current Oracle wording with all errata applied - •Document significant errata in a comment above the card definition
- •Common errata: "Bury" -> "Destroy + can't be regenerated", "Remove from game" -> "Exile", creature type updates
- •The
- •
Parse the oracle text to understand:
- •Card type (creature, instant, sorcery, enchantment, artifact, planeswalker, etc.)
- •Abilities (spell effect, triggered, activated, static, replacement)
- •Targeting requirements
- •Keywords (simple and parameterized)
- •Conditions and restrictions
Phase 2: Check Existing SDK Components
Before implementing anything new, check what already exists using the DSL facades and raw types.
- •
Check DSL facades first - these are the primary API:
- •
Effects.*- effect construction (mtg-sdk/.../dsl/Effects.kt) - •
Targets.*- target requirements (mtg-sdk/.../dsl/Targets.kt) - •
Triggers.*- trigger types (mtg-sdk/.../dsl/Triggers.kt) - •
Filters.*- filter construction (mtg-sdk/.../dsl/Filters.kt) - •
Costs.*- ability costs (mtg-sdk/.../dsl/Costs.kt) - •
Conditions.*- conditions (mtg-sdk/.../dsl/Conditions.kt) - •
DynamicAmounts.*- dynamic values (mtg-sdk/.../dsl/DynamicAmounts.kt) - •
EffectPatterns.*- common patterns (mtg-sdk/.../dsl/EffectPatterns.kt)
- •
- •
Check raw effect types in
mtg-sdk/.../scripting/effect/:- •
DamageEffects.kt,LifeEffects.kt,DrawingEffects.kt,RemovalEffects.kt - •
PermanentEffects.kt,LibraryEffects.kt,ManaEffects.kt,TokenEffects.kt - •
CompositeEffects.kt,CombatEffects.kt,PlayerEffects.kt,StackEffects.kt
- •
- •
Check other SDK types:
- •
Keyword.kt- keywords - •
KeywordAbility.kt- parameterized keywords (Ward, Protection, Cycling, etc.) - •
StaticAbilitytypes - for permanent/global effects - •
ReplacementEffect.kt- replacement effects - •
AdditionalCost.kt- additional spell costs - •
ActivationRestriction.kt- activation restrictions
- •
- •
Check existing card implementations with similar mechanics:
bashgrep -r "<keyword-or-effect>" mtg-sets/src/main/kotlin/
For a full inventory, see reference.md.
Phase 3: Identify Missing Components
Compare the card's needs against existing components:
- •Everything exists -> Skip to Phase 5
- •New effect needed -> Phase 4 (implement new effect + executor)
- •New keyword needed -> Phase 4 (add keyword)
- •New static ability needed -> Phase 4
- •New replacement effect needed -> Phase 4
- •New trigger needed -> Phase 4
- •New condition needed -> Phase 4
Phase 4: Implement Missing Backend Components
If new components are needed:
4.1 Add Effect Type in the appropriate file under mtg-sdk/.../scripting/effect/:
- •
DamageEffects.kt,LifeEffects.kt,DrawingEffects.kt,RemovalEffects.kt - •
PermanentEffects.kt,LibraryEffects.kt,ManaEffects.kt,TokenEffects.kt - •
CompositeEffects.kt,CombatEffects.kt,PlayerEffects.kt,StackEffects.kt
4.2 Create Effect Executor in rules-engine/.../handlers/effects/{category}/
- •Choose the matching category directory (combat/, damage/, drawing/, library/, life/, mana/, permanent/, player/, removal/, stack/, token/, composite/, information/)
4.3 Register Executor in the appropriate {Category}Executors.kt module file
4.4 Add DSL Facade (recommended) in mtg-sdk/.../dsl/Effects.kt for new effects
4.5 Add New Keyword (if needed) in mtg-sdk/.../core/Keyword.kt
- •Also update
web-client/src/types/enums.ts(Keyword enum + KeywordDisplayNames) - •Also update
web-client/src/assets/icons/keywords/index.tsif it should have an icon
4.6 Add Static Ability (if needed) in mtg-sdk/.../scripting/StaticAbility.kt
4.7 Add Replacement Effect (if needed) in mtg-sdk/.../scripting/ReplacementEffect.kt
4.8 Add Trigger (if needed) in the appropriate file under mtg-sdk/.../scripting/trigger/
- •Also add a facade entry in
mtg-sdk/.../dsl/Triggers.kt
4.9 Add Condition (if needed) in the appropriate file under mtg-sdk/.../scripting/condition/
- •Also add a facade entry in
mtg-sdk/.../dsl/Conditions.kt
For code templates, see examples.md.
Phase 5: Create Card Definition
File: mtg-sets/src/main/kotlin/com/wingedsheep/mtg/sets/definitions/{set}/cards/{CardName}.kt
Use the card DSL. Prefer DSL facades (Effects.*, Targets.*, Triggers.*, Filters.*, etc.) over raw constructors. For complete examples of creatures, spells, triggered abilities, activated abilities, static abilities, equipment, auras, modal spells, and X-cost spells, see examples.md.
Phase 6: Register Card in Set
File: mtg-sets/src/main/kotlin/com/wingedsheep/mtg/sets/definitions/{set}/{Set}Set.kt
Add the card to the allCards list (alphabetically or by collector number).
Phase 7: Create Scenario Test (Required for New Effects/Keywords)
If this card uses a NEW effect, keyword, trigger, condition, or static ability, create a scenario test in game-server.
File: game-server/src/test/kotlin/com/wingedsheep/gameserver/scenarios/{CardName}ScenarioTest.kt
For test template and available helper methods, see examples.md and reference.md.
Phase 7b: Create E2E Test (Required for New Frontend/UI Mechanics)
If the card introduces a new frontend or UI mechanic (e.g., a new decision type, overlay, targeting flow, zone interaction, or player prompt that didn't exist before), create an E2E browser test.
File: e2e-scenarios/tests/{set}/{card-name}.spec.ts
- •Mirror the scenario test from Phase 7 but run against the full stack (server + client)
- •Use the
createGamefixture to set up the board state - •Use
GamePagehelpers (clickCard,selectAction,answerYes,resolveStack,expectOnBattlefield, etc.) - •See existing tests in
e2e-scenarios/tests/for patterns - •Tip: Give both players at least one card in their library to prevent draw-from-empty-library losses when the game auto-advances turns
Phase 8: Run Tests and Verify
# Run specific test just test-class MyCardScenarioTest # Run all game-server tests just test-server # Verify the build just build
Checklists
Simple Card (existing effects only):
- • Fetch card data from Scryfall with set-specific URL (e.g.,
&set=ons) - • Verify metadata matches the target set (rarity, collector number, artist, flavor text)
- • Verify image URI is from Scryfall response (never hallucinate, include query param)
- • Check for errata (oracle text vs printed text)
- • Create card file in
mtg-sets/.../cards/ - • Add to set's
allCardslist
Card with New Effect:
- • All of the above, plus:
- • Add effect type to appropriate file in
mtg-sdk/.../scripting/effect/ - • Add DSL facade in
Effects.kt(recommended) - • Create executor in
rules-engine/.../handlers/effects/{category}/ - • Register in appropriate
{Category}Executors.kt - • Write scenario test
- • Write E2E test if a new frontend/UI mechanic was introduced
- • Run tests
Card with New Keyword:
- • All of the simple card steps, plus:
- • Add keyword to
mtg-sdk/.../core/Keyword.kt - • Update
web-client/src/types/enums.ts(enum + display names) - • Update keyword handlers if needed
- • Write scenario test
- • Run tests
Card with Static/Replacement/Trigger/Condition:
- • All of the simple card steps, plus:
- • Add type to appropriate SDK file
- • Add DSL facade entry if applicable
- • Implement handler/executor if needed
- • Write scenario test
- • Write E2E test if a new frontend/UI mechanic was introduced
- • Run tests
Important Rules
- •Always fetch from Scryfall first - Never guess card text or stats
- •ALWAYS use set-specific API URL - Use
&set=<code>to get correct metadata for the specific printing - •NEVER hallucinate image URIs - Always use the exact
image_uris.normalfrom Scryfall API response - •Check DSL facades first - Use
Effects.*,Targets.*,Triggers.*, etc. before checking raw types - •Check existing effects - Most common effects already exist
- •Use immutable patterns - Never modify state in place
- •Test new mechanics - All new effects/keywords/triggers/conditions need tests
- •Follow naming conventions - CardName should match file name
- •Keep effects data-only - Logic goes in executors, not effect data classes