feat(engine): Android polish sweep + hint button + watch replay

Draw-Three waste fan: slot.saturating_sub(1) was a constant shift that
hid slot-0 even when the pile had fewer cards than visible. Fixed to
slot.saturating_sub(rendered_len.saturating_sub(visible)) so small piles
fan correctly and only a genuine buffer card gets hidden. New regression
test covers the small-pile case.

Android toast: game-over "press D / N" message now shows touch-friendly
copy ("Tap the stock...") on Android via cfg gate.

Onboarding: SLIDE_COUNT drops from 3 to 2 on Android so first-time
users skip the keyboard-shortcuts slide (irrelevant on touchscreen).
spawn_slide dispatch is gated identically.

Hint button: added HintButton to the HUD action bar (order 4, between
Help and Modes). Clicking it triggers the async solver hint — same path
as the H key — via optional resources so headless tests stay clean.
All button-order and tooltip tests updated for the new 7-button bar.

Watch Replay: win-summary modal now shows a "Watch Replay" secondary
button alongside "Play Again". It loads the most recent entry from
ReplayHistoryResource and hands it to start_replay_playback, dismissing
the modal. Falls back to an info toast when the replay or playback
plugin is unavailable.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
funman300
2026-05-11 20:28:20 -07:00
parent 3110702c74
commit 2b01f741b4
5 changed files with 214 additions and 43 deletions
+5 -3
View File
@@ -1072,9 +1072,11 @@ fn check_no_moves(
}
if !moves_ok && !*already_fired {
toast.write(InfoToastEvent(
"No moves available \u{2014} press D to draw or N for a new game".to_string(),
));
#[cfg(target_os = "android")]
let no_moves_msg = "No moves available \u{2014} tap the stock to draw or start a new game";
#[cfg(not(target_os = "android"))]
let no_moves_msg = "No moves available \u{2014} press D to draw or N for a new game";
toast.write(InfoToastEvent(no_moves_msg.to_string()));
*already_fired = true;
// Only spawn the overlay if one does not already exist.
if game_over_screens.is_empty() {