Commander Format: Core Rules Integration
This document explains how the Commander format extends and modifies the core Magic: The Gathering rules in Rummage.
Architecture Overview
Commander builds upon the format-agnostic core MTG rules through a layered architecture:
┌───────────────────────────────────────────┐
│ Commander Format │
│ ┌─────────────┐ ┌────────────────────┐ │
│ │ Commander │ │ Commander-Specific │ │
│ │ Components │ │ Systems │ │
│ └─────────────┘ └────────────────────┘ │
├───────────────────────────────────────────┤
│ Extension Points │
│ ┌─────────────┐ ┌────────────────────┐ │
│ │ Plugin │ │ Override │ │
│ │ Registration│ │ Mechanisms │ │
│ └─────────────┘ └────────────────────┘ │
├───────────────────────────────────────────┤
│ Core MTG Rules │
│ ┌─────────────┐ ┌────────────────────┐ │
│ │ Base │ │ Base │ │
│ │ Components │ │ Systems │ │
│ └─────────────┘ └────────────────────┘ │
└───────────────────────────────────────────┘
This approach provides:
- Clean Separation: Format-specific code doesn't contaminate core rules
- Reusability: Core rules can support multiple formats
- Testability: Format mechanics can be tested in isolation
- Extensibility: New formats can be added without modifying core code
Core Extension Points
Commander leverages these primary extension mechanisms:
1. Zone Management Extensions
The core rules provide standard zones (library, hand, battlefield, etc.) which Commander extends with:
#![allow(unused)] fn main() { // Core system handles standard zones fn core_zone_setup(mut commands: Commands) { // Create standard zones for each player // ... } // Commander plugin adds Command Zone support fn commander_zone_setup(mut commands: Commands, players: Query<Entity, With<Player>>) { for player in &players { // Create Command Zone for this player commands.spawn(( ZoneType::Command, ZoneContents::default(), BelongsToPlayer(player), CommandZone, Name::new("Command Zone"), )); } } }
2. Game Setup Extensions
Commander modifies initial game parameters:
#![allow(unused)] fn main() { // Commander plugin modifies core game setup fn commander_game_setup( mut game_config: ResMut<GameConfig>, mut commands: Commands, players: Query<Entity, With<Player>>, ) { // Set Commander-specific configuration game_config.starting_life = 40; game_config.mulligan_rule = MulliganRule::CommanderFreeFirst; // Add Commander-specific components to players for player in &players { commands.entity(player).insert(CommanderDamageTracker::default()); } } }
3. Rules System Extensions
Commander adds new rule checks and state-based actions:
#![allow(unused)] fn main() { // Core state-based action system fn core_state_based_actions(/* ... */) { // Check creature death, player life, etc. // ... } // Commander adds additional state-based actions fn commander_state_based_actions( players: Query<(Entity, &CommanderDamageTracker)>, mut game_events: EventWriter<GameEvent>, ) { // Check commander damage thresholds for (player, damage_tracker) in &players { for (&commander, &damage) in damage_tracker.damage_taken.iter() { if damage >= 21 { game_events.send(GameEvent::PlayerLost { player, reason: LossReason::CommanderDamage(commander), }); } } } } }
Commander-Specific Overrides
In certain cases, Commander needs to override default core behavior:
Zone Transfer Overrides
When a commander would change zones, Commander rules allow for special handling:
#![allow(unused)] fn main() { // Commander zone transfer override fn commander_zone_transfer_system( mut zone_events: EventReader<ZoneChangeEvent>, mut player_choices: EventWriter<PlayerChoiceEvent>, commanders: Query<Entity, With<Commander>>, ) { for event in zone_events.iter() { // If this is a commander... if commanders.contains(event.entity) { // And it's moving to graveyard/exile/hand/library... if matches!(event.to, ZoneType::Graveyard | ZoneType::Exile | ZoneType::Hand | ZoneType::Library ) { // Give the controller a choice player_choices.send(PlayerChoiceEvent { player: get_controller(event.entity), choice_type: ChoiceType::CommanderZoneChange { commander: event.entity, default_zone: event.to, command_zone: ZoneType::Command, }, timeout: Duration::from_secs(30), }); } } } } }
Commander Tax Implementation
The Commander format adds tax to casting commanders from the Command Zone:
#![allow(unused)] fn main() { // Commander casting cost modification fn apply_commander_tax( mut cast_events: EventReader<PrepareTocastEvent>, mut cast_costs: Query<&mut ManaCost>, commanders: Query<&Commander>, zones: Query<(&ZoneContents, &ZoneType)>, ) { for event in cast_events.iter() { // Check if this is a commander being cast if let Ok(commander) = commanders.get(event.card) { // Check if it's being cast from Command Zone if is_in_command_zone(event.card, &zones) { // Apply commander tax if let Ok(mut cost) = cast_costs.get_mut(event.card) { let tax_amount = commander.cast_count * 2; cost.add_generic(tax_amount); } } } } } }
Plugin Implementation
The Commander format is implemented as a Bevy plugin:
#![allow(unused)] fn main() { pub struct CommanderPlugin; impl Plugin for CommanderPlugin { fn build(&self, app: &mut App) { // Register Commander-specific components app.register_type::<Commander>() .register_type::<CommanderDamageTracker>() .register_type::<CommanderTax>(); // Add Commander-specific resources app.init_resource::<CommanderConfig>(); // Add Commander setup systems app.add_systems(Startup, ( commander_game_setup, commander_zone_setup, )); // Add Commander-specific gameplay systems app.add_systems(PreUpdate, ( commander_zone_transfer_system .after(core_zone_change_system), )); // Add Commander rule enforcement systems app.add_systems(Update, ( apply_commander_tax, track_commander_damage, commander_state_based_actions .after(core_state_based_actions), )); // Add Commander-specific events app.add_event::<CommanderCastEvent>() .add_event::<CommanderDamageEvent>(); } } }
Example: Commander Zone Integration
The Command Zone is central to the Commander format. Here's its complete implementation:
#![allow(unused)] fn main() { // Commander-specific components #[derive(Component, Reflect)] pub struct Commander { pub owner: Entity, pub cast_count: u32, } #[derive(Component, Reflect)] pub struct CommandZone; // Command Zone setup fn commander_zone_setup( mut commands: Commands, players: Query<Entity, With<Player>>, mut decks: Query<(&mut DeckList, &BelongsToPlayer)>, ) { // Create Command Zone for each player for player in &players { // Create Command Zone entity let command_zone = commands.spawn(( ZoneType::Command, ZoneContents::default(), BelongsToPlayer(player), CommandZone, Name::new("Command Zone"), )).id(); // Find player's deck and locate commander if let Some((mut deck_list, _)) = decks .iter_mut() .find(|(_, belongs_to)| belongs_to.0 == player) { // Extract commander(s) from deck if let Some(commander_card) = deck_list.extract_commander() { // Spawn commander entity let commander_entity = commands.spawn(( Card::from_definition(commander_card), Commander { owner: player, cast_count: 0, }, InZone(ZoneType::Command), )).id(); // Add commander to command zone commands.entity(command_zone) .update_component(|mut contents: Mut<ZoneContents>| { contents.entities.push(commander_entity); }); } } } } }
Testing Integration
Commander integration testing focuses on verifying that format-specific rules correctly interact with core systems:
#![allow(unused)] fn main() { #[test] fn test_commander_zone_transfer() { // Setup test environment with both core and commander plugins let mut app = App::new(); app.add_plugins(CoreRulesPlugin) .add_plugins(CommanderPlugin); // Setup test commander and zones let (player, commander) = setup_test_commander(&mut app); // Test that commander can move to command zone instead of graveyard app.world.send_event(ZoneChangeEvent { entity: commander, from: ZoneType::Battlefield, to: ZoneType::Graveyard, }); // Handle player choice to move to command zone resolve_commander_zone_choice(&mut app, player, commander, ZoneType::Command); // Verify commander is in command zone let zone = get_entity_zone(&app, commander); assert_eq!(zone, ZoneType::Command, "Commander should be in Command Zone"); } }
Conclusion
The Commander format implementation builds upon the core MTG rules through a clean plugin architecture that:
- Extends base functionality with Commander-specific features
- Overrides certain behaviors to match Commander rules
- Adds new components and systems unique to Commander
This approach maintains the integrity of the core rules while enabling the unique gameplay experience of the Commander format.
For detailed information on specific Commander mechanics, see: