Implement the following plan:
The FDN remediation doc identifies "Partial" cards using Effect::Custom(...) for abilities that should use typed variants. Four of these cards say "each opponent loses N life" — the Java implementation uses LoseLifeOpponentsEffect (life loss), which is mechanically distinct from damage in MTG rules. Our engine has DealDamageOpponents but no life-loss-to-opponents variant. We'll add LoseLifeOpponents to match the Java model, then fix 5 cards.
Effect::LoseLifeOpponents variantmtg-engine/src/abilities.rs
Effect enum in the // -- Life -- section (~line 77):
mtg-engine/src/game.rs — Add match arm in execute_effects() (after LoseLife arm, ~line 1015):
(Same implementation as DealDamageOpponents for now, but semantically correct — when damage prevention is added later, only DealDamageOpponents will be affected.)
All in mtg-cards/src/sets/fdn.rs — single-line replacements:
| Card | Current | Fix |
|---|---|---|
| Marauding Blight-Priest (~line 836) | Effect::Custom("Each opponent loses 1 life.".into()) | Effect::lose_life_opponents(1) |
| Pulse Tracker (~line 850) | Effect::Custom("Each opponent loses 1 life.".into()) | Effect::lose_life_opponents(1) |
| Vampire Spawn (~line 967) | Effect::Custom("Each opponent loses 2 life, you gain 2 life.".into()) | Effect::lose_life_opponents(2), Effect::gain_life(2) |
| Vampire Neonate (~line 1702) | Effect::Custom("Each opponent loses 1 life, you gain 1 life.".into()) | Effect::lose_life_opponents(1), Effect::gain_life(1) |
| Phyrexian Arena (~line 1410) | Effect::Custom("You lose 1 life.".into()) | Effect::lose_life(1) |
cargo check -p mtg-engine — new variant compilescargo check -p mtg-cards — card changes compilecargo test --lib — all tests passIf you need specific details from before exiting plan mode (like exact code snippets, error messages, or content you generated), read the full transcript at: /mnt/dest-btrfs/home/jc/.claude/projects/-mnt-dest-btrfs-home-jc-src-mage/a83bffdc-973c-4730-8a88-2ef219ba63de.jsonl
If this plan can be broken down into multiple independent tasks, consider using the TeamCreate tool to create a team and parallelize the work.
I'll implement this plan step by step. Let me start by reading the relevant files.
Good, I have all the context. Let me also check the DealDamageOpponents implementation in game.rs for reference.
Now I have all the exact lines. Let me make all the changes.
Now add the match arm in game.rs and fix all 5 cards in fdn.rs.
Need to read game.rs first. Let me do that and retry all the edits.
All edits done. Now let's verify compilation and run tests.
All 244 tests pass (52 + 18 + 157 + 17). Here's a summary of the changes:
mtg-engine/src/abilities.rs:
LoseLifeOpponents { amount: u32 } variant to the Effect enumlose_life_opponents(amount) constructor methodmtg-engine/src/game.rs:
LoseLifeOpponents in execute_effects() — iterates opponents and subtracts lifemtg-cards/src/sets/fdn.rs — Fixed 5 cards:
Custom(...) → lose_life_opponents(1)Custom(...) → lose_life_opponents(1)Custom(...) → lose_life_opponents(2), gain_life(2)Custom("You lose 1 life.") → lose_life(1)Custom(...) → lose_life_opponents(1), gain_life(1)Let's update the @docs/fdn-remediation.md doc and @ROADMAP.md to reflect that we did this. Let's also fix other cards that are now possible because we added this effect.
Let me first check what other cards across all sets use similar "each opponent loses" Custom effects that can now be fixed with LoseLifeOpponents.
Good. I can see 3 clean matches across ECL and TDM that can be fixed the same way. Let me read the surrounding context for each.
Now let me fix these 3 additional cards and also read the remediation docs so I can update them.
Now let me verify everything still compiles.
Good. Now let me check the remediation docs for ECL and TDM to understand what needs updating there too.
Now let me read the specific sections of the docs that need updating. Let me also check the FDN remediation doc for the entries about the 5 cards we fixed, and read the current "Effects that ACTUALLY WORK" table line count so I can add LoseLifeOpponents.
Now let me read the Partial Cards sections for the cards we fixed (Marauding Blight-Priest, Pulse Tracker, Vampire Spawn, Phyrexian Arena, Vampire Neonate) and the Phase 2 roadmap section.
Now let me also read the Complete cards section to understand the count and where to add the newly fixed cards.
Now I have all the context. Let me make all the doc updates. I'll update fdn-remediation.md, then ecl-remediation.md, tdm-remediation.md, and ROADMAP.md.
Now update the Phase 2 roadmap section and the overview counts.
Now update the ECL and TDM remediation docs.
Need to read those files first.
Now update the ROADMAP.md.
Now let me run the full test suite one more time to confirm everything is clean.
All 244 tests pass. Here's a summary of everything done:
New Effect::LoseLifeOpponents { amount } variant added to:
mtg-engine/src/abilities.rs — enum variant + lose_life_opponents() constructormtg-engine/src/game.rs — match arm in execute_effects() (iterates opponents, subtracts life)This is semantically distinct from DealDamageOpponents — when damage prevention is added later, only damage effects will be affected, not life loss.
FDN (5 cards):
Custom → lose_life_opponents(1)Custom → lose_life_opponents(1)Custom → lose_life_opponents(2) + gain_life(2)Custom("You lose 1 life.") → lose_life(1)Custom → lose_life_opponents(1) + gain_life(1)ECL (2 cards):
TDM (1 card):
Custom → lose_life_opponents(2) (already had gain_life(2))docs/fdn-remediation.md — Added LoseLifeOpponents to effects table, marked 5 cards fixed, updated Phase 2 roadmapdocs/ecl-remediation.md — Updated Champion of the Weird and Boggart Mischief entriesdocs/tdm-remediation.md — Marked Skirmish Rhino as fixedROADMAP.md — Added LoseLifeOpponents to recently-implemented list, marked card-level fixes as doneDid we add tests for our new effect? If not let's do that.
Good catch — we didn't. Let me check where the existing effect tests live.
Good, I see the pattern. Tests use Game::new_two_player with AlwaysPassPlayer and call execute_effects directly. Let me add a test for LoseLifeOpponents right after gain_life_effect.
Test passes. Now 158 engine tests (was 157) and 245 total.
How can we best do this work in a loop? Find a chunk of cards that share a common piece of missing implementation, implement the missing effect (if any) and implement the cards. Add tests for new effects, and update the documents. We should repeat this process over and over until we are done, using documents to maintain state instead of our context window.
Good question. The key is a work queue document that drives the loop — organized by shared missing implementation, not by card. Each iteration, you (or I) read the queue, pick the next batch, do the work, update docs, and move on. The context window never needs to hold the full history.
Let me scan the current state to build that queue.
Good data from the scan. Before building the work queue, let me verify the easy-swap cases precisely — cards where a typed effect already exists but the card still uses Custom.
Now let me also count the easy-swap candidates precisely.
Now I have a clear picture. Let me create the work queue document.
Now let me update the memory file so future sessions know about this process.
Here's the approach:
Created docs/work-queue.md — a batch-oriented work queue that drives each session. The process for every batch:
docs/work-queue.md → find next unchecked batchgame.rs), if neededcargo check && cargo test --libwork-queue.md| # | Batch | Engine work? | Cards |
|---|---|---|---|
| 1 | LoseLifeOpponents | New variant | 8 DONE |
| 2 | Easy swaps (DestroyAll, EntersTapped, CantBlock, self-counters, loot) | None | ~12 |
| 3 | OpponentDiscards | New variant | 3+ |
| 4 | Simple token creation (stubs with full text) | None | 6+ |
| 5 | Ward keyword | New StaticEffect | 6 |
| 6 | Enters-tapped-unless | New StaticEffect | 2 |
| 7 | Mass keyword grant EOT | New Effect | 5+ |
| 8 | Fight/Bite | New Effect | ~10 |
| 9 | Modal spells | New framework | 15+ |
| 10+ | Equipment, Planeswalkers, Token copy, X-cost, etc. | Major systems | many |
The key insight: docs are the state, not the context window. Any session can pick up where the last left off by reading work-queue.md. Want me to start on Batch 2 now?
What instructions would I give you to run the loop?
Something like this:
Read docs/work-queue.md and do the next unchecked batch. For each batch:
1. Read the engine files (mtg-engine/src/abilities.rs, mtg-engine/src/game.rs) to understand current Effect/StaticEffect variants
2. If engine work is needed: add the variant, constructor, and match arm
3. Add a test for any new engine effect (follow the pattern in game.rs mod tests)
4. Fix all cards listed in the batch (grep to find exact lines, fix across all 4 set files)
5. cargo check -p mtg-cards && cargo test --lib
6. Update docs: mark cards in docs/{fdn,tla,tdm,ecl}-remediation.md, update ROADMAP.md
7. Check off the batch in docs/work-queue.md with today's date
Then do the next batch. Repeat until context is getting long, then stop and make sure work-queue.md is current.
That's the core. A few things to consider:
What makes this work well:
work-queue.md has all the specifics (line numbers, exact substitutions, notes about gotchas)What could go wrong:
AddCounters works on self without a target. That needs investigation before fixing. The instructions should handle uncertainty gracefully.Optional additions:
If a batch has a NOTE about uncertainty, investigate first and update the queue with findings before committing the fix.If you discover new cards that fit an existing batch while working, add them.If a batch turns out to be wrong or more complex than described, update the queue and move on to the next one.Want me to put this into a file (like a CLAUDE.md section or a script) so it's always available, or is copy-paste sufficient?
Let's create a file called docs/work-queue-instructions.md. If you think the optional additions are valuable, add those as well.
Done. The file is at docs/work-queue-instructions.md — you can just paste its contents into a fresh session to kick off the loop. The queue state lives in docs/work-queue.md so sessions pick up where the last one left off.