feat(engine): foundation completion flourish — King-on-foundation celebration
Now that foundations are unlocked and "completing" one is a real moment (rather than a foregone conclusion based on suit assignment), each Ace-through-King run gets its own small celebration when the King lands. Three layers fire on a single FoundationCompletedEvent emitted by game_plugin's handle_move when a successful move leaves a PileType::Foundation pile holding 13 cards: 1. King card scale-pulse via a new FoundationFlourish component. Triangular curve 1.0 → 1.15 → 1.0 over MOTION_FOUNDATION_FLOURISH _SECS (0.4s) — same shape as the existing ScorePulse so the feel matches. 2. Pile-marker tint flourish via FoundationMarkerFlourish — the foundation marker's sprite colour lerps to STATE_SUCCESS for the first half of the duration then fades back. Reuses the existing success-signal palette; no new colour token. 3. Audio cue: foundation_complete.wav, a synthesised C6→E6→G6 triad with 2nd-harmonic warmth and AR decay (~240 ms). Sits an octave above win_fanfare's root so the layered fourth-completion + win cascade reads cleanly. Generated via solitaire_assetgen's foundation_complete() function and embedded via include_bytes!(). The visual systems run .after(GameMutation) so the post-move pile state is visible when the King is identified. Both flourish components remove themselves once elapsed time exceeds duration — no animation queue or scheduler integration needed. Pure foundation_flourish_scale(elapsed, duration) helper is unit-tested for the curve, edge clamps, and zero-duration safety. Three integration tests on the firing logic verify the event fires exactly once when a King completes a foundation, doesn't fire for non-foundation moves, and doesn't fire when the foundation is at 12 cards. The fourth completion still co-occurs with the win cascade — the two layer cleanly because the flourish's scale is on the King card sprite while the cascade is a screen-shake + per-card rotation, and the foundation_complete ping is a higher octave than the win fanfare's root. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
//! Cross-system events used by the engine's plugins.
|
||||
|
||||
use bevy::prelude::Message;
|
||||
use solitaire_core::card::Suit;
|
||||
use solitaire_core::game_state::GameMode;
|
||||
use solitaire_core::pile::PileType;
|
||||
use solitaire_data::AchievementRecord;
|
||||
@@ -60,6 +61,28 @@ pub struct GameWonEvent {
|
||||
pub time_seconds: u64,
|
||||
}
|
||||
|
||||
/// Fired by `GamePlugin` whenever a successful move lands a card on a
|
||||
/// foundation pile that, after the move, contains all 13 cards of its
|
||||
/// suit (Ace → King). Drives the per-suit completion flourish — a brief
|
||||
/// scale pulse on the King card and a golden tint on the foundation
|
||||
/// pile marker — plus a short audio ping.
|
||||
///
|
||||
/// Fired once per per-suit completion. The fourth completion will
|
||||
/// co-occur with `GameWonEvent` and the win cascade — they layer
|
||||
/// cleanly because the flourish is purely decorative and lives on a
|
||||
/// dedicated marker component.
|
||||
///
|
||||
/// This event is a UI/audio cue only. It does **not** cross
|
||||
/// `solitaire_sync` and is not persisted.
|
||||
#[derive(Message, Debug, Clone, Copy)]
|
||||
pub struct FoundationCompletedEvent {
|
||||
/// Foundation pile slot (0..=3) that just reached 13 cards.
|
||||
pub slot: u8,
|
||||
/// The suit of the completed foundation, taken from the bottom card
|
||||
/// (always an Ace by construction).
|
||||
pub suit: Suit,
|
||||
}
|
||||
|
||||
/// Fired when a card's face-up state changes during gameplay.
|
||||
#[derive(Message, Debug, Clone, Copy)]
|
||||
pub struct CardFlippedEvent(pub u32);
|
||||
|
||||
Reference in New Issue
Block a user