# Trace: Add Life-Loss Effect and Fix Cards Systematically

Agent: claude-code | Model: Claude Opus 4.6 | Project: mage

---

## User

[Request interrupted by user for tool use]

## User

Implement the following plan:

# Plan: Fix 5 FDN Cards + Add LoseLifeOpponents Effect

## Context

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.

## Step 1: Add `Effect::LoseLifeOpponents` variant

**`mtg-engine/src/abilities.rs`**
- Add variant to `Effect` enum in the `// -- Life --` section (~line 77):
  ```rust
  /// Each opponent loses life.
  LoseLifeOpponents { amount: u32 },
  ```
- Add constructor (~line 583):
  ```rust
  pub fn lose_life_opponents(amount: u32) -> Self {
      Effect::LoseLifeOpponents { amount }
  }
  ```

**`mtg-engine/src/game.rs`** — Add match arm in `execute_effects()` (after `LoseLife` arm, ~line 1015):
```rust
Effect::LoseLifeOpponents { amount } => {
    let opponents: Vec<PlayerId> = self.state.turn_order.iter()
        .filter(|&&id| id != controller)
        .copied()
        .collect();
    for opp in opponents {
        if let Some(player) = self.state.players.get_mut(&opp) {
            player.life -= *amount as i32;
        }
    }
}
```
(Same implementation as `DealDamageOpponents` for now, but semantically correct — when damage prevention is added later, only `DealDamageOpponents` will be affected.)

## Step 2: Fix 5 FDN cards

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)` |

## Verification

1. `cargo check -p mtg-engine` — new variant compiles
2. `cargo check -p mtg-cards` — card changes compile
3. `cargo test --lib` — all tests pass


If 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.

## Assistant

I'll implement this plan step by step. Let me start by reading the relevant files.

## Assistant

### Tool: Read

**Args:**
```json
{
  "file_path": "/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-engine/src/abilities.rs",
  "limit": 20,
  "offset": 70
}
```

## Assistant

**Result** (success):
```
    70→    /// Deal damage to each opponent.
    71→    DealDamageOpponents { amount: u32 },
    72→
    73→    // -- Life --
    74→    /// Gain life.
    75→    GainLife { amount: u32 },
    76→    /// Lose life (target player).
    77→    LoseLife { amount: u32 },
    78→    /// Set life total.
    79→    SetLife { amount: i32 },
    80→
    81→    // -- Destroy / Remove --
    82→    /// Destroy target permanent.
    83→    Destroy,
    84→    /// Destroy all permanents matching filter.
    85→    DestroyAll { filter: String },
    86→    /// Exile target permanent.
    87→    Exile,
    88→    /// Sacrifice a permanent (owner chooses).
    89→    Sacrifice { filter: String },
```

## Assistant

### Tool: Read

**Args:**
```json
{
  "file_path": "/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-engine/src/abilities.rs",
  "limit": 20,
  "offset": 575
}
```

## Assistant

**Result** (success):
```
   575→    pub fn gain_life(amount: u32) -> Self {
   576→        Effect::GainLife { amount }
   577→    }
   578→
   579→    /// "Lose N life."
   580→    pub fn lose_life(amount: u32) -> Self {
   581→        Effect::LoseLife { amount }
   582→    }
   583→
   584→    /// "Target creature gets +N/+M until end of turn."
   585→    pub fn boost_until_eot(power: i32, toughness: i32) -> Self {
   586→        Effect::BoostUntilEndOfTurn { power, toughness }
   587→    }
   588→
   589→    /// "Target creature gets +N/+M."
   590→    pub fn boost_permanent(power: i32, toughness: i32) -> Self {
   591→        Effect::BoostPermanent { power, toughness }
   592→    }
   593→
   594→    /// "Create N token(s)."
```

## Assistant

### Tool: Grep

**Args:**
```json
{
  "-C": 5,
  "output_mode": "content",
  "path": "/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-engine/src/game.rs",
  "pattern": "LoseLife\\b"
}
```

## Assistant

**Result** (success):
```
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-                    }
```

## Assistant

### Tool: Grep

**Args:**
```json
{
  "-n": true,
  "output_mode": "content",
  "path": "/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-cards/src/sets/fdn.rs",
  "pattern": "Effect::Custom\\(\\\"(Each opponent loses|You lose 1 life)"
}
```

## Assistant

**Result** (success):
```
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())],
```

## Assistant

## Assistant

Good, I have all the context. Let me also check the `DealDamageOpponents` implementation in game.rs for reference.

## Assistant

### Tool: Grep

**Args:**
```json
{
  "-C": 10,
  "output_mode": "content",
  "path": "/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-engine/src/game.rs",
  "pattern": "DealDamageOpponents"
}
```

## Assistant

**Result** (success):
```
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-                }
```

## Assistant

### Tool: Read

**Args:**
```json
{
  "file_path": "/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-cards/src/sets/fdn.rs",
  "limit": 25,
  "offset": 830
}
```

## Assistant

**Result** (success):
```
   830→    CardData { id, owner, name: "Marauding Blight-Priest".into(), mana_cost: ManaCost::parse("{2}{B}"),
   831→        card_types: vec![CardType::Creature], subtypes: vec![SubType::Vampire, SubType::Cleric],
   832→        power: Some(3), toughness: Some(2), rarity: Rarity::Common,
   833→        abilities: vec![
   834→            Ability::triggered(id,
   835→                "Whenever you gain life, each opponent loses 1 life.",
   836→                vec![EventType::GainLife],
   837→                vec![Effect::Custom("Each opponent loses 1 life.".into())],
   838→                TargetSpec::None),
   839→        ],
   840→        ..Default::default() }
   841→}
   842→
   843→fn pulse_tracker(id: ObjectId, owner: PlayerId) -> CardData {
   844→    CardData { id, owner, name: "Pulse Tracker".into(), mana_cost: ManaCost::parse("{B}"),
   845→        card_types: vec![CardType::Creature], subtypes: vec![SubType::Vampire, SubType::Rogue],
   846→        power: Some(1), toughness: Some(1), rarity: Rarity::Common,
   847→        abilities: vec![
   848→            Ability::attacks_triggered(id,
   849→                "Whenever Pulse Tracker attacks, each opponent loses 1 life.",
   850→                vec![Effect::Custom("Each opponent loses 1 life.".into())],
   851→                TargetSpec::None),
   852→        ],
   853→        ..Default::default() }
   854→}
