Caveat: The messages below were generated by the user while running local commands. DO NOT respond to these messages or otherwise consider them in your response unless the user explicitly asks you to.
Caveat: Caveat: The messages below were generated by the user while running local commands. DO NOT respond to these messages or otherwise consider them in your response unless the user explicitly asks you to.
model
Set model to [1mDefault (Opus 4.6 · Most capable for complex work)[22m
Stdout: Set model to [1mDefault (Opus 4.6 · Most capable for complex work)[22m
Caveat: The messages below were generated by the user while running local commands. DO NOT respond to these messages or otherwise consider them in your response unless the user explicitly asks you to.
Caveat: Caveat: The messages below were generated by the user while running local commands. DO NOT respond to these messages or otherwise consider them in your response unless the user explicitly asks you to.
extra-usage
Login successful
Stdout: Login successful
I would like to use this program as an AI Gym for RL, but it is not optimized for that. It is not thread safe, and does not effectively use CPU when I have tried to scale it up. I think we should port this to Rust as that would enable both thread safety and efficient CPU usage. We don't need to port everything. Just what we need for a game engine, the AI player framework, and some cards. Let's create an agent team to do this work. One teammate on the engine, one teammate on the AI players, one on porting the cards for TLA, TDM and LCL. We might port other cards later, but want to minimize the card porting as our AI Gym needs to mostly focus on modern sets. Ask me any questions that would help clarify this procress.
The user wants to port parts of the XMage Java Magic: The Gathering engine to Rust, specifically for use as an AI Gym for reinforcement learning. They want:
Let me first understand the scope by exploring the codebase, then ask clarifying questions before creating a plan.
Let me think about what I need to explore:
The core engine architecture (Mage/ module)
The AI player framework
The card sets TLA, TDM, and LCL - what sets are these?
How the game loop works and the dependencies between modules
I should also clarify what they're looking for in terms of the RL interface, which AI player implementations they need, whether they want network play or just local simulation, and what format they expect for observations and actions.
Let me start exploring the codebase to understand the overall architecture and whether they need Python bindings for the Rust engine.
This is a major undertaking! Let me explore the codebase to understand the architecture before asking clarifying questions.
Perfect! Now I have a comprehensive understanding. Let me compile the detailed exploration report:
## Comprehensive Exploration of Mage Core Engine (XMage)
Based on my thorough exploration of the Mage core engine module, here's a detailed architectural overview for planning a Rust port:
---
## 1. MAIN GAME LOOP & INITIALIZATION
**Key Files:**
- `/mnt/dest-btrfs/home/jc/src/mage/Mage/src/main/java/mage/game/GameImpl.java` (175KB - massive)
- `/mnt/dest-btrfs/home/jc/src/mage/Mage/src/main/java/mage/game/Game.java` (27KB interface)
- `/mnt/dest-btrfs/home/jc/src/mage/Mage/src/main/java/mage/game/GameState.java` (65KB)
**Architecture:**
- `Game` is the central interface managing all game state, event dispatch, and turn progression
- `GameImpl` is the main implementation (~175KB), handles:
- Game state management and copying (full game snapshots)
- Card/permanent tracking across zones
- Event firing and processing
- Rollback support (bookmarks for player decision undo)
- Infinite loop detection
- Mana pool management
- LKI (Last Known Information) tracking for zone changes
**Game State Structure (GameState):**
- `Players` + `PlayerList` (static, turn order)
- `Turn` object (phases/steps)
- `SpellStack` (spells/abilities on stack)
- `Battlefield` (permanents)
- `Exile` zone, `Graveyard` per player
- `ContinuousEffects` (all layered/replacement/prevention effects)
- `TriggeredAbilities` + `DelayedTriggeredAbilities` + `Watchers`
- `Combat` state
- `zones` map - tracks which zone each card is in
**Key Pattern: Game Copying**
- Game state is fully copied for simulations (AI, playable calc)
- Deep copy semantics throughout
---
## 2. TURN/PHASE/STEP STRUCTURE
**Key Files:**
- `/mnt/dest-btrfs/home/jc/src/mage/Mage/src/main/java/mage/game/turn/Turn.java` (15KB)
- `/mnt/dest-btrfs/home/jc/src/mage/Mage/src/main/java/mage/game/turn/Phase.java` (9KB)
- `/mnt/dest-btrfs/home/jc/src/mage/Mage/src/main/java/mage/game/turn/Step.java` (2.6KB)
**Enum Constants:**
- `TurnPhase` (5 phases): BEGINNING, PRECOMBAT_MAIN, COMBAT, POSTCOMBAT_MAIN, END
- `PhaseStep` (13 steps with indices):
- UNTAP (0), UPKEEP (1), DRAW (2)
- PRECOMBAT_MAIN (3), BEGIN_COMBAT (4)
- DECLARE_ATTACKERS (5), DECLARE_BLOCKERS (6)
- FIRST_COMBAT_DAMAGE (7), COMBAT_DAMAGE (8), END_COMBAT (9)
- POSTCOMBAT_MAIN (10), END_TURN (11), CLEANUP (12)
**Turn Execution Flow:**
```
Turn.play() iterates through phases:
- Each Phase has steps
- Each Step calls phase.play(game, activePlayer)
- Phases/steps can be skipped via TurnMods
- Extra phases can be added mid-turn
- Priority passes after each phase (players can respond)
```
**Key Classes:**
- Individual step classes: `UntapStep`, `UpkeepStep`, `DrawStep`, `PreCombatMainStep`, `CombatPhase`, etc.
- Each extends `Phase` or `Step` and implements `play(game, playerId)` method
- Phases/steps handle events and priority assignment
---
## 3. PRIORITY & DECISION SYSTEM
**Key Files:**
- `/mnt/dest-btrfs/home/jc/src/mage/Mage/src/main/java/mage/game/Game.java` (priority/event methods)
- `/mnt/dest-btrfs/home/jc/src/mage/Mage/src/main/java/mage/players/Player.java` (large interface)
**Game.java Priority Methods:**
```java
UUID getPriorityPlayerId();
void firePriorityEvent(UUID playerId);
void fireSelectTargetEvent(...);
void fireSelectEvent(UUID playerId, String message);
void fireGetChoiceEvent(...);
void fireGetAmountEvent(...);
void fireGetModeEvent(...);
// ... many more
```
**Decision Types:**
- `firePriorityEvent()` - asks player if they want to play spells/abilities
- `fireSelectTargetEvent()` - target selection
- `fireGetChoiceEvent()` - choice between abilities
- `fireGetModeEvent()` - select mode for modal spell
- `firePlayManaEvent()` - mana payment
- `fireAskPlayerEvent()` - generic yes/no questions
**Key Pattern:**
- Game fires events to listeners (UI/server)
- Players respond through `PlayerQueryEvent` listeners
- Game is **event-driven** - waits for responses
---
## 4. EVENT SYSTEM
**Key Files:**
- `/mnt/dest-btrfs/home/jc/src/mage/Mage/src/main/java/mage/game/events/GameEvent.java` (interface/enum)
- `/mnt/dest-btrfs/home/jc/src/mage/Mage/src/main/java/mage/game/events/` (120+ event types)
- `/mnt/dest-btrfs/home/jc/src/mage/Mage/src/main/java/mage/game/events/EventDispatcher.java`
**Event Types (600+):**
Major categories:
- Zone changes: `ZoneChangeEvent`, `ZoneChangeGroupEvent`, `ZoneChangeBatchEvent`
- Combat: `DeclareAttackerEvent`, `DeclareBlockerEvent`, `CombatDamageEvent`, `DamagedEvent`
- Damage: `DamageEvent`, `DamageBatchEvent`, `PreventDamageEvent`, `PreventedDamageEvent`
- Life: `LifeLostEvent`, `LifeLostBatchEvent`
- Permanents: `EntersTheBattlefieldEvent`, `TappedEvent`, `UntappedEvent`, `CounterRemovedEvent`
- Spells: `TargetEvent`, `CounterEvent`, `CastEvent`
- Mana: `ManaEvent`, `ManaPaidEvent`, `TappedForManaEvent`
- Batch events for efficiency: `DamagedBatchEvent`, `MilledBatchEvent`, `SacrificedPermanentBatchEvent`
**Event Flow:**
1. **Fire Event**: `game.fireEvent(event)` or `game.addSimultaneousEvent(event)`
2. **Check Event Type**: `TriggeredAbility.checkEventType(event, game)` - fast filter
3. **Check Trigger**: `TriggeredAbility.checkTrigger(event, game)` - full evaluation
4. **Trigger Ability**: Add to triggered abilities list
5. **Resolve**: Ability effects execute
**Listeners:**
- `TableEventListener` - for UI updates
- `PlayerQueryEventListener` - for player decisions
- `TriggeredAbility.checkEventType()/checkTrigger()` - ability triggering
---
## 5. ZONE SYSTEM
**Key Files:**
- `/mnt/dest-btrfs/home/jc/src/mage/Mage/src/main/java/mage/constants/Zone.java`
- `/mnt/dest-btrfs/home/jc/src/mage/Mage/src/main/java/mage/game/ZonesHandler.java` (27KB)
- `/mnt/dest-btrfs/home/jc/src/mage/Mage/src/main/java/mage/game/ZoneChangeInfo.java`
**Zones:**
```java
public enum Zone {
HAND(false), // private to player
GRAVEYARD(true), // public
LIBRARY(false), // private
BATTLEFIELD(true), // public
STACK(true), // public
EXILED(true), // public
COMMAND(true), // public (emblems, commanders, planes)
ALL(false), // wildcard
OUTSIDE(false) // outside game
}
```
**Zone Tracking:**
- `GameState.zones` - Map<UUID, Zone> - each card's current zone
- `GameState.battlefield` - Battlefield object - permanents
- `GameState.stack` - SpellStack object - spells/abilities
- Per-player: Hand, Library, Graveyard
**Zone Change Process (ZonesHandler):**
1. **Check Source Zone**: Ensure card is in correct zone
2. **Remove from Source**: Card removed from source zone
3. **Place in Destination**: Card added to destination zone with metadata
4. **Fire Events**: `ZoneChangeEvent`, `ZoneChangeGroupEvent` (for batch moves)
**Special Rules Handled:**
- Meld cards split when leaving battlefield
- Double-faced cards: enter with front face, can be forced to main side
- Modal double-faced cards
- Auto-meld card handling
- Simultaneous zone changes (all permanents enter at same time)
**LKI (Last Known Information):**
- `GameState.lki` - Map<Zone, Map<UUID, MageObject>>
- Tracks card state before leaving a zone
- Used for triggers that check "left the battlefield" conditions
- Cleared after effects resolve
---
## 6. COMBAT SYSTEM
**Key Files:**
- `/mnt/dest-btrfs/home/jc/src/mage/Mage/src/main/java/mage/game/combat/Combat.java` (98KB)
- `/mnt/dest-btrfs/home/jc/src/mage/Mage/src/main/java/mage/game/combat/CombatGroup.java` (39KB)
**Combat Structure:**
- `Combat` object per game (only one active combat)
- `CombatGroup` - represents attacker(s) vs defender(s)
- Multiple combat groups for multiplayer
**Combat Flow:**
1. **Declare Attackers Step**: `declareAttacker(attackerId, defenderId)`
- Creates/updates CombatGroup
- Checks attacker requirements/restrictions
- Validates defender (planeswalker or player)
2. **Declare Blockers Step**: `declareBlocker(blockerId, attackingCreatureId)`
- Adds blocker to CombatGroup
- Checks blocker restrictions
3. **Combat Damage Step**:
- Assign damage to defenders
- Apply damage: `DamageEvent`, `DamagedEvent`
- Check for lethal damage
- Destroy creatures with lethal toughness
**Combat Class Methods:**
- `declareAttacker()`, `declareBlocker()`
- `assignDamage()`, `handleCombatDamage()`
- `getAttackers()`, `getBlockers()`
- Restriction/Requirement checks for attacks/blocks
---
## 7. MANA SYSTEM
**Key Files:**
- `/mnt/dest-btrfs/home/jc/src/mage/Mage/src/main/java/mage/Mana.java` (50KB)
- `/mnt/dest-btrfs/home/jc/src/mage/Mage/src/main/java/mage/ManaSymbol.java` (10KB)
- Mana costs in `/mnt/dest-btrfs/home/jc/src/mage/Mage/src/main/java/mage/abilities/costs/mana/`
**Mana Representation:**
```java
public class Mana {
protected int white, blue, black, red, green; // colored mana
protected int generic; // generic mana {X}
protected int colorless; // colorless mana {C}
protected int any; // wildcard mana {*}
protected boolean flag;
}
```
**Mana Colors:**
- `ColoredManaSymbol` enum: W, U, B, R, G
- `ManaType` enum: WHITE, BLUE, BLACK, RED, GREEN, GENERIC, COLORLESS, SNOW
**Mana Costs:**
- Fixed costs: {1}{U}{B} etc.
- Variable costs: {X}, {Y}
- Generic alternatives: hybrid mana {W/U}, {2/W}
- Conditional mana in `ConditionalMana.java`
**Mana Pool:**
- Per-player mana pool
- Holds available mana after land tapping
- Cleared at end of phase (mana burn if not used)
- Conditional mana restrictions
**Key Pattern:**
- Overflow checking throughout (CardUtil.addWithOverflowCheck)
- Negative values not allowed (logged error)
---
## 8. STACK & SPELL RESOLUTION
**Key Files:**
- `/mnt/dest-btrfs/home/jc/src/mage/Mage/src/main/java/mage/game/stack/StackObject.java`
- `/mnt/dest-btrfs/home/jc/src/mage/Mage/src/main/java/mage/game/stack/StackObjectImpl.java`
- `/mnt/dest-btrfs/home/jc/src/mage/Mage/src/main/java/mage/game/stack/StackAbility.java`
- `/mnt/dest-btrfs/home/jc/src/mage/Mage/src/main/java/mage/game/stack/Spell.java`
- `SpellStack` (in GameState)
**StackObject Interface:**
```java
public interface StackObject extends MageObject, Controllable {
boolean resolve(Game game);
UUID getSourceId();
void counter(Ability source, Game game);
Ability getStackAbility();
boolean chooseNewTargets(...);
void createCopyOnStack(...);
}
```
**Stack Resolution Order:**
1. Player gains priority after object put on stack
2. Players pass priority
3. When all players pass: resolve top object on stack
4. `StackObject.resolve(game)` called:
- Spell effects execute
- Checks targets still valid
- If no valid targets: fizzles
5. Fire events, triggers go on stack
6. Return to step 1
**Counter & Copying:**
- `counter(source, game)` - removes from stack
- `createCopyOnStack()` - creates independent copy
- Applies `StackObjectCopyApplier` for custom behavior
---
## 9. ABILITY SYSTEM
**Key Files:**
- `/mnt/dest-btrfs/home/jc/src/mage/Mage/src/main/java/mage/abilities/Ability.java` (19KB interface)
- `/mnt/dest-btrfs/home/jc/src/mage/Mage/src/main/java/mage/abilities/AbilityImpl.java` (77KB)
- `/mnt/dest-btrfs/home/jc/src/mage/Mage/src/main/java/mage/abilities/SpellAbility.java` (16KB)
- `/mnt/dest-btrfs/home/jc/src/mage/Mage/src/main/java/mage/abilities/ActivatedAbilityImpl.java` (11KB)
- `/mnt/dest-btrfs/home/jc/src/mage/Mage/src/main/java/mage/abilities/TriggeredAbilityImpl.java` (25KB)
- `/mnt/dest-btrfs/home/jc/src/mage/Mage/src/main/java/mage/abilities/StaticAbility.java` (simple)
**Ability Hierarchy:**
```
Ability (interface)
└─ AbilityImpl
├─ SpellAbility (cast spell)
├─ ActivatedAbility
│ └─ ActivatedAbilityImpl
├─ TriggeredAbility
│ └─ TriggeredAbilityImpl
├─ StaticAbility
└─ DelayedTriggeredAbility / StateTriggeredAbility
```
**Ability Components:**
- `Costs` - non-mana costs (tap, sacrifice, discard, etc.)
- `ManaCosts` - mana cost {1}{U} etc.
- `Effects` - what happens (create tokens, deal damage, etc.)
- `Targets` - what is targeted
- `Modes` - for modal spells (choose one/two of...)
**Key Methods:**
- `canActivate(playerId, game)` - can player activate?
- `activate(game, mandatory)` - activate ability
- `resolve(source, stackAbility, game)` - execute effects
**AbilityType Enum:**
```
SPELL, ACTIVATED, TRIGGERED, STATIC, LOYALTY, MANA, SPECIAL_ACTION
```
---
## 10. EFFECTS SYSTEM
**Key Files:**
- `/mnt/dest-btrfs/home/jc/src/mage/Mage/src/main/java/mage/abilities/effects/Effect.java` (2.8KB)
- `/mnt/dest-btrfs/home/jc/src/mage/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java` (70KB)
- `/mnt/dest-btrfs/home/jc/src/mage/Mage/src/main/java/mage/abilities/effects/ContinuousEffectImpl.java` (20KB)
- `/mnt/dest-btrfs/home/jc/src/mage/Mage/src/main/java/mage/abilities/effects/common/` (26KB directory)
**Effect Hierarchy:**
```
Effect (interface)
├─ OneShotEffect - one-time effects (deal damage, create token)
└─ ContinuousEffect - ongoing effects
├─ ContinuousEffectImpl - typed effects
├─ ReplacementEffect - prevents events
├─ RestrictionEffect - prevent actions
├─ RequirementEffect - force actions
├─ PreventionEffect - prevent damage
└─ AsThoughEffect - special "as though" rules
```
**ContinuousEffects Manager:**
- Tracks all 8-9 categories of effects
- Applies them in layers (copy, control, text, type, color, ability, P/T, see Layer enum)
- Removal based on duration (end of turn, end of combat, etc.)
**Effect Categories:**
1. **Layered** - ordered application (7 layers)
2. **Replacement** - prevent events
3. **Prevention** - prevent damage
4. **Requirement** - force attacks/blocks
5. **Restriction** - prevent attacks/blocks
6. **CostModification** - change casting costs
7. **AsThough** - "as though" effects
8. **SpliceCard** - splice mechanics
**Duration:**
```java
public enum Duration {
EndOfTurn,
EndOfCombat,
EndOfGame,
OneUse,
Permanent,
WhileOnBattlefield,
WhileInGraveyard,
// ...
}
```
---
## 11. CONTINUOUS EFFECTS LAYERS (Critical Architecture!)
**From ContinuousEffects.java:**
```java
// Layer order (applied in sequence):
1. Copy effects (copiable values, excluding P/T)
2. Control changes
3. Text changes
4. Type changes
5. Color changes
6. Ability changes
7. Power/Toughness changes
```
**Each Layer Contains:**
- List of effects
- Ordered by timestamp
- Applied to all permanents
- Effects can depend on previous layers
**Key Pattern:**
- Effects have source object and player controller
- Timestamp tracks order within layer
- Layer reapplication on effect addition/removal
- Efficient: only affected objects recalculated
---
## 12. TRIGGERED ABILITIES & WATCHERS
**TriggeredAbilities:**
- Stored in `GameState.triggers` + `GameState.delayed`
- Added via ability source
- Each triggered ability:
- Has source (creature, enchantment, etc.)
- Can be optional (player choice)
- Can be limited per turn/game
- Has intervening if-clause conditions
- Removed if conditions no longer met
**Watchers:**
- `/mnt/dest-btrfs/home/jc/src/mage/Mage/src/main/java/mage/watchers/Watcher.java`
- Subscribe to specific game events
- Track conditions (e.g., "creatures damaged you this turn")
- Scope: GAME, PLAYER, CARD
**Watcher Types (examples):**
- `AttackedThisTurnWatcher` - track attacking creatures
- `BlockedAttackerWatcher` - track blocked creatures
- `AmountOfDamageAPlayerReceivedThisTurnWatcher` - damage tracking
- `AbilityResolvedWatcher` - ability execution tracking
- 100+ common watchers
---
## 13. KEY INTERFACES & THEIR IMPLEMENTATIONS
**Core Interfaces:**
1. **Game** - Game.java (27KB interface)
- Implementation: GameImpl (175KB)
- Variants: GameCommanderImpl, GameCanadianHighlanderImpl, GameTinyLeadersImpl
2. **Player** - Player.java (large)
- Implementations: PlayerImpl, HumanPlayer, ComputerPlayer, TestPlayer
3. **Card** - Card.java (10KB)
- Implementation: CardImpl (44KB)
- Variants: DoubleFacedCard, SplitCard, AdventureCard, MeldCard, etc.
4. **Permanent** - Permanent.java (13KB)
- Implementation: PermanentImpl (83KB)
- Variants: PermanentCard, PermanentToken, PermanentMeld
5. **Ability** - Ability.java (19KB)
- Implementations in ability hierarchy
6. **MageObject** - MageObject.java (18KB)
- Base for Card, Permanent, StackObject
- Trackable objects in game
7. **GameEvent** - GameEvent.java + 120+ specific events
- Event dispatcher pattern
---
## 14. FILTERING & TARGETING
**Filter System:**
- `/mnt/dest-btrfs/home/jc/src/mage/Mage/src/main/java/mage/filter/` (complex)
- Filter types:
- `FilterCard` - filter cards in any zone
- `FilterPermanent` - filter permanents on battlefield
- `FilterCreaturePermanent` - creatures only
- `FilterSpell` - spells on stack
- `FilterPlayer` - filter players
- `FilterMana` - mana requirement matching
**Predicates:**
- `/mnt/dest-btrfs/home/jc/src/mage/Mage/src/main/java/mage/filter/predicate/`
- Composable predicates (AND, OR, NOT)
- Example: owned by player, is creature, color white
**Target System:**
- `/mnt/dest-btrfs/home/jc/src/mage/Mage/src/main/java/mage/target/`
- `Target` interface - validation, selection
- `TargetImpl` (33KB)
- Variants: TargetCard, TargetPermanent, TargetPlayer, TargetSpell, TargetAmount
- `Targets` - collection of targets for ability
**StaticFilters:**
- `/mnt/dest-btrfs/home/jc/src/mage/Mage/src/main/java/mage/filter/StaticFilters.java` (55KB!)
- Pre-built filters for common patterns (singleton pattern)
- Optimized for reuse
---
## 15. KEY CONSTANTS & ENUMS
**Critical Enums in `/mnt/dest-btrfs/home/jc/src/mage/Mage/src/main/java/mage/constants/`:**
1. **Zone** - card location (9 values)
2. **TurnPhase** - game phases (5 values)
3. **PhaseStep** - turn steps with indices (13 values)
4. **CardType** - spell/permanent type
5. **SubType** (32KB) - creature types, land types, etc. (1000+)
6. **SuperType** - legendary, snow, etc.
7. **ColoredManaSymbol** - W, U, B, R, G
8. **ManaType** - colored + generic + colorless
9. **Duration** - effect timing
10. **Layer** - effect layer
11. **AbilityType** - ability classification
12. **EffectType** - effect type classification
13. **Outcome** - effect purpose (damage, gain life, etc.)
14. **AsThoughEffectType** - as-though variants
15. **ComparisonType** - comparison operators
16. **Duration** - when effect expires
17. **GameEvent.EventType** - 600+ event types (not enumed, static ints)
18. **RangeOfInfluence** - multiplayer range
19. **MultiplayerAttackOption** - attack rules variant
---
## 16. PERMANENT MECHANICS
**Permanent States:**
- Tapped/untapped
- Phased in/out
- Transformed (double-faced cards)
- Flipped (old mechanic)
- Attached (aura/equipment)
- Counters (various types)
- Monstrous/Renowned/Suspected flags
- Class level (class subtype leveling)
**Permanent Operations:**
- `tap()` - tap with event
- `setTapped()` - silent tap
- `untap()`
- `transform()` - transform if double-faced
- `phaseOut()` / `phaseIn()`
- `addCounters()` / `removeCounters()`
- Damage tracking
---
## 17. ARCHITECTURAL PATTERNS
**Copyable Pattern:**
```java
public interface Copyable<T> {
T copy();
}
```
- Everything is copyable for simulations/rollback
- Deep copy semantics
**Singleton Pattern:**
- `MageSingleton` - for abilities that don't change per instance
- Used for keyword abilities (Flying, Vigilance, etc.)
- Optimizes memory
**Lazy Evaluation:**
- Costs/targets computed when needed
- Effects applied on demand
- Watchers notify on events
**Event-Driven:**
- Game loop waits for player decisions
- Events trigger abilities
- Callback system to UI
**State Machine:**
- Turn phases/steps define valid game states
- Transitions explicit
**LKI (Last Known Information):**
- Objects tracked even after leaving zones
- Essential for "leaving the battlefield" triggers
---
## 18. SPECIAL MECHANICS SUPPORT
- **Meld Cards**: Split into parts on leaving battlefield
- **Double-Faced Cards**: One face up on battlefield, can transform
- **Modal Double-Faced Cards**: Choose mode on entry
- **Split Cards**: Two spell halves, can cast either
- **Leveler Cards**: Changing power with class
- **Adventure Cards**: Instant in hand, spell on field
- **Token Creatures**: Generated permanents
- **Emblems**: Command zone items
- **Planes/Phenomena**: Planar cascade
---
## 19. SIZE METRICS (Core Engine Only)
```
Total Files: 3,643 Java files
Core files (Mage/ only):
- GameImpl.java: 175 KB (massive game loop)
- PermanentImpl.java: 83 KB
- ContinuousEffects.java: 70 KB
- Game.java: 27 KB (interface)
- GameState.java: 65 KB
- Combat.java: 98 KB
- CombatGroup.java: 39 KB
- AbilityImpl.java: 77 KB
- TriggeredAbilityImpl.java: 25 KB
- CardImpl.java: 44 KB
- ZonesHandler.java: 27 KB
- Turn.java: 15 KB
- Phase.java: 9 KB
- Mana.java: 50 KB
- Targets: Complex system
- Effects: Complex layered system
- 120+ GameEvent types
```
---
## 20. CRITICAL PATTERNS FOR RUST PORT
1. **Immutability & Cloning**: Game state is fully copyable - embrace Rust's ownership
2. **UUID-Based References**: All objects referenced by UUID, not pointer - compatible with Rust
3. **Event-Driven**: Async/await could work well
4. **State Machine**: Phases/steps are explicit states - pattern matches
5. **Layered Effects**: Order-dependent computation - trait-based approach
6. **Timestamp Ordering**: Multiple ordered queues - Vec<T> with metadata
7. **Watchers**: Observer pattern - could use trait objects or channels
8. **Deep Copies**: Game state snapshots - costly, consider Cow or RefCell patterns
---
## KEY FILES TO STUDY FIRST (For Rust Port Planning)
1. `/mnt/dest-btrfs/home/jc/src/mage/Mage/src/main/java/mage/game/GameImpl.java` - Main game loop
2. `/mnt/dest-btrfs/home/jc/src/mage/Mage/src/main/java/mage/game/GameState.java` - Game state structure
3. `/mnt/dest-btrfs/home/jc/src/mage/Mage/src/main/java/mage/game/turn/Turn.java` - Turn execution
4. `/mnt/dest-btrfs/home/jc/src/mage/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java` - Effects layers
5. `/mnt/dest-btrfs/home/jc/src/mage/Mage/src/main/java/mage/game/ZonesHandler.java` - Zone management
6. `/mnt/dest-btrfs/home/jc/src/mage/Mage/src/main/java/mage/game/combat/Combat.java` - Combat system
7. `/mnt/dest-btrfs/home/jc/src/mage/Mage/src/main/java/mage/abilities/AbilityImpl.java` - Ability framework
8. `/mnt/dest-btrfs/home/jc/src/mage/Mage/src/main/java/mage/game/permanent/PermanentImpl.java` - Permanent state
agentId: ad03e63 (for resuming to continue this agent's work if needed)
<usage>total_tokens: 77467
tool_uses: 37
duration_ms: 133115</usage>Perfect! I have gathered comprehensive information. Let me now create a detailed report for the user.
## Comprehensive AI Player Framework Analysis
I've conducted a thorough exploration of the XMage AI player framework. Here's a detailed breakdown of how AI players are implemented and how they make decisions:
---
### **1. CORE PLAYER INTERFACE & DECISION POINTS**
**Key File:** `/mnt/dest-btrfs/home/jc/src/mage/Mage/src/main/java/mage/players/Player.java` (1,270 lines)
The `Player` interface defines all decision methods that must be implemented by any player type:
**Critical Decision Methods:**
- **`priority(Game game)`** (line 667) - Called when player has priority on the stack. AI decides which spell/ability to activate next, or pass
- **`selectAttackers(Game game, UUID attackingPlayerId)`** (line 758) - Combat: which creatures to attack with
- **`selectBlockers(Ability source, Game game, UUID defendingPlayerId)`** (line 760) - Combat: which creatures to block with
- **`choose(Outcome outcome, Target target, Ability source, Game game)`** (line 673) - Choose targets for spells/abilities
- **`chooseUse(Outcome outcome, String message, Ability source, Game game)`** (line 687) - Binary choices (Yes/No)
- **`chooseTarget(Outcome outcome, Target target, Ability source, Game game)`** (line 679) - Target selection
- **`playMana(Ability ability, ManaCost unpaid, String promptText, Game game)`** (line 695) - Mana payment strategy
- **`announceX(int min, int max, ...)`** (line 749) - Choose value for X in spells
- **`getAmount(int min, int max, ...)`** (line 765) - Choose numeric amounts
- **`pickCard(List<Card> cards, Deck deck, Draft draft)`** (line 815) - Draft card selection
---
### **2. PLAYER TYPE HIERARCHY**
**File:** `/mnt/dest-btrfs/home/jc/src/mage/Mage/src/main/java/mage/players/PlayerType.java`
```
PlayerType enum:
- HUMAN (false, true)
- COMPUTER_DRAFT_BOT (true, false)
- COMPUTER_MONTE_CARLO (true, true)
- COMPUTER_MAD (true, true)
```
**Class Hierarchy:**
```
Player (interface)
├─ PlayerImpl (abstract base implementation)
│ ├─ HumanPlayer (in Mage.Player.Human)
│ ├─ ComputerPlayer (base AI in Mage.Player.AI)
│ │ ├─ ComputerPlayer6 (simulation-based AI - "MAD")
│ │ │ └─ SimulatedPlayer2 (for game simulations)
│ │ ├─ ComputerPlayerMCTS (Monte Carlo Tree Search AI)
│ │ │ └─ SimulatedPlayerMCTS (for MCTS simulations)
│ │ └─ ComputerDraftPlayer (Draft-only AI)
│ └─ ComputerPlayerControllableProxy (control wrapper)
```
---
### **3. AI PLAYER IMPLEMENTATIONS**
#### **A. ComputerPlayer (Base AI)**
**File:** `/mnt/dest-btrfs/home/jc/src/mage/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java`
**Algorithm:** Heuristic-based rule system
- Uses `PossibleTargetsSelector` to categorize targets as "good" or "bad"
- Evaluates outcomes (Damage, Benefit, etc.) to guide decision
- Minimal computation; immediate responses to choices
- **Key methods:**
- `choose()` → `makeChoice()` - target selection via heuristics
- `priority()` - just passes (minimal implementation)
- `selectAttackers()`/`selectBlockers()` - empty stubs (overridden in subclasses)
- `chooseUse()` - always returns true for non-AIDontUseIt outcomes
- `playMana()` - mana payment via sorted producers list
**Scoring Components:**
- `PossibleTargetsComparator` - ranks targets
- `PermanentComparator` - evaluates permanents
- `PermanentEvaluator` - calculates permanent value
- No game tree simulation
---
#### **B. ComputerPlayer6 (MAD Bot - Minimax with Alpha-Beta)**
**File:** `/mnt/dest-btrfs/home/jc/src/mage/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer6.java` (1,251 lines)
**Algorithm:** Minimax with Alpha-Beta pruning + Game Simulation
- Creates full game copy with `SimulatedPlayer2` for all players
- Performs depth-limited minimax tree search with **alpha-beta pruning**
- Configurable search parameters: `maxDepth`, `maxNodes`, `maxThinkTimeSecs`
- Multithreaded simulations using `ExecutorService` (5 threads by default)
**Decision Flow for `priority(Game game)`:**
```
priority() called
→ createSimulation(game) // Deep copy of game with simulated players
→ root = SimulationNode2(playerId, sim)
→ addActionsTimed() // Run simulation with timeout
→ addActions(root, maxDepth, α=MIN, β=MAX)
→ simulatePriority(node, game, depth, α, β)
→ currentPlayer.simulatePriority(game) // Get all playable abilities
→ optimize(game, allActions) // Apply tree optimizers
→ For each action:
→ game = game.createSimulationForAI()
→ activate(action)
→ applyEffects()
→ newNode = SimulationNode2(parent, sim, depth, playerId)
→ val = addActions(newNode, depth-1, α, β) // Recursive minimax
→ Update α/β based on player ownership
→ Alpha-beta pruning: break if α ≥ β
→ act(game) // Execute best action chain
```
**Key Classes:**
- **`SimulationNode2`** - Tree node storing: game, score, abilities, combat
- **`SimulatedPlayer2`** - Extends ComputerPlayer; replaces real players in simulations
- **`GameStateEvaluator2`** - Scores game states (see below)
- **`TreeOptimizer`** - Optimizes action tree (WrongCodeUsageOptimizer, DiscardCardOptimizer, etc.)
**Configurable Parameters:**
- `maxDepth = skill` (default 4, max 8)
- `maxNodes = 5000` actions before stopping
- `maxThinkTimeSecs = skill * 3` seconds timeout per decision
- `COMPUTER_MAX_THREADS_FOR_SIMULATIONS = 5`
**Combat Decision (selectAttackers/selectBlockers):**
- Uses `declareAttackers(game, activePlayerId)`:
1. Check alpha strike (all-in attack to kill opponent)
2. Find safe attackers via `CombatEvaluator` and ability checks
3. Filter by creature type and ability (flying, reach, deathtouch, indestructible)
4. Prioritize attacking planeswalkers > battles > players
5. Uses `CombatUtil.canKillOpponent()` to evaluate kill threats
- Uses `declareBlockers(game, activePlayerId)`:
1. Filter blockable attackers
2. Filter valid blockers
3. Call `CombatUtil.blockWithGoodTrade2()` to find block assignment
4. Declare each blocker via `player.declareBlocker()`
---
#### **C. ComputerPlayerMCTS (Monte Carlo Tree Search)**
**File:** `/mnt/dest-btrfs/home/jc/src/mage/Mage.Server.Plugins/Mage.Player.AIMCTS/src/mage/player/ai/ComputerPlayerMCTS.java`
**Algorithm:** Monte Carlo Tree Search (MCTS)
- **Selection:** UCT = wins/visits + sqrt(2) * sqrt(ln(parent.visits)/visits)
- **Expansion:** Expand first unvisited child
- **Simulation:** Play out game randomly to terminal state
- **Backpropagation:** Update visit/win counts up tree
**Key Classes:**
- **`MCTSNode`** - MCTS tree node with visits/wins/action/game/combat
- **`MCTSPlayer`** - Simulated player for MCTS (extends ComputerPlayer)
- **`MCTSNextActionFactory`** - Creates next action nodes (Priority, SelectAttackers, SelectBlockers)
**Decision Flow:**
```
priority() called
→ getNextAction(game, NextAction.PRIORITY)
→ calculateActions(game, NextAction.PRIORITY)
→ applyMCTS(game, action) // Run MCTS iterations
→ root.select() // UCT selection
→ expand() // Create children
→ simulate() // Playout to terminal
→ backpropagate(result) // Update visits/wins
→ root.getAction() // Return best action
→ activateAbility()
```
---
### **4. GAME STATE EVALUATION & SCORING**
**File:** `/mnt/dest-btrfs/home/jc/src/mage/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/score/GameStateEvaluator2.java`
**Evaluation Formula:**
```java
score = (playerLifeScore - opponentLifeScore)
+ (playerPermanentsScore - opponentPermanentsScore)
+ (playerHandScore - opponentHandScore)
```
**Component Scoring:**
1. **Life Score:**
- Win: +100,000,000
- Loss: -100,000,000
- Mid-game: `ArtificialScoringSystem.getLifeScore(life)`
2. **Permanent Score:**
- `ArtificialScoringSystem.getFixedPermanentScore()` - Base card value
- `ArtificialScoringSystem.getDynamicPermanentScore()` - Stat-based scoring
- `ArtificialScoringSystem.getCombatPermanentScore()` - Combat-relevant bonuses
- **Aura Penalty:** -1000 if aura with Outcome.Detriment on own permanent
3. **Hand Score:**
- `HAND_CARD_SCORE = 5` points per card
**Note:** Designed for 2-player games only (gets first opponent)
---
### **5. DECISION INTERFACE: HOW CHOICES ARE PRESENTED & COLLECTED**
**Flow Pattern:**
```
Game Engine
→ Player.priority(Game game) // Main decision point
↓
[AI Internal Logic]
↓
→ activateAbility(ability) // Execute chosen ability
↓
→ Player.choose() // For targets/modes
→ Player.playMana() // For mana payment
→ Player.chooseUse() // For yes/no choices
↓
[Game continues]
```
**Key Methods Called During Ability Resolution:**
- **Target Selection:** `choose(Outcome, Target, Ability, Game)`
- Outcome: hint (GOOD/BAD/DAMAGE/etc.)
- Target: defines valid targets via filters
- AI calls: `target.add(itemId, game)` for each target
- **Modal Spell Choices:** `choose(Outcome, Choice, Game)`
- Choice: set of option strings
- AI calls: `choice.setChoice(selected)`
- **Amount Selection:** `getAmount(min, max, message, source, game)`
- Returns int value
- Used for: damage allocation, counter distribution
- **Mana Payment:** `playMana(Ability, ManaCost, String, Game)`
- Called for each unpaid mana cost
- AI activates mana abilities: `activateAbility(manaAbility, game)`
- Returns true if mana paid
---
### **6. GAME CONFIGURATION & AI VS AI PLAY**
**File:** `/mnt/dest-btrfs/home/jc/src/mage/Mage.Server/src/main/java/mage/server/game/PlayerFactory.java`
**Player Creation:**
```java
PlayerFactory.createPlayer(PlayerType playerType, String name,
RangeOfInfluence range, int skill)
```
**Skill Levels (int skill):**
- Affects AI behavior:
- MAD: `maxDepth = max(4, skill)`, `maxThinkTimeSecs = skill * 3`
- MCTS: `maxThinkTime = skill * 2.0`
**Game Types (in Mage.Server.Plugins):**
- `Mage.Game.TwoPlayerDuel` - Standard 1v1
- `Mage.Game.FreeForAll` - Multiplayer
- `Mage.Game.Commander` - Commander format
- Other variants
**AI vs AI Configuration:**
```java
Game game = GameFactory.createGame(...)
players.add(PlayerFactory.createPlayer(COMPUTER_MAD, "AI1", RangeOfInfluence.ALL, 5))
players.add(PlayerFactory.createPlayer(COMPUTER_MAD, "AI2", RangeOfInfluence.ALL, 5))
// Both players completely autonomous - make all decisions internally
```
---
### **7. CRITICAL FILES FOR RL TRAINING INTEGRATION**
**Core Decision Points:**
1. `/mnt/dest-btrfs/home/jc/src/mage/Mage/src/main/java/mage/players/Player.java` - Interface
2. `/mnt/dest-btrfs/home/jc/src/mage/Mage/src/main/java/mage/players/PlayerImpl.java` - Base implementation
3. `/mnt/dest-btrfs/home/jc/src/mage/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java` - Base AI
4. `/mnt/dest-btrfs/home/jc/src/mage/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer6.java` - Most sophisticated AI
5. `/mnt/dest-btrfs/home/jc/src/mage/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/score/GameStateEvaluator2.java` - State scoring
**Simulation Framework:**
6. `/mnt/dest-btrfs/home/jc/src/mage/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/SimulatedPlayer2.java` - Simulation player
7. `/mnt/dest-btrfs/home/jc/src/mage/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/SimulationNode2.java` - Tree node
8. `/mnt/dest-btrfs/home/jc/src/mage/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/PossibleTargetsSelector.java` - Target ranking
---
### **8. DECISION TREE EXAMPLE: PRIORITY DECISION**
```
Game State: Your turn, precombat main phase, priority to you
→ Player.priority(game) called
→ AI Creates simulation copy with simulated players
→ Calls simulatePriority(SimulationNode2, game, depth=maxDepth, α, β)
→ Gets all playable abilities:
- Cast Fireball (with 15 possible targets)
- Cast creature spell (no targets)
- Activate mana ability
- Pass
→ For each ability:
→ Simulate game if ability activated
→ Calculate score at depth N-1
→ Update α/β values
→ Alpha-beta pruning may skip remaining actions
→ Recursively evaluate opponent's responses
→ Return best action (e.g., "Cast Fireball targeting opponent")
→ Execute: player.activateAbility(Fireball, game)
→ If targets not set: player.choose(Outcome.Damage, fireballs_targets, source, game)
→ AI calls: target.add(opponentId, game)
```
---
### **9. SUMMARY: RL TRAINING INTERFACE**
**For RL training, you'll need to:**
1. **Observe game state:** `GameStateEvaluator2.evaluate(playerId, game)` → PlayerEvaluateScore
2. **Get available actions:** `SimulatedPlayer2.simulatePriority(game)` → List<Ability>
3. **Execute action:** `player.activateAbility((ActivatedAbility) ability, game)`
4. **Get next state:** Game automatically transitions after activation
5. **Terminal check:** `game.checkIfGameIsOver()`
**Key RL Integration Points:**
- Replace `ComputerPlayer6.priority()` with RL policy
- Keep `selectAttackers()` and `selectBlockers()` as heuristics or make them RL decisions
- Use `GameStateEvaluator2` or custom value function for rewards
- Leverage existing `SimulatedPlayer2` framework for exploration/simulation
This architecture is highly modular and designed for exactly this kind of extension.
agentId: aef8aa1 (for resuming to continue this agent's work if needed)
<usage>total_tokens: 93422
tool_uses: 47
duration_ms: 85699</usage>Perfect! I have gathered comprehensive information. Let me create a detailed final report.
## XMage Card Sets and Implementation Analysis
Based on my exploration of the XMage codebase, here's a comprehensive breakdown of the three sets you requested:
### 1. SET IDENTIFICATION & CARD COUNTS
**TLA - Avatar: The Last Airbender**
- File: `/mnt/dest-btrfs/home/jc/src/mage/Mage.Sets/src/mage/sets/AvatarTheLastAirbender.java`
- Set Code: "TLA"
- Release Date: November 21, 2025
- Total Cards: **392 registered variants** (including multiple versions of the same card)
- Type: EXPANSION
**TDM - Tarkir: Dragonstorm**
- File: `/mnt/dest-btrfs/home/jc/src/mage/Mage.Sets/src/mage/sets/TarkirDragonstorm.java`
- Set Code: "TDM"
- Release Date: April 11, 2025
- Total Cards: **432 registered variants**
- Type: EXPANSION
**LCL - The Lost Caverns of Ixalan Commander** (Actually LCC)
- File: `/mnt/dest-btrfs/home/jc/src/mage/Mage.Sets/src/mage/sets/TheLostCavernsOfIxalanCommander.java`
- Set Code: "LCC" (not LCL - note this)
- Release Date: November 17, 2023
- Total Cards: **313 registered variants**
- Type: SUPPLEMENTAL (Commander deck product, no boosters)
---
### 2. CARD IMPLEMENTATION COMPLEXITY PATTERNS
I examined multiple card implementations from these sets. Here's the complexity range:
**Simple Creatures (Low Complexity)**
- Example: `/mnt/dest-btrfs/home/jc/src/mage/cards/a/AppaLoyalSkyBison.java` (Appa, Loyal Sky Bison - TLA)
- 66 lines total
- Basic structure: Static keyword abilities + triggered ability with modal choice
- Uses: FlyingAbility singleton, EntersBattlefieldOrAttacksSourceTriggeredAbility, Mode API
- Pattern: Card extends CardImpl → configure P/T → add abilities
**Simple Instant/Sorcery (Low Complexity)**
- Example: `/mnt/dest-btrfs/home/jc/src/mage/cards/a/AirbendersReversal.java` (TLA)
- 44 lines total
- Modal spell with targeting
- Uses: DestroyTargetEffect, custom AirbendTargetEffect
- Adds multiple modes with different targets
**Complex Planeswalker (Very High Complexity)**
- Example: `/mnt/dest-btrfs/home/jc/src/mage/cards/u/UginEyeOfTheStorms.java` (TDM)
- 127 lines total
- Multiple ability types: CastSourceTriggeredAbility, SpellCastControllerTriggeredAbility, LoyaltyAbility
- Custom OneShotEffect subclass for library search
- Implements: filtering with custom predicates, dynamic card selection, Play from Exile effects
- Complex nested effects with conditions
**Double-Faced Transforming Creature (High Complexity)**
- Example: `/mnt/dest-btrfs/home/jc/src/mage/cards/a/AangAtTheCrossroads.java` (TLA)
- 89 lines total
- Extends TransformingDoubleFacedCard (not CardImpl)
- Front side: EntersBattlefieldTriggeredAbility with LookLibraryAndPickControllerEffect, LeavesBattlefieldAllTriggeredAbility with delayed trigger
- Back side: Static ability providing buff, BeginningOfCombatTriggeredAbility with custom effect
- Uses: custom SubType filters, Duration management, complex effect composition
**Complex Legendary Creature (High Complexity)**
- Example: `/mnt/dest-btrfs/home/jc/src/mage/cards/b/BetorKinToAll.java` (TDM)
- 153 lines (with 3 inner classes)
- Implements custom Condition enum
- Implements custom DynamicValue enum for toughness calculation
- Multiple conditional effects chained in single ability
- Uses: ConditionalOneShotEffect, Stream API for game state queries, hint system
- Complex interaction logic with enum-based state
**Split Card (Moderate Complexity)**
- Example: `/mnt/dest-btrfs/home/jc/src/mage/cards/c/CommitMemory.java` (LCC)
- 96 lines total
- Extends SplitCard (not CardImpl)
- Aftermath mechanic implementation
- Each half has separate effect: custom OneShotEffect subclass + standard effects
- Uses: getLeftHalfCard() and getRightHalfCard() APIs
---
### 3. CORE ABILITIES AND EFFECTS USED
**Keyword Abilities** (237+ types available in mage.abilities.common):
- Singleton instances: `FlyingAbility.getInstance()`, `VigilanceAbility.getInstance()`
- Custom mechanic abilities: `AirbendTargetEffect`, `EarthbendTargetEffect`
- Standard triggers: `EntersBattlefieldTriggeredAbility`, `AttacksTriggeredAbility`, `BeginningOfCombatTriggeredAbility`
- Activated abilities: `LoyaltyAbility` (for planeswalkers)
**Common Effect Types** (801+ effect classes):
- **OneShotEffect subclasses** (one-time effects):
- DestroyTargetEffect
- DrawCardSourceControllerEffect
- ExileTargetEffect
- LookLibraryAndPickControllerEffect
- Custom OneShotEffect implementations
- Example imports in cards: `mage.abilities.effects.common.*`
- **ContinuousEffect subclasses** (ongoing effects):
- GainAbilityTargetEffect / GainAbilityControlledEffect
- GainAbilitySourceEffect
- **Ability Composition**:
- ConditionalOneShotEffect (wraps effects with conditions)
- CreateDelayedTriggeredAbilityEffect (for chains like "transform at next upkeep")
**Filtering & Targeting**:
```java
FilterPermanent, FilterCard, FilterSpell, FilterCreatureCard
FilterNonlandPermanent, FilterNonlandCard
// Applied with predicates:
AnotherPredicate, ColorlessPredicate, ManaValuePredicate
TargetController, AnotherPredicate
```
**Common Imports Across Cards**:
```java
import mage.MageInt; // Power/Toughness
import mage.abilities.Ability;
import mage.abilities.common.*; // Trigger & activation types
import mage.abilities.effects.common.*; // Standard effects
import mage.abilities.effects.keyword.*; // Custom keyword effects
import mage.cards.CardImpl; // Base card class
import mage.cards.CardSetInfo; // Set registration
import mage.constants.*; // CardType, SubType, Rarity, etc.
import mage.filter.*; // Filtering system
import mage.target.*; // Targeting system
```
---
### 4. CARD REGISTRATION SYSTEM
**How Cards Are Associated with Sets:**
1. **Set Definition Structure** (e.g., AvatarTheLastAirbender.java):
```java
public final class AvatarTheLastAirbender extends ExpansionSet {
private AvatarTheLastAirbender() {
super("Avatar: The Last Airbender", "TLA",
ExpansionSet.buildDate(2025, 11, 21),
SetType.EXPANSION);
this.hasBasicLands = true;
this.enablePlayBooster(358);
// ... card registrations follow
}
}
```
2. **Card Registration Via SetCardInfo**:
```java
cards.add(new SetCardInfo(
"Appa, Loyal Sky Bison", // Card name
9, // Card number
Rarity.UNCOMMON, // Rarity
mage.cards.a.AppaLoyalSkyBison.class, // Card class
NON_FULL_USE_VARIOUS // Graphic styling
));
```
3. **SetCardInfo Class** (ExpansionSet.java, lines 86-150):
- Stores: name, cardNumber (int/String), rarity, cardClass, graphicInfo
- Bidirectional link: Set knows which card class to instantiate via reflection
- Supports: Variant art versions (same card appears multiple times with different numbers)
4. **Card Class Location Convention**:
- Package: `mage.cards.<first-letter>`
- File: `/mnt/dest-btrfs/home/jc/src/mage/Mage.Sets/src/mage/cards/a/AppaLoyalSkyBison.java`
- Total: **30,796 card files** in the codebase (across all sets, 28K+ unique cards)
5. **Scanning Mechanism**:
- Sets are singletons: `public static AvatarTheLastAirbender getInstance()`
- Cards are registered in constructor via cards.add()
- No manual set registry needed - sets auto-register via class loading
- CardRepository scans these registrations
---
### 5. MOST COMMON EFFECT & ABILITY PATTERNS
Ranked by frequency across examined cards:
**1. Triggered Abilities (Most Common)**
- `EntersBattlefieldTriggeredAbility` - When card enters
- `AttacksTriggeredAbility` - When creature attacks
- `BeginningOfCombatTriggeredAbility` - Start of combat phase
- `LeavesBattlefieldAllTriggeredAbility` - When creatures leave
- `SpellCastControllerTriggeredAbility` - Whenever you cast spell
**2. Standard Effects (Infrastructure)**
- Drawing cards: DrawCardSourceControllerEffect
- Destroying: DestroyTargetEffect
- Damage: DamageTargetEffect
- Exiling: ExileTargetEffect
- Life gain/loss: GainLifeEffect / LoseLifeEffect
- Card lookup: LookLibraryAndPickControllerEffect
**3. Conditional & Wrapper Effects**
- `ConditionalOneShotEffect` - Wraps effect with activation condition
- `CreateDelayedTriggeredAbilityEffect` - Deferred triggers
- Custom OneShotEffect subclasses - For complex logic
**4. Filtering Patterns**
- `FilterPermanent` with predicates for targeting
- `FilterCreatureCard` for library searches
- `FilterSpellOrPermanent` for broad effects
- Custom filters with `add(Predicate)` chains
**5. Ability Structure Pattern**
```java
Ability ability = new [TriggerType]Ability(new [EffectType]Effect());
ability.addTarget(new Target[Type]()); // or addMode() for modal
ability.addHint(hint); // Adds UI hint
this.addAbility(ability); // Register to card
```
---
### 6. COMPLEXITY DISTRIBUTION SUMMARY
- **Total unique card implementations**: 30,796 files
- **TLA cards**: 392 entries (with art variants)
- **TDM cards**: 432 entries (with art variants)
- **LCC cards**: 313 entries (no boosters)
**Breakdown by Complexity**:
- **Simple** (creatures, basic spells): ~60% - single trigger + basic effect
- **Moderate** (split cards, lords, combat effects): ~25% - 2-3 abilities, modal choices
- **Complex** (planeswalkers, transforms, combos): ~10% - 4+ abilities, custom effects, state tracking
- **Very Complex** (legendary creatures with enum conditions): ~5% - custom DynamicValue, Condition implementations
---
### 7. KEY TECHNICAL PATTERNS
**Card Base Classes**:
- `CardImpl` - Standard card (1-faced)
- `TransformingDoubleFacedCard` - Double-faced transform cards
- `SplitCard` - Split/Aftermath cards
- All have standard: `copy()` and private copy constructor
**Ability Composition Layers**:
1. Core Ability type (TriggeredAbility, ActivatedAbility, StaticAbility)
2. Effect(s) added with `addEffect()`
3. Target(s) added with `addTarget()`
4. Mode(s) added with `addMode()` for modal choices
5. Hints added with `addHint()` for UI
**Common Effect Nesting**:
```
ConditionalOneShotEffect(
effect: new DrawCardSourceControllerEffect(1),
condition: BetorKinToAllCondition.TWENTY,
text: "description"
)
```
---
**File Paths Summary**:
- Set definitions: `/mnt/dest-btrfs/home/jc/src/mage/Mage.Sets/src/mage/sets/*.java` (570 set files)
- Card implementations: `/mnt/dest-btrfs/home/jc/src/mage/Mage.Sets/src/mage/cards/<letter>/*.java` (30,796 total)
- Core infrastructure: `/mnt/dest-btrfs/home/jc/src/mage/Mage/src/main/java/mage/`
- Abilities: `mage/abilities/common/` (237+ trigger/ability types)
- Effects: `mage/abilities/effects/common/` (801 effect implementations)
- Constants: `mage/constants/` (CardType, SubType, Rarity, Zone, etc.)
agentId: aac6bf1 (for resuming to continue this agent's work if needed)
<usage>total_tokens: 105894
tool_uses: 32
duration_ms: 134396</usage>Excellent - I now have a thorough understanding of the codebase. Let me synthesize what I've learned and ask the user some critical clarifying questions before designing the plan.
Key findings:
The core engine is massive (~175KB GameImpl.java alone) with deep Java OOP patterns
Three AI players: base heuristic (ComputerPlayer), minimax (ComputerPlayer6/MAD), and MCTS (ComputerPlayerMCTS)
Sets: TLA = Avatar: The Last Airbender (392 cards), TDM = Tarkir: Dragonstorm (432 cards), LCL might be LCC = Lost Caverns of Ixalan Commander (313 cards) - need to clarify
Cards span from simple keyword abilities to complex custom effects and planeswalkers
Before I can design a solid plan, I need to clarify a few critical things: whether LCL refers to a different set than LCC, what kind of RL interface they're expecting (Python bindings, Gym-like API, etc.), whether we're targeting multiplayer or just 1v1 gameplay, and what scope of game variants we need to support.
I should also understand if they need the full server/client architecture or just local simulation, whether to start with simple cards or port all complexity levels, and how they want to validate the Rust port against the original Java implementation.
Great, I've completed a thorough exploration of the codebase. Here's what I found and some critical questions before I design the plan.
Core Engine: Massive (~175KB GameImpl.java alone), deeply OOP with full-copy semantics for AI simulation. UUID-based object references (Rust-friendly). Event-driven with 600+ event types, 7-layer continuous effects system, complex combat/mana/zone handling.
AI Players: Three tiers - base heuristic (ComputerPlayer), minimax with alpha-beta (ComputerPlayer6/MAD), and MCTS (ComputerPlayerMCTS). The decision interface is well-defined in Player.java with ~15 decision methods (priority, target selection, combat, mana payment, etc.).
Sets: TLA = Avatar: The Last Airbender (~392 cards), TDM = Tarkir: Dragonstorm (~432 cards). However, "LCL" didn't match any set code - the closest match was LCC (Lost Caverns of Ixalan Commander, 313 cards). Cards range from simple keyword creatures (~60%) to complex planeswalkers/transforms (~15%).
I have several questions that will significantly shape the plan:
User has answered your questions: "What set did you mean by 'LCL'? The closest match I found was LCC (The Lost Caverns of Ixalan Commander). Or did you mean LCI (The Lost Caverns of Ixalan, the main set)?"="I meant ECL - Lorwyn Eclipsed", "What RL interface do you want? This determines whether we need Python bindings."="Python bindings (PyO3)", "What game format should we support? This affects engine complexity significantly."="Standard 1v1 only", "How should we verify correctness of the Rust port against the Java engine?"="Port Java tests". You can now continue with the user's answers in mind.
OK, so the user meant ECL - Lorwyn Eclipsed, not LCL. Let me search for that set code.
Let me also clarify my understanding:
Let me explore the ECL set, and then I can proceed to Phase 2 (Design) with a Plan agent.
Thanks for the clarifications! Let me quickly look up the ECL (Lorwyn Eclipsed) set, then move to the design phase.
Perfect! I now have all the information needed. Let me compile my findings: ## Summary: ECL (Lorwyn Eclipsed) Set **File Path:** `/mnt/dest-btrfs/home/jc/src/mage/Mage.Sets/src/mage/sets/LorwynEclipsed.java` **Release Date:** January 23, 2026 (as specified in the constructor: `buildDate(2026, 1, 23)`) **Card Count:** The set registers **227 unique card entries** across **351 booster cards** total (which includes multiple variations and alternate art versions). **Set Details:** - Set code: "ECL" - Set name: "Lorwyn Eclipsed" - Type: EXPANSION - Booster configuration: 351 cards with play booster support - Includes basic lands with full art variants ### Card Complexity Examples **1. Simple Card - EclipsedBoggart (2/3 creature, uncommon)** - Straightforward design with single triggered ability - Uses `EntersBattlefieldTriggeredAbility` with library search - Implements a static filter for card predicate matching - ~50 lines of code **2. Mid-Complex Card - AbigaleEloquentFirstYear (Rare legendary)** - Multiple keyword abilities (Flying, First Strike, Lifelink) - Complex triggered ability on entry that: - Removes all abilities from target creature - Adds multiple counter types (flying, first strike, lifelink) - Uses optional targeting (0-1 creature) - ~68 lines of code **3. Complex Card - OkoLorwynLiege (Mythic planeswalker)** - Double-faced transforming card implementation - Multiple loyalty abilities across both sides - Custom effect class (`OkoShadowmoorScionEffect`) for emblem creation - Complex conditional transformation logic - Emblem system integration - ~123 lines of code The set demonstrates complexity ranging from simple creatures with triggered abilities to complex planeswalkers with custom effects and transformations. agentId: a96ae28 (for resuming to continue this agent's work if needed) <usage>total_tokens: 45693 tool_uses: 10 duration_ms: 16154</usage>
Now I have all the information I need. Let me summarize:
Now let me move to Phase 2 (Design) by launching a Plan agent to design the implementation approach.
Good - ECL has 227 unique cards. Now I have all the context needed. Let me move to the design phase.
I now have a comprehensive understanding of the codebase. Let me deliver the detailed implementation plan.
---
# XMage to Rust Port: Detailed Architectural Plan for RL AI Training
## 1. Scope Summary
After thorough examination of the XMage codebase, here are the key metrics driving this plan:
**Core engine files** (17,091 lines total across 9 files): `GameImpl.java` (4,270 lines), `PermanentImpl.java` (2,202 lines), `AbilityImpl.java` (1,966 lines), `Combat.java` (1,956 lines), `GameState.java` (1,758 lines), `Mana.java` (1,540 lines), `ContinuousEffects.java` (1,427 lines), `StaticFilters.java` (1,430 lines), `ZonesHandler.java` (542 lines).
**Card counts**: TLA (Avatar: The Last Airbender) = 392 entries / ~392 unique cards; TDM (Tarkir: Dragonstorm) = 432 entries / ~291 unique cards; ECL (Lorwyn Eclipsed) = 413 entries / ~283 unique cards. Total: ~966 unique card implementations needed.
**Set-specific mechanics**: Waterbend (TLA -- tap artifacts/creatures to pay generic mana), Airbend (TLA -- exile + recast for {2}), Mobilize (TDM -- create tapped/attacking Warrior tokens on attack).
**AI Players**: `ComputerPlayer.java` (1,350 lines, heuristic base), `ComputerPlayer6.java` (1,251 lines, minimax search), `GameStateEvaluator2.java` (~150 lines).
**Keyword abilities**: 246 distinct keyword ability files in the engine.
**Player decision interface**: ~15 distinct decision methods (`priority`, `choose`, `chooseTarget`, `chooseUse`, `choosePile`, `chooseTriggeredAbility`, `selectAttackers`, `selectBlockers`, `chooseTargetAmount`, etc.)
---
## 2. Cargo Workspace Structure
```
mtg-rl/
Cargo.toml # workspace root
crates/
mtg-engine/ # Core rules engine (Teammate 1)
Cargo.toml
src/
lib.rs
game.rs # GameImpl equivalent
state.rs # GameState equivalent
turn.rs # Turn/Phase/Step
priority.rs # Priority loop + stack resolution
combat.rs # Combat system
zones.rs # ZonesHandler + zone management
battlefield.rs # Battlefield struct
mana.rs # Mana + ManaPool
stack.rs # SpellStack + StackObject
events.rs # GameEvent types + event dispatch
effects/
mod.rs
continuous.rs # ContinuousEffects + 7-layer system
one_shot.rs # OneShotEffect trait
replacement.rs # ReplacementEffect
prevention.rs # PreventionEffect
restriction.rs # RestrictionEffect + RequirementEffect
as_though.rs # AsThoughEffect
cost_modification.rs # CostModificationEffect
abilities/
mod.rs
ability.rs # Ability trait + AbilityImpl
activated.rs # ActivatedAbility
triggered.rs # TriggeredAbility
static_ability.rs # StaticAbility
spell.rs # SpellAbility
mana_ability.rs # ManaAbility
keywords/
mod.rs # All keyword abilities
flying.rs
trample.rs
deathtouch.rs
... (per keyword)
cards/
mod.rs
card.rs # Card trait + CardData struct
permanent.rs # Permanent struct (PermanentImpl)
token.rs # Token generation
player/
mod.rs
player.rs # Player trait (decision interface)
player_impl.rs # PlayerImpl base logic
mana_pool.rs # ManaPool
filters/
mod.rs
filter.rs # Filter trait + FilterPermanent/FilterCard
predicates.rs # Predicate trait + common predicates
static_filters.rs # Pre-built locked filters
targets/
mod.rs
target.rs # Target trait
target_card.rs
target_permanent.rs
target_player.rs
watchers/
mod.rs
watcher.rs # Watcher trait
common/ # Commonly-used watchers
counters.rs # Counter types
constants.rs # All enums (Zone, PhaseStep, CardType, SubType, etc.)
types.rs # ObjectId, PlayerId type aliases
error.rs # Error types
mtg-cards/ # Card implementations (Teammate 3)
Cargo.toml
src/
lib.rs # Card registry
registry.rs # Card name -> CardFactory map
sets/
mod.rs
tla.rs # TLA set registration
tdm.rs # TDM set registration
ecl.rs # ECL set registration
cards/
tla/ # ~392 card implementations
mod.rs
aangs_iceberg.rs
aang_swift_savior.rs
...
tdm/ # ~291 card implementations
mod.rs
...
ecl/ # ~283 card implementations
mod.rs
...
keywords/ # Set-specific keywords
waterbend.rs
airbend.rs
mobilize.rs
mtg-ai/ # AI players + Gym interface (Teammate 2)
Cargo.toml
src/
lib.rs
heuristic_player.rs # ComputerPlayer port
minimax_player.rs # ComputerPlayer6 port
evaluator.rs # GameStateEvaluator2 port
random_player.rs # Random action player (RL baseline)
observation.rs # Game state -> tensor encoding
action.rs # Action space encoding/decoding
gym.rs # Gymnasium environment logic
mtg-python/ # PyO3 bindings (Teammate 2)
Cargo.toml # [lib] crate-type = ["cdylib"]
src/
lib.rs # #[pymodule] entry point
env.rs # MtgEnv class (Gymnasium)
types.rs # Python-exposed types
python/
mtg_rl/
__init__.py
env.py # Gymnasium wrapper
wrappers.py # Observation/reward wrappers
mtg-tests/ # Integration tests (shared)
Cargo.toml
src/
lib.rs
test_framework.rs # CardTestPlayerBase equivalent
test_helpers.rs # addCard, castSpell, execute, assert*
tests/
tla/ # Ported JUnit tests for TLA cards
tdm/ # Ported JUnit tests for TDM cards
ecl/ # Ported JUnit tests for ECL cards
engine/ # Core engine tests
```
---
## 3. Core Data Structures and Rust Equivalents
### 3.1 Identity System
Java uses `UUID` everywhere. Rust equivalent:
```rust
// types.rs
use uuid::Uuid;
pub type ObjectId = Uuid; // cards, permanents, abilities
pub type PlayerId = Uuid; // players
pub type AbilityId = Uuid; // individual ability instances
/// Typed wrappers for safety (prevents mixing IDs)
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct CardId(pub Uuid);
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct PermanentId(pub Uuid);
```
### 3.2 Handling the Java Inheritance Hierarchy
Java's approach: `CardImpl extends MageObjectImpl implements Card`, `PermanentImpl extends CardImpl implements Permanent`, `PermanentCard extends PermanentImpl`, etc. Deep inheritance with 5+ levels.
**Rust approach: Composition + Traits + Enums**
**MageObject** (base for all game objects): Use a trait with a shared data struct.
```rust
pub trait MageObject: Clone + Send + Sync {
fn id(&self) -> ObjectId;
fn name(&self) -> &str;
fn card_types(&self) -> &CardTypeSet;
fn sub_types(&self) -> &SubTypeSet;
fn super_types(&self) -> &SuperTypeSet;
fn color(&self) -> &ObjectColor;
fn mana_cost(&self) -> &ManaCost;
fn power(&self) -> Option<i32>;
fn toughness(&self) -> Option<i32>;
fn abilities(&self) -> &AbilityList;
}
```
**Card**: Not a trait object in the game engine. Instead, a data struct + factory function.
```rust
#[derive(Clone, Debug)]
pub struct CardData {
pub id: ObjectId,
pub owner_id: PlayerId,
pub name: String,
pub card_types: CardTypeSet,
pub sub_types: SubTypeSet,
pub super_types: SuperTypeSet,
pub color: ObjectColor,
pub mana_cost: ManaCost,
pub power: Option<MageInt>,
pub toughness: Option<MageInt>,
pub abilities: Vec<Box<dyn Ability>>,
pub rarity: Rarity,
// zone tracking
pub attachments: Vec<ObjectId>,
}
```
**Permanent**: Extends CardData with battlefield-specific state.
```rust
#[derive(Clone, Debug)]
pub struct Permanent {
pub card: CardData, // composition, not inheritance
pub controller_id: PlayerId,
pub tapped: bool,
pub flipped: bool,
pub transformed: bool,
pub face_down: bool,
pub phased_in: bool,
pub summoning_sick: bool,
pub damage: i32,
pub loyalty: i32,
pub counters: Counters,
pub attached_to: Option<ObjectId>,
pub order: u64,
// combat state
pub attacking: bool,
pub blocking: bool,
pub blocked: bool,
}
```
### 3.3 Ability System
Java uses deep inheritance: `AbilityImpl -> ActivatedAbilityImpl -> SpellAbility`, `AbilityImpl -> TriggeredAbilityImpl -> EntersBattlefieldTriggeredAbility`, etc.
**Rust approach: trait + enum for dispatch**
```rust
pub trait Ability: DynClone + Send + Sync + std::fmt::Debug {
fn id(&self) -> AbilityId;
fn source_id(&self) -> ObjectId;
fn controller_id(&self) -> PlayerId;
fn zone(&self) -> Zone;
fn ability_type(&self) -> AbilityType;
fn effects(&self) -> &[Box<dyn Effect>];
fn targets(&self) -> &[Box<dyn Target>];
fn costs(&self) -> &CostList;
fn mana_cost(&self) -> &ManaCost;
fn can_activate(&self, game: &Game) -> bool;
fn activate(&self, game: &mut Game) -> Result<bool, GameError>;
fn resolve(&self, game: &mut Game) -> Result<bool, GameError>;
fn uses_stack(&self) -> bool;
fn get_rule(&self) -> String;
}
clone_trait_object!(Ability);
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum AbilityType {
Spell,
Activated,
Triggered,
Static,
Mana,
Special,
PlayLand,
}
```
**Keyword abilities as singleton-like constants**: In Java, `FlyingAbility.getInstance()` returns a singleton. In Rust, use `lazy_static` or `const` where possible.
```rust
pub fn flying_ability() -> Box<dyn Ability> {
Box::new(FlyingAbility::new())
}
// Or for zero-cost keywords, use a flag-based system:
bitflags! {
pub struct KeywordFlags: u64 {
const FLYING = 1 << 0;
const TRAMPLE = 1 << 1;
const DEATHTOUCH = 1 << 2;
const FIRST_STRIKE = 1 << 3;
const DOUBLE_STRIKE = 1 << 4;
const VIGILANCE = 1 << 5;
const REACH = 1 << 6;
const LIFELINK = 1 << 7;
const HASTE = 1 << 8;
const HEXPROOF = 1 << 9;
const INDESTRUCTIBLE = 1 << 10;
const FLASH = 1 << 11;
const MENACE = 1 << 12;
// ... ~50 pure keyword abilities
}
}
```
This bitflags approach is a major performance win: checking `permanent.keywords.contains(FLYING)` is a single bitwise AND versus Java's hash lookup.
### 3.4 Effect System
```rust
pub trait Effect: DynClone + Send + Sync + std::fmt::Debug {
fn outcome(&self) -> Outcome;
fn get_text(&self) -> String;
}
pub trait OneShotEffect: Effect {
fn apply(&self, game: &mut Game, source: &dyn Ability) -> Result<bool, GameError>;
}
pub trait ContinuousEffect: Effect {
fn apply(&self, layer: Layer, sub_layer: SubLayer,
source: &dyn Ability, game: &mut Game) -> Result<bool, GameError>;
fn duration(&self) -> Duration;
fn layer(&self) -> Layer;
fn sub_layer(&self) -> SubLayer;
fn get_order(&self) -> u64;
fn set_order(&mut self, order: u64);
fn is_dependent_to(&self, others: &[&dyn ContinuousEffect]) -> HashSet<Uuid>;
}
pub trait ReplacementEffect: ContinuousEffect {
fn replaces_event(&self, event: &GameEvent, game: &Game) -> bool;
fn applies(&self, event: &GameEvent, source: &dyn Ability, game: &Game) -> bool;
fn replace_event(&self, event: &mut GameEvent, source: &dyn Ability,
game: &mut Game) -> Result<bool, GameError>;
}
```
### 3.5 Event System
Java has 600+ event types in `GameEvent.EventType`. In Rust, use an enum with structured data.
```rust
#[derive(Clone, Debug)]
pub struct GameEvent {
pub event_type: EventType,
pub target_id: ObjectId,
pub source_id: ObjectId,
pub player_id: PlayerId,
pub amount: i32,
pub flag: bool,
pub data: Option<String>,
pub zone: Option<Zone>,
pub applied_effects: Vec<ObjectId>,
}
/// All 600+ event types from Java, as a flat enum
/// Batch events carry Vec<GameEvent> of sub-events
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum EventType {
Beginning,
PreventDamage, PreventedDamage,
PlayTurn, ExtraTurn,
BeginTurn,
ChangePhase, PhaseChanged,
ChangeStep, StepChanged,
// ... full enumeration of all types ...
ZoneChange,
ZoneChangeBatch(Vec<GameEvent>),
DrawCard, DrewCard,
DamagePlayer,
DamagedPlayer,
DamagedBatchForAll(Vec<GameEvent>),
// ...
CastSpell,
SpellCast,
ActivateAbility,
ActivatedAbility,
DeclareAttacker,
AttackerDeclared,
DeclareBlocker,
BlockerDeclared,
// ...
}
```
### 3.6 Continuous Effects Layer System
Direct translation of the 7-layer system from `ContinuousEffects.apply()`:
```rust
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum Layer {
CopyEffects1, // 613.1a
ControlChangingEffects2, // 613.1b
TextChangingEffects3, // 613.1c
TypeChangingEffects4, // 613.1d
ColorChangingEffects5, // 613.1e
AbilityEffects6, // 613.1f
PTChangingEffects7, // 613.1g
PlayerEffects, // 613.10
RulesEffects,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum SubLayer {
CopyEffects1a,
FaceDown1b,
CharacteristicDefining7a,
SetPT7b,
ModifyPT7c,
Counters7d,
SwitchPT7e,
NA,
}
impl ContinuousEffectsManager {
/// The core 7-layer apply function, direct translation of
/// ContinuousEffects.apply() from Java
pub fn apply(&mut self, game: &mut Game) {
self.remove_inactive_effects(game);
let mut active_effects = self.get_layered_effects(game);
// Layer 1: Copy effects (1a then 1b)
self.apply_copy_layer(&active_effects, game);
active_effects = self.get_layered_effects(game); // reload after copy
// Layer 2: Control-changing (loop until stable)
self.apply_control_layer(&active_effects, game);
// Layers 3-5: Text, Type, Color
self.apply_layer(&active_effects, Layer::TextChangingEffects3, game);
active_effects = self.get_layered_effects(game);
self.apply_layer(&active_effects, Layer::TypeChangingEffects4, game);
active_effects = self.get_layered_effects(game);
self.apply_layer(&active_effects, Layer::ColorChangingEffects5, game);
active_effects = self.get_layered_effects(game);
// Layer 6: Ability add/remove (with dependency resolution loop)
self.apply_ability_layer(&active_effects, game);
// Layer 7: P/T (sub-layers 7a through 7e)
self.apply_pt_layer(&active_effects, game);
}
}
```
### 3.7 Game State and Cloning
The critical design for RL: game state must be cheaply cloneable for AI search. Java does deep copy via copy constructors.
```rust
#[derive(Clone)]
pub struct GameState {
pub players: HashMap<PlayerId, Player>,
pub player_order: Vec<PlayerId>, // APNAP order
pub active_player_id: PlayerId,
pub priority_player_id: PlayerId,
pub turn: Turn,
pub turn_mods: Vec<TurnMod>,
pub turn_num: u32,
pub step_num: u32,
pub stack: SpellStack,
pub battlefield: Battlefield,
pub exile: Exile,
pub command: Command,
pub effects: ContinuousEffectsManager,
pub triggers: TriggeredAbilities,
pub delayed: DelayedTriggeredAbilities,
pub triggered_queue: Vec<Box<dyn TriggeredAbility>>,
pub combat: Combat,
pub watchers: WatcherMap,
pub zones: HashMap<ObjectId, Zone>,
pub zone_change_counter: HashMap<ObjectId, u32>,
pub card_state: HashMap<ObjectId, CardState>,
pub values: HashMap<String, Value>,
pub game_over: bool,
pub has_day_night: bool,
pub is_daytime: bool,
}
```
**Performance optimization for cloning**: Consider using `im` (immutable data structures) or `rpds` crate for `HashMap`/`Vec` fields that rarely change, enabling structural sharing between cloned states. This is critical for minimax search where thousands of states are explored.
```toml
# In Cargo.toml for mtg-engine
[dependencies]
im = "15" # Persistent/immutable data structures with structural sharing
```
```rust
// Battlefield with structural sharing
use im::HashMap as ImHashMap;
#[derive(Clone)]
pub struct Battlefield {
field: ImHashMap<ObjectId, Permanent>,
entering: ImHashMap<ObjectId, Permanent>,
}
```
---
## 4. Card Implementation Approach
### 4.1 Hybrid Data-Driven + Code Approach
Most cards (~60%) are simple combinations of existing abilities/effects. These should be data-driven. Complex cards (~15%) need custom Rust code.
**Card factory pattern**:
```rust
/// A card factory function constructs a CardData with all abilities
pub type CardFactory = fn(ObjectId, PlayerId) -> CardData;
/// Registry maps card names to their factories
pub struct CardRegistry {
factories: HashMap<String, CardFactory>,
}
impl CardRegistry {
pub fn create_card(&self, name: &str, owner_id: PlayerId) -> Option<CardData> {
self.factories.get(name).map(|f| f(ObjectId::new_v4(), owner_id))
}
}
```
**Simple card example** (Aang's Iceberg -- flash enchantment with ETB exile and waterbend sacrifice):
```rust
pub fn aangs_iceberg(id: ObjectId, owner: PlayerId) -> CardData {
let filter = FilterPermanent::new("other target nonland permanent")
.with_predicate(NonlandPredicate)
.with_predicate(AnotherPredicate);
let mut card = CardData::new(id, owner, "Aang's Iceberg",
&[CardType::Enchantment], "{2}{W}");
card.add_ability(flash());
let mut etb = EntersBattlefieldTriggeredAbility::new(
ExileUntilSourceLeavesEffect::new()
);
etb.add_target(TargetPermanent::new(0, 1, filter));
card.add_ability(etb);
card.add_ability(SimpleActivatedAbility::new(
DoIfCostPaid::new(ScryEffect::new(2), SacrificeSourceCost::new()),
WaterbendCost::new(3),
));
card
}
```
**Complex card example** (cards needing custom effects write their own `OneShotEffect` / `ContinuousEffect` implementations):
```rust
// Custom effect for Airbend
#[derive(Clone, Debug)]
pub struct AirbendTargetEffect;
impl OneShotEffect for AirbendTargetEffect {
fn apply(&self, game: &mut Game, source: &dyn Ability) -> Result<bool, GameError> {
let player = game.get_player(source.controller_id())?;
let targets = source.targets()[0].get_targets(game);
// exile targets, add cast-from-exile permission
// fire AIRBENDED event
// ... (direct port of Java logic)
}
}
```
### 4.2 Card Complexity Tiers
Based on examining the three target sets:
**Tier 1 -- Vanilla/simple keywords** (~30%): Creatures with keyword abilities, basic lands, simple stat creatures. These are literally just `CardData` constructors with `card.keywords |= FLYING | TRAMPLE`. Time: 2-5 minutes each.
**Tier 2 -- Standard abilities** (~35%): Cards using existing effect/ability building blocks (ETB triggers, static buffs, activated abilities with standard effects). Direct composition of engine primitives. Time: 10-15 minutes each.
**Tier 3 -- Moderate complexity** (~20%): Modal spells, conditional effects, lord abilities, triggered abilities with filters, equipment, auras. Require understanding effect composition. Time: 15-30 minutes each.
**Tier 4 -- Complex** (~15%): Planeswalkers, transforming double-faced cards, saga-like cards, cards with custom watchers, cards creating delayed triggered abilities, unique game-altering effects. Require custom Rust code. Time: 30-90 minutes each.
---
## 5. Player Decision Interface and AI Design
### 5.1 Player Trait
The critical interface -- direct port of Java's 15 decision methods:
```rust
pub trait PlayerDecisionMaker: Send + Sync {
/// Priority decision: cast spell, activate ability, or pass
fn priority(&mut self, game: &Game) -> PlayerAction;
/// Choose from available targets
fn choose_target(&mut self, outcome: Outcome, target: &dyn Target,
source: &dyn Ability, game: &Game) -> Vec<ObjectId>;
/// Yes/No decision
fn choose_use(&mut self, outcome: Outcome, message: &str,
source: &dyn Ability, game: &Game) -> bool;
/// Choose from a set of options
fn choose(&mut self, outcome: Outcome, choices: &[String],
game: &Game) -> usize;
/// Choose triggered ability ordering
fn choose_triggered_ability(&mut self, abilities: &[&dyn TriggeredAbility],
game: &Game) -> usize;
/// Declare attackers
fn select_attackers(&mut self, game: &Game) -> Vec<(ObjectId, PlayerId)>;
/// Declare blockers
fn select_blockers(&mut self, game: &Game) -> Vec<(ObjectId, ObjectId)>;
/// Choose pile (Fact or Fiction, etc.)
fn choose_pile(&mut self, pile1: &[&CardData], pile2: &[&CardData],
game: &Game) -> bool; // true = pile1
/// Choose X value
fn choose_x_value(&mut self, min: u32, max: u32,
source: &dyn Ability, game: &Game) -> u32;
/// Mulligan decision
fn choose_mulligan(&mut self, hand: &[&CardData], game: &Game) -> bool;
/// Choose mode for modal spell
fn choose_mode(&mut self, modes: &[String], min: usize, max: usize,
source: &dyn Ability, game: &Game) -> Vec<usize>;
/// Assign damage order for combat
fn assign_damage(&mut self, attacker: &Permanent,
blockers: &[&Permanent], damage: i32,
game: &Game) -> Vec<(ObjectId, i32)>;
}
/// All possible player actions during priority
#[derive(Clone, Debug)]
pub enum PlayerAction {
Pass,
CastSpell { card_id: ObjectId, targets: Vec<ObjectId>, modes: Vec<usize> },
ActivateAbility { ability_id: AbilityId, targets: Vec<ObjectId> },
PlayLand { card_id: ObjectId },
SpecialAction(SpecialActionType),
}
```
### 5.2 RL Action Space
The variable-size action space of MTG is the hardest RL design problem. Design:
```rust
/// Flattened action representation for RL
#[derive(Clone, Debug)]
pub struct ActionSpace {
pub legal_actions: Vec<Action>,
}
#[derive(Clone, Debug)]
pub enum Action {
Pass,
PlayLand(usize), // index into playable lands
CastSpell(usize), // index into castable spells
ActivateAbility(usize), // index into activatable abilities
AttackWith(Vec<bool>), // bitmap of creatures to attack with
BlockWith(Vec<(usize, usize)>), // (blocker_idx, attacker_idx) pairs
Choose(usize), // index into available choices
TargetSelect(usize), // index into legal targets
}
```
For the RL interface, use a two-phase approach:
1. **Action type selection**: Choose from `{Pass, PlayLand, CastSpell, ActivateAbility}`
2. **Action parameterization**: Choose targets/modes for the selected action
This avoids a combinatorial explosion while still being expressive.
### 5.3 Observation Space
```rust
/// Fixed-size observation tensor for neural networks
pub struct Observation {
// Player state (self and opponent)
pub life_totals: [f32; 2], // normalized
pub mana_available: [f32; 12], // 6 types x 2 players
pub cards_in_hand: [f32; 2],
pub cards_in_library: [f32; 2],
pub cards_in_graveyard: [f32; 2],
// Battlefield encoding (fixed-size with padding)
pub my_permanents: [[f32; PERM_FEATURES]; MAX_PERMANENTS],
pub opp_permanents: [[f32; PERM_FEATURES]; MAX_PERMANENTS],
// Hand encoding
pub my_hand: [[f32; CARD_FEATURES]; MAX_HAND],
// Stack encoding
pub stack: [[f32; STACK_FEATURES]; MAX_STACK],
// Phase/turn info
pub phase_step: [f32; 13], // one-hot PhaseStep
pub is_my_turn: f32,
pub turn_number: f32,
// Legal action mask
pub action_mask: Vec<bool>,
}
const PERM_FEATURES: usize = 32; // power, toughness, keywords, types, etc.
const CARD_FEATURES: usize = 24; // CMC, types, keywords, etc.
const MAX_PERMANENTS: usize = 30; // padding limit
const MAX_HAND: usize = 15;
const MAX_STACK: usize = 10;
```
### 5.4 Gymnasium Integration via PyO3
```rust
// mtg-python/src/env.rs
#[pyclass]
pub struct MtgEnv {
game: Game,
player_agent: PlayerId, // which player the RL agent controls
opponent: Box<dyn PlayerDecisionMaker>,
action_history: Vec<Action>,
}
#[pymethods]
impl MtgEnv {
#[new]
fn new(deck1: Vec<String>, deck2: Vec<String>,
opponent_type: &str) -> PyResult<Self> { ... }
/// Gymnasium reset: returns (observation, info)
fn reset(&mut self, seed: Option<u64>) -> PyResult<(PyObject, PyObject)> { ... }
/// Gymnasium step: returns (observation, reward, terminated, truncated, info)
fn step(&mut self, action: usize) -> PyResult<(PyObject, f64, bool, bool, PyObject)> {
// 1. Decode action index to Action enum
// 2. Apply action to game
// 3. Run game forward until next decision point for agent
// (opponent decisions handled by built-in AI)
// 4. Encode new observation
// 5. Calculate reward (life differential, win/loss)
// 6. Check termination
}
/// Returns legal actions as a mask
fn action_mask(&self) -> PyResult<Vec<bool>> { ... }
/// Returns the observation space spec
#[staticmethod]
fn observation_space() -> PyResult<PyObject> { ... }
/// Returns the action space spec
fn action_space(&self) -> PyResult<PyObject> { ... }
/// Run a complete game with two AI players, no human input
fn simulate(&mut self) -> PyResult<(PlayerId, f64)> { ... }
}
```
Python side:
```python
# mtg_rl/env.py
import gymnasium as gym
from mtg_python import MtgEnv as _MtgEnv
class MtgGymEnv(gym.Env):
metadata = {"render_modes": ["ansi"]}
def __init__(self, deck1, deck2, opponent="heuristic"):
self._env = _MtgEnv(deck1, deck2, opponent)
self.observation_space = ...
self.action_space = gym.spaces.Discrete(MAX_ACTIONS)
def reset(self, seed=None, options=None):
return self._env.reset(seed)
def step(self, action):
return self._env.step(action)
def action_masks(self):
return self._env.action_mask()
```
---
## 6. Thread Safety Design
The entire reason for this port. Key decisions:
1. **Game state is `Send + Sync`**: All structs derive `Clone` and contain no `Rc`, `RefCell`, or raw pointers. Use `Arc` only for shared read-only data (card database, static filters).
2. **No global mutable state**: Unlike Java's singleton patterns, all state lives in the `Game` struct. Card factories are pure functions. Keyword ability "singletons" are constructed per-card (zero cost with bitflags).
3. **Parallel game execution**: Each `Game` instance is self-contained. Run thousands concurrently:
```rust
use rayon::prelude::*;
fn run_games(n: usize, deck1: &Deck, deck2: &Deck) -> Vec<GameResult> {
(0..n).into_par_iter().map(|seed| {
let mut game = Game::new_two_player(deck1.clone(), deck2.clone());
game.set_seed(seed as u64);
game.play_to_completion()
}).collect()
}
```
4. **AI simulation parallelism**: The minimax AI can explore branches in parallel using `rayon`:
```rust
impl MinimaxPlayer {
fn search(&self, game: &Game, depth: u32) -> (PlayerAction, i32) {
if depth == 0 || game.is_over() {
return (PlayerAction::Pass, self.evaluate(game));
}
let actions = game.get_legal_actions(self.player_id);
actions.par_iter() // parallel search over branches
.map(|action| {
let mut sim = game.clone(); // cheap with im:: structures
sim.apply_action(action);
let (_, score) = self.search(&sim, depth - 1);
(action.clone(), score)
})
.max_by_key(|(_, score)| *score)
.unwrap_or((PlayerAction::Pass, i32::MIN))
}
}
```
---
## 7. Phased Implementation Order with Team Dependencies
### Phase 1: Foundation (Weeks 1-3) -- All teams start in parallel
**Teammate 1 (Engine)** -- Priority: Core types and game loop skeleton
1. `constants.rs` -- All enums (Zone, PhaseStep, CardType, SubType, Layer, etc.)
2. `types.rs` -- ObjectId, PlayerId, type aliases
3. `mana.rs` -- Mana struct and arithmetic
4. `counters.rs` -- Counter types
5. `card.rs` -- CardData struct
6. `permanent.rs` -- Permanent struct
7. `player/player.rs` -- Player trait and PlayerImpl with life/hand/library/graveyard
8. `player/mana_pool.rs` -- ManaPool
9. `battlefield.rs` -- Battlefield container
10. `stack.rs` -- SpellStack
11. `zones.rs` -- Zone management, zone change logic
12. `state.rs` -- GameState struct with all fields
13. `turn.rs` -- Turn/Phase/Step structures
14. `game.rs` -- Game struct shell with `new_two_player()`, `start()`, `play()`
**Teammate 2 (AI)** -- Priority: Player interface and evaluation
1. Define the `PlayerDecisionMaker` trait (must be stable early -- all teams depend on it)
2. `random_player.rs` -- Random legal action player (simplest implementation, good for testing)
3. `evaluator.rs` -- Port `GameStateEvaluator2` scoring logic
4. `observation.rs` -- Design observation encoding
5. `action.rs` -- Design action space encoding
**Teammate 3 (Cards)** -- Priority: Card registry and simple cards
1. `registry.rs` -- CardRegistry with factory pattern
2. `sets/tla.rs`, `sets/tdm.rs`, `sets/ecl.rs` -- Set registration stubs
3. Begin implementing Tier 1 cards (vanilla creatures, basic lands) for all three sets
4. Implement basic tokens (RedWarriorToken for Mobilize, etc.)
**Dependency**: Teammate 2 delivers `PlayerDecisionMaker` trait by end of Week 1. Teammate 1 integrates it. Teammate 3 needs `CardData` struct from Teammate 1 by end of Week 1.
### Phase 2: Core Systems (Weeks 3-6)
**Teammate 1 (Engine)**:
1. `events.rs` -- Full GameEvent type enum, fire_event, replace_event
2. `priority.rs` -- The priority loop (port of `GameImpl.playPriority`)
3. `abilities/ability.rs` -- Ability trait implementations
4. `abilities/activated.rs` -- ActivatedAbility
5. `abilities/triggered.rs` -- TriggeredAbility + trigger checking
6. `abilities/spell.rs` -- SpellAbility + casting sequence
7. `abilities/static_ability.rs` -- StaticAbility
8. `abilities/mana_ability.rs` -- Mana abilities (special, don't use stack)
9. `effects/continuous.rs` -- ContinuousEffectsManager with full 7-layer apply
10. `effects/replacement.rs` -- ReplacementEffect dispatch
11. `effects/one_shot.rs` -- Common one-shot effects (DestroyEffect, DamageEffect, DrawEffect, etc.)
12. `combat.rs` -- Combat system (declare attackers, declare blockers, damage assignment)
13. `watchers/watcher.rs` -- Watcher trait + common watchers
14. `filters/` -- Filter/Predicate system
15. `targets/` -- Target system
**Teammate 2 (AI)**:
1. `heuristic_player.rs` -- Port ComputerPlayer base heuristic AI (1,350 lines Java)
2. Start `minimax_player.rs` -- Port ComputerPlayer6 search (depends on game.clone() working)
3. Begin `gym.rs` -- Gymnasium environment logic (depends on Engine Phase 2 priority loop)
4. Start `mtg-python/src/lib.rs` -- PyO3 module skeleton
**Teammate 3 (Cards)**:
1. `keywords/waterbend.rs` -- WaterbendCost implementation
2. `keywords/airbend.rs` -- AirbendTargetEffect
3. `keywords/mobilize.rs` -- MobilizeAbility
4. Implement all Tier 2 cards (standard abilities) across TLA, TDM, ECL
5. Port common building-block abilities: `EntersBattlefieldTriggeredAbility`, `SimpleActivatedAbility`, `SimpleStaticAbility`, `AttacksTriggeredAbility`
6. Port common effects: `DestroyTargetEffect`, `ExileTargetEffect`, `BoostControlledEffect`, `GainAbilityControlledEffect`, `CreateTokenEffect`, `DrawCardSourceControllerEffect`, `DamageTargetEffect`, `CounterTargetEffect`, `ScryEffect`
**Dependency**: Teammate 1 must deliver working event system and ability framework by Week 4 for Teammate 3's Tier 2+ cards. Teammate 1 must deliver priority loop by Week 5 for Teammate 2's Gym integration.
### Phase 3: Integration and Complex Cards (Weeks 6-9)
**Teammate 1 (Engine)**:
1. Full game loop testing: can play a complete game with simple cards
2. State-based actions (SBA) checking
3. Stack resolution nuances (fizzle, counter, copy)
4. Last-known information (LKI) system
5. Transform/double-faced card support
6. Aura/equipment attachment system
7. Performance optimization: profiling, im:: data structures tuning
8. Concurrency testing: run 1000 games in parallel
**Teammate 2 (AI)**:
1. Complete `minimax_player.rs` with parallel search
2. Complete `gym.rs` and `mtg-python/` PyO3 bindings
3. Python package (`pip install`) setup
4. Reward shaping design for RL training
5. Observation space fine-tuning based on actual game flow
6. Self-play infrastructure setup
**Teammate 3 (Cards)**:
1. All Tier 3 cards (moderate complexity) across three sets
2. All Tier 4 cards (complex: planeswalkers, DFCs, sagas)
3. Card-specific tests for every complex card
**Dependency**: Engine must be feature-complete for standard rules by Week 7.
### Phase 4: Testing and Polish (Weeks 9-12)
**All teammates**:
1. Port JUnit tests to Rust integration tests (7 existing TDM tests, plus engine tests)
2. Write new tests for cards without existing tests
3. Fuzz testing: random game play with crash detection
4. Correctness validation: compare game traces between Java XMage and Rust port
5. Performance benchmarks: games/second, clone cost, observation encoding speed
6. Documentation
---
## 8. Handling ~1,000 Cards Efficiently
### 8.1 Shared Building Blocks
Most cards are assembled from a small set of reusable components. The engine must provide these building blocks:
**Triggered abilities** (from Java common package): `EntersBattlefieldTriggeredAbility`, `DiesTriggeredAbility`, `AttacksTriggeredAbility`, `DealsCombatDamageToAPlayerTriggeredAbility`, `BeginningOfEndStepTriggeredAbility`, `SpellCastControllerTriggeredAbility`, `WhenYouAttackDelayedTriggeredAbility`
**Effects** (from Java common package): `DestroyTargetEffect`, `ExileTargetEffect`, `DamageTargetEffect`, `DrawCardSourceControllerEffect`, `GainLifeEffect`, `LoseLifeTargetEffect`, `BoostTargetEffect`, `BoostControlledEffect`, `GainAbilityTargetEffect`, `GainAbilityControlledEffect`, `CreateTokenEffect`, `ReturnToHandTargetEffect`, `PutOnLibraryTargetEffect`, `CounterTargetEffect`, `AddCountersSourceEffect`, `AddCountersTargetEffect`, `ScryEffect`, `ExileUntilSourceLeavesEffect`, `DoIfCostPaid`, `TransformSourceEffect`, `AddCombatAndMainPhaseEffect`, `UntapAllControllerEffect`
**Costs**: `TapSourceCost`, `SacrificeSourceCost`, `SacrificeTargetCost`, `DiscardCost`, `PayLifeCost`, `ExileFromGraveCost`, `WaterbendCost`
### 8.2 Code Generation (optional accelerator)
A Perl-to-Rust transpiler could help. The existing `gen-card.pl` generates Java cards from templates. We could write a tool that reads the Java card source and generates a Rust skeleton. This would handle Tier 1 and many Tier 2 cards automatically.
Pattern to match:
```
Java: this.addAbility(FlyingAbility.getInstance());
Rust: card.keywords |= KeywordFlags::FLYING;
Java: this.addAbility(new EntersBattlefieldTriggeredAbility(new DestroyTargetEffect()));
Rust: card.add_ability(etb_trigger(destroy_target()));
```
### 8.3 Prioritization
Port cards in this order within each set:
1. Basic lands (5 per set)
2. Common creatures with keywords only
3. Common/uncommon spells with standard effects
4. Uncommon creatures with abilities
5. Rares/mythics
6. Planeswalkers and DFCs last
---
## 9. Test Framework
Port the `CardTestPlayerBase` declarative test API:
```rust
// mtg-tests/src/test_framework.rs
pub struct GameTest {
game: Game,
player_a: PlayerId,
player_b: PlayerId,
test_player_a: TestPlayer,
test_player_b: TestPlayer,
}
impl GameTest {
pub fn new() -> Self { ... }
pub fn add_card(&mut self, zone: Zone, player: PlayerId, card_name: &str) { ... }
pub fn add_card_count(&mut self, zone: Zone, player: PlayerId,
card_name: &str, count: usize) { ... }
pub fn cast_spell(&mut self, turn: u32, step: PhaseStep,
player: PlayerId, spell: &str, target: &str) { ... }
pub fn activate_ability(&mut self, turn: u32, step: PhaseStep,
player: PlayerId, ability_text: &str) { ... }
pub fn attack(&mut self, turn: u32, player: PlayerId, attacker: &str) { ... }
pub fn block(&mut self, turn: u32, player: PlayerId,
blocker: &str, attacker: &str) { ... }
pub fn set_choice(&mut self, player: PlayerId, choice: &str) { ... }
pub fn set_stop_at(&mut self, turn: u32, step: PhaseStep) { ... }
pub fn execute(&mut self) { ... }
// Assertions
pub fn assert_life(&self, player: PlayerId, expected: i32) { ... }
pub fn assert_permanent_count(&self, player: PlayerId,
card_name: &str, expected: usize) { ... }
pub fn assert_graveyard_count(&self, player: PlayerId,
card_name: &str, expected: usize) { ... }
pub fn assert_power_toughness(&self, player: PlayerId,
card_name: &str, power: i32, toughness: i32) { ... }
pub fn assert_hand_count(&self, player: PlayerId, expected: usize) { ... }
}
```
Example ported test (from `DragonfireBladeTest.java`):
```rust
#[test]
fn dragonfire_blade_colors_test() {
let mut test = GameTest::new();
test.add_card(Zone::Battlefield, PLAYER_A, "Dragonfire Blade");
test.add_card_count(Zone::Battlefield, PLAYER_A, "Forest", 10);
test.add_card(Zone::Battlefield, PLAYER_A, "Ornithopter");
test.add_card(Zone::Battlefield, PLAYER_A, "Aegis Turtle");
test.add_card(Zone::Battlefield, PLAYER_A, "Grizzled Leotau");
test.add_card(Zone::Battlefield, PLAYER_A, "Mantis Rider");
test.add_card(Zone::Battlefield, PLAYER_A, "Glint-Eye Nephilim");
test.activate_ability(1, PhaseStep::PrecombatMain, PLAYER_A, "Equip {4}");
// ... chain equip activations
test.set_stop_at(1, PhaseStep::EndTurn);
test.execute();
test.assert_power_toughness(PLAYER_A, "Glint-Eye Nephilim", 4, 4);
}
```
---
## 10. Key Architectural Decisions Summary
| Decision | Choice | Rationale |
|---|---|---|
| Object identity | `Uuid` crate, typed wrappers | Direct translation from Java, `Send + Sync` |
| Inheritance | Composition + traits | Rust has no inheritance; traits + embedded structs are idiomatic |
| Card impl | Factory functions + registry | No class hierarchy needed; cards are data + behavior |
| Keywords | Bitflags + trait objects for complex | O(1) keyword checks, huge performance win |
| Persistent state | `im` crate HashMap/Vector | Structural sharing makes `game.clone()` near-O(1) for unchanged parts |
| Effect layers | Direct port of Java 7-layer system | Correctness requires exact replication of 613 rules |
| Action space | Two-phase (type selection + parameterization) | Avoids combinatorial explosion in RL |
| Python bindings | PyO3 + maturin build | Industry standard for Rust-Python interop |
| Parallelism | `rayon` for games + AI search | Zero-cost thread pool, work-stealing |
| Testing | Custom test harness mirroring Java API | Enables 1:1 test porting from JUnit |
---
## 11. Risk Mitigation
1. **Correctness of rules engine**: The 7-layer continuous effects system is the hardest part. Extensive unit tests for each layer, plus comparison testing against the Java engine on identical game states.
2. **Action space for RL**: MTG has a massive action space. Start with simplified action encoding and iterate. The `action_masks()` method is critical for masking invalid actions.
3. **Card volume**: ~1,000 cards is significant. The building-block approach means most cards are 10-30 lines of Rust. A code generation tool could automate ~40% of simple cards.
4. **Combat system complexity**: Banding (if any cards in these sets use it), damage assignment order, and prevention effects interact heavily. Port `Combat.java` line-by-line with thorough tests.
5. **Performance target**: Aim for 10,000+ games/second on a modern CPU for AI training viability. The `im` crate + bitflags + `rayon` should achieve this comfortably.
---
### Critical Files for Implementation
- `/mnt/dest-btrfs/home/jc/src/mage/Mage/src/main/java/mage/game/GameImpl.java` -- The 4,270-line game loop, priority system, and state management; the primary file to port for the core engine
- `/mnt/dest-btrfs/home/jc/src/mage/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java` -- The 1,427-line 7-layer continuous effects system; the most complex subsystem requiring exact correctness
- `/mnt/dest-btrfs/home/jc/src/mage/Mage/src/main/java/mage/players/Player.java` -- The player decision interface (~760 lines); defines the contract between engine and AI/RL agent
- `/mnt/dest-btrfs/home/jc/src/mage/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer6.java` -- The 1,251-line minimax AI; the search algorithm to port for the AI teammate
- `/mnt/dest-btrfs/home/jc/src/mage/Mage/src/main/java/mage/game/combat/Combat.java` -- The 1,956-line combat system; essential for correct game play and one of the most intricate subsystems
agentId: a2393fb (for resuming to continue this agent's work if needed)
<usage>total_tokens: 119439
tool_uses: 78
duration_ms: 565029</usage>This is an incredibly detailed plan. Let me now move to Phase 3 (Review) - I should read a few critical files to validate the plan, then write the final plan to the plan file. Let me also make sure the plan aligns with the user's request for a team-based approach with three teammates.
Let me now write the final plan file and then call ExitPlanMode. The plan from the agent is very thorough. Let me distill it into a clean, actionable plan file.
One thing I want to verify - the user said they wanted to "create an agent team to do this work" with specific teammates. So the plan should be structured around the team creation and task assignment.
Now I'm writing out the plan file with the team structure and task assignments clearly defined.
Excellent - the design is comprehensive. Let me now write the final plan.
The plan is comprehensive and ready for review. Let me call ExitPlanMode.
The plan covers:
im crate for cheap game state cloning, bitflags for keywords, rayon for parallelism, all types Send + SyncThe user doesn't want to proceed with this tool use. The tool use was rejected (eg. if it was a file edit, the new_string was NOT written to the file). To tell you how to proceed, the user said: Will porting the tests for our limited set cover sufficient game mechanics? Should we consider porting more cards or more tests to get general game rule coverage?
The user raises a good point. The tests for TLA, TDM, and ECL cards will only test mechanics those cards use. But the core engine needs to handle all standard MTG rules correctly. Let me explore what tests exist in the Java codebase for core engine rules, and what mechanics the three target sets cover vs. what they miss.
Great question. Let me check what the existing test coverage looks like for both card-specific tests and core engine rules.
Perfect! Now let me create a comprehensive summary with all the information gathered. Let me compile the full analysis: ## Test Coverage Investigation Summary ### 1. Card-Specific Tests for Target Sets **Tarkir Dragonstorm (TDM):** - **7 test files** exist in `/mnt/dest-btrfs/home/jc/src/mage/Mage.Tests/src/test/java/org/mage/test/cards/single/tdm/` 1. DragonfireBladeTest.java (Equipment, color-dependent costs) 2. HostOfTheHereafterTest.java (Counters, creature death triggers, counter movement) 3. HundredBattleVeteranTest.java (Counters, cast-from-graveyard, conditional boosts) 4. MistriseVillageTest.java (Cannot be countered effects, modal spells, planeswalker interactions) 5. NewWayForwardTest.java (Damage prevention, replacement effects, double strike) 6. StalwartSuccessorTest.java (Menace, counter triggers, token generation) 7. TaigamMasterOpportunistTest.java (Modal deck choice, MDFC mechanics, spell copying, delay interactions) **Lorwyn Eclipsed (ECL):** - **0 test files** exist - 413 cards in the set, but NO dedicated tests **Threshold Legacy Ascendancy (TLA):** - **DOES NOT EXIST** in the codebase - No card implementations, no tests ### 2. Core Engine Test Coverage | Category | Test Files | Key Topics | |----------|-----------|-----------| | **Combat** | 12 | Attacking, blocking, damage calculation, first strike, flyers, lifelink, removal from combat, power/toughness calculations post-damage | | **Game** | 5 | Draw conditions, game ends, planeswalker-specific (FreeformUnlimitedCommander format tests) | | **State-Based Actions (SBA)** | 1 | Planeswalker legend rule (duplicate planeswalker destruction) | | **Turn Modification** | 2 | Extra turns, skip turn effects | | **Last Known Information (LKI)** | 1 | Card state tracking after zone changes | **Missing Core Test Categories:** - Stack (priority, spell fizzle, countering) - Mana (payment, mana pool, conditional mana) - Zones (zone transitions, zone management) - Continuous Effects (layer system, timestamp ordering) - Triggered Abilities (comprehensive trigger testing) - Replacement Effects (prevention, substitution) - Spell Copy mechanics - X spells - Fight effects ### 3. Core Mechanics Present in TDM/ECL Cards **TDM Mechanics (from 7 tests + card names):** 1. **Equipment** - Cost adjustment based on equipped creature's colors (Dragonfire Blade) 2. **Counters** - +1/+1 counters, other counter types (Host, Hundred-Battle, Stalwart) 3. **Triggered Abilities** - Dies triggers, enters battlefield triggers, attack triggers 4. **Modal Spells** - Spell options that can be chosen (Twinmaw Stormbrood) 5. **MDFC (Modal Double-Faced Cards)** - Cards with modal front/back casting (Akoum Warrior/Akoum Teeth) 6. **Planeswalkers** - Planeswalker abilities and interactions (Narset, Parter of Veils) 7. **Spell Casting Restrictions** - Cannot be countered effects (Mistrise Village) 8. **Damage Prevention** - Replacement effects for damage (New Way Forward) 9. **Cast from Graveyard** - Alternative casting rules 10. **Double Strike** - Combat mechanics (Blade Historian mentioned in tests) 11. **Menace** - Combat restriction (Stalwart Successor) **ECL Mechanics (from card names in set file):** - Likely similar mechanics given it's a Lorwyn set remaster - 413 cards cover diverse mechanics but **NO TEST COVERAGE** ### 4. Critical Test Coverage Gaps **Major Gaps for Rust Porting:** 1. **No Stack Tests** - Cannot test counterspell interactions, spell fizzling, priority 2. **No Mana Tests** - Cannot verify mana payment, production, conditional mana 3. **No Zone Transition Tests** - Cannot test complex zone changes like cards leaving/entering from library 4. **No Replacement Effect Tests** - Only one card-specific test (New Way Forward), no systematic coverage 5. **No Copy/Clone Tests** - No tests for spell copying, creature copying, or clone mechanics 6. **No Triggered Ability Theory Tests** - Only card-specific trigger tests, no systematic test suite 7. **No Continuous Effects Tests** - No tests for the 7-layer system that applies all effects **Set-Specific Gaps:** - **ECL has ZERO tests** despite 413 cards - **TDM has only 7 tests** covering ~2% of 432 cards - **TLA doesn't exist** - user may be referencing a different set code ### 5. Mechanics Tested by TDM Cards **Tested (at least partially):** - Equipment and Auras (Dragonfire Blade) - Counters on creatures (Host, Veteran, Successor) - Cannot be countered (Mistrise Village) - Damage prevention/redirection (New Way Forward) - Planeswalker abilities (Narset tests) - MDFC casting mechanics (Taigam tests) - Triggered abilities on death (Host, Successor) - Modal spell selection (Taigam, Mistrise) **Likely NOT tested (common MTG mechanics):** - Sacrifice mechanics - Bounce/return to hand effects - Token generation (mentioned in Successor test but not thoroughly) - Enchantment auras on creatures - Legendary rules - Ward/hexproof variations - Deathtouch - Trample/overtrample - Flashing mechanics - Haste - Card draw effects (mentioned but not primary tests) - Land ramp/tutoring effects - Exile-until-leaves mechanics - Storm/cascade mechanics - Transform cards - Adventure cards - Mutate mechanics - Companion mechanics ### File Locations - Test directory: `/mnt/dest-btrfs/home/jc/src/mage/Mage.Tests/src/test/java/org/mage/test/` - TDM set: `/mnt/dest-btrfs/home/jc/src/mage/Mage.Sets/src/mage/sets/TarkirDragonstorm.java` - ECL set: `/mnt/dest-btrfs/home/jc/src/mage/Mage.Sets/src/mage/sets/LorwynEclipsed.java` - TDM tests: `/mnt/dest-btrfs/home/jc/src/mage/Mage.Tests/src/test/java/org/mage/test/cards/single/tdm/` - Combat tests: `/mnt/dest-btrfs/home/jc/src/mage/Mage.Tests/src/test/java/org/mage/test/combat/` ### Recommendation for Rust Porting The test coverage for TDM and ECL is **INSUFFICIENT** for a complete Rust port of the core engine. While TDM has targeted tests for 7 specific cards, this represents less than 2% of the set, and the core engine tests lack critical coverage for: - Stack manipulation (the most complex subsystem) - Mana system verification - Zone transitions - Replacement effect ordering - Continuous effect layer application You'll need to either: 1. **Develop supplementary tests** for the missing core mechanics 2. **Use existing card tests** as integration tests but understand they're not comprehensive 3. **Focus porting on the mechanics that ARE tested** (equipment, counters, some triggers) agentId: a9ef593 (for resuming to continue this agent's work if needed) <usage>total_tokens: 64344 tool_uses: 52 duration_ms: 255836</usage>
Good findings. The test coverage situation is:
The bigger issue is that core engine tests are sparse:
Combat: 12 tests
Game: 5 tests
SBA: 1 test
Turn modification: 2 tests
LKI: 1 test
Missing entirely: Stack, mana, zones, continuous effects, triggered abilities, replacement effects
So the user's concern is valid - just porting the card-specific tests won't give us confidence in the core engine. I need to port the existing core engine tests across all those systems, pull in additional card tests from other sets that exercise the mechanics we're relying on, and identify where we have gaps that might need new tests. I should also figure out which tests from other sets would be most valuable - like counterspell tests or token generation tests that cover fundamental mechanics.
The analysis reveals a significant coverage gap. Here's the situation:
Existing tests for our target sets:
Core engine tests that exist (not card-specific):
Core subsystems with NO test coverage at all:
So you're right to be concerned — porting only TLA/TDM/ECL card tests would leave massive gaps in engine verification. I see three options:
User has answered your questions: "How should we handle the test coverage gaps for the core engine?"="Port core + cherry-pick (Recommended)", "For the cards teammate, should we consider adding a 4th set with simpler/more foundational cards (like a core set) to ensure basic mechanics are covered?"="I think a core/starter set would be good - is Magic Foundations (FDN) a good choice there?". You can now continue with the user's answers in mind.
Let me check if FDN (Magic Foundations) exists in the codebase and what it looks like - it should be a good core set choice with fundamental mechanics.
Let me check what FDN (Magic Foundations) looks like in the codebase.
Great! Now I have enough information. Let me compile the final answer: ## Summary: Foundations (FDN) Set Analysis **1. File Path, Card Count & Release Date:** - **File Path:** `/mnt/dest-btrfs/home/jc/src/mage/Mage.Sets/src/mage/sets/Foundations.java` - **Card Count:** 730 unique card entries (includes multiple versions/arts of the same card) - **Release Date:** November 15, 2024 - **Set Code:** FDN - **Type:** EXPANSION set **2. Foundational MTG Mechanics Present:** YES - This is definitely a core set with comprehensive foundational mechanics: - **Basic Lands:** Yes - All 5 basic lands (Island, Mountain, Swamp, Plains, Forest) with multiple art versions - **Simple Removal:** Yes - Cancel (common counterspell), Burst Lightning, Bolt Bend, Obliterating Bolt - **Vanilla/Simple Creatures:** Yes - Bear Cub, Knight of Grace, Knight of Malice, Ambush Wolf, and many others - **Equipment:** Implied present (based on card name scanning) - **Auras:** Implied present (based on enchantment mechanics) - **Token Creators:** Present in the set - **Counterspells:** Yes - Cancel and other blue control spells The set emphasizes foundational mechanics with 361 cards designed for Play Boosters and 487 total unique cards including Collector Booster exclusives (cards #282-487). **3. Test Coverage:** - **FDN Tests:** Only 1 test file exists: `RiteOfPassageTest.java` - **Location:** `/mnt/dest-btrfs/home/jc/src/mage/Mage.Tests/src/test/java/org/mage/test/cards/single/fdn/` - **Coverage:** Minimal - only 1 test for the entire set **4. Core Set Assessment:** **YES** - FDN is explicitly a good "core" foundational set: - Named "Foundations" to emphasize core MTG mechanics - Contains all 5 basic lands - Includes removal (Lightning, Cancel), vanilla creatures, and traditional MTG archetypes - Designed with Play Booster limits (361) suggesting limited format playability - Features keyword abilities, equipment, auras, and token mechanics - Planeswalkers and tribal themes included (Ajani, Arahbo, et al.) However, test coverage is severely underdeveloped - only 1 test for 730 card entries. agentId: a7bee2a (for resuming to continue this agent's work if needed) <usage>total_tokens: 28889 tool_uses: 27 duration_ms: 32032</usage>
FDN is a great choice - it's literally called "Foundations" and has all the basic mechanics. 730 card entries but many are reprints with new art. The key thing is it has counterspells (Cancel), basic removal, vanilla creatures, equipment, auras, tokens - all the foundational stuff.
However, 730 cards is a lot. We may want to only port a subset - the most foundational/simple cards. Let me update the plan with this information.
Actually, let me think about this differently. The user wants a core set for coverage, not necessarily to port ALL 730 cards. We could port a curated subset (maybe 100-150 of the most foundational cards) from FDN that covers the mechanics missing from our other 3 sets.
So I'll add FDN as a fourth set with a focused subset of foundational cards, cherry-pick tests from the existing sets to cover the core engine, and write new tests specifically for the gaps like multi-layer effects, stack interactions, and mana mechanics.
FDN (Foundations) is an excellent choice — it's literally named for this purpose and contains all the fundamentals: counterspells (Cancel), basic removal, vanilla creatures, equipment, auras, tokens, and basic lands. It has 730 card entries though, so we should port a curated subset focused on mechanics coverage rather than the whole set.
Let me update the plan to incorporate both the FDN set and the test strategy.
Plan updated. FDN is a great choice — it's literally "Magic: The Gathering Foundations" with ~730 card entries covering all core mechanics. We'll port a curated ~150-card subset focused on filling mechanics gaps (counterspells, removal, vanilla creatures, equipment, auras, tokens).
The updated test strategy is:
The user doesn't want to proceed with this tool use. The tool use was rejected (eg. if it was a file edit, the new_string was NOT written to the file). To tell you how to proceed, the user said: I'm not sure our performance targets are realistic. Let's get a baseline and evaluate that once we have something in place rather than targetting 10k/sec. The PPO agent should be defered to a future phase.
Good feedback. Let me update the plan to:
Good calls. Let me update the plan to remove the speculative performance target and defer the RL agent training.
Also removing the performance target from Phase 3:
Updated. The plan now:
The user doesn't want to proceed with this tool use. The tool use was rejected (eg. if it was a file edit, the new_string was NOT written to the file). STOP what you are doing and wait for the user to tell you how to proceed.