- Collapse nested-if patterns into let-chains across 13 plugins (42 instances)
- Add #[allow(clippy::too_many_arguments)] to 5 Bevy systems in card_plugin
and input_plugin where ECS parameter count exceeds the lint threshold
- Gate Theme import in table_plugin under #[cfg(test)] — only used by
test-only colour helpers; removing the unconditional import silences the
unused-import lint without breaking the test suite
- Wrap ButtonInput<MouseButton> in Option<> in update_input_platform so that
tests using MinimalPlugins (no InputPlugin) no longer panic on startup
All 789 tests pass; cargo clippy --workspace -- -D warnings is clean.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
P0 fixes:
- Register WinSummaryPlugin, SelectionPlugin, CardAnimationPlugin in main.rs
(all three were exported but never wired — features silently did nothing)
- game_state::draw(): increment move_count on waste→stock recycle, not just
on normal draws; add move_count_increments_on_recycle regression test
P1 fixes:
- solitaire_server/Cargo.toml: remove duplicate dev-dependencies
(solitaire_sync, uuid, chrono, jsonwebtoken were in both sections)
P2 — input_plugin refactor:
- Split 198-line handle_keyboard() into three focused systems under 110 lines each:
handle_keyboard_core (U/N/Z/D/Space), handle_keyboard_hint (H), handle_keyboard_forfeit (G)
- Introduce KeyboardConfirmState resource to share countdown timers across systems
- Add three new unit tests: all_hints_suggests_draw_*, all_hints_is_empty_when_truly_stuck,
new_game_confirm_window_is_positive
P2 — achievement predicate tests (solitaire_core):
- Add 10 direct unit tests for speed_demon, lightning, no_undo, high_scorer,
on_a_roll, comeback predicates (previously only covered via check_achievements())
- 141 core tests now passing
P2 — server tests:
- solitaire_server/src/sync.rs: 4 unit tests for merge logic (no DB required)
- solitaire_server/src/leaderboard.rs: 2 unit tests for entry shape and sort order
P3 — documentation:
- Add struct-level /// to 12 Plugin structs (ChallengePlugin, CursorPlugin,
AnimationPlugin, HelpPlugin, PausePlugin, AudioPlugin, DailyChallengePlugin,
HudPlugin, LeaderboardPlugin, OnboardingPlugin, TimeAttackPlugin, WeeklyGoalsPlugin)
- Add field-level /// to Card, Pile, Deck, GameState, AchievementContext, AchievementDef
- Add /// to WeeklyGoalKind, WeeklyGoalDef, WeeklyGoalContext, StatsExt::update_on_win
card_animation module (new files from previous session):
- chain.rs, diagnostics.rs, tuning.rs, updated interaction.rs/animation.rs/mod.rs/lib.rs
- Remove unused HOVER_SCALE_DEFAULT / DRAG_LIFT_SCALE_DEFAULT / HOVER_LERP_SPEED_DEFAULT constants
- Add handle_touch_stock_tap so touch users can draw from the stock pile
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Event/EventReader/EventWriter renamed to Message/MessageReader/MessageWriter
- add_event → add_message for all 67 call sites
- ScrollPosition changed to tuple struct ScrollPosition(Vec2)
- CursorIcon import moved from bevy::winit::cursor to bevy::window
- WindowResolution::from((f32,f32)) removed — use (u32,u32) tuple
- World::send_event → World::write_message in test code
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Resolves 15 violations found by `cargo clippy --workspace --tests -D warnings`:
- Remove unused imports (Card, Rank) in cursor_plugin tests
- Replace absurd i32::MAX comparison with a meaningful >= 0 check
- Use range .contains() instead of manual >= && <= (manual_range_contains)
- Move impl FromRequestParts before test module in middleware.rs (items_after_test_module)
- Move _VEC3_REFERENCED const before test module in input_plugin.rs
- Convert runtime assert on constant to const { assert!(...) }
- Use .contains() instead of .iter().any() for slice membership
- Replace .get(...).is_none() with !.contains_key(...) in HashMap checks
- Collapse Default::default() + field assignment into struct literal initializers
across solitaire_sync, solitaire_data, and solitaire_engine test helpers
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Round 7 — Input & feedback
- H key cycles hints; F1 opens help (conflict resolved)
- N key cancels active Time Attack session
- Hint text distinguishes "draw from stock" vs "recycle waste"
- Forfeit (G) clears AutoCompleteState so chime does not bleed into new deal
- Zen mode timer clears immediately on Z press
- HUD shows recycle count in both draw modes
- Settings scroll position persisted across open/close
Round 8 — Polish & clarity
- Undo unavailable fires "Nothing to undo" toast
- Streak-break toast on forfeit/abandon when streak > 1
- F11 fullscreen toggle with toast; documented in help and home screens
- H-after-win, new-game countdown expiry, Tab-no-cards toasts
- Win cascade duration/stagger scales with animation speed setting
- Draw-Three cycle counter HUD ("Cycle: N/3")
- Forfeit requires G×2 confirmation within 3 s (mirrors N key)
Round 9 — Game feel & information
- Escape dismisses game-over/stuck overlay (PausePlugin skips Escape when overlay visible)
- Shake animation on rejected drag before snap-back
- Forfeit countdown cancels when any other key is pressed (U/H/D/Z/Space)
- Tab wrap-around fires "Back to first card" toast
- HUD selection indicator shows active Tab-selected pile in yellow
- Challenge time-limit HUD turns orange < 60s, red < 30s
- Win summary shows XP breakdown (+50 base, +25 no-undo, +N speed)
- Game-over overlay: "No more moves available" with clear N/Escape/G instructions
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Task #56 — HudAutoComplete badge: shows "AUTO" in green when AutoCompleteState.active
is true; announce_auto_complete system fires InfoToastEvent on leading edge.
Task #57 — ConfirmNewGameScreen: intercepts NewGameRequestEvent when move_count > 0 and
is_won=false, shows modal with Y/N keyboard handling.
Task #58 — GameOverScreen: spawns when check_no_moves detects no legal moves; handle_game_over_input
responds to N (new game) and U (undo + despawn). Fix animation system ordering
(chain enqueue_toasts → drive_toast_display) to eliminate flaky test.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add FeedbackAnimPlugin with three card feedback animations:
- #54 ShakeAnim: horizontal shake on MoveRejectedEvent targeting
destination pile cards; 0.3 s damped sine wave
- #55 SettleAnim: Y-scale bounce on valid placement (StateChangedEvent);
1.0 → 0.92 → 1.0 over 0.15 s for all top-of-pile cards
- #69 Deal animation: slides each card from stock position to its deal
position on NewGameRequestEvent (move_count == 0), using existing
CardAnim with 0.04 s per-card stagger
Pure-function helpers shake_offset, settle_scale, and deal_stagger_delay
are public and covered by 6 unit tests. Fix pre-existing compile/clippy
errors: stubbed handle_confirm_input/handle_game_over_input, removed dead
CycleCardBack/CycleBackground variants, annotated ambient_handle field,
and fixed draw_mode.clone() in pause_plugin.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously every SettingsChangedEvent fired an "SFX: X%" toast, even
when the music volume was adjusted in the Settings panel. Now the toast
handler tracks the previous SFX and music levels independently and only
shows a toast for whichever value actually changed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ProgressPlugin now fires XpAwardedEvent on every win. AnimationPlugin
shows a "+N XP" toast so players see XP feedback immediately after
winning. Server leaderboard opt-in endpoint also now validates that
display_name is at most 32 characters.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces silent info!() log calls with on-screen toasts when the player
presses Z/X/T without reaching the required unlock level. Any system
can now fire InfoToastEvent(message) to surface a brief text overlay
without depending on a specific plugin.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Pressing N during an active game (move_count > 0, not won) now shows a
"Press N again to start a new game" toast and only starts a new game if
N is pressed a second time within 3 seconds. Starting a fresh game or
pressing N after a win still acts immediately.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
"You Win!" now displays "You Win! Score: N Time: M:SS" so the player
sees their result without opening the stats screen.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When is_auto_completable flips true (stock/waste empty, all cards
face-up), AutoCompletePlugin fires MoveRequestEvent every 120 ms,
driving cards to the foundation one at a time without player input.
An "Auto-completing…" toast announces the sequence.
- solitaire_core: add next_auto_complete_move() to GameState with
3 new unit tests
- solitaire_engine: new AutoCompletePlugin with detect + drive systems
and 4 unit tests; animation_plugin shows one-shot toast on activation
- solitaire_app: register AutoCompletePlugin
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- solitaire_server: Axum auth, sync push/pull, leaderboard, daily
challenge, account deletion, JWT middleware, rate limiting via
tower_governor, SQLite migrations, health endpoint
- solitaire_server: expose build_test_router (no rate limiting) so
integration tests work without a peer IP in oneshot requests
- solitaire_sync: SyncPayload, merge logic, shared API types
- solitaire_data: SyncProvider trait, LocalOnlyProvider,
SolitaireServerClient, auth_tokens keyring integration, blanket
Box<dyn SyncProvider> impl
- solitaire_data/settings: derive Default on SyncBackend (clippy fix)
- .sqlx/: offline query cache so server compiles without a live DB
- sqlx: removed non-existent "offline" feature flag
- keyring v2: fixed Entry::new() returning Result<Entry>
- sqlx 0.8: all SQLite TEXT columns wrapped in Option<T>
- Integration tests: max_connections(1) on in-memory pool so all
connections share the same schema
All 191 tests pass; cargo clippy -D warnings clean.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- solitaire_data::Settings { sfx_volume, first_run_complete } with
atomic JSON persistence and clamping sanitizer.
- SettingsPlugin (engine): [ / ] adjust SFX volume by 0.1, clamped;
persists on change; emits SettingsChangedEvent. No-op at rails.
- AudioPlugin applies sfx_volume to kira's main track at startup
and on every change so live tweaks take effect without restart.
- Brief "SFX: N%" toast on each change. Help cheat sheet updated.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- HelpPlugin: full-window cheat sheet listing every keybinding,
toggled with H or ?. Three unit tests cover open/close/slash.
- AnimationPlugin: ChallengeAdvancedEvent now surfaces as a
3-second "Challenge N cleared!" toast.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Core: GameMode::TimeAttack variant (no scoring/undo changes — session marker only)
- Engine: TimeAttackPlugin with TimeAttackResource, TimeAttackEndedEvent,
T hotkey (gated to level >= 5), auto-deal on win, summary toast
- Engine: Stats overlay (S) gains an Unlocks subsection (card backs /
backgrounds, sorted/deduped) and a live Time Attack panel while active
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Phase 6 part 3 (partial):
- AnimationPlugin now shows a 3-second toast on DailyChallengeCompletedEvent
and WeeklyGoalCompletedEvent.
- Stats overlay (S key) appends a Progression section with level, total XP,
daily streak, and a live Weekly Goals list pulling from WEEKLY_GOALS.
Special modes (Time Attack / Challenge / Zen) and unlock UI deferred.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Phase 6 part 2 (partial):
- daily_seed_for(date) and PlayerProgress::record_daily_completion in
solitaire_data, with streak logic that increments on consecutive days,
resets on a skipped day, and is idempotent on same-day re-completions.
- DailyChallengePlugin tracks today's seed, awards +100 XP and updates
the streak when the player wins a game whose seed matches. Pressing C
starts a new game with the daily seed.
- LevelUpEvent toast in AnimationPlugin announces level changes.
- AchievementContext gains daily_challenge_streak; daily_devotee
achievement unlocks at streak >= 7. AchievementPlugin reads
ProgressResource and runs after ProgressUpdate.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
On GameWonEvent, build an AchievementContext from StatsResource + GameState
+ wall-clock hour, evaluate ALL_ACHIEVEMENTS, flip newly-satisfied records
to unlocked, persist atomically, and emit AchievementUnlockedEvent for
each new unlock. AnimationPlugin's toast resolves the event's ID to the
achievement's display name via achievement_plugin::display_name_for.
Introduces StatsUpdate system set so AchievementPlugin can reliably run
after StatsResource reflects the win. AchievementPlugin::headless() used
in tests to avoid touching ~/.local/share/solitaire_quest/achievements.json.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- CardAnim component lerps cards from old to new position on every move
- card_plugin now adds CardAnim instead of teleporting cards on state change
- Snap-back on invalid drag reuses the same mechanism (StateChangedEvent)
- Win cascade flies all 52 cards off-screen with staggered delay on GameWonEvent
- Achievement toast scaffold wired to AchievementUnlockedEvent (Phase 5 content)
- Fix input_plugin test: click Queen's visible strip, not geometric centre
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>