```

## Assistant

### Tool: Read

**Args:**
```json
{
  "file_path": "/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-cards/src/sets/fdn.rs",
  "limit": 15,
  "offset": 960
}
```

## Assistant

**Result** (success):
```
   960→
   961→fn vampire_spawn(id: ObjectId, owner: PlayerId) -> CardData {
   962→    CardData { id, owner, name: "Vampire Spawn".into(), mana_cost: ManaCost::parse("{2}{B}"),
   963→        card_types: vec![CardType::Creature], subtypes: vec![SubType::Vampire],
   964→        power: Some(2), toughness: Some(3), rarity: Rarity::Common,
   965→        abilities: vec![
   966→            Ability::enters_battlefield_triggered(id,
   967→                "When Vampire Spawn enters, each opponent loses 2 life and you gain 2 life.",
   968→                vec![Effect::Custom("Each opponent loses 2 life, you gain 2 life.".into())],
   969→                TargetSpec::None),
   970→        ],
   971→        ..Default::default() }
   972→}
   973→
   974→fn viashino_pyromancer(id: ObjectId, owner: PlayerId) -> CardData {
```

## Assistant

### Tool: Read

**Args:**
```json
{
  "file_path": "/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-cards/src/sets/fdn.rs",
  "limit": 15,
  "offset": 1405
}
```

## Assistant

**Result** (success):
```
  1405→    // Enchantment {1}{B}{B}. (Upkeep: draw a card, lose 1 life)
  1406→    CardData { id, owner, name: "Phyrexian Arena".into(), mana_cost: ManaCost::parse("{1}{B}{B}"),
  1407→        card_types: vec![CardType::Enchantment], rarity: Rarity::Rare,
  1408→        abilities: vec![
  1409→            Ability::triggered(id,
  1410→                "At the beginning of your upkeep, you draw a card and you lose 1 life.",
  1411→                vec![EventType::UpkeepStepPre],
  1412→                vec![Effect::draw_cards(1), Effect::Custom("You lose 1 life.".into())],
  1413→                TargetSpec::None),
  1414→        ],
  1415→        ..Default::default() }
  1416→}
  1417→
  1418→fn swiftfoot_boots(id: ObjectId, owner: PlayerId) -> CardData {
  1419→    // Artifact — Equipment {2}. Equip {1}. Hexproof + Haste.
```

## Assistant

### Tool: Read

**Args:**
```json
{
  "file_path": "/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-cards/src/sets/fdn.rs",
  "limit": 15,
  "offset": 1698
}
```

## Assistant

**Result** (success):
```
  1698→    CardData { id, owner, name: "Vampire Neonate".into(), mana_cost: ManaCost::parse("{B}"),
  1699→        card_types: vec![CardType::Creature], subtypes: vec![SubType::Vampire],
  1700→        power: Some(0), toughness: Some(3), rarity: Rarity::Common,
  1701→        abilities: vec![
  1702→            Ability::activated(id,
  1703→                "{2}, {T}: Each opponent loses 1 life and you gain 1 life.",
  1704→                vec![Cost::pay_mana("{2}"), Cost::tap_self()],
  1705→                vec![Effect::Custom("Each opponent loses 1 life, you gain 1 life.".into())],
  1706→                TargetSpec::None),
  1707→        ],
  1708→        ..Default::default() }
  1709→}
  1710→
  1711→fn hungry_ghoul(id: ObjectId, owner: PlayerId) -> CardData {
  1712→    // 2/2 Zombie for {1}{B}. {1}, sacrifice creature: +1/+1 counter.
```

## Assistant

Now I have all the exact lines. Let me make all the changes.

## Assistant

### Tool: Edit

**Args:**
```json
{
  "file_path": "/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-engine/src/abilities.rs",
  "new_string": "    /// Lose life (target player).\n    LoseLife { amount: u32 },\n    /// Each opponent loses life.\n    LoseLifeOpponents { amount: u32 },",
  "old_string": "    /// Lose life (target player).\n    LoseLife { amount: u32 },",
  "replace_all": false
}
```

## Assistant

**Result** (success):
```
The file /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-engine/src/abilities.rs has been updated successfully.
```

## Assistant

### Tool: Edit

**Args:**
```json
{
  "file_path": "/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-engine/src/abilities.rs",
  "new_string": "    /// \"Lose N life.\"\n    pub fn lose_life(amount: u32) -> Self {\n        Effect::LoseLife { amount }\n    }\n\n    /// \"Each opponent loses N life.\"\n    pub fn lose_life_opponents(amount: u32) -> Self {\n        Effect::LoseLifeOpponents { amount }\n    }",
  "old_string": "    /// \"Lose N life.\"\n    pub fn lose_life(amount: u32) -> Self {\n        Effect::LoseLife { amount }\n    }",
  "replace_all": false
}
```

## Assistant

**Result** (success):
```
The file /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-engine/src/abilities.rs has been updated successfully.
```

## Assistant

Now add the match arm in game.rs and fix all 5 cards in fdn.rs.

## Assistant

### Tool: Edit

**Args:**
```json
{
  "file_path": "/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-engine/src/game.rs",
  "new_string": "                Effect::LoseLifeOpponents { amount } => {\n                    let opponents: Vec<PlayerId> = self.state.turn_order.iter()\n                        .filter(|&&id| id != controller)\n                        .copied()\n                        .collect();\n                    for opp in opponents {\n                        if let Some(player) = self.state.players.get_mut(&opp) {\n                            player.life -= *amount as i32;\n                        }\n                    }\n                }\n                Effect::DealDamageOpponents { amount } => {",
  "old_string": "                Effect::DealDamageOpponents { amount } => {",
  "replace_all": false
}
```

## Assistant

**Result** (error):
```
<tool_use_error>File has not been read yet. Read it first before writing to it.</tool_use_error>
```

## Assistant

### Tool: Edit

**Args:**
```json
{
  "file_path": "/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-cards/src/sets/fdn.rs",
  "new_string": "                vec![Effect::lose_life_opponents(1)],\n                TargetSpec::None),\n        ],\n        ..Default::default() }\n}\n\nfn pulse_tracker",
  "old_string": "                vec![Effect::Custom(\"Each opponent loses 1 life.\".into())],\n                TargetSpec::None),\n        ],\n        ..Default::default() }\n}\n\nfn pulse_tracker",
  "replace_all": false
}
```

## Assistant

**Result** (error):
```
<tool_use_error>Sibling tool call errored</tool_use_error>
```

## Assistant

### Tool: Edit

**Args:**
```json
{
  "file_path": "/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-cards/src/sets/fdn.rs",
  "new_string": "                vec![Effect::lose_life_opponents(1)],\n                TargetSpec::None),\n        ],\n        ..Default::default() }\n}",
  "old_string": "                vec![Effect::Custom(\"Each opponent loses 1 life.\".into())],\n                TargetSpec::None),\n        ],\n        ..Default::default() }\n}",
  "replace_all": false
}
```

## Assistant

**Result** (error):
```
<tool_use_error>Sibling tool call errored</tool_use_error>
```

## Assistant

### Tool: Edit

**Args:**
```json
{
  "file_path": "/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-cards/src/sets/fdn.rs",
  "new_string": "                vec![Effect::lose_life_opponents(2), Effect::gain_life(2)],",
  "old_string": "                vec![Effect::Custom(\"Each opponent loses 2 life, you gain 2 life.\".into())],",
  "replace_all": false
}
```

## Assistant

**Result** (error):
```
<tool_use_error>Sibling tool call errored</tool_use_error>
```

## Assistant

### Tool: Edit

**Args:**
```json
{
  "file_path": "/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-cards/src/sets/fdn.rs",
  "new_string": "                vec![Effect::draw_cards(1), Effect::lose_life(1)],",
  "old_string": "                vec![Effect::draw_cards(1), Effect::Custom(\"You lose 1 life.\".into())],",
  "replace_all": false
}
```

## Assistant

**Result** (error):
```
<tool_use_error>Sibling tool call errored</tool_use_error>
```

## Assistant

### Tool: Edit

**Args:**
```json
{
  "file_path": "/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-cards/src/sets/fdn.rs",
  "new_string": "                vec![Effect::lose_life_opponents(1), Effect::gain_life(1)],",
  "old_string": "                vec![Effect::Custom(\"Each opponent loses 1 life, you gain 1 life.\".into())],",
  "replace_all": false
}
```

## Assistant

**Result** (error):
```
<tool_use_error>Sibling tool call errored</tool_use_error>
```

## Assistant

Need to read game.rs first. Let me do that and retry all the edits.

## Assistant

### Tool: Read

**Args:**
```json
{
  "file_path": "/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-engine/src/game.rs",
  "limit": 15,
  "offset": 1015
}
```

## Assistant

**Result** (success):
```
  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→                }
  1028→                Effect::AddCounters { counter_type, count } => {
  1029→                    let ct = crate::counters::CounterType::from_name(counter_type);
```

## Assistant

### Tool: Edit

**Args:**
```json
{
  "file_path": "/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-engine/src/game.rs",
  "new_string": "                Effect::LoseLifeOpponents { amount } => {\n                    let opponents: Vec<PlayerId> = self.state.turn_order.iter()\n                        .filter(|&&id| id != controller)\n                        .copied()\n                        .collect();\n                    for opp in opponents {\n                        if let Some(player) = self.state.players.get_mut(&opp) {\n                            player.life -= *amount as i32;\n                        }\n                    }\n                }\n                Effect::DealDamageOpponents { amount } => {",
  "old_string": "                Effect::DealDamageOpponents { amount } => {",
  "replace_all": false
}
```

## Assistant

**Result** (success):
```
The file /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-engine/src/game.rs has been updated successfully.
```

## Assistant

### Tool: Edit

**Args:**
```json
{
  "file_path": "/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-cards/src/sets/fdn.rs",
  "new_string": "                vec![Effect::lose_life_opponents(1)],\n                TargetSpec::None),\n        ],\n        ..Default::default() }\n}\n\nfn pulse_tracker",
  "old_string": "                vec![Effect::Custom(\"Each opponent loses 1 life.\".into())],\n                TargetSpec::None),\n        ],\n        ..Default::default() }\n}\n\nfn pulse_tracker",
  "replace_all": false
}
```

## Assistant

**Result** (success):
```
The file /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-cards/src/sets/fdn.rs has been updated successfully.
```

## Assistant

### Tool: Edit

**Args:**
```json
{
  "file_path": "/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-cards/src/sets/fdn.rs",
  "new_string": "                vec![Effect::lose_life_opponents(1)],\n                TargetSpec::None),\n        ],\n        ..Default::default() }\n}",
  "old_string": "                vec![Effect::Custom(\"Each opponent loses 1 life.\".into())],\n                TargetSpec::None),\n        ],\n        ..Default::default() }\n}",
  "replace_all": false
}
```

## Assistant

**Result** (success):
```
The file /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-cards/src/sets/fdn.rs has been updated successfully.
```

## Assistant

### Tool: Edit

**Args:**
```json
{
  "file_path": "/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-cards/src/sets/fdn.rs",
  "new_string": "                vec![Effect::lose_life_opponents(2), Effect::gain_life(2)],",
  "old_string": "                vec![Effect::Custom(\"Each opponent loses 2 life, you gain 2 life.\".into())],",
  "replace_all": false
}
```

## Assistant

**Result** (success):
```
The file /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-cards/src/sets/fdn.rs has been updated successfully.
```

## Assistant

### Tool: Edit

**Args:**
```json
{
  "file_path": "/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-cards/src/sets/fdn.rs",
  "new_string": "                vec![Effect::draw_cards(1), Effect::lose_life(1)],",
  "old_string": "                vec![Effect::draw_cards(1), Effect::Custom(\"You lose 1 life.\".into())],",
  "replace_all": false
}
```

## Assistant

**Result** (success):
```
The file /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-cards/src/sets/fdn.rs has been updated successfully.
```

## Assistant

### Tool: Edit

**Args:**
```json
{
  "file_path": "/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-cards/src/sets/fdn.rs",
  "new_string": "                vec![Effect::lose_life_opponents(1), Effect::gain_life(1)],",
  "old_string": "                vec![Effect::Custom(\"Each opponent loses 1 life, you gain 1 life.\".into())],",
  "replace_all": false
}
```

## Assistant

**Result** (success):
```
The file /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-cards/src/sets/fdn.rs has been updated successfully.
```

## Assistant

All edits done. Now let's verify compilation and run tests.

## Assistant

### Tool: Bash

**Args:**
```json
{
  "command": "cd /mnt/dest-btrfs/home/jc/src/mage/mtg-rl && cargo check -p mtg-engine 2>&1",
  "description": "Check mtg-engine compiles with new variant",
  "timeout": 120000
}
```

## Assistant

**Result** (success):
```
    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.24s
```

## Assistant

### Tool: Bash

**Args:**
```json
{
  "command": "cargo check -p mtg-cards 2>&1",
  "description": "Check mtg-cards compiles with card fixes",
  "timeout": 120000
}
```

## Assistant

**Result** (success):
```
    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.84s
```

## Assistant

### Tool: Bash

**Args:**
```json
{
  "command": "cargo test --lib 2>&1",
  "description": "Run all library tests",
  "timeout": 300000
}
```

## Assistant

**Result** (success):
```
   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.27s
```

## Assistant

All 244 tests pass (52 + 18 + 157 + 17). Here's a summary of the changes:

**mtg-engine/src/abilities.rs:**
- Added `LoseLifeOpponents { amount: u32 }` variant to the `Effect` enum
- Added `lose_life_opponents(amount)` constructor method

**mtg-engine/src/game.rs:**
- Added match arm for `LoseLifeOpponents` in `execute_effects()` — iterates opponents and subtracts life

**mtg-cards/src/sets/fdn.rs** — Fixed 5 cards:
- **Marauding Blight-Priest**: `Custom(...)` → `lose_life_opponents(1)`
- **Pulse Tracker**: `Custom(...)` → `lose_life_opponents(1)`
- **Vampire Spawn**: `Custom(...)` → `lose_life_opponents(2), gain_life(2)`
- **Phyrexian Arena**: `Custom("You lose 1 life.")` → `lose_life(1)`
- **Vampire Neonate**: `Custom(...)` → `lose_life_opponents(1), gain_life(1)`

## User

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.

## Assistant

Let me first check what other cards across all sets use similar "each opponent loses" Custom effects that can now be fixed with `LoseLifeOpponents`.

## Assistant

### Tool: Grep

**Args:**
```json
{
  "-n": true,
  "output_mode": "content",
  "path": "/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-cards/src/sets/",
  "pattern": "Effect::Custom\\(\"Each opponent loses"
}
```

## Assistant

**Result** (success):
```
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)],
```

## Assistant

### Tool: Grep

**Args:**
```json
{
  "-n": true,
  "output_mode": "content",
  "path": "/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-cards/src/sets/",
  "pattern": "Effect::Custom\\(\"You lose \\d+ life"
}
```

## Assistant

**Result** (success):
```
No matches found
```

## Assistant

### Tool: Grep

**Args:**
```json
{
  "-n": true,
  "output_mode": "content",
  "path": "/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-cards/src/sets/",
  "pattern": "Effect::Custom\\(\".*loses? \\d+ life"
}
```

## Assistant

**Result** (success):
```
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())],
```

## Assistant

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.

## Assistant

### Tool: Read

**Args:**
```json
{
  "file_path": "/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-cards/src/sets/ecl.rs",
  "limit": 15,
  "offset": 458
}
```

## Assistant

**Result** (success):
```
   458→    CardData { id, owner, name: "Champion of the Weird".into(), mana_cost: ManaCost::parse("{3}{B}"),
   459→        card_types: vec![CardType::Creature], subtypes: vec![SubType::Goblin, SubType::Berserker],
   460→        power: Some(5), toughness: Some(5), rarity: Rarity::Uncommon,
   461→        abilities: vec![
   462→            Ability::activated(id,
   463→                "{1}{B}, Put a -1/-1 counter on this creature: Each opponent loses 2 life.",
   464→                vec![Cost::pay_mana("{1}{B}"), Cost::Custom("Put a -1/-1 counter on this creature".into())],
   465→                vec![Effect::Custom("Each opponent loses 2 life.".into())],
   466→                TargetSpec::None),
   467→            Ability::triggered(id,
   468→                "When this creature leaves the battlefield, return the exiled card to its owner's hand.",
   469→                vec![EventType::ZoneChanged],
   470→                vec![Effect::Custom("Return exiled card to hand.".into())],
   471→                TargetSpec::None),
   472→        ],
```

## Assistant

### Tool: Read

**Args:**
```json
{
  "file_path": "/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-cards/src/sets/ecl.rs",
  "limit": 15,
  "offset": 1826
}
```

## Assistant

**Result** (success):
```
  1826→        abilities: vec![
  1827→            Ability::enters_battlefield_triggered(id,
  1828→                "When Boggart Mischief enters, you may put a -1/-1 counter on a creature you control. If you do, create two 1/1 black Goblin Rogue creature tokens.",
  1829→                vec![Effect::Custom("Blight 1 to create two 1/1 Goblin Rogue tokens.".into())],
  1830→                TargetSpec::None),
  1831→            Ability::any_creature_dies_triggered(id,
  1832→                "Whenever a Goblin you control dies, each opponent loses 1 life and you gain 1 life.",
  1833→                vec![Effect::Custom("Each opponent loses 1 life, you gain 1 life.".into())],
  1834→                TargetSpec::None),
  1835→        ],
  1836→        ..Default::default() }
  1837→}
  1838→
  1839→fn darkness_descends(id: ObjectId, owner: PlayerId) -> CardData {
  1840→    // Sorcery {2}{B}{B}. Put two -1/-1 counters on each creature.
```

## Assistant

### Tool: Read

**Args:**
```json
{
  "file_path": "/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-cards/src/sets/tdm.rs",
  "limit": 15,
  "offset": 1033
}
```

## Assistant

**Result** (success):
```
  1033→    CardData { id, owner, name: "Skirmish Rhino".into(), mana_cost: ManaCost::parse("{W}{B}{G}"),
  1034→        card_types: vec![CardType::Creature], subtypes: vec![SubType::Rhino],
  1035→        power: Some(3), toughness: Some(4), keywords: KeywordAbilities::TRAMPLE,
  1036→        rarity: Rarity::Common,
  1037→        abilities: vec![
  1038→            Ability::enters_battlefield_triggered(id,
  1039→                "When this creature enters, each opponent loses 2 life and you gain 2 life.",
  1040→                vec![Effect::Custom("Each opponent loses 2 life.".into()), Effect::gain_life(2)],
  1041→                TargetSpec::None),
  1042→        ],
  1043→        ..Default::default() }
  1044→}
  1045→
  1046→fn summit_intimidator(id: ObjectId, owner: PlayerId) -> CardData {
  1047→    CardData { id, owner, name: "Summit Intimidator".into(), mana_cost: ManaCost::parse("{3}{R}"),
```

## Assistant

Now let me fix these 3 additional cards and also read the remediation docs so I can update them.

## Assistant

### Tool: Edit

**Args:**
```json
{
  "file_path": "/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-cards/src/sets/ecl.rs",
  "new_string": "                vec![Effect::lose_life_opponents(2)],",
  "old_string": "                vec![Effect::Custom(\"Each opponent loses 2 life.\".into())],",
  "replace_all": false
}
```

## Assistant

**Result** (success):
```
The file /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-cards/src/sets/ecl.rs has been updated successfully.
```

## Assistant

### Tool: Edit

**Args:**
```json
{
  "file_path": "/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-cards/src/sets/ecl.rs",
  "new_string": "                vec![Effect::lose_life_opponents(1), Effect::gain_life(1)],",
  "old_string": "                vec![Effect::Custom(\"Each opponent loses 1 life, you gain 1 life.\".into())],",
  "replace_all": false
}
```

## Assistant

**Result** (success):
```
The file /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-cards/src/sets/ecl.rs has been updated successfully.
```

## Assistant

### Tool: Edit

**Args:**
```json
{
  "file_path": "/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-cards/src/sets/tdm.rs",
  "new_string": "                vec![Effect::lose_life_opponents(2), Effect::gain_life(2)],",
  "old_string": "                vec![Effect::Custom(\"Each opponent loses 2 life.\".into()), Effect::gain_life(2)],",
  "replace_all": false
}
```

## Assistant

**Result** (success):
```
The file /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-cards/src/sets/tdm.rs has been updated successfully.
```

## Assistant

Now let me verify everything still compiles.

## Assistant

### Tool: Bash

**Args:**
```json
{
  "command": "cargo check -p mtg-cards 2>&1",
  "description": "Check all card changes compile",
  "timeout": 120000
}
```

## Assistant

**Result** (success):
```
    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.74s
```

## Assistant

Good. Now let me check the remediation docs for ECL and TDM to understand what needs updating there too.

## Assistant

### Tool: Grep

**Args:**
```json
{
  "-C": 3,
  "-n": true,
  "output_mode": "content",
  "path": "/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/docs/",
  "pattern": "Champion of the Weird|Boggart Mischief|Skirmish Rhino"
}
```

## Assistant

**Result** (success):
```
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)**
```

## Assistant

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`.

## Assistant

### Tool: Read

**Args:**
```json
{
  "file_path": "/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/docs/fdn-remediation.md",
  "limit": 48,
  "offset": 35
}
```

## Assistant

**Result** (success):
```
    35→**Effects that ACTUALLY WORK in `execute_effects()`:**
    36→
    37→| Effect Variant | What It Does |
    38→|---|---|
    39→| `Effect::DealDamage { amount }` | Deals damage to target permanents or opponent |
    40→| `Effect::Destroy` | Destroys target permanents (respects indestructible) |
    41→| `Effect::Exile` | Exiles target permanents |
    42→| `Effect::Bounce` | Returns target permanents to owner's hand |
    43→| `Effect::DrawCards { count }` | Controller draws N cards |
    44→| `Effect::GainLife { amount }` | Controller gains N life |
    45→| `Effect::LoseLife { amount }` | Controller loses N life |
    46→| `Effect::DealDamageOpponents { amount }` | Each opponent loses N life |
    47→| `Effect::AddCounters { counter_type, count }` | Puts counters on target permanents |
    48→| `Effect::BoostUntilEndOfTurn { power, toughness }` | Grants +N/+M (simplified via counters) |
    49→| `Effect::TapTarget` | Taps target permanent |
    50→| `Effect::UntapTarget` | Untaps target permanent |
    51→| `Effect::CounterSpell` | Counters target spell on stack |
    52→| `Effect::AddMana { mana }` | Adds mana to controller's pool |
    53→| `Effect::DiscardCards { count }` | Controller discards N cards |
    54→| `Effect::Mill { count }` | Mills N cards from library to graveyard |
    55→| `Effect::CreateToken { token_name, count }` | Creates N token creatures (now parses P/T and keywords from token_name) |
    56→| `Effect::Scry { count }` | Scry N cards (look at top N, reorder/bottom) |
    57→| `Effect::SearchLibrary { filter }` | Search library for matching card, put in hand |
    58→| `Effect::ReturnFromGraveyard` | Return target card from graveyard to hand |
    59→| `Effect::Reanimate` | Return target card from graveyard to battlefield |
    60→| `Effect::GainKeywordUntilEndOfTurn { keyword }` | Grant keyword ability until end of turn |
    61→| `Effect::GainKeyword { keyword }` | Grant keyword ability permanently |
    62→| `Effect::LoseKeyword { keyword }` | Remove keyword ability |
    63→| `Effect::Indestructible` | Grant indestructible until end of turn |
    64→| `Effect::Hexproof` | Grant hexproof until end of turn |
    65→| `Effect::CantBlock` | Target creature can't block this turn |
    66→| `Effect::Sacrifice { filter }` | Owner sacrifices a permanent matching filter |
    67→| `Effect::DestroyAll { filter }` | Destroy all permanents matching filter |
    68→| `Effect::DealDamageAll { amount, filter }` | Deal damage to all permanents matching filter |
    69→| `Effect::RemoveCounters { counter_type, count }` | Remove counters from target |
    70→| `Effect::CreateTokenTappedAttacking { token_name, count }` | Create tokens tapped and attacking |
    71→| `Effect::BoostPermanent { power, toughness }` | Permanent +N/+M boost |
    72→| `Effect::SetPowerToughness { power, toughness }` | Set base power/toughness |
    73→
    74→**Effects that are NO-OPS** (fall through to `_ => {}`):
    75→- `Effect::SetLife` -- Set life total not implemented
    76→- `Effect::MustBlock` -- Force blocking not implemented
    77→- `Effect::PreventCombatDamage` -- Damage prevention not implemented
    78→- `Effect::GainControl` / `Effect::GainControlUntilEndOfTurn` -- Control change not implemented
    79→- `Effect::GainProtection` -- Protection not implemented
    80→- `Effect::Custom(...)` -- Always a no-op
    81→- All `StaticEffect::Custom(...)` -- Always a no-op
    82→- All `Cost::Custom(...)` -- Cost payment likely broken
```

## Assistant

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.

## Assistant

### Tool: Read

**Args:**
```json
{
  "file_path": "/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/docs/fdn-remediation.md",
  "limit": 25,
  "offset": 245
}
```

## Assistant

**Result** (success):
```
   245→  - **What it should do**: Create variable number of tokens
   246→  - **Fix needed**: Add dynamic token count support or implement as Custom logic
   247→
   248→- [ ] **Marauding Blight-Priest** -- What works: 3/2 creature. What's broken: GainLife trigger `Effect::Custom("Each opponent loses 1 life.")`.
   249→  - **Java source**: `Mage.Sets/src/mage/cards/m/MaraudingBlightPriest.java`
   250→  - **What it should do**: Each opponent loses 1 life when you gain life
   251→  - **Fix needed**: Replace with `Effect::DealDamageOpponents { amount: 1 }` (or `Effect::LoseLife` targeting opponents)
   252→
   253→- [ ] **Pulse Tracker** -- What works: 1/1 creature. What's broken: Attack trigger `Effect::Custom("Each opponent loses 1 life.")`.
   254→  - **Java source**: `Mage.Sets/src/mage/cards/p/PulseTracker.java`
   255→  - **What it should do**: Each opponent loses 1 life when it attacks
   256→  - **Fix needed**: Replace with `Effect::DealDamageOpponents { amount: 1 }`
   257→
   258→- [ ] **Vampire Spawn** -- What works: 2/3 creature. What's broken: ETB `Effect::Custom("Each opponent loses 2 life, you gain 2 life.")`.
   259→  - **Java source**: `Mage.Sets/src/mage/cards/v/VampireSpawn.java`
   260→  - **What it should do**: Each opponent loses 2 life, you gain 2 life
   261→  - **Fix needed**: Replace with `Effect::DealDamageOpponents { amount: 2 }` + `Effect::GainLife { amount: 2 }`
   262→
   263→- [ ] **Axgard Cavalry** -- What works: creature body, tap cost. GainKeywordUntilEndOfTurn NOW WORKS (NOW IMPLEMENTED).
   264→
   265→- [ ] **Surrak, the Hunt Caller** -- What works: 5/4 haste creature. GainKeywordEOT NOW WORKS (NOW IMPLEMENTED).
   266→
   267→- [ ] **Wildheart Invoker** -- What works: creature + `BoostUntilEndOfTurn(5,5)`. GainKeywordEOT NOW WORKS (NOW IMPLEMENTED).
   268→
   269→- [ ] **Crusader of Odric** -- What works: creature body. What's broken: `StaticEffect::Custom("P/T = number of creatures you control.")`.
```

## Assistant

### Tool: Read

**Args:**
```json
{
  "file_path": "/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/docs/fdn-remediation.md",
  "limit": 30,
  "offset": 296
}
```

## Assistant

**Result** (success):
```
   296→- [ ] **Feldon's Cane** -- What works: tap cost. What's broken: `Cost::Custom("Exile")` + `Effect::Custom("Shuffle your graveyard into your library.")`.
   297→  - **Fix needed**: Add self-exile cost support + graveyard shuffle effect
   298→
   299→- [ ] **Phyrexian Arena** -- What works: `DrawCards(1)` on upkeep. What's broken: `Effect::Custom("You lose 1 life.")`.
   300→  - **Fix needed**: Replace with `Effect::LoseLife { amount: 1 }`
   301→
   302→- [ ] **Swiftfoot Boots** -- What works: `GrantKeyword("hexproof, haste")`. What's broken: Equip `Effect::Custom("Attach...")`.
   303→  - **Fix needed**: Implement Equipment attach effect
   304→
   305→- [ ] **Vampire Interloper** -- What works: 2/1 flying creature. What's broken: `StaticEffect::CantBlock` -- need to verify the engine applies this.
   306→  - **Fix needed**: Verify CantBlock is applied in combat system
   307→
   308→- [ ] **Juggernaut** -- What works: 5/3 artifact creature. What's broken: `StaticEffect::Custom("Attacks each combat if able.")`.
   309→  - **Fix needed**: Add must-attack enforcement
   310→
   311→- [ ] **Tempest Djinn** -- What works: 0/4 flying creature. What's broken: `StaticEffect::Custom("+1/+0 for each basic Island you control.")`.
   312→  - **Fix needed**: Add dynamic P/T boost based on land count
   313→
   314→- [ ] **Tolarian Terror** -- What works: 5/5 ward creature. What's broken: `CostReduction` for instants/sorceries in graveyard -- works conceptually but may not be correctly applied.
   315→  - **Fix needed**: Verify cost reduction engine support
   316→
   317→- [ ] **Solemn Simulacrum** -- What works: Dies trigger `DrawCards(1)`. SearchLibrary NOW WORKS (NOW IMPLEMENTED).
   318→
   319→- [ ] **Vampire Neonate** -- What works: 0/3 creature. What's broken: Activated `Effect::Custom("Each opponent loses 1 life, you gain 1 life.")`.
   320→  - **Fix needed**: Replace with `Effect::DealDamageOpponents { amount: 1 }` + `Effect::GainLife { amount: 1 }`
   321→
   322→- [ ] **Goblin Smuggler** -- What works: 2/2 haste creature. What's broken: Activated `Effect::Custom("Target creature with power 2 or less can't be blocked this turn.")`.
   323→  - **Fix needed**: Add can't-be-blocked-this-turn effect
   324→
   325→- [ ] **Frenzied Goblin** -- What works: 1/1 creature. What's broken: Attack trigger `Effect::Custom("Target creature can't block this turn.")`.
```

## Assistant

### Tool: Read

**Args:**
```json
{
  "file_path": "/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/docs/fdn-remediation.md",
  "limit": 40,
  "offset": 580
}
```

## Assistant

**Result** (success):
```
   580→---
   581→
   582→## Priority Remediation Roadmap
   583→
   584→### Phase 1: Engine effects (unblocks many cards at once)
   585→1. **Implement `GainKeywordUntilEndOfTurn`** in `execute_effects()` -- **DONE**
   586→2. **Implement `Scry`** in `execute_effects()` -- **DONE**
   587→3. **Implement `DestroyAll`** in `execute_effects()` -- **DONE**
   588→4. **Implement `SearchLibrary`** in `execute_effects()` -- **DONE**
   589→5. **Implement `ReturnFromGraveyard`** in `execute_effects()` -- **DONE**
   590→6. **Implement `Reanimate`** in `execute_effects()` -- **DONE**
   591→7. **Implement `Sacrifice`** in `execute_effects()` -- **DONE**
   592→8. **Implement `DealDamageAll`** in `execute_effects()` -- **DONE**
   593→
   594→### Phase 2: Fix easy card-level issues
   595→1. Fix Phyrexian Arena: change `Effect::Custom("You lose 1 life.")` to `Effect::LoseLife { amount: 1 }`
   596→2. Fix Vampire Spawn/Pulse Tracker/Marauding Blight-Priest: use `DealDamageOpponents` + `GainLife`
   597→3. Fix Vampire Neonate: use `DealDamageOpponents` + `GainLife`
   598→4. Fix Diregraf Ghoul: use `StaticEffect::EntersTapped`
   599→5. Fix incomplete dual lands (copy Azorius Guildgate pattern to other guildgates)
   600→6. Fix incomplete gain lands (copy Bloodfell Caves pattern)
   601→
   602→### Phase 3: Equipment system
   603→1. Implement Equipment attach/detach mechanics
   604→2. Fix Basilisk Collar, Swiftfoot Boots, all Equipment stubs
   605→
   606→### Phase 4: Modal spells and complex effects
   607→1. Implement modal spell framework (choose-one, choose-two)
   608→2. Implement token copying
   609→3. Implement mass boost/keyword effects
   610→4. Fix Abrade, Boros Charm, Slagstorm, Valorous Stance, etc.
   611→
   612→### Phase 5: Complete remaining stubs
   613→1. Planeswalkers (loyalty counter system)
   614→2. Complex legendary creatures
   615→3. X-cost spells
   616→4. Remaining enchantments
   617→
```

## Assistant

Now let me also read the Complete cards section to understand the count and where to add the newly fixed cards.

## Assistant

### Tool: Read

**Args:**
```json
{
  "file_path": "/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/docs/fdn-remediation.md",
  "limit": 12,
  "offset": 1
}
```

## Assistant

**Result** (success):
```
     1→# FDN (Foundations) Card Remediation
     2→
     3→## Overview
     4→- Total cards in Rust: 512 (+ 5 basic lands via `basic_lands::register`)
     5→- Total cards in Java set: 517 unique names (same 512 + 5 basic lands)
     6→- **Complete: 119** (+~20 cards now work due to newly implemented effects, reclassification pending)
     7→- **Partial: 126**
     8→- **Stub: 267**
     9→- **Missing: 0** (all Java cards accounted for)
    10→
    11→---
    12→
```

## Assistant

### Tool: Read

**Args:**
```json
{
  "file_path": "/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/docs/fdn-remediation.md",
  "limit": 12,
  "offset": 198
}
```

## Assistant

**Result** (success):
```
   198→- [x] **Billowing Shriekmass** -- ETB: `Mill(3)` (threshold static is Custom - partial, but ETB works)
   199→- [x] **Deadly Plot** -- Spell: `Destroy` target creature
   200→- [x] **Pacifism** -- Static: `CantAttack` + `CantBlock` on enchanted creature
   201→- [x] **Alesha, Who Laughs at Fate** -- Attack trigger: `AddCounters("+1/+1", 1)` (second ability uses `Reanimate` which now works)
   202→- [x] **Stromkirk Noble** -- Combat damage trigger: `AddCounters("+1/+1", 1)` (can't-be-blocked-by-Humans is Custom)
   203→
   204→**Reclassification note:** Several cards listed above have minor partial elements (e.g., kicker not working, one Custom effect alongside working ones). For precise classification, see the Partial section below which lists the exact broken parts.
   205→
   206→**Total truly complete (all effects functional, no Custom):** 95 cards
   207→
   208→---
   209→
```

## Assistant

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.

## Assistant

### Tool: Edit

**Args:**
```json
{
  "file_path": "/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/docs/fdn-remediation.md",
  "new_string": "| `Effect::DealDamageOpponents { amount }` | Each opponent loses N life (damage) |\n| `Effect::LoseLifeOpponents { amount }` | Each opponent loses N life (life loss, not damage) |",
  "old_string": "| `Effect::DealDamageOpponents { amount }` | Each opponent loses N life |",
  "replace_all": false
}
```

## Assistant

**Result** (success):
```
The file /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/docs/fdn-remediation.md has been updated successfully.
```

## Assistant

### Tool: Edit

**Args:**
```json
{
  "file_path": "/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/docs/fdn-remediation.md",
  "new_string": "- [x] **Marauding Blight-Priest** -- GainLife trigger: `LoseLifeOpponents(1)` -- **FIXED** (was Custom, now uses `Effect::lose_life_opponents(1)`)\n\n- [x] **Pulse Tracker** -- Attack trigger: `LoseLifeOpponents(1)` -- **FIXED** (was Custom, now uses `Effect::lose_life_opponents(1)`)\n\n- [x] **Vampire Spawn** -- ETB: `LoseLifeOpponents(2)` + `GainLife(2)` -- **FIXED** (was Custom, now uses typed effects)",
  "old_string": "- [ ] **Marauding Blight-Priest** -- What works: 3/2 creature. What's broken: GainLife trigger `Effect::Custom(\"Each opponent loses 1 life.\")`.\n  - **Java source**: `Mage.Sets/src/mage/cards/m/MaraudingBlightPriest.java`\n  - **What it should do**: Each opponent loses 1 life when you gain life\n  - **Fix needed**: Replace with `Effect::DealDamageOpponents { amount: 1 }` (or `Effect::LoseLife` targeting opponents)\n\n- [ ] **Pulse Tracker** -- What works: 1/1 creature. What's broken: Attack trigger `Effect::Custom(\"Each opponent loses 1 life.\")`.\n  - **Java source**: `Mage.Sets/src/mage/cards/p/PulseTracker.java`\n  - **What it should do**: Each opponent loses 1 life when it attacks\n  - **Fix needed**: Replace with `Effect::DealDamageOpponents { amount: 1 }`\n\n- [ ] **Vampire Spawn** -- What works: 2/3 creature. What's broken: ETB `Effect::Custom(\"Each opponent loses 2 life, you gain 2 life.\")`.\n  - **Java source**: `Mage.Sets/src/mage/cards/v/VampireSpawn.java`\n  - **What it should do**: Each opponent loses 2 life, you gain 2 life\n  - **Fix needed**: Replace with `Effect::DealDamageOpponents { amount: 2 }` + `Effect::GainLife { amount: 2 }`",
  "replace_all": false
}
```

## Assistant

**Result** (success):
```
The file /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/docs/fdn-remediation.md has been updated successfully.
```

## Assistant

### Tool: Edit

**Args:**
```json
{
  "file_path": "/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/docs/fdn-remediation.md",
  "new_string": "- [x] **Phyrexian Arena** -- Upkeep: `DrawCards(1)` + `LoseLife(1)` -- **FIXED** (was Custom, now uses `Effect::lose_life(1)`)",
  "old_string": "- [ ] **Phyrexian Arena** -- What works: `DrawCards(1)` on upkeep. What's broken: `Effect::Custom(\"You lose 1 life.\")`.\n  - **Fix needed**: Replace with `Effect::LoseLife { amount: 1 }`",
  "replace_all": false
}
```

## Assistant

**Result** (success):
```
The file /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/docs/fdn-remediation.md has been updated successfully.
```

## Assistant

### Tool: Edit

**Args:**
```json
{
  "file_path": "/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/docs/fdn-remediation.md",
  "new_string": "- [x] **Vampire Neonate** -- Activated: `LoseLifeOpponents(1)` + `GainLife(1)` -- **FIXED** (was Custom, now uses typed effects)",
  "old_string": "- [ ] **Vampire Neonate** -- What works: 0/3 creature. What's broken: Activated `Effect::Custom(\"Each opponent loses 1 life, you gain 1 life.\")`.\n  - **Fix needed**: Replace with `Effect::DealDamageOpponents { amount: 1 }` + `Effect::GainLife { amount: 1 }`",
  "replace_all": false
}
```

## Assistant

**Result** (success):
```
The file /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/docs/fdn-remediation.md has been updated successfully.
```

## Assistant

Now update the Phase 2 roadmap section and the overview counts.

## Assistant

### Tool: Edit

**Args:**
```json
{
  "file_path": "/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/docs/fdn-remediation.md",
  "new_string": "### Phase 2: Fix easy card-level issues\n1. ~~Fix Phyrexian Arena: change `Effect::Custom(\"You lose 1 life.\")` to `Effect::LoseLife { amount: 1 }`~~ -- **DONE**\n2. ~~Fix Vampire Spawn/Pulse Tracker/Marauding Blight-Priest: use `LoseLifeOpponents` + `GainLife`~~ -- **DONE**\n3. ~~Fix Vampire Neonate: use `LoseLifeOpponents` + `GainLife`~~ -- **DONE**\n4. Fix Diregraf Ghoul: use `StaticEffect::EntersTapped`",
  "old_string": "### Phase 2: Fix easy card-level issues\n1. Fix Phyrexian Arena: change `Effect::Custom(\"You lose 1 life.\")` to `Effect::LoseLife { amount: 1 }`\n2. Fix Vampire Spawn/Pulse Tracker/Marauding Blight-Priest: use `DealDamageOpponents` + `GainLife`\n3. Fix Vampire Neonate: use `DealDamageOpponents` + `GainLife`\n4. Fix Diregraf Ghoul: use `StaticEffect::EntersTapped`",
  "replace_all": false
}
```

## Assistant

**Result** (success):
```
The file /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/docs/fdn-remediation.md has been updated successfully.
```

## Assistant

Now update the ECL and TDM remediation docs.

## Assistant

### Tool: Edit

**Args:**
```json
{
  "file_path": "/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/docs/ecl-remediation.md",
  "new_string": "- [ ] **Champion of the Weird** — What works: `LoseLifeOpponents(2)` on activated ability. What's broken: `Cost::Custom` (blight counter), behold mechanic, and return-exiled-card effect.\n  - **Java source**: `Mage.Sets/src/mage/cards/c/ChampionOfTheWeird.java`\n  - **What it should do**: Behold a Goblin + exile it. {1}{B}, blight 1: each opponent loses 2 life. Leaves: return exiled card.\n  - **Fix needed**: Implement behold mechanic, blight cost, and return-exiled-card effect. (`LoseLifeOpponents` now works.)",
  "old_string": "- [ ] **Champion of the Weird** — What works: none of the effects. What's broken: Both `Cost::Custom` and `Effect::Custom` on all abilities.\n  - **Java source**: `Mage.Sets/src/mage/cards/c/ChampionOfTheWeird.java`\n  - **What it should do**: Behold a Goblin + exile it. {1}{B}, blight 1: each opponent loses 2 life. Leaves: return exiled card.\n  - **Fix needed**: Implement behold mechanic, `LoseLife` for opponents, and return-exiled-card effect.",
  "replace_all": false
}
```

## Assistant

**Result** (error):
```
<tool_use_error>File has not been read yet. Read it first before writing to it.</tool_use_error>
```

## Assistant

### Tool: Edit

**Args:**
```json
{
  "file_path": "/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/docs/ecl-remediation.md",
  "new_string": "- [ ] **Boggart Mischief** — What works: dies trigger `LoseLifeOpponents(1)` + `GainLife(1)`. What's broken: ETB still `Effect::Custom` (blight choice + token creation).\n  - **Java source**: `Mage.Sets/src/mage/cards/b/BoggartMischief.java`\n  - **What it should do**: ETB: blight 1 to create 2 Goblin tokens. Goblin dies: opponents lose 1, you gain 1.\n  - **Fix needed**: Blight choice + conditional token creation on ETB. (Dies trigger now works.)",
  "old_string": "- [ ] **Boggart Mischief** — What works: stats. What's broken: Both ETB and dies triggers are `Effect::Custom`.\n  - **Java source**: `Mage.Sets/src/mage/cards/b/BoggartMischief.java`\n  - **What it should do**: ETB: blight 1 to create 2 Goblin tokens. Goblin dies: opponents lose 1, you gain 1.\n  - **Fix needed**: Complex — needs blight choice + conditional token + drain trigger.",
  "replace_all": false
}
```

## Assistant

**Result** (error):
```
<tool_use_error>Sibling tool call errored</tool_use_error>
```

## Assistant

### Tool: Edit

**Args:**
```json
{
  "file_path": "/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/docs/tdm-remediation.md",
  "new_string": "- [x] **Skirmish Rhino** — 3/4 trample for {W}{B}{G}. ETB: `LoseLifeOpponents(2)` + `GainLife(2)` -- **FIXED** (was Custom, now uses `Effect::lose_life_opponents(2)`).",
  "old_string": "- [ ] **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).\n  - **Fix needed**: Replace `Custom(\"Each opponent loses 2 life.\")` with `Effect::DealDamageOpponents { amount: 2 }`.",
  "replace_all": false
}
```

## Assistant

**Result** (error):
```
<tool_use_error>Sibling tool call errored</tool_use_error>
```

## Assistant

Need to read those files first.

## Assistant

### Tool: Read

**Args:**
```json
{
  "file_path": "/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/docs/ecl-remediation.md",
  "limit": 12,
  "offset": 168
}
```

## Assistant

**Result** (success):
```
   168→  - **What it should do**: {2}{W}: Gains indestructible until end of turn.
   169→  - **Status**: Keyword grant now implemented. Card may be fully functional.
   170→
   171→- [ ] **Champion of the Weird** — What works: none of the effects. What's broken: Both `Cost::Custom` and `Effect::Custom` on all abilities.
   172→  - **Java source**: `Mage.Sets/src/mage/cards/c/ChampionOfTheWeird.java`
   173→  - **What it should do**: Behold a Goblin + exile it. {1}{B}, blight 1: each opponent loses 2 life. Leaves: return exiled card.
   174→  - **Fix needed**: Implement behold mechanic, `LoseLife` for opponents, and return-exiled-card effect.
   175→
   176→- [ ] **Champions of the Perfect** — What works: `draw_cards(1)` on spell cast. What's broken: LTB returns exiled card (Custom).
   177→  - **Java source**: `Mage.Sets/src/mage/cards/c/ChampionsOfThePerfect.java`
   178→  - **What it should do**: Behold+exile Elf. Creature spell: draw. Leaves: return exiled card.
   179→  - **Fix needed**: Implement behold mechanic and return-exiled-card.
```

## Assistant

### Tool: Read

**Args:**
```json
{
  "file_path": "/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/docs/ecl-remediation.md",
  "limit": 12,
  "offset": 423
}
```

## Assistant

**Result** (success):
```
   423→  - **What it should do**: +1/+0, then fights opponent's creature.
   424→  - **Fix needed**: Implement `Effect::Fight`.
   425→
   426→- [ ] **Boggart Mischief** — What works: stats. What's broken: Both ETB and dies triggers are `Effect::Custom`.
   427→  - **Java source**: `Mage.Sets/src/mage/cards/b/BoggartMischief.java`
   428→  - **What it should do**: ETB: blight 1 to create 2 Goblin tokens. Goblin dies: opponents lose 1, you gain 1.
   429→  - **Fix needed**: Complex — needs blight choice + conditional token + drain trigger.
   430→
   431→- [ ] **Burning Curiosity** — What works: nothing. What's broken: `Effect::Custom("exile top 3, play until next end")`.
   432→  - **Java source**: `Mage.Sets/src/mage/cards/b/BurningCuriosity.java`
   433→  - **What it should do**: Exile top 3, play until next end step.
   434→  - **Fix needed**: Implement impulse draw effect.
```

## Assistant

### Tool: Read

**Args:**
```json
{
  "file_path": "/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/docs/tdm-remediation.md",
  "limit": 8,
  "offset": 354
}
```

## Assistant

**Result** (success):
```
   354→  - **Java source**: `Mage.Sets/src/mage/cards/s/SibsigAppraiser.java`
   355→  - **Fix needed**: "Look at top N, distribute" effect.
   356→
   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).
   358→  - **Fix needed**: Replace `Custom("Each opponent loses 2 life.")` with `Effect::DealDamageOpponents { amount: 2 }`.
   359→
   360→- [ ] **Snakeskin Veil** — Instant {G}. What works: AddCounters +1/+1 + Hexproof until EOT. **(NOW IMPLEMENTED — Hexproof NOW WORKS)**
   361→
