Read docs/work-queue.md and do the next unchecked batch. For each batch:
mtg-engine/src/abilities.rs, mtg-engine/src/game.rs) to understand current Effect/StaticEffect variantsgame.rs mod tests)cargo check -p mtg-cards && cargo test --libdocs/{fdn,tla,tdm,ecl}-remediation.md, update ROADMAP.mddocs/work-queue.md with today's dateIf 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.
I'll start by reading the work queue to find the next unchecked batch.
Next batch is Batch 3: OpponentDiscards. Let me read the engine files and find the affected cards.
Let me read the relevant parts of game.rs and find the cards.
Let me read the DiscardCards implementation and the cards in more detail.
Now let me find the end of the execute_effects match and the test section in game.rs.
Good. Now I have all the context I need. Let me implement the batch:
Effect::DiscardOpponents { count } variant + constructor + match arm in game.rsLet me start with the engine changes.
Now add the constructor in the Effect impl block:
Now add the match arm in game.rs. Let me add it right after the existing DiscardCards handler:
Now add a test. Let me append it before the closing } of the tests module:
Now let me verify the engine compiles and the test passes:
Need to check how cards are added to a player's hand in the existing tests. Let me look at how hand works:
The hand just needs add — I don't need to insert into a cards map. The move_card_to_graveyard_inner only needs the card to exist in the hand (via hand.remove). Let me simplify the test:
The discard isn't working. Let me check move_card_to_graveyard_inner:
OK so move_card_to_graveyard_inner doesn't do hand.remove. Looking back at the DiscardCards handler, it does player.hand.remove(card_id) separately, then calls move_card_to_graveyard_inner. The issue is that in my DiscardOpponents handler, choose_discard returns an empty vec because AlwaysPassPlayer::choose_discard returns vec![]. Let me check:
Yes, AlwaysPassPlayer::choose_discard returns vec![], and so the decision maker returns empty — no cards get discarded. The fallback hand.iter().rev().take(...) only fires when self.decision_makers.get_mut(&opp) returns None, but it won't because we registered decision makers for both players.
I need to adjust the test to not register a decision maker for p2, or use a different approach. Let me look at how the existing DiscardCards test works... actually there's no separate DiscardCards test in game.rs. Let me fix the test by directly testing without decision makers (the fallback path), or better, let me check the hand count after the fact properly. The issue is the decision maker returns empty vec.
Let me fix the test to verify the fallback path by creating the game differently, or I can just directly verify the mechanism works by checking that the effect processes without a decision maker:
Engine work done. Now let me fix the cards. Let me handle each one:
Burglar Rat (FDN line 617) — simple swap to Effect::discard_opponents(1)
Dream Seizer (ECL line 596) — compound: add_counters("-1/-1", 1) + discard_opponents(1) (self-counter uses Batch 2 source fallback)
Arbiter of Woe (FDN line 5340) — compound: discard_opponents(1) + lose_life_opponents(2) + draw_cards(1) + gain_life(2)
Bloodtithe Collector (FDN line 5520) — conditional ("if an opponent lost life this turn"), keep as Custom with note, or implement partially
Let me fix the straightforward ones first:
Good. Now fix Dream Seizer (ECL):
Good. Now fix Arbiter of Woe (FDN line 5340):
Now for Bloodtithe Collector — it's conditional ("if an opponent lost life this turn"). We don't have conditional effect support, so I'll partially fix it by using the typed effect but note it's still conditional:
This one has a conditional — "if an opponent lost life this turn". The condition check isn't implemented, but I can still replace the Custom effect with the typed effect. The trigger will fire unconditionally (which is a simplification, but better than being a no-op Custom):
Now let me verify compilation and run all tests:
All 247 tests pass. Now let me also scan for any other cards across all sets that might use "opponent discards" patterns I missed:
I see one more potential match: FDN line 6433 — Kiora, "draw two cards, then discard two cards" — that's already just controller loot, not opponent discard. The rest are either controller self-discard or complex conditional effects. No additional opponent discards patterns missed.
Now let me update the docs. First, the work queue:
Now update the per-set remediation docs. Let me read them to find the right sections:
Now update ECL remediation:
Now update ROADMAP.md:
Now update the memory to record the new API:
Let me do a final verification that everything compiles and all tests pass:
All 247 tests pass (52 + 18 + 160 + 17). Batch 3 is complete.
Engine changes:
Effect::DiscardOpponents { count: u32 } variant to the Effect enumEffect::discard_opponents(count) constructorexecute_effects — iterates opponents, each calls choose_discard on their decision maker, then moves discarded cards to graveyarddiscard_opponents_effect (with a DiscardingPlayer decision maker that actually selects cards to discard)Cards fixed (4):
| Card | Set | Change |
|---|---|---|
| Burglar Rat | FDN | Custom("Each opponent discards...") → discard_opponents(1) |
| Arbiter of Woe | FDN | Compound Custom → discard_opponents(1), lose_life_opponents(2), draw_cards(1), gain_life(2) |
| Bloodtithe Collector | FDN | Conditional Custom → discard_opponents(1) (condition not modeled) |
| Dream Seizer | ECL | Compound Custom → add_counters("-1/-1", 1), discard_opponents(1) |
Docs updated: work-queue.md, fdn-remediation.md, ecl-remediation.md, ROADMAP.md, MEMORY.md