From 02ababa65f7791c54cda5721cdff8cd38b0e9b5f Mon Sep 17 00:00:00 2001 From: funman300 Date: Tue, 5 May 2026 20:34:48 +0000 Subject: [PATCH] feat(engine): wire Stats Watch Replay button to in-engine playback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Promotes the Stats overlay's Watch Replay button from a stub InfoToastEvent ("playback coming in a future build") to actually starting in-engine playback via the new replay_playback::start_replay_playback API. Pressing the button when a replay exists resets the game to the recorded deal and the ReplayOverlayPlugin's banner takes over. The handler reads ReplayPlaybackState via Option> so headless test fixtures that don't register ReplayPlaybackPlugin keep compiling — they fall back to a descriptive "Replay ready" toast. The "no replay yet" branch still surfaces the existing "win a game first" toast. Plugin registration in solitaire_app/src/main.rs picks up ReplayPlaybackPlugin and ReplayOverlayPlugin alongside the existing StatsPlugin. They run in any order — the playback plugin owns the state resource, the overlay plugin spawns/despawns based on state changes, and Stats's button handler dispatches into the playback plugin's API. Co-Authored-By: Claude Opus 4.7 (1M context) --- solitaire_app/src/main.rs | 5 +++- solitaire_engine/src/stats_plugin.rs | 43 +++++++++++++++++++--------- 2 files changed, 34 insertions(+), 14 deletions(-) diff --git a/solitaire_app/src/main.rs b/solitaire_app/src/main.rs index 07bc103..bda51da 100644 --- a/solitaire_app/src/main.rs +++ b/solitaire_app/src/main.rs @@ -10,7 +10,8 @@ use solitaire_engine::{ AudioPlugin, AutoCompletePlugin, CardAnimationPlugin, CardPlugin, ChallengePlugin, CursorPlugin, DailyChallengePlugin, FeedbackAnimPlugin, FontPlugin, GamePlugin, HelpPlugin, HomePlugin, HudPlugin, InputPlugin, LeaderboardPlugin, OnboardingPlugin, PausePlugin, - ProfilePlugin, ProgressPlugin, RadialMenuPlugin, SelectionPlugin, SettingsPlugin, SplashPlugin, + ProfilePlugin, ProgressPlugin, RadialMenuPlugin, ReplayOverlayPlugin, ReplayPlaybackPlugin, + SelectionPlugin, SettingsPlugin, SplashPlugin, StatsPlugin, SyncPlugin, TablePlugin, ThemePlugin, ThemeRegistryPlugin, TimeAttackPlugin, UiFocusPlugin, UiModalPlugin, UiTooltipPlugin, WeeklyGoalsPlugin, WinSummaryPlugin, }; @@ -117,6 +118,8 @@ fn main() { .add_plugins(FeedbackAnimPlugin) .add_plugins(CardAnimationPlugin) .add_plugins(AutoCompletePlugin) + .add_plugins(ReplayPlaybackPlugin) + .add_plugins(ReplayOverlayPlugin) .add_plugins(StatsPlugin::default()) .add_plugins(ProgressPlugin::default()) .add_plugins(AchievementPlugin::default()) diff --git a/solitaire_engine/src/stats_plugin.rs b/solitaire_engine/src/stats_plugin.rs index bde9064..4746354 100644 --- a/solitaire_engine/src/stats_plugin.rs +++ b/solitaire_engine/src/stats_plugin.rs @@ -187,27 +187,44 @@ fn refresh_latest_replay_on_win( /// Click handler for the "Watch replay" button. /// -/// Replay playback lives on the sync server's web UI rather than in -/// the desktop client. This handler currently surfaces a clear toast -/// pointing the player there once the upload + URL is wired; until -/// then it acknowledges the click and signals that the feature is on -/// the way. +/// Starts in-engine replay playback when the Watch Replay button is +/// pressed. If no replay has been recorded yet, surfaces an +/// [`InfoToastEvent`] instead. The playback path resets the live +/// game to the recorded deal and ticks through the move list via +/// [`crate::replay_playback`]; the [`crate::replay_overlay`] banner +/// surfaces while playback runs. fn handle_watch_replay_button( + mut commands: Commands, buttons: Query<&Interaction, (With, Changed)>, latest: Res, + playback: Option>, mut toast: MessageWriter, ) { if !buttons.iter().any(|i| *i == Interaction::Pressed) { return; } - let message = match &latest.0 { - Some(replay) => format!( - "Replay ready ({}) \u{2014} web playback coming in a future build", - format_replay_caption(replay), - ), - None => "No replay recorded yet \u{2014} win a game first.".to_string(), - }; - toast.write(InfoToastEvent(message)); + match (&latest.0, playback) { + (Some(replay), Some(mut playback)) => { + crate::replay_playback::start_replay_playback( + &mut commands, + &mut playback, + replay.clone(), + ); + } + (Some(replay), None) => { + // ReplayPlaybackPlugin not registered (headless test + // fixtures); fall back to a descriptive toast. + toast.write(InfoToastEvent(format!( + "Replay ready ({})", + format_replay_caption(replay) + ))); + } + (None, _) => { + toast.write(InfoToastEvent( + "No replay recorded yet \u{2014} win a game first.".to_string(), + )); + } + } } /// Pure helper: render a one-line caption for a [`Replay`] suitable