```

## Assistant

### Tool: Edit

**Args:**
```json
{
  "file_path": "/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/docs/ecl-remediation.md",
  "new_string": "- [ ] **Champion of the Weird** — What works: `LoseLifeOpponents(2)` on activated ability. What's broken: `Cost::Custom` (blight counter), behold mechanic, and return-exiled-card effect.\n  - **Java source**: `Mage.Sets/src/mage/cards/c/ChampionOfTheWeird.java`\n  - **What it should do**: Behold a Goblin + exile it. {1}{B}, blight 1: each opponent loses 2 life. Leaves: return exiled card.\n  - **Fix needed**: Implement behold mechanic, blight cost, and return-exiled-card effect. (`LoseLifeOpponents` now works.)",
  "old_string": "- [ ] **Champion of the Weird** — What works: none of the effects. What's broken: Both `Cost::Custom` and `Effect::Custom` on all abilities.\n  - **Java source**: `Mage.Sets/src/mage/cards/c/ChampionOfTheWeird.java`\n  - **What it should do**: Behold a Goblin + exile it. {1}{B}, blight 1: each opponent loses 2 life. Leaves: return exiled card.\n  - **Fix needed**: Implement behold mechanic, `LoseLife` for opponents, and return-exiled-card effect.",
  "replace_all": false
}
```

## Assistant

**Result** (success):
```
The file /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/docs/ecl-remediation.md has been updated successfully.
```

## Assistant

### Tool: Edit

**Args:**
```json
{
  "file_path": "/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/docs/ecl-remediation.md",
  "new_string": "- [ ] **Boggart Mischief** — What works: dies trigger `LoseLifeOpponents(1)` + `GainLife(1)`. What's broken: ETB still `Effect::Custom` (blight choice + token creation).\n  - **Java source**: `Mage.Sets/src/mage/cards/b/BoggartMischief.java`\n  - **What it should do**: ETB: blight 1 to create 2 Goblin tokens. Goblin dies: opponents lose 1, you gain 1.\n  - **Fix needed**: Blight choice + conditional token creation on ETB. (Dies trigger now works.)",
  "old_string": "- [ ] **Boggart Mischief** — What works: stats. What's broken: Both ETB and dies triggers are `Effect::Custom`.\n  - **Java source**: `Mage.Sets/src/mage/cards/b/BoggartMischief.java`\n  - **What it should do**: ETB: blight 1 to create 2 Goblin tokens. Goblin dies: opponents lose 1, you gain 1.\n  - **Fix needed**: Complex — needs blight choice + conditional token + drain trigger.",
  "replace_all": false
}
```

## Assistant

**Result** (success):
```
The file /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/docs/ecl-remediation.md has been updated successfully.
```

## Assistant

### Tool: Edit

**Args:**
```json
{
  "file_path": "/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/docs/tdm-remediation.md",
  "new_string": "- [x] **Skirmish Rhino** — 3/4 trample for {W}{B}{G}. ETB: `LoseLifeOpponents(2)` + `GainLife(2)` -- **FIXED** (was Custom, now uses `Effect::lose_life_opponents(2)`).",
  "old_string": "- [ ] **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).\n  - **Fix needed**: Replace `Custom(\"Each opponent loses 2 life.\")` with `Effect::DealDamageOpponents { amount: 2 }`.",
  "replace_all": false
}
```

## Assistant

**Result** (success):
```
The file /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/docs/tdm-remediation.md has been updated successfully.
```

## Assistant

Now update the ROADMAP.md.

## Assistant

### Tool: Read

**Args:**
```json
{
  "file_path": "/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/ROADMAP.md",
  "limit": 15,
  "offset": 9
}
```

## Assistant

**Result** (success):
```
     9→These `Effect` enum variants exist in `abilities.rs` but have no implementation in `execute_effects()` -- they fall through to `_ => {}` and silently do nothing at runtime.
    10→
    11→**Recently implemented:** The following Effect variants were previously no-ops but now have working implementations in `execute_effects()`: `Scry`, `SearchLibrary`, `ReturnFromGraveyard`, `Reanimate`, `GainKeywordUntilEndOfTurn`, `GainKeyword`, `LoseKeyword`, `Indestructible`, `Hexproof`, `CantBlock`, `Sacrifice`, `DestroyAll`, `DealDamageAll`, `RemoveCounters`, `CreateTokenTappedAttacking`, `BoostPermanent`, `SetPowerToughness`. Token stat parsing is also implemented (`CreateToken` now parses P/T and keywords from `token_name`).
    12→
    13→| Effect Variant | Description | Cards Blocked |
    14→|---------------|-------------|---------------|
    15→| `GainControl` | Gain control of target permanent | ~5 |
    16→| `GainControlUntilEndOfTurn` | Threaten/Act of Treason effects | ~5 |
    17→| `GainProtection` | Target gains protection from quality | ~5 |
    18→| `PreventCombatDamage` | Fog / damage prevention | ~5 |
    19→| `MustBlock` | Target creature must block | ~3 |
    20→| `SetLife` | Set a player's life total | ~2 |
    21→| `Custom(String)` | Catch-all for untyped effects | ~400+ |
    22→
    23→Note: `Custom(String)` is used when no typed variant exists. Each Custom needs to be individually replaced with a typed variant or a new variant added.
