fix(multi): resolve 26 bugs found in comprehensive codebase review
Build and Deploy / build-and-push (push) Successful in 3m40s
Build and Deploy / build-and-push (push) Successful in 3m40s
Core fixes (issues #12, #13, #22): - #12: undo now preserves score delta instead of restoring snapshot score - #13: take_from_foundation defaults to false (non-standard house rule) - #22: check_win validates full suit sequence, not just card count Engine fixes: - #8: replay keyboard input guard against non-replay state - #9: help modal scrims.is_empty() guard added - #10: settings modal scrims.is_empty() guard added - #11: sync_plugin builds payload at poll time (not task-spawn time) - #14: server replay mode case-sensitivity fix ("Classic") - #15: play_by_seed_plugin confirmed flag set to true on launch - #16: replay back-step debounce via Local<bool> + StateChangedEvent; register StateChangedEvent in ReplayOverlayPlugin (fixes 52 tests) - #17: time-attack timer ignores win-summary overlay - #18: HUD dropdown glyphs U+25BE → U+2193 (FiraMono-safe arrow) - #19: theme plugin applies immediate visual update on A→B→A switch - #20: SyncAuthError / SyncBusyOverlay split into separate entities so auth errors are visible after busy overlay is hidden - #21: handle_forfeit ordered before update_stats_on_new_game - #23: server merge uses correct avg_time_seconds and games_lost math - #24: win_summary migrated to ModalScrim pattern - #25: card_animation apply_deferred between animation systems - #26: cursor_plugin HashMap access uses .get() with fallback - #27: auto_complete mid-sequence deactivation guard - #28: feedback_anim SettleAnim ordered before FoundationFlourish - #29: achievement_plugin iterates all win events; adds scrims guard - #30: leaderboard modal scrims.is_empty() guard added - #31: server auth tmp file cleanup on rename failure - #32: sync_setup modal scrims.is_empty() guard added - #33: font_plugin uses match fallback; TokioRuntimeResource graceful current-thread fallback on runtime init failure Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -193,7 +193,7 @@ impl GameState {
|
||||
is_auto_completable: false,
|
||||
undo_count: 0,
|
||||
recycle_count: 0,
|
||||
take_from_foundation: true,
|
||||
take_from_foundation: false,
|
||||
schema_version: GAME_STATE_SCHEMA_VERSION,
|
||||
undo_stack: VecDeque::new(),
|
||||
}
|
||||
@@ -407,7 +407,7 @@ impl GameState {
|
||||
self.score = if self.mode == GameMode::Zen {
|
||||
0
|
||||
} else {
|
||||
(snapshot.score + scoring_undo()).max(0)
|
||||
(self.score + scoring_undo()).max(0)
|
||||
};
|
||||
self.move_count = snapshot.move_count;
|
||||
self.is_won = false;
|
||||
@@ -416,12 +416,25 @@ impl GameState {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns `true` when all four foundation slots each contain 13 cards.
|
||||
/// Returns `true` when all four foundation slots each contain a valid A→K
|
||||
/// sequence of a single suit.
|
||||
///
|
||||
/// Counting 13 cards is not sufficient — a corrupt save could produce 13
|
||||
/// arbitrary cards per pile and permanently lock the game via `GameAlreadyWon`.
|
||||
pub fn check_win(&self) -> bool {
|
||||
(0..4_u8).all(|slot| {
|
||||
self.piles
|
||||
.get(&PileType::Foundation(slot))
|
||||
.is_some_and(|p| p.cards.len() == 13)
|
||||
(0..4_u8).all(|slot| self.is_valid_foundation_pile(slot))
|
||||
}
|
||||
|
||||
fn is_valid_foundation_pile(&self, slot: u8) -> bool {
|
||||
let Some(pile) = self.piles.get(&PileType::Foundation(slot)) else {
|
||||
return false;
|
||||
};
|
||||
if pile.cards.len() != 13 {
|
||||
return false;
|
||||
}
|
||||
let suit = pile.cards[0].suit;
|
||||
pile.cards.iter().enumerate().all(|(i, card)| {
|
||||
card.suit == suit && card.rank.value() == (i as u8 + 1)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1395,12 +1408,9 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn take_from_foundation_allowed_by_default() {
|
||||
let mut g = setup_take_from_foundation_game();
|
||||
assert!(g.take_from_foundation, "standard Klondike allows take-from-foundation by default");
|
||||
g.move_cards(PileType::Foundation(0), PileType::Tableau(0), 1).unwrap();
|
||||
assert_eq!(g.piles[&PileType::Foundation(0)].cards.len(), 1);
|
||||
assert_eq!(g.piles[&PileType::Tableau(0)].cards.len(), 2);
|
||||
fn take_from_foundation_disabled_by_default() {
|
||||
let g = setup_take_from_foundation_game();
|
||||
assert!(!g.take_from_foundation, "take_from_foundation is off by default (non-standard rule)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user