feat(engine,core,data): add Challenge mode with seed list and level-5 gate

Phase 6 part 4b (partial):
- GameMode::Challenge variant in solitaire_core. undo() returns
  RuleViolation when mode is Challenge so the player commits to each
  decision.
- solitaire_data::challenge defines a stable CHALLENGE_SEEDS list with
  challenge_seed_for(index) wrapping modulo length.
- PlayerProgress.challenge_index (serde-default for older saves) tracks
  how far the player has progressed.
- ChallengePlugin advances the cursor on Challenge-mode wins, persists,
  and emits ChallengeAdvancedEvent. Pressing X starts a Challenge-mode
  game with the current seed; gated to level >= CHALLENGE_UNLOCK_LEVEL (5).
- InputPlugin's Z key (Zen mode) is now also gated to level >= 5.

Time Attack and unlock UI still deferred.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
funman300
2026-04-25 17:18:32 -07:00
parent 09d62f4255
commit 788ac9f65a
8 changed files with 346 additions and 9 deletions
+6
View File
@@ -67,6 +67,11 @@ pub struct PlayerProgress {
pub weekly_goal_week_iso: Option<String>,
pub unlocked_card_backs: Vec<usize>,
pub unlocked_backgrounds: Vec<usize>,
/// Index of the next Challenge-mode seed the player will be served.
/// Increments on each Challenge-mode win. Out-of-range values wrap modulo
/// `CHALLENGE_SEEDS.len()` at lookup time.
#[serde(default)]
pub challenge_index: u32,
pub last_modified: DateTime<Utc>,
}
@@ -81,6 +86,7 @@ impl Default for PlayerProgress {
weekly_goal_week_iso: None,
unlocked_card_backs: vec![0], // back #0 always available
unlocked_backgrounds: vec![0], // background #0 always available
challenge_index: 0,
last_modified: DateTime::UNIX_EPOCH,
}
}