```

## Assistant

### Tool: Read

**Args:**
```json
{
  "file_path": "/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/ROADMAP.md",
  "limit": 25,
  "offset": 110
}
```

## Assistant

**Result** (success):
```
   110→These unblock the most cards per effort invested.
   111→
   112→1. **Token stat parsing** -- **DONE** -- Parse power/toughness/keywords from `token_name` string in `CreateToken`. Unblocks ~30 cards that already create tokens but with wrong stats.
   113→
   114→2. **Fix easy card-level bugs** -- Many cards use `Effect::Custom(...)` when a typed variant already exists. Examples:
   115→   - Phyrexian Arena: `Custom("You lose 1 life.")` -> `LoseLife { amount: 1 }`
   116→   - Pulse Tracker/Marauding Blight-Priest/Vampire Spawn: `Custom("Each opponent loses N life")` -> `DealDamageOpponents { amount: N }`
   117→   - Diregraf Ghoul: `StaticEffect::Custom("Enters tapped.")` -> `StaticEffect::EntersTapped`
   118→   - Incomplete dual lands: copy the Azorius Guildgate / Bloodfell Caves pattern
   119→   - Sourbread Auntie/Sting-Slinger/Blighted Blackthorn: self-counter Custom -> `AddCounters` targeting self
   120→
   121→3. **Fight mechanic** -- New `Effect::Fight` variant. Two creatures deal damage equal to their power to each other. Unblocks ~10 cards.
   122→
   123→### Phase 2: Key Missing Mechanics
   124→
   125→4. **Equipment system** -- Attach/detach, equip cost, stat/keyword application. Unblocks ~15 cards.
   126→
   127→5. **Modal spells** -- Mode selection in `PlayerDecisionMaker` trait, mode-conditional effect resolution. Unblocks ~20 cards.
   128→
   129→6. **Impulse draw** -- "Exile top N, may play until end of [next] turn." Track exiled-playable cards in game state. Unblocks ~10 cards.
   130→
   131→7. **Earthbend** (TLA-specific) -- "Look at top N, put a land to hand, rest on bottom." Unblocks ~20 TLA cards.
   132→
   133→### Phase 3: Advanced Systems
   134→
