Commit Graph

16 Commits

Author SHA1 Message Date
funman300 372b6423d8 refactor(core): derive score/undo/recycle from upstream session stats
Replace the bespoke WXP scoring engine with the upstream
card_game/klondike session stats, eliminating duplicated state that
could drift from the single source of truth.

score()/undo_count()/recycle_count() now read session.stats(); the -15
undo penalty is configured as SessionConfig::undo_penalty and applied by
the upstream score formula. Save schema bumped v4 -> v5 (the three
counters are no longer persisted -- they are rebuilt by replaying the
forward instruction history on load).

- Remove GameState fields score, undo_count, recycle_count (#87)
- Remove score_history / is_recycle_history undo journal (#86)
- Remove KlondikeAdapter::apply_undo_score and the score_for_* helpers,
  plus pre_instruction_score_delta / will_flip_tableau_source (#84)

These three issues are a single atomic change: each removed field/helper
is consumed by the same draw/apply_instruction/undo/serde/PartialEq
paths, so they cannot compile or pass tests in isolation.

Behaviour changes (intentional): the escalating recycle penalty and
per-step score floor are gone (upstream linear scoring, floored once at
0); recycle_count is now cumulative; undo_count resets across save/load.

Refs #84, #86, #87

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 10:36:31 -07:00
funman300 056459619b refactor(core): derive draw_mode/is_won/move_count/is_auto_completable from session
Remove the draw_mode, move_count, is_won, and is_auto_completable fields
from GameState; they are now &self methods deriving from the underlying
card_game session (draw_mode from session config, move_count from history
length, is_won/is_auto_completable from check_win/check_auto_complete).

Tests previously fabricated these via direct field writes, which is no
longer possible. Add gated test-support overrides on TestPileState
(won/auto_completable/move_count) plus setters set_test_won,
set_test_auto_completable, set_test_move_count, and set_test_draw_mode
(re-deals the seed). All compiled out in production builds.

Fix the field->method ripple across solitaire_data, solitaire_wasm, and
solitaire_engine. Add a test-support dev-dependency to solitaire_data for
the won-game storage test.

cargo test --workspace and cargo clippy --workspace -- -D warnings pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 09:24:03 -07:00
funman300 6e407a3ea7 fix(engine,server): safe area clamp, analytics batch, achievement save order, daily rollover, replay validation, leaderboard opt-in (#56, #60, #61, #62, #66, #68)
Build and Deploy / build-and-push (push) Successful in 3m54s
- #66: Clamp safe-area insets to 25% of window height with warn!() on excess
- #68: Move fire_flush outside per-event loop in analytics (batch flush once)
- #56: Persist progress before marking reward_granted to prevent XP loss on crash
- #60: Add DateRolloverTimer + check_date_rollover system for midnight seed refresh
- #62: Add validate_header() in replay upload with mode/draw_mode allowlists
- #61: Restore two-query leaderboard opt-in check (SELECT then UPDATE); original
       queries already in .sqlx cache; EXISTS variant would require sqlx prepare

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-28 13:07:22 -07:00
funman300 18d7937b51 refactor(core): derive Copy for DrawMode; drop redundant .clone() calls (M-18)
DrawMode is a fieldless two-variant enum — it is trivially bitwise-
copyable. Adding Copy + updating choose_winnable_seed to take the value
directly eliminates 13 superfluous .clone() calls across solitaire_core,
solitaire_engine, solitaire_assetgen, and solitaire_wasm.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 21:18:23 -07:00
funman300 4b9d008be2 refactor(workspace): sweep low-risk clippy::pedantic findings
Conservative cleanup pass — applied only the high-signal pedantic
lints whose fixes either remove genuine waste or read more naturally,
skipping anything stylistic that would bloat the diff.

- map_unwrap_or: 29 .map(...).unwrap_or(...) sites collapsed to
  .map_or / .is_some_and / .map_or_else equivalents
- uninlined_format_args: 7 production format!/write!/println! sites
  rewritten to the inline-argument style; assert! sites in test code
  intentionally untouched
- match_same_arms: 2 redundant arms collapsed where the bodies were
  identical and the merger didn't obscure intent

Public API is unchanged. No dependencies added or removed. The
pedantic warning count dropped from 840 to 807 (-33). Out-of-scope
findings — needless_pass_by_value on Bevy Res params, false-positive
explicit_iter_loop on Bevy Query iterators, items_after_statements
inside test mods, and the "ask before changing" merge logic in
solitaire_sync — were intentionally deferred.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 02:46:32 +00:00
funman300 7cda2a9f1a fix(engine): resolve all clippy warnings introduced by PNG asset pipeline
CI / Test & Lint (push) Failing after 1m34s
CI / Release Build (push) Has been skipped
- 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>
2026-04-29 03:35:41 +00:00
funman300 ffc79447d4 fix+refactor+docs: P0–P3 todo list items
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>
2026-04-28 22:02:52 +00:00
funman300 21d0c289b5 chore(deps): migrate to Bevy 0.18
- BorderRadius is no longer a Component; moved into Node.border_radius
  field at all 15 spawn sites across 6 plugin files
- Events<T> renamed to Messages<T> in test code (12 files)
- KeyboardEvents SystemParam renamed to KeyboardMessages to match the
  MessageWriter rename done in the 0.17 hop
- WindowResolution::from((f32,f32)) removed; use (u32,u32) tuple in main.rs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 13:48:41 -07:00
funman300 648cd44387 chore(deps): migrate to Bevy 0.17
- 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>
2026-04-28 13:04:44 -07:00
funman300 c8553dc8c5 chore(deps): migrate to Bevy 0.16, axum 0.8, and other package updates
- Bump bevy 0.15 → 0.16; fixes all breaking API changes:
  ChildBuilder → ChildSpawnerCommands, Parent → ChildOf,
  despawn_descendants → despawn_related::<Children>(),
  despawn_recursive → despawn (now recursive by default),
  EventWriter::send → write, Query::{get_single,get_single_mut}
  → {single,single_mut}, ChildOf::get → parent()
- Bump axum 0.7 → 0.8; remove axum::async_trait from FromRequestParts
- Bump tower_governor 0.4 → 0.8; fix GovernorLayer::new() API
- Bump jsonwebtoken 9 → 10 with rust_crypto feature only
- Bump thiserror 1 → 2, dirs 5 → 6, bcrypt 0.15 → 0.19,
  reqwest 0.12 → 0.13 (rustls feature rename)
- Regenerate .sqlx offline cache for sqlx compile-time query checks

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 12:31:12 -07:00
funman300 59a023ed5e chore(workspace): fix all clippy warnings in test code
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>
2026-04-28 18:02:27 +00:00
root 3eb7901023 feat(engine): fire XpAwardedEvent for daily and weekly bonus XP
Daily challenge completions (+100 XP) and weekly goal bonuses (+75 XP)
now fire XpAwardedEvent so the player sees a "+N XP" toast — consistent
with the post-win XP toast already shown by ProgressPlugin.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 03:23:55 +00:00
root bc021acfd0 feat(data,engine): add WinUnder-5min and WinDrawThree weekly goal types
Adds two new weekly goals — "Win 1 game in under 5 minutes" and
"Win 1 Draw-3 game" — broadening variety beyond the existing three.
WeeklyGoalContext gains a draw_mode field so the new WinDrawThree
variant can match on draw mode. Existing tests updated to pre-complete
new goals where the win conditions overlap.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 03:13:33 +00:00
root bd48813900 fix(engine): fire LevelUpEvent when weekly bonus XP crosses a level threshold
evaluate_weekly_goals discarded the return value of add_xp(bonus_xp),
so a level-up triggered by a weekly goal completion never fired
LevelUpEvent — the level-up toast and mode-unlock at L5 were silently
skipped. Now captures prev_level, checks leveled_up_from(), and sends
LevelUpEvent matching the pattern used by progress_plugin.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 01:16:11 +00:00
root 45ef3a2058 fix(engine): reset weekly goals at app startup if the ISO week changed
evaluate_weekly_goals only ran the roll on GameWonEvent, so stale goal
progress from a prior week would linger until the player's next win.
A new Startup system calls roll_weekly_goals_if_new_week on launch and
persists immediately when the week has rolled over.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 01:02:05 +00:00
funman300 b730902d76 feat(engine): add weekly goals with ISO-week rollover and +75 XP bonus
Phase 6 part 2b:
- solitaire_data::weekly defines WeeklyGoalKind, WeeklyGoalDef,
  WeeklyGoalContext, current_iso_week_key, and three starter goals
  (5 wins, 3 no-undo wins, 3 fast wins).
- PlayerProgress gains weekly_goal_week_iso, roll_weekly_goals_if_new_week,
  and record_weekly_progress (returns true exactly once per goal completion).
- WeeklyGoalsPlugin evaluates GameWonEvent against WEEKLY_GOALS, rolls the
  week if needed, increments matching counters, awards WEEKLY_GOAL_XP for
  newly-completed goals, persists progress, and fires
  WeeklyGoalCompletedEvent.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-24 19:25:18 -07:00