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.
1005- Effect::GainLife { amount } => {
1006- if let Some(player) = self.state.players.get_mut(&controller) {
1007- player.life += *amount as i32;
1008- }
1009- }
1010: Effect::LoseLife { amount } => {
1011- // Controller loses life (target player effects will use
1012- // SelectedTargets for proper player targeting)
1013- if let Some(player) = self.state.players.get_mut(&controller) {
1014- player.life -= *amount as i32;
1015- }837: vec![Effect::Custom("Each opponent loses 1 life.".into())],
850: vec![Effect::Custom("Each opponent loses 1 life.".into())],
968: vec![Effect::Custom("Each opponent loses 2 life, you gain 2 life.".into())],
1412: vec![Effect::draw_cards(1), Effect::Custom("You lose 1 life.".into())],
1705: vec![Effect::Custom("Each opponent loses 1 life, you gain 1 life.".into())],
5979: vec![Effect::Custom("Each opponent loses X life. You gain life equal to the life lost this way.".into())],Now I have all the context I need. Let me also check the DealDamageOpponents implementation in game.rs to match the pattern.
Good, I have all the context. Let me also check the DealDamageOpponents implementation in game.rs for reference.
1007- player.life += *amount as i32;
1008- }
1009- }
1010- Effect::LoseLife { amount } => {
1011- // Controller loses life (target player effects will use
1012- // SelectedTargets for proper player targeting)
1013- if let Some(player) = self.state.players.get_mut(&controller) {
1014- player.life -= *amount as i32;
1015- }
1016- }
1017: Effect::DealDamageOpponents { amount } => {
1018- let opponents: Vec<PlayerId> = self.state.turn_order.iter()
1019- .filter(|&&id| id != controller)
1020- .copied()
1021- .collect();
1022- for opp in opponents {
1023- if let Some(player) = self.state.players.get_mut(&opp) {
1024- player.life -= *amount as i32;
1025- }
1026- }
1027- }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.
cd /mnt/dest-btrfs/home/jc/src/mage/mtg-rl && cargo check -p mtg-engine 2>&1 Checking mtg-engine v0.1.0 (/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-engine)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 2.24scargo check -p mtg-cards 2>&1 Checking mtg-cards v0.1.0 (/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-cards)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.84scargo test --lib 2>&1 Compiling mtg-engine v0.1.0 (/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-engine)
Compiling mtg-cards v0.1.0 (/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-cards)
Compiling mtg-ai v0.1.0 (/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-ai)
warning: fields `action` and `depth` are never read
--> mtg-ai/src/minimax_player.rs:62:5
|
60 | struct SearchNode {
| ---------- fields in this struct
61 | /// The action that led to this node (None for root).
62 | action: Option<PlayerAction>,
| ^^^^^^
...
70 | depth: u32,
| ^^^^^
|
= note: `SearchNode` has derived impls for the traits `Debug` and `Clone`, but these are intentionally ignored during dead code analysis
= note: `#[warn(dead_code)]` on by default
warning: associated functions `new_root` and `new_child` are never used
--> mtg-ai/src/minimax_player.rs:74:8
|
73 | impl SearchNode {
| --------------- associated functions in this implementation
74 | fn new_root(is_maximizing: bool) -> Self {
| ^^^^^^^^
...
84 | fn new_child(action: PlayerAction, depth: u32, is_maximizing: bool) -> Self {
| ^^^^^^^^^
warning: `mtg-ai` (lib) generated 2 warnings
Compiling mtg-python v0.1.0 (/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-python)
Compiling mtg-tests v0.1.0 (/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-tests)
warning: `mtg-ai` (lib test) generated 1 warning (1 duplicate)
Finished `test` profile [unoptimized + debuginfo] target(s) in 4.67s
Running unittests src/lib.rs (target/debug/deps/mtg_ai-a3cefc6981511e97)
running 52 tests
test action_space::tests::action_mask_pass_only ... ok
test action_space::tests::action_mask_with_castable_spell ... ok
test action_space::tests::phase1_activate_ability_roundtrip ... ok
test action_space::tests::phase1_all_indices_are_unique ... ok
test action_space::tests::phase1_cast_spell_roundtrip ... ok
test action_space::tests::phase1_out_of_range_returns_none ... ok
test action_space::tests::phase1_pass_roundtrip ... ok
test action_space::tests::phase1_play_land_roundtrip ... ok
test action_space::tests::phase2_roundtrip ... ok
test action_space::tests::reward_intermediate_negative_when_losing_advantage ... ok
test action_space::tests::reward_intermediate_positive_when_gaining_advantage ... ok
test action_space::tests::reward_win_is_positive ... ok
test action_space::tests::reward_loss_is_negative ... ok
test evaluator::tests::creature_permanent_scores_positively ... ok
test evaluator::tests::equal_board_state_scores_zero ... ok
test evaluator::tests::flying_creature_scores_higher ... ok
test evaluator::tests::keyword_scores_are_reasonable ... ok
test evaluator::tests::life_score_above_20 ... ok
test evaluator::tests::life_score_lookup_table ... ok
test evaluator::tests::loss_state_gives_min_score ... ok
test evaluator::tests::more_life_is_better ... ok
test evaluator::tests::win_state_gives_max_score ... ok
test gym::tests::env_action_mask_has_correct_size ... ok
test gym::tests::env_reset_returns_correct_size_observation ... ok
test gym::tests::env_space_sizes ... ok
test gym::tests::env_step_returns_valid_result ... ok
test heuristic_player::tests::heuristic_attacks_with_all ... ok
test heuristic_player::tests::heuristic_choose_use_says_yes_except_ai_dont_use ... ok
test heuristic_player::tests::heuristic_damage_assignment_correct_total ... ok
test heuristic_player::tests::heuristic_mulligan_keeps_small_hands ... ok
test heuristic_player::tests::heuristic_mulligan_mulligans_7_card_hand ... ok
test heuristic_player::tests::heuristic_passes_when_only_option ... ok
test heuristic_player::tests::heuristic_pile_prefers_larger_for_good ... ok
test gym::tests::env_truncates_at_max_turns ... ok
test heuristic_player::tests::heuristic_prefers_land_over_pass ... ok
test heuristic_player::tests::heuristic_prefers_land_over_spell ... ok
test heuristic_player::tests::heuristic_prefers_spell_over_pass ... ok
test minimax_player::tests::minimax_ab_alpha_beta_pruning ... ok
test minimax_player::tests::minimax_free_spell_preferred_over_regular ... ok
test minimax_player::tests::minimax_passes_when_only_option ... ok
test minimax_player::tests::minimax_prefers_land_over_spell ... ok
test minimax_player::tests::minimax_prefers_spell_over_pass ... ok
test minimax_player::tests::minimax_search_stats_tracked ... ok
test observation::tests::life_normalized_correctly ... ok
test observation::tests::observation_has_correct_size ... ok
test observation::tests::observation_size_constant_matches ... ok
test observation::tests::permanents_are_padded ... ok
test observation::tests::phase_one_hot_encoding ... ok
test random_player::tests::random_player_choose_use_returns_bool ... ok
test random_player::tests::random_player_chooses_from_legal_actions ... ok
test random_player::tests::random_player_passes_when_only_option ... ok
test random_player::tests::random_player_damage_assignment_sums_correctly ... ok
test result: ok. 52 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running unittests src/lib.rs (target/debug/deps/mtg_cards-c237fd9c64d2d797)
running 18 tests
test keywords::behold::tests::behold_elf_cost ... ok
test keywords::behold::tests::behold_goblin_cost ... ok
test keywords::behold::tests::behold_rules_text_format ... ok
test keywords::blight::tests::blight_1_cost ... ok
test keywords::blight::tests::blight_rules_text_plural ... ok
test keywords::blight::tests::blight_2_cost ... ok
test keywords::blight::tests::blight_rules_text_singular ... ok
test keywords::mobilize::tests::mobilize_1_creates_triggered_ability ... ok
test keywords::mobilize::tests::mobilize_2_creates_two_tokens ... ok
test keywords::mobilize::tests::mobilize_3 ... ok
test registry::tests::ecl_specific_cards ... ok
test registry::tests::fdn_tier3_cards ... ok
test registry::tests::ecl_tier2_spells ... ok
test registry::tests::registry_create_and_lookup ... ok
test registry::tests::fdn_tier2_cards ... ok
test registry::tests::registry_card_counts ... ok
test registry::tests::registry_set_query ... ok
test registry::tests::tdm_tier2_spells ... ok
test result: ok. 18 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running unittests src/lib.rs (target/debug/deps/mtg_engine-10c8e693567e8570)
running 157 tests
test abilities::tests::ability_store ... ok
test abilities::tests::attacks_triggered ... ok
test abilities::tests::active_zones ... ok
test abilities::tests::activated_ability ... ok
test abilities::tests::combat_damage_triggered ... ok
test abilities::tests::compose_realistic_card_lightning_bolt ... ok
test abilities::tests::compose_realistic_card_llanowar_elves ... ok
test abilities::tests::compose_realistic_card_mulldrifter ... ok
test abilities::tests::cost_builders ... ok
test abilities::tests::compose_realistic_lord ... ok
test abilities::tests::dies_triggered ... ok
test abilities::tests::end_step_triggered ... ok
test abilities::tests::effect_builders ... ok
test abilities::tests::mana_ability ... ok
test abilities::tests::etb_triggered ... ok
test abilities::tests::spell_ability ... ok
test abilities::tests::static_ability_boost ... ok
test abilities::tests::optional_trigger ... ok
test abilities::tests::static_effect_builders ... ok
test abilities::tests::triggered_ability ... ok
test abilities::tests::upkeep_triggered ... ok
test card::tests::create_basic_creature ... ok
test combat::tests::blocked_damage ... ok
test card::tests::create_basic_land ... ok
test combat::tests::blocker_damage_timing ... ok
test combat::tests::blocking ... ok
test combat::tests::combat_clear ... ok
test combat::tests::combat_state_basics ... ok
test combat::tests::deathtouch_minimizes_damage ... ok
test combat::tests::double_strike_both_steps ... ok
test combat::tests::first_strike_timing ... ok
test combat::tests::flying_blocks ... ok
test combat::tests::menace_requires_two_blockers ... ok
test combat::tests::trample_overflow ... ok
test combat::tests::unblocked_damage ... ok
test counters::tests::add_and_remove_counters ... ok
test counters::tests::counter_type_from_name ... ok
test counters::tests::pt_modification ... ok
test effects::tests::boost_effect ... ok
test effects::tests::continuous_effects_manager ... ok
test effects::tests::keyword_grant_effect ... ok
test effects::tests::remove_from_source ... ok
test effects::tests::replacement_effects ... ok
test effects::tests::timestamp_ordering ... ok
test events::tests::applied_effects_tracking ... ok
test events::tests::combat_events ... ok
test events::tests::damage_player_convenience ... ok
test events::tests::event_builder ... ok
test events::tests::event_log ... ok
test events::tests::prevention ... ok
test events::tests::spell_cast_event ... ok
test events::tests::zone_change_event ... ok
test filters::tests::filter_card_data ... ok
test filters::tests::filter_color ... ok
test filters::tests::filter_creature ... ok
test filters::tests::filter_controller ... ok
test filters::tests::filter_keyword ... ok
test filters::tests::filter_nonland_permanent ... ok
test filters::tests::filter_mana_value ... ok
test filters::tests::filter_tapped_untapped ... ok
test filters::tests::filter_power_comparison ... ok
test filters::tests::filter_not_combinator ... ok
test game::tests::bounce_effect ... ok
test game::tests::gain_life_effect ... ok
test game::tests::counter_annihilation_applied ... ok
test mana::tests::can_pay_colored ... ok
test game::tests::spell_effects_execute_on_resolve ... ok
test game::tests::legend_rule_applied ... ok
test game::tests::pay_costs_tap_and_sacrifice ... ok
test mana::tests::cost_colors ... ok
test game::tests::draw_cards_effect ... ok
test mana::tests::mana_arithmetic ... ok
test mana::tests::mana_display ... ok
test mana::tests::parse_simple_cost ... ok
test mana_pool::tests::add_and_spend_colored ... ok
test game::tests::exile_effect ... ok
test mana_pool::tests::clear_empties_pool ... ok
test mana_pool::tests::any_mana_pays_colored ... ok
test mana_pool::tests::color_array ... ok
test game::tests::fizzle_when_target_removed ... ok
test mana_pool::tests::spend_generic_prefers_colorless ... ok
test mana_pool::tests::try_pay_atomic ... ok
test mana::tests::can_pay_generic ... ok
test game::tests::activated_ability_goes_on_stack ... ok
test permanent::tests::damage_and_lethal ... ok
test permanent::tests::counters_modify_pt ... ok
test permanent::tests::creature_basics ... ok
test permanent::tests::defender_cannot_attack ... ok
test filters::tests::filter_or_combinator ... ok
test permanent::tests::haste_ignores_summoning_sickness ... ok
test game::tests::game_creation ... ok
test permanent::tests::keyword_grants ... ok
test game::tests::legal_actions_include_pass ... ok
test game::tests::mana_ability_and_spell_cast ... ok
test player::tests::starting_values ... ok
test state::tests::can_cast_sorcery ... ok
test permanent::tests::tap_untap ... ok
test player::tests::discard_check ... ok
test state::tests::initial_state ... ok
test player::tests::find_card_in_zones ... ok
test player::tests::game_over_states ... ok
test state::tests::sba_counter_annihilation ... ok
test state::tests::next_player_wraps ... ok
test player::tests::land_plays ... ok
test state::tests::sba_legend_rule ... ok
test state::tests::sba_legend_rule_different_controllers ... ok
test state::tests::sba_indestructible_survives_damage ... ok
test player::tests::life_changes ... ok
test state::tests::sba_lethal_damage ... ok
test state::tests::sba_life_loss ... ok
test player::tests::poison_counters ... ok
test state::tests::sba_no_counter_annihilation_when_only_one_type ... ok
test mana::tests::cannot_pay_insufficient ... ok
test state::tests::sba_poison ... ok
test state::tests::sba_planeswalker_zero_loyalty ... ok
test game::tests::draw_cards_from_empty_library_causes_loss ... ok
test targets::tests::mark_player_illegal ... ok
test state::tests::sba_zero_toughness ... ok
test state::tests::should_end_one_player_left ... ok
test targets::tests::target_definition_basics ... ok
test targets::tests::selected_targets_players ... ok
test state::tests::zone_tracking ... ok
test targets::tests::target_definition_up_to ... ok
test targets::tests::divided_amounts ... ok
test targets::tests::empty_targets ... ok
test turn::tests::end_turn_skips_to_cleanup ... ok
test targets::tests::mark_illegal ... ok
test turn::tests::extra_turns ... ok
test turn::tests::extra_turns_lifo ... ok
test turn::tests::step_to_phase_mapping ... ok
test targets::tests::selected_targets_objects ... ok
test watchers::tests::custom_watcher_player_scope ... ok
test turn::tests::priority_rules ... ok
test watchers::tests::custom_watcher_reset ... ok
test turn::tests::priority_tracking ... ok
test watchers::tests::reset_clears_stats ... ok
test turn::tests::turn_rotation ... ok
test watchers::tests::track_attackers ... ok
test watchers::tests::track_cards_drawn ... ok
test turn::tests::turn_step_sequence ... ok
test watchers::tests::track_creatures_died ... ok
test watchers::tests::custom_watcher_card_scope ... ok
test watchers::tests::track_enters_battlefield ... ok
test watchers::tests::custom_watcher_game_scope ... ok
test turn::tests::first_strike_step ... ok
test zones::tests::exile_zones ... ok
test watchers::tests::track_life_gained ... ok
test zones::tests::library_draw ... ok
test zones::tests::library_peek ... ok
test watchers::tests::track_spells_cast ... ok
test zones::tests::battlefield_add_remove ... ok
test zones::tests::library_put_on_top_and_bottom ... ok
test zones::tests::stack_lifo ... ok
test zones::tests::battlefield_controlled_by ... ok
test zones::tests::graveyard_ordering ... ok
test zones::tests::hand_add_remove ... ok
test game::tests::game_runs_to_completion ... ok
test result: ok. 157 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.02s
Running unittests src/lib.rs (target/debug/deps/mtg_python-d2f2ac684d6d76ac)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running unittests src/lib.rs (target/debug/deps/mtg_tests-fac26c7f0fd5aee5)
running 17 tests
test concurrency::tests::game_state_clone_is_independent ... ok
test framework::tests::framework_bolt_kills_bear ... ok
test framework::tests::framework_direct_effect_execution ... ok
test framework::tests::framework_custom_starting_life ... ok
test framework::tests::framework_exile_and_assert ... ok
test concurrency::tests::gymnasium_env_runs_end_to_end ... ok
test framework::tests::framework_draw_cards_effect ... ok
test framework::tests::framework_life_totals ... ok
test framework::tests::framework_multiple_permanents ... ok
test framework::tests::framework_gain_life_effect ... ok
test framework::tests::framework_setup_graveyard ... ok
test framework::tests::framework_setup_hand ... ok
test framework::tests::framework_setup_battlefield ... ok
test concurrency::tests::parallel_gymnasium_envs ... ok
test concurrency::tests::throughput_baseline ... ok
test concurrency::tests::parallel_games_produce_varied_results ... ok
test concurrency::tests::parallel_games_complete_without_panic ... ok
test result: ok. 17 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.27sAll 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.
mtg-cards/src/sets/fdn.rs:5979: vec![Effect::Custom("Each opponent loses X life. You gain life equal to the life lost this way.".into())],
mtg-cards/src/sets/ecl.rs:465: vec![Effect::Custom("Each opponent loses 2 life.".into())],
mtg-cards/src/sets/ecl.rs:1833: vec![Effect::Custom("Each opponent loses 1 life, you gain 1 life.".into())],
mtg-cards/src/sets/tdm.rs:1040: vec![Effect::Custom("Each opponent loses 2 life.".into()), Effect::gain_life(2)],No matches found
mtg-cards/src/sets/fdn.rs:5340: vec![Effect::Custom("When this creature enters, each opponent discards a card and loses 2 life. You draw a card and gain 2 life.".into())],
mtg-cards/src/sets/fdn.rs:6740: vec![Effect::Custom("Whenever an opponent casts a spell, that player loses 5 life unless they discard a card.".into())],
mtg-cards/src/sets/tdm.rs:1040: vec![Effect::Custom("Each opponent loses 2 life.".into()), Effect::gain_life(2)],
mtg-cards/src/sets/ecl.rs:465: vec![Effect::Custom("Each opponent loses 2 life.".into())],
mtg-cards/src/sets/ecl.rs:974: vec![Effect::Custom("Blight 2 or lose 3 life.".into())],
mtg-cards/src/sets/ecl.rs:1833: vec![Effect::Custom("Each opponent loses 1 life, you gain 1 life.".into())],
mtg-cards/src/sets/ecl.rs:3742: vec![Effect::Custom("When this creature enters, mill three cards. Then if there is an Elf card in your graveyard, each opponent loses 2 life and you gain 2 life.".into())],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.
cargo check -p mtg-cards 2>&1 Checking mtg-cards v0.1.0 (/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-cards)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.74sGood. Now let me check the remediation docs for ECL and TDM to understand what needs updating there too.
docs/ecl-remediation.md-168- - **What it should do**: {2}{W}: Gains indestructible until end of turn.
docs/ecl-remediation.md-169- - **Status**: Keyword grant now implemented. Card may be fully functional.
/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/docs/ecl-remediation.md-170-
docs/ecl-remediation.md:171:- [ ] **Champion of the Weird** — What works: none of the effects. What's broken: Both `Cost::Custom` and `Effect::Custom` on all abilities.
docs/ecl-remediation.md-172- - **Java source**: `Mage.Sets/src/mage/cards/c/ChampionOfTheWeird.java`
docs/ecl-remediation.md-173- - **What it should do**: Behold a Goblin + exile it. {1}{B}, blight 1: each opponent loses 2 life. Leaves: return exiled card.
docs/ecl-remediation.md-174- - **Fix needed**: Implement behold mechanic, `LoseLife` for opponents, and return-exiled-card effect.
--
docs/ecl-remediation.md-423- - **What it should do**: +1/+0, then fights opponent's creature.
docs/ecl-remediation.md-424- - **Fix needed**: Implement `Effect::Fight`.
/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/docs/ecl-remediation.md-425-
docs/ecl-remediation.md:426:- [ ] **Boggart Mischief** — What works: stats. What's broken: Both ETB and dies triggers are `Effect::Custom`.
docs/ecl-remediation.md-427- - **Java source**: `Mage.Sets/src/mage/cards/b/BoggartMischief.java`
docs/ecl-remediation.md-428- - **What it should do**: ETB: blight 1 to create 2 Goblin tokens. Goblin dies: opponents lose 1, you gain 1.
docs/ecl-remediation.md-429- - **Fix needed**: Complex — needs blight choice + conditional token + drain trigger.
--
docs/tdm-remediation.md-354- - **Java source**: `Mage.Sets/src/mage/cards/s/SibsigAppraiser.java`
docs/tdm-remediation.md-355- - **Fix needed**: "Look at top N, distribute" effect.
/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/docs/tdm-remediation.md-356-
docs/tdm-remediation.md:357:- [ ] **Skirmish Rhino** — 3/4 trample for {W}{B}{G}. What works: GainLife 2. What's broken: `Effect::Custom("Each opponent loses 2 life.")` (should use DealDamageOpponents).
docs/tdm-remediation.md-358- - **Fix needed**: Replace `Custom("Each opponent loses 2 life.")` with `Effect::DealDamageOpponents { amount: 2 }`.
/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/docs/tdm-remediation.md-359-
docs/tdm-remediation.md-360-- [ ] **Snakeskin Veil** — Instant {G}. What works: AddCounters +1/+1 + Hexproof until EOT. **(NOW IMPLEMENTED — Hexproof NOW WORKS)**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.
cargo test --lib 2>&1 | tail -20test concurrency::tests::game_state_clone_is_independent ... ok test framework::tests::framework_custom_starting_life ... ok test framework::tests::framework_direct_effect_execution ... ok test framework::tests::framework_draw_cards_effect ... ok test framework::tests::framework_bolt_kills_bear ... ok test framework::tests::framework_gain_life_effect ... ok test framework::tests::framework_exile_and_assert ... ok test concurrency::tests::gymnasium_env_runs_end_to_end ... ok test framework::tests::framework_life_totals ... ok test framework::tests::framework_multiple_permanents ... ok test framework::tests::framework_setup_hand ... ok test framework::tests::framework_setup_battlefield ... ok test framework::tests::framework_setup_graveyard ... ok test concurrency::tests::parallel_gymnasium_envs ... ok test concurrency::tests::throughput_baseline ... ok test concurrency::tests::parallel_games_produce_varied_results ... ok test concurrency::tests::parallel_games_complete_without_panic ... ok test result: ok. 17 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.30s
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.
1931: fn spell_effects_execute_on_resolve() {
2054: fn draw_cards_effect() {
2088: fn gain_life_effect() {
2113: fn exile_effect() {
2150: fn bounce_effect() {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.
cargo test --lib -p mtg-engine lose_life_opponents 2>&1 Compiling mtg-engine v0.1.0 (/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-engine)
Finished `test` profile [unoptimized + debuginfo] target(s) in 3.33s
Running unittests src/lib.rs (target/debug/deps/mtg_engine-fa37b04e04026fe5)
running 1 test
test game::tests::lose_life_opponents_effect ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 157 filtered out; finished in 0.00sTest 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.
The user wants to create a repeatable process for fixing cards in batches. Let me think about what they're asking:
They want this to be sustainable across context windows, using the docs as state. Let me think about the best approach.
The key insight is that we need a "work queue" document that tracks what's been done and what's next, organized by shared missing implementation. Let me look at what's currently in the remediation docs to understand the landscape of fixable card groups.
Now I'm reviewing the existing card groups to see what's already been addressed and what patterns emerge. I can see LoseLifeOpponents is done, there are several cards that just need simple effect swaps since the implementations already exist, and there are larger groups organized by the type of missing effect they need.
I'm thinking about how to structure this work systematically—organizing cards by their shared missing pieces, estimating the effort for each group, and tracking dependencies so we can tackle things in the right order. I should create a work queue document that groups everything clearly and gives us a concrete path forward.
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.
Excellent! Now let me create the final comprehensive report:
## COMPREHENSIVE CUSTOM EFFECT ANALYSIS & CATEGORIZATION
### EXECUTIVE SUMMARY
**Total Custom Usages: ~825 across 4 sets**
- FDN: 320 (largest set)
- TDM: 120
- TLA: 197
- ECL: 188
**Breakdown:**
- **~301 placeholders** (Static/ETB/Activated/Spell stubs) - LOW VALUE, can ignore
- **~524 real game effects** - Grouped into 22 categories for prioritized implementation
---
## HIGH-ROI GROUPS (Best bang-for-buck)
### **GROUP 1: PLACEHOLDER STUBS** (301 total - IGNORE THESE)
- "Static effect." (108)
- "ETB effect." (94)
- "Activated effect." (73)
- "Spell effect." (26)
- "Attack trigger." (22)
- Other minor stubs (Spell cast trigger, Dies effect, etc.)
**Engine work needed:** NONE - These disappear as cards get real implementations. They safely resolve to `Custom("{text}")` which returns `Ok(())`.
---
### **GROUP 2: COMBAT & EVASION** (14 cards across 3 sets)
**Description:** Creatures with special combat rules (unblockable, can't block, etc.)
**Examples from code:**
- Line 2019 FDN: `"Can't be blocked by Humans."`
- FDN also has: `"Can't be blocked."`, `"Can't be blocked this turn."` (multiple)
- TDM: `"This creature can't be blocked this turn."`
- ECL: `"Can't be blocked by more than one creature."`, `"Daunt — can't be blocked by power 2 or less."`
**Engine work needed:**
```
StaticEffect::CantBlock (permanent, all blockers)
StaticEffect::CantBlockEot (until end of turn)
StaticEffect::UnblockableByType("Humans") (specific types)
StaticEffect::CantBeBlockedByMoreThanN(1)
```
**Impact:** 8-10 FDN/TDM/ECL cards fixed immediately
---
### **GROUP 3: WARD & COST REDUCTION** (13+ unique patterns)
**Description:** Protective costs and spell cost reduction mechanics
**Examples from code:**
- Line 347 TDM: `StaticEffect::Custom("Ward {2}".into())` (5 occurrences in TDM alone)
- Line 2019 TDM: Same Ward pattern repeats
- FDN has 4 distinct "This spell costs {N} less to cast if..." patterns
- ECL: `Evoke {R/W}{R/W}`, `Evoke {U/B}{U/B}`, `Evoke {W/B}{W/B}`
**Engine work needed:**
```
StaticEffect::Ward { cost: ManaCost } or Effect::Ward { cost }
SpellCostReduction with condition support
Evoke variants as cost reduction mechanics
```
**Impact:** 6+ TDM/ECL cards fixed immediately (Ward alone)
---
### **GROUP 4: DISTRIBUTED COUNTERS** (6+ cards, TDM focus)
**Description:** Put +1/+1 counters distributed among multiple creatures
**Examples from code:**
- TDM: `"Distribute three +1/+1 counters among one, two, or three target creatures you control."`
- TDM: `"Distribute two +1/+1 counters among one or two target creatures you control."`
**Engine work needed:**
```
Effect::DistributeCounters {
count: i32,
filter: String,
max_targets: Option<i32>
}
```
**Impact:** 6+ TDM cards
---
### **GROUP 5: MORBID & THRESHOLD CONDITIONALS** (7 cards, FDN only)
**Description:** Conditional effects based on game state (creatures died, graveyard size, etc.)
**Examples from code:**
- Line 5605 FDN: `"Morbid -- At the beginning of your end step, if a creature died this turn put a +1/+1 counter on this creature."`
- Line 6692 FDN: `"Morbid -- At the beginning of your end step, if a creature died this turn, put two +1/+1 counters on target creature you control."`
- Line 7125 FDN: `"Morbid -- At the beginning of each end step, if a creature died this turn, untap this creature."`
- Also Threshold effects: `"Threshold -- This creature can't be blocked as long as there are seven or more cards in your graveyard."`
**Engine work needed:**
```
Proper event system for Morbid (creature died this turn)
StaticEffect::ConditionalPT { condition, power, toughness }
Threshold checking mechanism
```
**Impact:** 7 FDN cards isolated to one set
---
### **GROUP 6: SAGA/ENCHANTMENT PROGRESSION** (6 cards)
**Description:** Saga mechanics with multi-step lore counter progression
**Examples from code:**
- TDM (4x): `"(As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)"`
- TLA (2x): Similar saga patterns
**Engine work needed:**
- Lore counter tracking
- Episode 1/2/3 effect triggers at each threshold
- Sacrifice after 3 counters
**Impact:** 6 cards (4 TDM, 2 TLA)
---
### **GROUP 7: LOYALTY/PLANESWALKER EFFECTS** (8-10 cards)
**Description:** Planeswalker abilities with +1/0/−X loyalty costs
**Examples from code:**
- FDN: `"+1: Create a token that's a copy of target creature you control, except it has haste..."`
- FDN: `"+1: Look at the top four cards of your library..."`
- FDN: `"+1: Put a +1/+1 counter on up to one target creature."`
**Engine work needed:**
- Planeswalker loyalty tracking
- Loyalty ability cost system
**Impact:** 8-10 cards across FDN/other sets
---
### **GROUP 8: CREATE TOKENS** (20+ cards, all 4 sets)
**Description:** Token creation with various properties (generic, dynamic count, modified properties)
**Examples from code:**
- FDN: `"Create four 1/1 white Dog creature tokens."`, `"Create two 1/1 blue Faerie creature tokens with flying."`
- FDN: `"Create a token that's a copy of target creature you control, except it has haste and 'At the beginning of the end step, sacrifice this token.'"`
- TDM line 436: `"Create X 1/1 white Soldier creature tokens, where X is the number of creature cards in your graveyard."`
- TDM line 2170: `"Create X 1/1 white Spirit creature tokens with flying, where X is the number of creatures destroyed this way."`
- ECL: Kithkin token variations, token copy variants
**Engine work needed:**
```
Enhance CreateToken to support:
- Token copies with haste + sacrifice at EOT
- Tapped/attacking tokens (TDM patterns)
- Dynamic count (X = graveyard size, X = creatures destroyed, etc.)
- Flying variants for specific token types
```
**Impact:** 15+ cards across all 4 sets
---
### **GROUP 9: DRAW + CONDITIONAL/OPTIONAL EFFECTS** (15+ cards)
**Description:** Card draw linked to conditions, optional discard/draw combos
**Examples from code:**
- TDM: `"Draw a card if you control a creature with a counter on it. If you don't, put a +1/+1 counter on this creature."`
- TDM: `"Discard two cards unless you discard a creature card."`
- TDM: `"Discard up to two cards, then draw that many cards."`
- TDM: `"You may discard a card. If you do, draw a card."`
**Engine work needed:**
```
Effect::OptionalDiscard { count: i32 }
Conditional draw/discard chains
```
**Impact:** 15+ cards (FDN/TDM/ECL)
---
### **GROUP 10: CREATURE TYPE SELECTION** (8+ cards)
**Description:** Choose a creature type and apply type-based effects
**Examples from code:**
- FDN: `"Choose a creature type. This creature becomes that type."`
- ECL: `"Choose a creature type on ETB."`, `"Choose a creature type. Return all creature cards of the chosen type from your graveyard to the battlefield."`
- FDN: `"Chosen type creatures get +1/+1 per fellowship counter."`
**Engine work needed:**
- Type selection mechanism
- Type-based boosting/filtering
**Impact:** 8+ cards (FDN/ECL)
---
### **GROUP 11: LAND SEARCH & TUTORS** (12+ cards)
**Description:** Search library for lands or cards with specific conditions
**Examples from code:**
- FDN: `"Sacrifice a land, search for 2 basic lands tapped."`, `"Search your library for up to two basic lands and/or Gates..."`
**Engine work needed:**
- Enhanced `SearchLibrary` with multi-type support
- Sacrifice-as-cost mechanics
**Impact:** 12+ cards (mostly FDN)
---
### **GROUP 12: TRIGGERED ON-ATTACK** (8+ unique patterns)
**Description:** Various triggers when creatures attack
**Examples from code:**
- FDN: `"Whenever Drakuseth, Maw of Flames attacks, it deals 4 damage to any target and 3 damage to each of up to two other targets."`
- FDN: `"Whenever this creature attacks, you may sacrifice another creature. If you do, draw a card and this creature can't be blocked this turn."`
**Engine work needed:**
- Better attack trigger delegation
- Dynamic multi-target handling
**Impact:** 8+ cards (FDN dominant)
---
### **GROUP 13: MODAL/CHOICE EFFECTS** (15+ cards)
**Description:** Spells with "Choose one:" or "Choose two:" options
**Examples from code:**
- Line 2495 FDN: `"Choose one: Deal 3 damage to target creature, or destroy target artifact."`
- Line 2507 FDN: `"Choose one: 4 damage to target player/planeswalker; or permanents you control gain indestructible until EOT; or target creature gains double strike until EOT."`
- TDM: `"Choose one: Creatures target opponent controls get -1/-1 until end of turn; or return up to two creature cards from your graveyard to your hand."`
- ECL: `"Choose two: copy target Kithkin you control; target player creates 1/1 Kithkin token; ..."`
**Engine work needed:**
- Proper modal effect system with player choice resolution
- Support for X or Y or Z patterns
- Multi-mode (Choose two) support
**Impact:** 15+ cards across all sets (FDN dominant)
---
## MEDIUM-VALUE GROUPS
### **GROUP 14: BLIGHT MECHANIC** (5 cards, ECL focus)
`"As an additional cost to cast this spell, blight 1 or pay {3}."`, `"Blight 1, create a Treasure token."`, etc.
---
### **GROUP 15: COLOR-DEPENDENT EFFECTS** (8+ cards, ECL)
`"Deals X damage where X = colors among your permanents."`, `"Draw X cards where X = colors among your permanents."`, etc.
---
### **GROUP 16-21:** Additional medium-priority groups
Flicker/Bounce, Destruction variants, Graveyard recursion, Life gain/loss variants, Extra combat phases, Copy & casting effects
---
## RECOMMENDED IMPLEMENTATION ROADMAP
### **QUICK WINS (5-10 min each):**
1. **Combat/Evasion** - Add `StaticEffect::CantBlock`, `CantBlockEot` (fixes ~8-10 cards)
2. **Ward** - Convert to proper effect (fixes ~6 cards immediately in TDM/ECL)
3. **Enhanced CreateToken** - Support copies, haste+sac, dynamic count (fixes ~15+ cards)
### **MEDIUM-EFFORT (20-30 min each):**
4. **Distributed Counters** (6+ cards)
5. **Morbid/Threshold conditionals** (7 FDN cards)
6. **Modal/Choice effects** (15+ cards across all sets)
### **LONGER-TERM:**
7. Saga mechanics (6 cards)
8. Set-specific: Blight, Endure, Evoke, Harmonize, etc.
---
## FILE DISTRIBUTION SUMMARY
| Group | FDN | TDM | TLA | ECL | Total |
|-------|-----|-----|-----|-----|-------|
| Combat/Evasion | 9 | 2 | - | 1 | 14 |
| Ward & Cost Reduction | 4 | 6 | - | 3+ | 13+ |
| Create Tokens | 4 | 6 | 2 | 8+ | 20+ |
| Morbid/Threshold | 7 | - | - | - | 7 |
| Modal/Choice | 8+ | 4+ | - | 2+ | 15+ |
| Draw/Conditional | 3 | 8+ | 2 | 2+ | 15+ |
| Saga | - | 4 | 2 | - | 6 |
All exact file locations are in `/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-cards/src/sets/{fdn,tla,tdm,ecl}.rs`
agentId: a8903eb (for resuming to continue this agent's work if needed)
<usage>total_tokens: 52082
tool_uses: 12
duration_ms: 212009</usage>The user wants me to design a repeatable process for fixing cards in batches. The exploration agent gave me a good overview of the groups. Now I need to:
Let me also do my own quick verification of the "easy swap" cases - cards where a typed effect already exists but the card still uses Custom. Those are the quickest wins and should be the first batches.
Let me grep for specific easy-swap patterns to get accurate counts before building the 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.
grep -n 'Effect::Custom(' mtg-cards/src/sets/*.rs | grep -viE '(effect\.|static effect\.|activated effect\.|spell effect\.|attack trigger\.|etb effect\.|dies effect\.|spell cast trigger\.|combat damage trigger\.|triggered effect\.)' | grep -iE '(destroy all|each opponent discard|enters tapped|can.t block this turn|target creature can.t block|loses? \d+ life|gains? \d+ life|deal.? \d+ damage|draw.? (a |one |\d+ )card|scry|mill|create.*token|search.*library|return.*graveyard.*hand|exile target|counter target|tap target|untap target|sacrifice a|discard a card)' | head -60mtg-cards/src/sets/ecl.rs:431: vec![Effect::Custom("Target creature can't block this turn.".into())],
mtg-cards/src/sets/ecl.rs:596: vec![Effect::Custom("Put a -1/-1 counter on Dream Seizer. Each opponent discards a card.".into())],
mtg-cards/src/sets/ecl.rs:868: vec![Effect::Custom("Tap or untap target creature.".into())],
mtg-cards/src/sets/ecl.rs:992: vec![Effect::scry(1), Effect::Custom("Remove a -1/-1 counter from Heirloom Auntie.".into())],
mtg-cards/src/sets/ecl.rs:1162: vec![Effect::Custom("Create a tapped Mutavault land token.".into())],
mtg-cards/src/sets/ecl.rs:1334: vec![Effect::Custom("Blight 1, create a Treasure token.".into())],
mtg-cards/src/sets/ecl.rs:1438: vec![Effect::Custom("Put two -1/-1 counters on Sourbread Auntie.".into()), Effect::create_token("1/1 Goblin", 2)],
mtg-cards/src/sets/ecl.rs:1646: vec![Effect::Custom("Pay 1 life to draw a card.".into())],
mtg-cards/src/sets/ecl.rs:1735: abilities: vec![Ability::spell(id, vec![Effect::exile(), Effect::Custom("Its controller creates a 1/1 colorless Shapeshifter creature token with changeling.".into())], TargetSpec::Creature)],
mtg-cards/src/sets/ecl.rs:1829: vec![Effect::Custom("Blight 1 to create two 1/1 Goblin Rogue tokens.".into())],
mtg-cards/src/sets/ecl.rs:1871: vec![Effect::Custom("Put creature MV<=2 from hand onto battlefield with haste, sacrifice at end step.".into())],
mtg-cards/src/sets/ecl.rs:1909: vec![Effect::Custom("Vivid — This spell costs {1} less to cast for each color among permanents you control. Tap up to two target creatures. Put a stun counter on each of them. Draw a card.".into())],
mtg-cards/src/sets/ecl.rs:2108: vec![StaticEffect::Custom("Pay 2 life or enters tapped.".into())]),
mtg-cards/src/sets/ecl.rs:2173: vec![Effect::Custom("Choose two: copy target Kithkin you control; target player creates 1/1 Kithkin token; target creature +3/+3 until EOT; target creature you control fights target creature an opponent controls.".into())],
mtg-cards/src/sets/ecl.rs:3636: vec![Effect::Custom("+1: Create a 1/1 green and white Kithkin creature token.".into())],
mtg-cards/src/sets/ecl.rs:3704: vec![Effect::Custom("When this enchantment enters, create two 1/1 green and white Kithkin creature tokens.".into())],
mtg-cards/src/sets/ecl.rs:3709: vec![Effect::Custom("{4}{W}: Create a 1/1 green and white Kithkin creature token.".into())],
mtg-cards/src/sets/ecl.rs:3742: vec![Effect::Custom("When this creature enters, mill three cards. Then if there is an Elf card in your graveyard, each opponent loses 2 life and you gain 2 life.".into())],
mtg-cards/src/sets/ecl.rs:3791: vec![Effect::Custom("When this creature enters, you may discard a card. If you do, search your library for a creature card, reveal it, put it into your hand, then shuffle.".into())],
mtg-cards/src/sets/ecl.rs:3898: vec![Effect::Custom("Create a token that's a copy of target creature you control, except it has haste and \"At the beginning of the end step, sacrifice this token.\"".into())],
mtg-cards/src/sets/ecl.rs:3981: vec![Effect::Custom("When this creature enters, draw a card.".into())],
mtg-cards/src/sets/ecl.rs:4063: vec![Effect::Custom("Exile target creature you control, then return that card to the battlefield under its owner's control. Create a 1/1 colorless Shapeshifter creature token with changeling.".into())],
mtg-cards/src/sets/ecl.rs:4156: vec![Effect::Custom("Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. Then if you control seven or more lands and/or Treefolk, create a 3/4 green Treefolk creature token with re".into())],
mtg-cards/src/sets/ecl.rs:4217: vec![Effect::Custom("{1}, {T}, Tap another untapped creature you control: Tap target creature an opponent controls.".into())],
mtg-cards/src/sets/fdn.rs:617: vec![Effect::Custom("Each opponent discards a card.".into())],
mtg-cards/src/sets/fdn.rs:644: vec![StaticEffect::Custom("Enters tapped.".into())]),
mtg-cards/src/sets/fdn.rs:734: vec![Effect::Custom("Tap target creature defending player controls. It doesn't untap during its controller's next untap step.".into())],
mtg-cards/src/sets/fdn.rs:766: vec![Effect::Custom("Create 2/2 Homunculus tokens equal to instants/sorceries in your graveyard.".into())],
mtg-cards/src/sets/fdn.rs:1255: vec![Effect::Custom("Create a token that's a copy of target creature you control, except it has haste. Sacrifice it at the beginning of the next end step.".into())],
mtg-cards/src/sets/fdn.rs:1291: vec![Effect::Custom("Create a token that's a copy of target creature. If this spell was kicked, create five of those tokens instead.".into())],
mtg-cards/src/sets/fdn.rs:1316: vec![Effect::Custom("Create a token that's a copy of target creature you control.".into())],
mtg-cards/src/sets/fdn.rs:1780: vec![Effect::Custom("Target creature can't block this turn.".into())],
mtg-cards/src/sets/fdn.rs:1931: vec![Effect::Custom("Sacrifice a land, search for 2 basic lands tapped.".into())],
mtg-cards/src/sets/fdn.rs:2354: Effect::Custom("Until end of turn, target creature gains 'When this creature dies, return it to the battlefield tapped under its owner's control and create a Treasure token.'".into())],
mtg-cards/src/sets/fdn.rs:2417: vec![Effect::Custom("Destroy all creatures.".into())],
mtg-cards/src/sets/fdn.rs:3054: vec![Effect::Custom("Choose one: Scry 2; gain 3 life; flicker another creature.".into())],
mtg-cards/src/sets/fdn.rs:3086: vec![Effect::Custom("Each opponent mills until land revealed.".into())],
mtg-cards/src/sets/fdn.rs:5296: vec![Effect::Custom("Counter target noncreature spell. Its controller creates two Treasure tokens.".into())],
mtg-cards/src/sets/fdn.rs:5340: vec![Effect::Custom("When this creature enters, each opponent discards a card and loses 2 life. You draw a card and gain 2 life.".into())],
mtg-cards/src/sets/fdn.rs:5453: vec![Effect::Custom("Destroy target creature. Create a Food token.".into())],
mtg-cards/src/sets/fdn.rs:5520: vec![Effect::Custom("When this creature enters, if an opponent lost life this turn, each opponent discards a card.".into())],
mtg-cards/src/sets/fdn.rs:5556: vec![Effect::Custom("For each land you control, create a colorless Treasure artifact token with \"{T}, Sacrifice this artifact: Add one mana of any color.\"".into())],
mtg-cards/src/sets/fdn.rs:5572: vec![Effect::Custom("{3}, Sacrifice Burnished Hart: Search your library for up to two basic land cards, put them onto the battlefield tapped, then shuffle your library.".into())],
mtg-cards/src/sets/fdn.rs:5623: vec![Effect::Custom("When this creature enters, create a Food token.".into())],
mtg-cards/src/sets/fdn.rs:5652: vec![Effect::Custom("Return target creature card from your graveyard to your hand. If it's a Zombie card, draw a card.".into())],
mtg-cards/src/sets/fdn.rs:5687: vec![Effect::Custom("+1: Create a token that's a copy of target creature you control, except it has haste and \"At the beginning of the end step, sacrifice this token.\"".into())],
mtg-cards/src/sets/fdn.rs:5699: vec![Effect::Custom("Search your library for up to two basic lands and/or Gates and put them onto the battlefield tapped.".into())],
mtg-cards/src/sets/fdn.rs:5836: vec![Effect::Custom("Exile target creature or planeswalker that's black or red. Scry 1.".into())],
mtg-cards/src/sets/fdn.rs:5865: vec![Effect::Custom("At the beginning of your upkeep, if you control six or more lands, create a 5/5 red Dragon creature token with flying.".into())],
mtg-cards/src/sets/fdn.rs:5899: vec![Effect::Custom("When this creature enters, if you control another Elf, create a 1/1 green Elf Warrior creature token.".into())],
mtg-cards/src/sets/fdn.rs:5911: vec![Effect::Custom("As an additional cost to cast this spell, sacrifice a creature or pay {3}{B}.".into())],
mtg-cards/src/sets/fdn.rs:5967: vec![Effect::Custom("{2}, {tap}, Sacrifice Expedition Map: Search your library for a land card, reveal it, and put it into your hand. Then shuffle your library.".into())],
mtg-cards/src/sets/fdn.rs:5993: vec![Effect::Custom("At the beginning of your upkeep, create a token that's a copy of another target nonland permanent you control.".into())],
mtg-cards/src/sets/fdn.rs:6005: vec![Effect::Custom("Create two 1/1 blue Faerie creature tokens with flying. When you do, tap target creature an opponent controls.".into())],
mtg-cards/src/sets/fdn.rs:6088: vec![Effect::Custom("Destroy all creatures. You gain 1 life for each creature destroyed this way.".into())],
mtg-cards/src/sets/fdn.rs:6173: vec![Effect::Custom("Goblin Negotiation deals X damage to target creature. Create a number of 1/1 red Goblin creature tokens equal to the amount of excess damage dealt to that creature this way.".into())],
mtg-cards/src/sets/fdn.rs:6199: vec![Effect::Custom("When this creature enters, create two 3/3 white Knight creature tokens.".into())],
mtg-cards/src/sets/fdn.rs:6242: vec![Effect::Custom("When this creature enters, create a number of 1/1 white Rabbit creature tokens equal to the number of other creatures you control named Hare Apparent.".into())],
mtg-cards/src/sets/fdn.rs:6342: vec![Effect::Custom("When this creature enters, draw a card, then discard a card.".into())],
mtg-cards/src/sets/fdn.rs:6380: vec![Effect::Custom("Gain control of target creature until end of turn. Untap that creature. It gains haste until end of turn. Create a Treasure token.".into())],grep -n 'StaticEffect::Custom(' mtg-cards/src/sets/*.rs | grep -viE '(static effect\.)' | head -40mtg-cards/src/sets/ecl.rs:762: vec![StaticEffect::Custom("As long as it's your turn, has first strike.".into())]),
mtg-cards/src/sets/ecl.rs:1300: vec![StaticEffect::Custom("Can't be blocked by more than one creature.".into())]),
mtg-cards/src/sets/ecl.rs:1453: vec![StaticEffect::Custom("Power = colors among your permanents.".into())]),
mtg-cards/src/sets/ecl.rs:2091: vec![StaticEffect::Custom("Assigns combat damage equal to toughness.".into())]),
mtg-cards/src/sets/ecl.rs:2108: vec![StaticEffect::Custom("Pay 2 life or enters tapped.".into())]),
mtg-cards/src/sets/ecl.rs:2138: vec![StaticEffect::Custom("Enchanted creature can't untap or receive counters.".into())]),
mtg-cards/src/sets/ecl.rs:2189: vec![StaticEffect::Custom("Ward {2}".into())]),
mtg-cards/src/sets/ecl.rs:2213: vec![StaticEffect::Custom("Daunt — can't be blocked by power 2 or less.".into())]),
mtg-cards/src/sets/ecl.rs:2216: vec![StaticEffect::Custom("Conditional +2/+0 if another creature ETB'd this turn.".into())]),
mtg-cards/src/sets/ecl.rs:2240: vec![StaticEffect::Custom("Evoke {R/W}{R/W}".into())]),
mtg-cards/src/sets/ecl.rs:2340: vec![StaticEffect::Custom("Double damage from chosen type sources.".into())]),
mtg-cards/src/sets/ecl.rs:2404: vec![StaticEffect::Custom("Cast exiled creatures by removing 3 counters.".into())]),
mtg-cards/src/sets/ecl.rs:2428: vec![StaticEffect::Custom("Evoke {U/B}{U/B}".into())]),
mtg-cards/src/sets/ecl.rs:2482: vec![StaticEffect::Custom("Cost reduction for toughness > power creatures.".into())]),
mtg-cards/src/sets/ecl.rs:2499: vec![StaticEffect::Custom("Choose a creature type on ETB.".into())]),
mtg-cards/src/sets/ecl.rs:2519: vec![StaticEffect::Custom("Creature spells you cast have convoke.".into())]),
mtg-cards/src/sets/ecl.rs:2522: vec![StaticEffect::Custom("Transforms into Isilu, Carrier of Twilight.".into())]),
mtg-cards/src/sets/ecl.rs:2546: vec![StaticEffect::Custom("Evoke {W/B}{W/B}".into())]),
mtg-cards/src/sets/ecl.rs:3662: vec![StaticEffect::Custom("This creature gets +2/+0 for each other Goblin you control.".into())]),
mtg-cards/src/sets/ecl.rs:3996: vec![StaticEffect::Custom("This creature gets +1/+1 for each creature you control and each creature card in your graveyard.".into())]),
mtg-cards/src/sets/ecl.rs:4075: vec![StaticEffect::Custom("Conspire: tap two creatures to copy spell.".into())]),
mtg-cards/src/sets/fdn.rs:644: vec![StaticEffect::Custom("Enters tapped.".into())]),
mtg-cards/src/sets/fdn.rs:754: vec![StaticEffect::Custom("Protection from multicolored.".into())]),
mtg-cards/src/sets/fdn.rs:1086: vec![StaticEffect::Custom("P/T = number of creatures you control.".into())]),
mtg-cards/src/sets/fdn.rs:1100: vec![StaticEffect::Custom("Instant/sorcery cards are exiled instead of going to graveyard.".into())]),
mtg-cards/src/sets/fdn.rs:1496: vec![StaticEffect::Custom("Attacks each combat if able.".into())]),
mtg-cards/src/sets/fdn.rs:1565: vec![StaticEffect::Custom("+1/+0 for each basic Island you control.".into())]),
mtg-cards/src/sets/fdn.rs:1794: vec![StaticEffect::Custom("+1/+0 and haste if 2+ instants/sorceries in graveyard.".into())]),
mtg-cards/src/sets/fdn.rs:2019: vec![StaticEffect::Custom("Can't be blocked by Humans.".into())]),
mtg-cards/src/sets/fdn.rs:2648: vec![StaticEffect::Custom("Prevent all combat damage to and from Fog Bank.".into())]),
mtg-cards/src/sets/fdn.rs:2755: vec![StaticEffect::Custom("Life gain replacement: gain +1.".into())]),
mtg-cards/src/sets/fdn.rs:2758: vec![StaticEffect::Custom("Conditional +2/+2 if life >= 25.".into())]),
mtg-cards/src/sets/fdn.rs:2914: vec![StaticEffect::Custom("Chosen type creatures get +1/+1 per fellowship counter.".into())]),
mtg-cards/src/sets/fdn.rs:2932: vec![StaticEffect::Custom("Threshold: +2/+1 if graveyard >= 7.".into())]),
mtg-cards/src/sets/fdn.rs:3067: vec![StaticEffect::Custom("Cost reduction: {1} less per Cat.".into())]),
mtg-cards/src/sets/fdn.rs:3083: vec![StaticEffect::Custom("P/T = cards in opponents' graveyards.".into())]),
mtg-cards/src/sets/fdn.rs:3137: vec![StaticEffect::Custom("You have hexproof.".into())]),
mtg-cards/src/sets/fdn.rs:3140: vec![StaticEffect::Custom("Prevent noncombat damage to other creatures you control.".into())]),
mtg-cards/src/sets/fdn.rs:3153: vec![StaticEffect::Custom("Can't be countered.".into())]),
mtg-cards/src/sets/fdn.rs:5603: vec![StaticEffect::Custom("Ward {2}".into())]),Now let me also count the easy-swap candidates precisely.
# Count exact easy-swap patterns
echo "=== Enters tapped ==="
grep -n 'StaticEffect::Custom("Enters tapped' mtg-cards/src/sets/*.rs
echo ""
echo "=== Destroy all creatures ==="
grep -n 'Effect::Custom("Destroy all creatures' mtg-cards/src/sets/*.rs
echo ""
echo "=== Each opponent discards a card ==="
grep -n 'Effect::Custom("Each opponent discards a card' mtg-cards/src/sets/*.rs
echo ""
echo "=== Target creature can't block this turn ==="
grep -n "Effect::Custom(\"Target creature can.t block this turn" mtg-cards/src/sets/*.rs
echo ""
echo "=== draw a card (simple ETB/trigger) ==="
grep -n 'Effect::Custom("When this creature enters, draw a card' mtg-cards/src/sets/*.rs
echo ""
echo "=== draw a card, then discard ==="
grep -n 'Effect::Custom(".*draw a card, then discard a card' mtg-cards/src/sets/*.rs
echo ""
echo "=== Ward {2} ==="
grep -n 'StaticEffect::Custom("Ward' mtg-cards/src/sets/*.rs
echo ""
echo "=== Self -1/-1 counter ==="
grep -n 'Effect::Custom("Put.*-1/-1 counter.*on.*\(this\|Sourbread\|Heirloom\|Sting\|Blighted\|Dream\)' mtg-cards/src/sets/*.rs=== Enters tapped ===
mtg-cards/src/sets/fdn.rs:644: vec![StaticEffect::Custom("Enters tapped.".into())]),
mtg-cards/src/sets/tdm.rs:2110: vec![StaticEffect::Custom("Enters tapped unless you control Plains or Island.".into())]),
mtg-cards/src/sets/tdm.rs:2153: vec![StaticEffect::Custom("Enters tapped unless you control Swamp or Mountain.".into())]),
=== Destroy all creatures ===
mtg-cards/src/sets/fdn.rs:2417: vec![Effect::Custom("Destroy all creatures.".into())],
mtg-cards/src/sets/fdn.rs:6088: vec![Effect::Custom("Destroy all creatures. You gain 1 life for each creature destroyed this way.".into())],
=== Each opponent discards a card ===
mtg-cards/src/sets/fdn.rs:617: vec![Effect::Custom("Each opponent discards a card.".into())],
=== Target creature can't block this turn ===
mtg-cards/src/sets/ecl.rs:431: vec![Effect::Custom("Target creature can't block this turn.".into())],
mtg-cards/src/sets/fdn.rs:1780: vec![Effect::Custom("Target creature can't block this turn.".into())],
=== draw a card (simple ETB/trigger) ===
mtg-cards/src/sets/ecl.rs:3981: vec![Effect::Custom("When this creature enters, draw a card.".into())],
mtg-cards/src/sets/fdn.rs:6342: vec![Effect::Custom("When this creature enters, draw a card, then discard a card.".into())],
=== draw a card, then discard ===
mtg-cards/src/sets/fdn.rs:6342: vec![Effect::Custom("When this creature enters, draw a card, then discard a card.".into())],
=== Ward {2} ===
mtg-cards/src/sets/ecl.rs:2189: vec![StaticEffect::Custom("Ward {2}".into())]),
mtg-cards/src/sets/fdn.rs:5603: vec![StaticEffect::Custom("Ward {2}".into())]),
mtg-cards/src/sets/tdm.rs:347: vec![StaticEffect::Custom("Ward {2}".into())]),
mtg-cards/src/sets/tdm.rs:1990: vec![StaticEffect::Custom("Ward {2}".into())]),
mtg-cards/src/sets/tdm.rs:3729: vec![StaticEffect::Custom("Ward {2}".into())]),
mtg-cards/src/sets/tdm.rs:4078: vec![StaticEffect::Custom("Ward -- Discard a card.".into())]),
=== Self -1/-1 counter ===
mtg-cards/src/sets/ecl.rs:354: vec![Effect::Custom("Put two -1/-1 counters on Blighted Blackthorn.".into()), Effect::draw_cards(1), Effect::lose_life(1)],
mtg-cards/src/sets/ecl.rs:358: vec![Effect::Custom("Put two -1/-1 counters on Blighted Blackthorn.".into()), Effect::draw_cards(1), Effect::lose_life(1)],
mtg-cards/src/sets/ecl.rs:596: vec![Effect::Custom("Put a -1/-1 counter on Dream Seizer. Each opponent discards a card.".into())],
mtg-cards/src/sets/ecl.rs:1438: vec![Effect::Custom("Put two -1/-1 counters on Sourbread Auntie.".into()), Effect::create_token("1/1 Goblin", 2)],
mtg-cards/src/sets/ecl.rs:1467: vec![Effect::Custom("Put a -1/-1 counter on Sting-Slinger.".into()), Effect::damage_opponents(2)],Now I have a clear picture. Let me create the work queue document.
ls /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/docs/ecl-remediation.md fdn-remediation.md tdm-remediation.md tla-remediation.md
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?
The user wants a prompt/instruction they can paste into a new Claude Code session to kick off the loop. Let me think about what that would look like.
The key requirements:
Let me draft something concise but complete.
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.