```

## Assistant

### Tool: Edit

**Args:**
```json
{
  "file_path": "/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/ROADMAP.md",
  "new_string": "**Recently implemented:** The following Effect variants were previously no-ops but now have working implementations in `execute_effects()`: `Scry`, `SearchLibrary`, `ReturnFromGraveyard`, `Reanimate`, `GainKeywordUntilEndOfTurn`, `GainKeyword`, `LoseKeyword`, `Indestructible`, `Hexproof`, `CantBlock`, `Sacrifice`, `DestroyAll`, `DealDamageAll`, `RemoveCounters`, `CreateTokenTappedAttacking`, `BoostPermanent`, `SetPowerToughness`, `LoseLifeOpponents`. Token stat parsing is also implemented (`CreateToken` now parses P/T and keywords from `token_name`).",
  "old_string": "**Recently implemented:** The following Effect variants were previously no-ops but now have working implementations in `execute_effects()`: `Scry`, `SearchLibrary`, `ReturnFromGraveyard`, `Reanimate`, `GainKeywordUntilEndOfTurn`, `GainKeyword`, `LoseKeyword`, `Indestructible`, `Hexproof`, `CantBlock`, `Sacrifice`, `DestroyAll`, `DealDamageAll`, `RemoveCounters`, `CreateTokenTappedAttacking`, `BoostPermanent`, `SetPowerToughness`. Token stat parsing is also implemented (`CreateToken` now parses P/T and keywords from `token_name`).",
  "replace_all": false
}
```

## Assistant

**Result** (success):
```
The file /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/ROADMAP.md has been updated successfully.
```

## Assistant

### Tool: Edit

**Args:**
```json
{
  "file_path": "/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/ROADMAP.md",
  "new_string": "2. **Fix easy card-level bugs** -- Many cards use `Effect::Custom(...)` when a typed variant already exists. Examples:\n   - ~~Phyrexian Arena: `Custom(\"You lose 1 life.\")` -> `LoseLife { amount: 1 }`~~ **DONE**\n   - ~~Pulse Tracker/Marauding Blight-Priest/Vampire Spawn/Vampire Neonate: `Custom(\"Each opponent loses N life\")` -> `LoseLifeOpponents { amount: N }`~~ **DONE**\n   - ~~Skirmish Rhino (TDM), Champion of the Weird (ECL), Boggart Mischief (ECL): opponent life loss Custom -> `LoseLifeOpponents`~~ **DONE**\n   - Diregraf Ghoul: `StaticEffect::Custom(\"Enters tapped.\")` -> `StaticEffect::EntersTapped`\n   - Incomplete dual lands: copy the Azorius Guildgate / Bloodfell Caves pattern\n   - Sourbread Auntie/Sting-Slinger/Blighted Blackthorn: self-counter Custom -> `AddCounters` targeting self",
  "old_string": "2. **Fix easy card-level bugs** -- Many cards use `Effect::Custom(...)` when a typed variant already exists. Examples:\n   - Phyrexian Arena: `Custom(\"You lose 1 life.\")` -> `LoseLife { amount: 1 }`\n   - Pulse Tracker/Marauding Blight-Priest/Vampire Spawn: `Custom(\"Each opponent loses N life\")` -> `DealDamageOpponents { amount: N }`\n   - Diregraf Ghoul: `StaticEffect::Custom(\"Enters tapped.\")` -> `StaticEffect::EntersTapped`\n   - Incomplete dual lands: copy the Azorius Guildgate / Bloodfell Caves pattern\n   - Sourbread Auntie/Sting-Slinger/Blighted Blackthorn: self-counter Custom -> `AddCounters` targeting self",
  "replace_all": false
}
```

## Assistant

**Result** (success):
```
The file /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/ROADMAP.md has been updated successfully.
```

## Assistant

Now let me run the full test suite one more time to confirm everything is clean.

## Assistant

### Tool: Bash

**Args:**
```json
{
  "command": "cargo test --lib 2>&1 | tail -20",
  "description": "Run all tests, show summary",
  "timeout": 300000
}
```

## Assistant

**Result** (success):
```
test 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
```

## Assistant

All 244 tests pass. Here's a summary of everything done:

## Engine Changes

**New `Effect::LoseLifeOpponents { amount }` variant** added to:
- `mtg-engine/src/abilities.rs` — enum variant + `lose_life_opponents()` constructor
- `mtg-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.

