Hidden Information Management in MTG Commander
This document outlines the approach to managing hidden information in the MTG Commander game engine's multiplayer implementation.
Overview
Magic: The Gathering relies heavily on hidden information as a core gameplay mechanic. Cards in players' hands, the order of libraries, and face-down cards are all examples of information that must be hidden from some or all players. Properly securing this information is critical to maintaining game integrity.
Types of Hidden Information
- Player Hands: Cards in a player's hand should be visible only to that player
- Libraries: The order and content of libraries should be hidden from all players
- Face-down Cards: Cards played face-down should have their identity hidden
- Revealed Cards: Cards revealed to specific players should only be visible to those players
- Search Results: When a player searches their library, only they should see the results
Implementation Approach
Server-Side Information Management
The server maintains the authoritative game state and controls what information is sent to each client:
#![allow(unused)] fn main() { // System to update visible information for each client fn update_client_visible_information( mut server: ResMut<RepliconServer>, game_state: Res<GameState>, players: Query<(Entity, &Player, &Hand, &Library)>, connected_clients: Res<ConnectedClients>, ) { // For each connected client for client_id in connected_clients.clients.keys() { // Find the player entity for this client let player_entity = players .iter() .find(|(_, player, _, _)| player.client_id == *client_id) .map(|(entity, _, _, _)| entity); // Prepare visible game state for this client let mut visible_state = VisibleGameState { // Common visible information battlefield: game_state.battlefield.clone(), graveyards: game_state.graveyards.clone(), exiled_cards: game_state.exiled_cards.clone(), // Player-specific information player_hands: HashMap::new(), library_counts: HashMap::new(), library_tops: HashMap::new(), }; // Add information about each player's hand and library for (entity, player, hand, library) in players.iter() { // If this is the current player, show their full hand if Some(entity) == player_entity { visible_state.player_hands.insert(player.id, hand.cards.clone()); } else { // For other players, only show card count visible_state.player_hands.insert(player.id, vec![CardBack; hand.cards.len()]); } // For all players, only show library count, not contents visible_state.library_counts.insert(player.id, library.cards.len()); // Show top card if it's been revealed if library.top_revealed { visible_state.library_tops.insert(player.id, library.cards.first().cloned()); } } // Send the visible state to this client server.send_message(*client_id, ClientGameState { state: visible_state }); } } }
Client-Side Information Handling
The client displays only the information it receives from the server:
#![allow(unused)] fn main() { // Client system to update UI based on visible information fn update_game_ui( visible_state: Res<VisibleGameState>, mut ui_state: ResMut<UiState>, local_player_id: Res<LocalPlayerId>, ) { // Update battlefield display ui_state.battlefield_cards = visible_state.battlefield.clone(); // Update hand display (only shows full information for local player) if let Some(hand) = visible_state.player_hands.get(&local_player_id.0) { ui_state.hand_cards = hand.clone(); } // Update opponent hand displays (only shows card backs) for (player_id, hand) in visible_state.player_hands.iter() { if *player_id != local_player_id.0 { ui_state.opponent_hand_counts.insert(*player_id, hand.len()); } } // Update library displays (only shows counts) for (player_id, count) in visible_state.library_counts.iter() { ui_state.library_counts.insert(*player_id, *count); } // Update revealed top cards if any for (player_id, card) in visible_state.library_tops.iter() { if let Some(card) = card { ui_state.revealed_tops.insert(*player_id, card.clone()); } } } }
Security Measures
Encryption
All network traffic containing hidden information is encrypted:
#![allow(unused)] fn main() { // Example of encrypting sensitive game data fn encrypt_game_message( message: &GameMessage, encryption_key: &EncryptionKey, ) -> EncryptedMessage { // Serialize the message let serialized = bincode::serialize(message).expect("Failed to serialize message"); // Encrypt the serialized data let nonce = generate_nonce(); let cipher = ChaCha20Poly1305::new(encryption_key.as_ref().into()); let ciphertext = cipher .encrypt(&nonce, serialized.as_ref()) .expect("Encryption failed"); EncryptedMessage { ciphertext, nonce, } } }
Server Authority
The server is the single source of truth for all game state:
- Clients never directly modify hidden information
- All actions that would reveal information are processed by the server
- The server controls exactly what information each client can see
Validation
The server validates all client actions to ensure they don't attempt to access hidden information:
#![allow(unused)] fn main() { // Validate a player's attempt to look at cards fn validate_look_at_cards_action( mut commands: Commands, mut server: ResMut<RepliconServer>, mut look_events: EventReader<LookAtCardsEvent>, game_state: Res<GameState>, players: Query<(Entity, &Player)>, ) { for event in look_events.read() { let client_id = event.client_id; let target_zone = event.zone; let target_player_id = event.player_id; // Check if this action is allowed by a game effect let is_allowed = game_state .active_effects .iter() .any(|effect| effect.allows_looking_at(target_zone, target_player_id, client_id)); if !is_allowed { // Log potential information leak attempt warn!("Potential information leak attempt: Client {} tried to look at {:?} of player {} without permission", client_id, target_zone, target_player_id); // Reject the action server.send_message(client_id, ActionRejected { reason: "Not allowed to look at those cards".to_string() }); continue; } // Process the legitimate look action // ... } } }
Testing Hidden Information Security
Testing the hidden information system involves:
- Penetration Testing: Attempting to access hidden information through various attack vectors
- Protocol Analysis: Examining network traffic to ensure hidden information isn't leaked
- Edge Case Testing: Testing unusual game states that might reveal hidden information
For detailed testing procedures, see the Security Testing Strategy.
Future Enhancements
Planned improvements to hidden information management include:
- Enhanced encryption for particularly sensitive game actions
- Improved obfuscation of client-side game state
- Additional validation for complex card interactions involving hidden information
- Support for spectator mode with appropriate information hiding
This documentation will be updated as hidden information management evolves.