Snapshot/Resume Architecture
Snapshot Point Principle
Snapshots are taken BEFORE presenting a choice to the controller.
This design ensures:
- Clean pause point: External agents can review the game state up to this point
- Clear semantics: The snapshot represents “everything that has happened”
- Simple resume logic: When resuming, present the next choice fresh to the controller
Implementation
PREAMBLE Check (Before Choice)
All choice points check stop conditions BEFORE asking the controller:
#![allow(unused)]
fn main() {
// PREAMBLE: Check stop conditions BEFORE asking for choice
if let Some(result) = self.check_stop_conditions(controller, player_id)? {
return Ok(Some(result)); // Snapshot and stop HERE
}
// Ask controller to choose
let choice = controller.choose_spell_ability_to_play(&view, &available);
// Log choice to undo log
self.log_choice_point(player_id, Some(replay_choice));
}
Stop Condition Logic
When --stop-every both:choice:K is specified:
-
Before presenting choice K+1:
count_filtered_choices()returns K (K choices have been made)- PREAMBLE check: K >= K? Yes, snapshot
- Snapshot includes K choices (all made/executed/logged)
- Game pauses BEFORE presenting choice K+1
-
When resuming:
- Replay K choices with suppressed logging (already logged in previous segments)
- Clear replay mode
- Present choice K+1 fresh to the controller
Replay Mode
#![allow(unused)]
fn main() {
// Replay mode suppresses ALL logging because snapshots are taken BEFORE
// presenting choices. All choices in the snapshot were already made/executed/logged.
if self.replaying {
// Suppress stdout logging
}
// Clear replay mode before presenting the NEXT choice
if self.replaying && self.replay_choices_remaining == 0 {
self.replaying = false;
}
}
Example Timeline
Segment 1 (stop after 3 choices):
- Turn 1: Alice plays Forest → logged, executed
- Turn 2: Bob plays Forest → logged, executed
- Turn 3: Alice plays Forest → logged, executed
- PREAMBLE check: count=3, 3 >= 3? Yes
- Snapshot: Turn 3 start + 3 intra-turn choices
- Stop (BEFORE presenting “Alice casts Grizzly Bears”)
Segment 2 (resume from snapshot):
- Load: Turn 3 start, replay_mode(3)
- Replay Alice plays Forest (Turn 1) - suppressed logging
- Replay Bob plays Forest (Turn 2) - suppressed logging
- Replay Alice plays Forest (Turn 3) - suppressed logging
- Clear replay mode
- Present NEW choice: “Alice casts Grizzly Bears” ← Fresh controller decision
Benefits
- External agent workflow: Can pause, review game log/state, then decide next move
- No ambiguity: Snapshot never contains “partially made” choices
- Clean determinism: All choices in snapshot were completed in previous segments
- Simple testing: Can verify game state at natural pause points
Comparison to Old Design
Old (POSTAMBLE):
- Snapshot taken AFTER making choice K but BEFORE executing it
- Choice K in snapshot but never executed/logged to stdout
- Ambiguous: “Was this choice made or not?”
- Complex replay logic: Last choice needs special handling
New (PREAMBLE):
- Snapshot taken BEFORE presenting choice K+1
- All K choices in snapshot were fully made/executed/logged
- Clear: “These choices are done, next one is pending”
- Simple replay logic: Suppress all, then present new choice
Related Files
src/game/game_loop.rs: Main implementationcheck_stop_conditions(): PREAMBLE check before asking controllerwith_replay_mode(): Sets up replay mode when resuminglog_choice_point(): Decrements replay counter
src/game/snapshot.rs: GameSnapshot serializationsrc/undo.rs:rewind_to_turn_start()collects intra-turn choices