## Card Fixes (8 cards across 3 sets)

**FDN (5 cards):**
- **Marauding Blight-Priest** — `Custom` → `lose_life_opponents(1)`
- **Pulse Tracker** — `Custom` → `lose_life_opponents(1)`
- **Vampire Spawn** — `Custom` → `lose_life_opponents(2)` + `gain_life(2)`
- **Phyrexian Arena** — `Custom("You lose 1 life.")` → `lose_life(1)`
- **Vampire Neonate** — `Custom` → `lose_life_opponents(1)` + `gain_life(1)`

**ECL (2 cards):**
- **Champion of the Weird** — activated ability effect now works
- **Boggart Mischief** — dies trigger now works

**TDM (1 card):**
- **Skirmish Rhino** — `Custom` → `lose_life_opponents(2)` (already had `gain_life(2)`)

## Doc Updates
- `docs/fdn-remediation.md` — Added `LoseLifeOpponents` to effects table, marked 5 cards fixed, updated Phase 2 roadmap
- `docs/ecl-remediation.md` — Updated Champion of the Weird and Boggart Mischief entries
- `docs/tdm-remediation.md` — Marked Skirmish Rhino as fixed
- `ROADMAP.md` — Added `LoseLifeOpponents` to recently-implemented list, marked card-level fixes as done

## User

Did we add tests for our new effect? If not let's do that.

## Assistant

Good catch — we didn't. Let me check where the existing effect tests live.

## Assistant

### Tool: Grep

**Args:**
```json
{
  "-n": true,
  "output_mode": "content",
  "path": "/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-engine/src/game.rs",
  "pattern": "fn (gain_life_effect|spell_effects_execute|draw_cards_effect|bounce_effect|exile_effect)"
}
```

## Assistant

**Result** (success):
```
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() {
```

## Assistant

### Tool: Read

**Args:**
```json
{
  "file_path": "/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-engine/src/game.rs",
  "limit": 60,
  "offset": 2088
}
```

## Assistant

**Result** (success):
```
  2088→    fn gain_life_effect() {
  2089→        let p1 = PlayerId::new();
  2090→        let p2 = PlayerId::new();
  2091→
  2092→        let config = GameConfig {
  2093→            players: vec![
  2094→                PlayerConfig { name: "Alice".to_string(), deck: make_deck(p1) },
  2095→                PlayerConfig { name: "Bob".to_string(), deck: make_deck(p2) },
  2096→            ],
  2097→            starting_life: 20,
  2098→        };
  2099→
  2100→        let mut game = Game::new_two_player(
  2101→            config,
  2102→            vec![
  2103→                (p1, Box::new(AlwaysPassPlayer)),
  2104→                (p2, Box::new(AlwaysPassPlayer)),
  2105→            ],
  2106→        );
  2107→
  2108→        game.execute_effects(&[Effect::GainLife { amount: 5 }], p1, &[]);
  2109→        assert_eq!(game.state.players.get(&p1).unwrap().life, 25);
  2110→    }
  2111→
  2112→    #[test]
  2113→    fn exile_effect() {
  2114→        let p1 = PlayerId::new();
  2115→        let p2 = PlayerId::new();
  2116→
  2117→        let config = GameConfig {
  2118→            players: vec![
  2119→                PlayerConfig { name: "Alice".to_string(), deck: make_deck(p1) },
  2120→                PlayerConfig { name: "Bob".to_string(), deck: make_deck(p2) },
  2121→            ],
  2122→            starting_life: 20,
  2123→        };
  2124→
  2125→        let mut game = Game::new_two_player(
  2126→            config,
  2127→            vec![
  2128→                (p1, Box::new(AlwaysPassPlayer)),
  2129→                (p2, Box::new(AlwaysPassPlayer)),
  2130→            ],
  2131→        );
  2132→
  2133→        // Put a creature on the battlefield
  2134→        let bear_id = ObjectId::new();
  2135→        let mut bear = CardData::new(bear_id, p2, "Bear");
  2136→        bear.card_types = vec![CardType::Creature];
  2137→        bear.power = Some(2);
  2138→        bear.toughness = Some(2);
  2139→        bear.keywords = KeywordAbilities::empty();
  2140→        game.state.battlefield.add(Permanent::new(bear, p2));
  2141→
  2142→        // Exile it
  2143→        game.execute_effects(&[Effect::Exile], p1, &[bear_id]);
  2144→
  2145→        assert!(!game.state.battlefield.contains(bear_id));
  2146→        assert!(game.state.exile.contains(bear_id));
  2147→    }
```

## Assistant

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`.

## Assistant

### Tool: Edit

**Args:**
```json
{
  "file_path": "/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-engine/src/game.rs",
  "new_string": "    #[test]\n    fn lose_life_opponents_effect() {\n        let p1 = PlayerId::new();\n        let p2 = PlayerId::new();\n\n        let config = GameConfig {\n            players: vec![\n                PlayerConfig { name: \"Alice\".to_string(), deck: make_deck(p1) },\n                PlayerConfig { name: \"Bob\".to_string(), deck: make_deck(p2) },\n            ],\n            starting_life: 20,\n        };\n\n        let mut game = Game::new_two_player(\n            config,\n            vec![\n                (p1, Box::new(AlwaysPassPlayer)),\n                (p2, Box::new(AlwaysPassPlayer)),\n            ],\n        );\n\n        game.execute_effects(&[Effect::lose_life_opponents(3)], p1, &[]);\n        // Controller's life should be unchanged\n        assert_eq!(game.state.players.get(&p1).unwrap().life, 20);\n        // Opponent loses 3 life\n        assert_eq!(game.state.players.get(&p2).unwrap().life, 17);\n    }\n\n    #[test]\n    fn exile_effect() {",
  "old_string": "    #[test]\n    fn exile_effect() {",
  "replace_all": false
}
```

## Assistant

**Result** (success):
```
The file /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-engine/src/game.rs has been updated successfully.
```

## Assistant

### Tool: Bash

**Args:**
```json
{
  "command": "cargo test --lib -p mtg-engine lose_life_opponents 2>&1",
  "description": "Run the new test",
  "timeout": 120000
}
```

## Assistant

**Result** (success):
```
   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.00s
