Save/Load System Overview
This document provides a high-level overview of the save/load system in Rummage, explaining its core concepts and usage.
Basic Usage
Saving a Game
To save the current game state, send a SaveGameEvent
:
#![allow(unused)] fn main() { // Save to a specific slot world.send_event(SaveGameEvent { slot_name: "my_save".to_string(), }); }
The save system will:
- Collect all necessary game state information
- Serialize game zones, cards, and commanders
- Use
bevy_persistent
to persist the game state - Write it to the designated save file
- Update metadata with information about the save
Loading a Game
To load a previously saved game, send a LoadGameEvent
:
#![allow(unused)] fn main() { // Load from a specific slot world.send_event(LoadGameEvent { slot_name: "my_save".to_string(), }); }
The load system will:
- Use
bevy_persistent
to load the saved game state - Deserialize the game state data
- Recreate all necessary entities and resources
- Restore the game state, zone contents, and commander data
- Restore all entity relationships and card positions
Automatic Saving
The system includes an automatic save feature that triggers during state-based action checks:
#![allow(unused)] fn main() { // Configure auto-save behavior commands.insert_resource(SaveConfig { save_directory: PathBuf::from("saves"), auto_save_enabled: true, auto_save_frequency: 10, // Save every 10 state-based action checks }); }
Saved Data
The save system captures and restores the following data:
- Game State: Turn number, active player, priority holder, turn order, etc.
- Player Data: Life totals, mana pools, and other player-specific information
- Zone Data: Contents of all game zones (libraries, hands, battlefield, graveyard, etc.)
- Card Positions: Where each card is located in the game state
- Commander Information: Commander assignments, cast counts, and zone locations
Replay Functionality
The replay system allows stepping through a saved game:
#![allow(unused)] fn main() { // Start a replay from a save file world.send_event(StartReplayEvent { slot_name: "my_save".to_string(), }); // Step forward in the replay (multiple steps possible) world.send_event(StepReplayEvent { steps: 1 }); // Stop the current replay world.send_event(StopReplayEvent); }
During replay, the system:
- Loads the initial game state
- Applies recorded actions in sequence
- Updates the visual state of the game
- Allows stepping forward at the user's pace
Save Metadata
The system maintains metadata about all saves in the SaveMetadata
resource using bevy_persistent
:
#![allow(unused)] fn main() { // Access save metadata fn display_saves_system(save_metadata: Res<Persistent<SaveMetadata>>) { for save in &save_metadata.saves { println!("Save: {}, Turn: {}, Time: {}", save.slot_name, save.turn_number, save.timestamp); } } }
Configuration
The save system can be configured via the SaveConfig
resource:
#![allow(unused)] fn main() { let config = SaveConfig { // Directory where save files are stored save_directory: PathBuf::from("custom_saves"), // Whether auto-save is enabled auto_save_enabled: true, // How often auto-saves occur (in state-based action checks) auto_save_frequency: 20, }; }
Entity Serialization
The save/load system handles entity references by converting them to indices during serialization and rebuilding entities during deserialization. This preserves all relationships between entities despite the fact that entity IDs will change between sessions.
Visual Differential Testing
The save/load system includes built-in support for visual differential testing through integration with the snapshot system. This allows for:
- Visual Regression Testing: Compare game renders at different points in time to detect unintended visual changes
- State Verification: Visually confirm that game states are correctly preserved and restored
- Replay Validation: Ensure that replays accurately reproduce the original game visually
Key Features
- Automatic Captures: Images are automatically captured during save operations and replay steps
- Manual Triggers: Press F10 during replay to capture the current visual state
- Programmatic API: Functions to capture and compare game states from code
- Metadata Tracking: Each snapshot is tagged with save slot, turn number, and timestamps
Example Usage
Visual differential testing can be used in both manual testing and automated tests:
#![allow(unused)] fn main() { // In automated tests let result = run_visual_diff_test("test_save_game"); assert!(result.is_ok(), "Visual differences detected"); // In manual testing, press F10 during replay to take snapshots // at points of interest, then compare the resulting images }
Next Steps
- See Implementation for technical details
- Check the API Reference for a complete list of types and functions
- Look at Testing for how to test save/load functionality