feat(engine): add AchievementPlugin with persistent unlock tracking

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>
This commit is contained in:
funman300
2026-04-24 12:53:31 -07:00
parent 4589c52368
commit 1f6994a084
5 changed files with 256 additions and 6 deletions
+3 -1
View File
@@ -1,5 +1,6 @@
//! Bevy integration layer for Solitaire Quest.
pub mod achievement_plugin;
pub mod animation_plugin;
pub mod card_plugin;
pub mod events;
@@ -10,6 +11,7 @@ pub mod resources;
pub mod stats_plugin;
pub mod table_plugin;
pub use achievement_plugin::{AchievementPlugin, AchievementsResource};
pub use animation_plugin::{AnimationPlugin, CardAnim};
pub use card_plugin::{CardEntity, CardLabel, CardPlugin};
pub use events::{
@@ -20,5 +22,5 @@ pub use game_plugin::{GameMutation, GamePlugin};
pub use input_plugin::InputPlugin;
pub use layout::{compute_layout, Layout, LayoutResource};
pub use resources::{DragState, GameStateResource, SyncStatus, SyncStatusResource};
pub use stats_plugin::{StatsPlugin, StatsResource, StatsScreen};
pub use stats_plugin::{StatsPlugin, StatsResource, StatsScreen, StatsUpdate};
pub use table_plugin::{PileMarker, TableBackground, TablePlugin};