```

## Assistant

Test passes. Now 158 engine tests (was 157) and 245 total.

## User

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. 

## Assistant

## Assistant

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.

## Assistant

### Tool: Task

**Args:**
```json
{
  "description": "Scan all Custom effects by category",
  "mode": "bypassPermissions",
  "prompt": "I need to analyze all remaining `Effect::Custom(...)` and `StaticEffect::Custom(...)` usages across the 4 card set files to group them by what shared implementation they need. \n\nSearch these files:\n- /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-cards/src/sets/fdn.rs\n- /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-cards/src/sets/tla.rs\n- /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-cards/src/sets/tdm.rs\n- /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/mtg-cards/src/sets/ecl.rs\n\nFor each file, find ALL lines containing `Effect::Custom(` and `StaticEffect::Custom(`. Extract the string inside each Custom(...). \n\nThen categorize them into groups. I want the output as a structured list like:\n\n**GROUP NAME** (N cards across K sets)\n- Description of what engine work is needed\n- List of the distinct Custom strings that fall in this group\n- Which set files contain them and approximately how many\n\nFocus on finding groups where multiple cards share the same or very similar Custom string, since those are the best bang-for-buck. Also identify \"easy swap\" cases where a typed Effect variant already exists but the card still uses Custom.\n\nKnown typed effects that ALREADY WORK in execute_effects():\nDealDamage, Destroy, Exile, Bounce, DrawCards, GainLife, LoseLife, LoseLifeOpponents, DealDamageOpponents, AddCounters, BoostUntilEndOfTurn, TapTarget, UntapTarget, CounterSpell, AddMana, DiscardCards, Mill, CreateToken, Scry, SearchLibrary, ReturnFromGraveyard, Reanimate, GainKeywordUntilEndOfTurn, GainKeyword, LoseKeyword, Indestructible, Hexproof, CantBlock, Sacrifice, DestroyAll, DealDamageAll, RemoveCounters, CreateTokenTappedAttacking, BoostPermanent, SetPowerToughness\n\nKnown typed StaticEffects: Boost, GrantKeyword, CostReduction, CantAttack, CantBlock, EntersTapped\n\nDo NOT read the full files. Use Grep to extract the Custom lines and then analyze them.",
  "subagent_type": "Explore"
}
```

## Assistant

**Result** (success):
```
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>
```

## Assistant

## Assistant

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`.

## Assistant

### Tool: Bash

**Args:**
```json
{
  "command": "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 -60",
  "description": "Find easy-swap Custom effects matching existing typed variants"
}
```

## Assistant

**Result** (success):
```
mtg-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())],
```

## Assistant

### Tool: Bash

**Args:**
```json
{
  "command": "grep -n 'StaticEffect::Custom(' mtg-cards/src/sets/*.rs | grep -viE '(static effect\\.)' | head -40",
  "description": "Find non-placeholder StaticEffect::Custom usages"
}
```

## Assistant

**Result** (success):
```
mtg-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())]),
```

## Assistant

Now let me also count the easy-swap candidates precisely.

## Assistant

### Tool: Bash

**Args:**
```json
{
  "command": "# Count exact easy-swap patterns\necho \"=== Enters tapped ===\"\ngrep -n 'StaticEffect::Custom(\"Enters tapped' mtg-cards/src/sets/*.rs\n\necho \"\"\necho \"=== Destroy all creatures ===\"  \ngrep -n 'Effect::Custom(\"Destroy all creatures' mtg-cards/src/sets/*.rs\n\necho \"\"\necho \"=== Each opponent discards a card ===\"\ngrep -n 'Effect::Custom(\"Each opponent discards a card' mtg-cards/src/sets/*.rs\n\necho \"\"\necho \"=== Target creature can't block this turn ===\"\ngrep -n \"Effect::Custom(\\\"Target creature can.t block this turn\" mtg-cards/src/sets/*.rs\n\necho \"\"\necho \"=== draw a card (simple ETB/trigger) ===\"\ngrep -n 'Effect::Custom(\"When this creature enters, draw a card' mtg-cards/src/sets/*.rs\n\necho \"\"\necho \"=== draw a card, then discard ===\"\ngrep -n 'Effect::Custom(\".*draw a card, then discard a card' mtg-cards/src/sets/*.rs\n\necho \"\"\necho \"=== Ward {2} ===\"\ngrep -n 'StaticEffect::Custom(\"Ward' mtg-cards/src/sets/*.rs\n\necho \"\"\necho \"=== Self -1/-1 counter ===\"\ngrep -n 'Effect::Custom(\"Put.*-1/-1 counter.*on.*\\(this\\|Sourbread\\|Heirloom\\|Sting\\|Blighted\\|Dream\\)' mtg-cards/src/sets/*.rs",
  "description": "Count exact easy-swap patterns"
}
```

## Assistant

**Result** (success):
```
=== 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)],
```

## Assistant

Now I have a clear picture. Let me create the work queue document.

## Assistant

### Tool: Read

**Args:**
```json
{
  "file_path": "/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/docs/fdn-remediation.md",
  "limit": 1,
  "offset": 1
}
```

## Assistant

**Result** (success):
```
     1→# FDN (Foundations) Card Remediation
```

## Assistant

### Tool: Bash

**Args:**
```json
{
  "command": "ls /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/docs/",
  "description": "List existing docs"
}
```

## Assistant

**Result** (success):
```
ecl-remediation.md
fdn-remediation.md
tdm-remediation.md
tla-remediation.md
```

## Assistant

### Tool: Write

**Args:**
```json
{
  "content": "# Card Remediation Work Queue\n\nThis document drives the batch card-fix loop. Each batch groups cards that share a common missing implementation. Work top-to-bottom; check off batches as completed.\n\n## Process (for each batch)\n\n1. **Read this file** to find the next unchecked batch\n2. **Read the engine files** (`abilities.rs`, `game.rs`) to understand current state\n3. **Implement** the engine change (new Effect/StaticEffect variant + match arm), if needed\n4. **Add a test** for any new engine effect in `game.rs` `mod tests`\n5. **Fix the cards** listed in the batch (grep to find exact lines)\n6. **Verify**: `cargo check -p mtg-cards && cargo test --lib`\n7. **Update docs**: Mark cards fixed in `docs/{set}-remediation.md`, update ROADMAP.md\n8. **Check off** the batch below and note the date\n\n---\n\n## Batch 1: LoseLifeOpponents [DONE 2026-02-13]\n\n**Engine**: Added `Effect::LoseLifeOpponents { amount }` variant + constructor + match arm + test.\n\n**Cards fixed (8):**\n- [x] FDN: Marauding Blight-Priest, Pulse Tracker, Vampire Spawn, Phyrexian Arena (LoseLife), Vampire Neonate\n- [x] TDM: Skirmish Rhino\n- [x] ECL: Champion of the Weird (effect part), Boggart Mischief (dies trigger part)\n\n---\n\n## Batch 2: Easy swaps — existing typed effects\n\n**Engine**: No engine changes needed. These cards use `Custom(...)` or `StaticEffect::Custom(...)` for things that already have typed variants.\n\n### 2a: DestroyAll — Day of Judgment\n- [ ] FDN line ~2417: `Effect::Custom(\"Destroy all creatures.\")` → `Effect::DestroyAll { filter: \"creatures\".into() }`\n\n### 2b: EntersTapped — Diregraf Ghoul\n- [ ] FDN line ~644: `StaticEffect::Custom(\"Enters tapped.\")` → `StaticEffect::EntersTapped`\n\n### 2c: CantBlock (target) — Frenzied Goblin, Skoa Veteran\n- [ ] FDN line ~1780: `Effect::Custom(\"Target creature can't block this turn.\")` → `Effect::CantBlock`\n- [ ] ECL line ~431: same pattern\n- Verify: these cards already have `TargetSpec::OpponentCreature` or similar targeting\n\n### 2d: Self -1/-1 counters — ECL blight creatures\nThese use `Effect::Custom(\"Put N -1/-1 counter(s) on <self>.\")` alongside working effects. Replace with `Effect::AddCounters`:\n- [ ] ECL line ~354,358: Blighted Blackthorn — `Custom(\"Put two -1/-1 counters on Blighted Blackthorn.\")` → `Effect::add_counters(\"-1/-1\", 2)` (appears twice, two abilities)\n- [ ] ECL line ~1438: Sourbread Auntie — `Custom(\"Put two -1/-1 counters on Sourbread Auntie.\")` → `Effect::add_counters(\"-1/-1\", 2)`\n- [ ] ECL line ~1467: Sting-Slinger — `Custom(\"Put a -1/-1 counter on Sting-Slinger.\")` → `Effect::add_counters(\"-1/-1\", 1)`\n- [ ] ECL line ~992: Heirloom Auntie — `Custom(\"Remove a -1/-1 counter from Heirloom Auntie.\")` → `Effect::RemoveCounters { counter_type: \"-1/-1\", count: 1 }`\n- **NOTE**: `AddCounters` currently targets via `SelectedTargets`. These are self-targeting — verify the engine handles \"add counters to self\" when no target is selected, or if it needs the source object ID in targets. May need a small engine tweak (`AddCountersSelf` variant or default-to-source logic).\n\n### 2e: Loot (draw then discard) — Jace's Phantasm area card\n- [ ] FDN line ~6342: `Custom(\"draw a card, then discard a card\")` → `Effect::draw_cards(1), Effect::DiscardCards { count: 1 }`\n- Check which card this is and verify the trigger/context\n\n### 2f: Simple ETB draw — ECL stub card\n- [ ] ECL line ~3981: `Custom(\"When this creature enters, draw a card.\")` → `Effect::draw_cards(1)`\n- Check which card this is; it may be a stub with the wrong Custom text\n\n---\n\n## Batch 3: OpponentDiscards\n\n**Engine**: Need `Effect::DiscardOpponents { count }` or `Effect::OpponentDiscards { count }` — similar to `LoseLifeOpponents`. The existing `DiscardCards` targets the controller.\n\n**Cards:**\n- [ ] FDN line ~617: Burglar Rat — ETB `Custom(\"Each opponent discards a card.\")`\n- [ ] ECL line ~596: Dream Seizer — `Custom(\"Put a -1/-1 counter on Dream Seizer. Each opponent discards a card.\")` → `Effect::add_counters(\"-1/-1\", 1), Effect::opponent_discards(1)` (depends on Batch 2d self-counter fix working)\n- [ ] FDN line ~5520: (check card) — `Custom(\"...if an opponent lost life this turn, each opponent discards a card.\")` — conditional, may be harder\n\n---\n\n## Batch 4: Simple token creation — stub cards with full token text\n\n**Engine**: `CreateToken` already works and parses P/T + keywords from name. These cards use `Custom(...)` with text that describes standard token creation.\n\n**Cards** (grep for `Custom(\"Create` and `Custom(\"When this creature enters, create`):\n- [ ] FDN line ~6199: (card?) — `Custom(\"create two 3/3 white Knight creature tokens\")`\n- [ ] FDN line ~6005: (card?) — `Custom(\"Create two 1/1 blue Faerie creature tokens with flying...\")`\n- [ ] FDN line ~5623: (card?) — `Custom(\"create a Food token\")`\n- [ ] FDN line ~5899: (card?) — `Custom(\"create a 1/1 green Elf Warrior creature token\")` (conditional)\n- [ ] ECL line ~3704: (card?) — `Custom(\"create two 1/1 green and white Kithkin creature tokens\")`\n- [ ] ECL line ~3709: (card?) — `Custom(\"{4}{W}: Create a 1/1 green and white Kithkin creature token\")`\n- **NOTE**: Some of these are inside stubs where the entire ability structure is placeholder. Check if the `Ability::` wrapper is correct (trigger type, costs) before just swapping the effect. Some may need the whole ability rewritten.\n\n---\n\n## Batch 5: Ward keyword\n\n**Engine**: Ward is a triggered ability (\"When this becomes the target of a spell or ability an opponent controls, counter it unless that player pays {cost}\"). Options:\n- A) Add `KeywordAbilities::WARD` flag + hardcode Ward {2} behavior in the targeting/stack system\n- B) Model as a triggered ability with a special `Effect::Ward { cost }`\n- C) Simpler: Add `StaticEffect::Ward { cost: String }` that the engine recognizes during targeting\n\n**Cards (6):**\n- [ ] TDM line ~347, ~1990, ~3729: three creatures with `StaticEffect::Custom(\"Ward {2}\")`\n- [ ] TDM line ~4078: `StaticEffect::Custom(\"Ward -- Discard a card.\")`\n- [ ] ECL line ~2189: `StaticEffect::Custom(\"Ward {2}\")`\n- [ ] FDN line ~5603: `StaticEffect::Custom(\"Ward {2}\")`\n\n---\n\n## Batch 6: Enters-tapped-unless (conditional ETB tapped)\n\n**Engine**: May need `StaticEffect::EntersTappedUnless { condition }` or similar.\n\n**Cards:**\n- [ ] TDM line ~2110: `StaticEffect::Custom(\"Enters tapped unless you control Plains or Island.\")`\n- [ ] TDM line ~2153: `StaticEffect::Custom(\"Enters tapped unless you control Swamp or Mountain.\")`\n\n---\n\n## Batch 7: Mass keyword grant until EOT\n\n**Engine**: Need `Effect::GrantKeywordAllUntilEndOfTurn { filter, keyword }` or similar mass-buff effect.\n\n**Cards:**\n- [ ] FDN: Crash Through — `Custom(\"Creatures you control gain trample until end of turn.\")` + DrawCards(1)\n- [ ] FDN: Overrun — `Custom(\"Creatures you control get +3/+3 and gain trample until end of turn.\")`\n- [ ] FDN: Make a Stand — `Custom(\"Creatures you control get +1/+0 and gain indestructible until end of turn.\")`\n- [ ] FDN: Heroic Reinforcements — `Custom(...)` mass +1/+1 and haste\n- [ ] FDN: Balmor, Battlemage Captain — spell trigger mass +1/+0 and trample\n\n---\n\n## Batch 8: Fight/Bite mechanic\n\n**Engine**: New `Effect::Fight` (mutual damage) and/or `Effect::Bite` (one-way damage based on power).\n\n**Cards (~10):**\n- [ ] FDN: Bite Down\n- [ ] ECL: multiple fight cards\n- [ ] TLA: Earth Rumble, others\n\n---\n\n## Batch 9: Modal spells (Choose one/two)\n\n**Engine**: Modal framework — player chooses mode, effects resolve based on mode selection.\n\n**Cards (~15+):**\n- [ ] FDN: Abrade, Boros Charm, Slagstorm, Valorous Stance, Charming Prince\n- [ ] TDM: multiple\n- [ ] ECL: multiple\n\n---\n\n## Batch 10+: Larger systems\n\nThese require more significant engine work:\n- Equipment attach/detach (Batch 10)\n- Planeswalker loyalty (Batch 11)\n- Token copy (Batch 12)\n- X-cost spells (Batch 13)\n- Aura attachment (Batch 14)\n- Impulse draw / exile-and-play (Batch 15)\n- Sagas / lore counters (Batch 16)\n- Set mechanics: Earthbend (TLA), Blight (ECL), Vivid (ECL), Behold (ECL) (Batch 17+)\n\n---\n\n## How to pick the next batch\n\n1. Always do the lowest-numbered unchecked batch first\n2. If a batch has a NOTE about engine uncertainty, investigate before committing\n3. After finishing a batch, update this file AND the per-set remediation docs\n4. Run `cargo test --lib` before and after every batch\n",
  "file_path": "/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/docs/work-queue.md"
}
```

## Assistant

**Result** (success):
```
File created successfully at: /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/docs/work-queue.md
```

## Assistant

Now let me update the memory file so future sessions know about this process.

## Assistant

### Tool: Read

**Args:**
```json
{
  "file_path": "/mnt/dest-btrfs/home/jc/.claude/projects/-mnt-dest-btrfs-home-jc-src-mage/memory/MEMORY.md"
}
```

## Assistant

**Result** (success):
```
     1→# Memory
     2→
     3→## User Preferences
     4→- Allow Bash commands that use `cat`, `find`, `grep`, `sed`, `wc`, `sort`, `head`, `tail`, `ls`, `diff`, `comm`, `python3`, `for` loops, piped commands, etc. freely -- the user prefers not to be blocked on these. If a new similar read-only/harmless command comes up, add it to `.claude/settings.local.json` proactively.
     5→- User works on mtg-rl Rust workspace at `/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/`
     6→
     7→## Project: mtg-rl
     8→- 5 crates: mtg-engine, mtg-cards, mtg-ai, mtg-python, mtg-tests
     9→- Card registrations: FDN (512), TLA (280), TDM (273), ECL (268) = 1,333 total
    10→- 244 tests passing, ~38K lines of Rust, 55 source files
    11→- Performance: ~88 games/sec single-threaded, ~585/sec with rayon
    12→- All types Send + Sync verified at compile time
    13→- 3 git commits on master starting at ea5e3c0606
    14→- License: XMage is MIT; our AI-generated code has uncertain copyright status
    15→- SubType enum uses `SubType::Custom("Name".into())` for types not in the enum
    16→- A linter/hook runs on file save that can cause "File has been modified since read" errors -- use `sed` via Bash as a workaround when Edit tool keeps failing
    17→- Run from mtg-rl dir: `cd /mnt/dest-btrfs/home/jc/src/mage/mtg-rl && cargo check -p mtg-cards`
    18→
    19→## Batch Card Generation
    20→- Use Python scripts to generate card factories from Java source files for efficiency
    21→- Card names from Java set files (e.g., `Mage.Sets/src/mage/sets/AvatarTheLastAirbender.java`)
    22→- Use `sed -i` for insertions and `cat >>` for appends when linter races Edit tool
    23→- Tier data at `/tmp/{FDN,TLA,TDM,ECL}_tiers.txt` format: `TIER{N}|PascalCaseName`
    24→- Test thresholds in registry.rs need updating when adding cards
    25→
    26→## Correct mtg-engine API (confirmed through compile errors)
    27→- `Effect::gain_keyword_eot("keyword")` takes &str NOT KeywordAbilities
    28→- `StaticEffect::GrantKeyword { filter: String, keyword: String }` both String
    29→- `StaticEffect::CantBlock { filter: String }` struct variant (NOT unit)
    30→- `Cost::TapSelf` (NOT `Cost::Tap`), `Cost::Mana(Mana { ... })` (NOT ManaCost)
    31→- `EventType::UpkeepStep` NOT `Upkeep`, `EnteredTheBattlefield` NOT `EntersBattlefield`
    32→- `EventType::DamagedPlayer` for combat damage triggers (NOT `DealsCombatDamage`)
    33→- `EventType::GainLife` NOT `LifeGain`, `DeclareAttackers` NOT `Attacking`
    34→- `EventType::Custom(...)` does NOT exist
    35→- `Effect::Sacrifice { filter: String }` struct variant, not unit
    36→- `Effect::search_library(filter)` requires a filter string argument
    37→- NON-EXISTENT: `Effect::damage_self/drain_opponents/surveil/pump_self/boost_controlled/grant_keyword_controlled`
    38→- NON-EXISTENT: `StaticEffect::BoostCreatureType/BoostOtherCreatureType/Ward`
    39→- NON-EXISTENT: `KeywordAbilities::KICKER`, `KeywordAbilities::FLASHBACK`
    40→- Use `StaticEffect::Boost { filter: "other Elf you control".into(), power: 1, toughness: 1 }` for lords
    41→
```

## Assistant

### Tool: Edit

**Args:**
```json
{
  "file_path": "/mnt/dest-btrfs/home/jc/.claude/projects/-mnt-dest-btrfs-home-jc-src-mage/memory/MEMORY.md",
  "new_string": "- 245 tests passing, ~38K lines of Rust, 55 source files",
  "old_string": "- 244 tests passing, ~38K lines of Rust, 55 source files",
  "replace_all": false
}
```

## Assistant

**Result** (success):
```
The file /mnt/dest-btrfs/home/jc/.claude/projects/-mnt-dest-btrfs-home-jc-src-mage/memory/MEMORY.md has been updated successfully.
```

## Assistant

### Tool: Edit

**Args:**
```json
{
  "file_path": "/mnt/dest-btrfs/home/jc/.claude/projects/-mnt-dest-btrfs-home-jc-src-mage/memory/MEMORY.md",
  "new_string": "- Use `StaticEffect::Boost { filter: \"other Elf you control\".into(), power: 1, toughness: 1 }` for lords\n- `Effect::LoseLifeOpponents { amount }` — each opponent loses life (distinct from DealDamageOpponents)\n\n## Card Remediation Loop\n- Work queue at `docs/work-queue.md` drives the batch-fix loop\n- Process: read queue → implement engine change (if any) → add test → fix cards → verify → update docs → check off batch\n- Per-set remediation docs at `docs/{fdn,tla,tdm,ecl}-remediation.md`\n- ROADMAP.md tracks engine-level gaps\n- Batch 1 (LoseLifeOpponents) done; Batch 2 (easy swaps) is next",
  "old_string": "- Use `StaticEffect::Boost { filter: \"other Elf you control\".into(), power: 1, toughness: 1 }` for lords",
  "replace_all": false
}
```

## Assistant

**Result** (success):
```
The file /mnt/dest-btrfs/home/jc/.claude/projects/-mnt-dest-btrfs-home-jc-src-mage/memory/MEMORY.md has been updated successfully.
```

## Assistant

Here's the approach:

## The Loop

Created `docs/work-queue.md` — a batch-oriented work queue that drives each session. The process for every batch:

1. **Read `docs/work-queue.md`** → find next unchecked batch
2. **Implement** engine change (new Effect variant + match arm in `game.rs`), if needed
3. **Add test** for any new effect
4. **Fix cards** across all 4 sets
5. **Verify**: `cargo check && cargo test --lib`
6. **Update docs**: per-set remediation files + ROADMAP.md
7. **Check off** the batch in `work-queue.md`

## The Batches (17 total, ordered by effort)

| # | 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?

## User

What instructions would I give you to run the loop?

## Assistant

## Assistant

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)
- The remediation docs have per-card status so nothing gets lost
- "Then do the next batch" keeps it looping
- "Stop when context is getting long" prevents quality degradation

**What could go wrong:**
- Batch 2d (self -1/-1 counters) has a NOTE about whether `AddCounters` works on self without a target. That needs investigation before fixing. The instructions should handle uncertainty gracefully.
- Some "easy swap" cards may turn out to be stubs where the whole ability wrapper is wrong, not just the effect. The queue notes this but the agent needs to actually check.

**Optional additions:**
- Add `If a batch has a NOTE about uncertainty, investigate first and update the queue with findings before committing the fix.` 
- Add `If you discover new cards that fit an existing batch while working, add them.`
- Add `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?

## User

Let's create a file called docs/work-queue-instructions.md. If you think the optional additions are valuable, add those as well.

## Assistant

### Tool: Write

**Args:**
```json
{
  "content": "# Work Queue Instructions\n\nPaste this into a new Claude Code session to run the card remediation loop:\n\n---\n\nRead `docs/work-queue.md` and do the next unchecked batch. For each batch:\n\n1. Read the engine files (`mtg-engine/src/abilities.rs`, `mtg-engine/src/game.rs`) to understand current Effect/StaticEffect variants\n2. If the batch has a NOTE about uncertainty, investigate first and update the queue with findings before committing the fix\n3. If engine work is needed: add the variant, constructor, and match arm\n4. Add a test for any new engine effect (follow the pattern in `game.rs` `mod tests`)\n5. Fix all cards listed in the batch (grep to find exact lines, fix across all 4 set files)\n6. `cargo check -p mtg-cards && cargo test --lib`\n7. Update docs: mark cards in `docs/{fdn,tla,tdm,ecl}-remediation.md`, update `ROADMAP.md`\n8. Check off the batch in `docs/work-queue.md` with today's date\n9. If you discover new cards that fit the current or a future batch while working, add them to the queue\n\nIf a batch turns out to be wrong or more complex than described, update the queue with what you learned and move on to the next one.\n\nThen do the next batch. Repeat until context is getting long, then stop and make sure `work-queue.md` is fully current before ending.\n",
  "file_path": "/mnt/dest-btrfs/home/jc/src/mage/mtg-rl/docs/work-queue-instructions.md"
}
```

## Assistant

**Result** (success):
```
File created successfully at: /mnt/dest-btrfs/home/jc/src/mage/mtg-rl/docs/work-queue-instructions.md
```

## Assistant

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.
