Merge pull request 'chore: delete dead code approved from the PR #166 sweep' (#169) from chore/dead-code-removals into master
This commit was merged in pull request #169.
This commit is contained in:
+1
-3
@@ -145,7 +145,6 @@ Shared API contract types imported by both the game client (`solitaire_data`) an
|
||||
Owns:
|
||||
- `SyncPayload`, `SyncResponse`, `ConflictReport`
|
||||
- `ChallengeGoal`, `LeaderboardEntry`
|
||||
- `ApiError` enum
|
||||
- Merge logic (pure functions, no I/O)
|
||||
|
||||
### `solitaire_data`
|
||||
@@ -257,7 +256,7 @@ solitaire_sync::merge(local, remote)
|
||||
│
|
||||
▼
|
||||
Write merged result to disk
|
||||
│ fires SyncCompleteEvent
|
||||
│
|
||||
▼
|
||||
Bevy main thread reads updated StatsResource
|
||||
```
|
||||
@@ -377,7 +376,6 @@ struct StateChangedEvent;
|
||||
struct CardFlippedEvent(u32);
|
||||
struct GameWonEvent { score: i32, time_seconds: u64 }
|
||||
struct AchievementUnlockedEvent(AchievementRecord);
|
||||
struct SyncCompleteEvent(Result<SyncResponse, String>);
|
||||
```
|
||||
|
||||
### Layout System
|
||||
|
||||
@@ -201,27 +201,6 @@ pub(crate) fn advance_card_animations(
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Win cascade
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Win-cascade scatter targets — 8 points beyond the window edges.
|
||||
///
|
||||
/// Scaled by `radius` (pass `layout.card_size.x * 8.0` for a good result).
|
||||
pub fn win_scatter_targets(radius: f32) -> [Vec2; 8] {
|
||||
let r = radius;
|
||||
[
|
||||
Vec2::new(r, r),
|
||||
Vec2::new(-r, r),
|
||||
Vec2::new(r, -r),
|
||||
Vec2::new(-r, -r),
|
||||
Vec2::new(0.0, r),
|
||||
Vec2::new(0.0, -r),
|
||||
Vec2::new(r, 0.0),
|
||||
Vec2::new(-r, 0.0),
|
||||
]
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Tests
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -304,21 +283,4 @@ mod tests {
|
||||
.with_z_lift(12.0);
|
||||
assert!((anim.z_lift - 12.0).abs() < 1e-6);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn win_scatter_has_eight_targets() {
|
||||
let targets = win_scatter_targets(800.0);
|
||||
assert_eq!(targets.len(), 8);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn win_scatter_targets_are_off_center() {
|
||||
for t in win_scatter_targets(400.0) {
|
||||
let dist = t.length();
|
||||
assert!(
|
||||
dist > 100.0,
|
||||
"scatter target should be well off-center: {t:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,16 +31,6 @@
|
||||
//! ));
|
||||
//! ```
|
||||
//!
|
||||
//! # Win cascade with `Expressive` curve
|
||||
//!
|
||||
//! The existing `AnimationPlugin` drives the win cascade with `CardAnim`
|
||||
//! (linear). To use the curve-based cascade instead, disable
|
||||
//! `handle_win_cascade` in `AnimationPlugin` and register `WinCascadePlugin`
|
||||
//! (declared below) which uses `CardAnimation` + `MotionCurve::Expressive`.
|
||||
//!
|
||||
//! They **must not both be active** — both write to `Transform` on the same
|
||||
//! 52 entities and will race.
|
||||
//!
|
||||
//! # Coexistence rules
|
||||
//!
|
||||
//! | Condition | Safe? |
|
||||
@@ -58,24 +48,21 @@ pub mod interaction;
|
||||
pub mod timing;
|
||||
pub mod tuning;
|
||||
|
||||
pub use animation::{CardAnimation, win_scatter_targets};
|
||||
pub use animation::CardAnimation;
|
||||
pub use chain::AnimationChain;
|
||||
pub use curves::{MotionCurve, sample_curve};
|
||||
pub use diagnostics::{FrameTimeDiagnostics, WINDOW_SIZE as DIAG_WINDOW_SIZE};
|
||||
pub use interaction::{BufferedInput, HoverState, InputBuffer};
|
||||
pub use timing::{
|
||||
DEAL_INTERVAL_SECS, MAX_DURATION_SECS, MIN_DURATION_SECS, WIN_CASCADE_INTERVAL_SECS,
|
||||
cascade_delay, compute_duration, micro_vary,
|
||||
DEAL_INTERVAL_SECS, MAX_DURATION_SECS, MIN_DURATION_SECS, compute_duration, micro_vary,
|
||||
};
|
||||
pub use tuning::{AnimationTuning, InputPlatform};
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy::window::RequestRedraw;
|
||||
|
||||
use crate::card_plugin::CardEntity;
|
||||
use crate::events::{DrawRequestEvent, GameWonEvent, MoveRequestEvent, UndoRequestEvent};
|
||||
use crate::game_plugin::GameMutation;
|
||||
use crate::layout::LayoutResource;
|
||||
use crate::resources::DragState;
|
||||
|
||||
use animation::advance_card_animations;
|
||||
@@ -144,63 +131,6 @@ impl Plugin for CardAnimationPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Optional: win cascade with Expressive curve
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Optional plugin that replaces the linear win cascade in `AnimationPlugin`
|
||||
/// with an `Expressive`-curve cascade.
|
||||
///
|
||||
/// **Do not register this alongside `AnimationPlugin`'s win cascade** — they
|
||||
/// will race on the same card entities. To use this plugin, prevent
|
||||
/// `AnimationPlugin` from handling `GameWonEvent` (or remove it and manage
|
||||
/// win toasts manually).
|
||||
pub struct WinCascadePlugin;
|
||||
|
||||
impl Plugin for WinCascadePlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_systems(Update, trigger_expressive_win_cascade.after(GameMutation));
|
||||
}
|
||||
}
|
||||
|
||||
/// Inserts `CardAnimation` (Expressive curve) on every card when `GameWonEvent` fires.
|
||||
///
|
||||
/// Cards scatter to 8 off-screen positions with per-card stagger. The z-lift
|
||||
/// creates a "burst" effect as cards fly outward.
|
||||
fn trigger_expressive_win_cascade(
|
||||
mut events: MessageReader<GameWonEvent>,
|
||||
cards: Query<(Entity, &Transform), With<CardEntity>>,
|
||||
layout: Option<Res<LayoutResource>>,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
if events.read().next().is_none() {
|
||||
return;
|
||||
}
|
||||
|
||||
let radius = layout.as_ref().map_or(800.0, |l| l.0.card_size.x * 8.0);
|
||||
|
||||
let targets = win_scatter_targets(radius);
|
||||
|
||||
for (index, (entity, transform)) in cards.iter().enumerate() {
|
||||
let start_xy = transform.translation.truncate();
|
||||
let start_z = transform.translation.z;
|
||||
let target = targets[index % targets.len()];
|
||||
|
||||
commands.entity(entity).insert(
|
||||
CardAnimation::slide(
|
||||
start_xy,
|
||||
start_z,
|
||||
target,
|
||||
start_z + 60.0,
|
||||
MotionCurve::Expressive,
|
||||
)
|
||||
.with_delay(cascade_delay(index, WIN_CASCADE_INTERVAL_SECS))
|
||||
.with_duration(0.65)
|
||||
.with_z_lift(25.0),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Tests
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -433,19 +363,4 @@ mod tests {
|
||||
let state = HoverState::default();
|
||||
assert!(state.entity.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn win_scatter_produces_eight_distinct_points() {
|
||||
let targets = win_scatter_targets(600.0);
|
||||
assert_eq!(targets.len(), 8);
|
||||
// All must be different.
|
||||
for i in 0..8 {
|
||||
for j in (i + 1)..8 {
|
||||
assert_ne!(
|
||||
targets[i], targets[j],
|
||||
"scatter targets {i} and {j} must be distinct"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,17 +49,6 @@ pub fn micro_vary(duration: f32, entity_index: u32) -> f32 {
|
||||
duration * (1.0 + variation)
|
||||
}
|
||||
|
||||
/// Returns the pre-animation delay for card at `index` in a staggered cascade.
|
||||
///
|
||||
/// `delay = index × interval_secs`.
|
||||
#[inline]
|
||||
pub fn cascade_delay(index: usize, interval_secs: f32) -> f32 {
|
||||
index as f32 * interval_secs
|
||||
}
|
||||
|
||||
/// Recommended per-card interval for the win cascade (Normal speed).
|
||||
pub const WIN_CASCADE_INTERVAL_SECS: f32 = 0.018;
|
||||
|
||||
/// Recommended per-card interval for deal animations (Normal speed).
|
||||
pub const DEAL_INTERVAL_SECS: f32 = 0.022;
|
||||
|
||||
@@ -137,22 +126,4 @@ mod tests {
|
||||
"micro_vary should differ for different indices"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cascade_delay_zero_index_is_zero() {
|
||||
assert_eq!(cascade_delay(0, 0.018), 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cascade_delay_scales_linearly() {
|
||||
let interval = 0.018;
|
||||
for i in 0..52usize {
|
||||
let expected = i as f32 * interval;
|
||||
let actual = cascade_delay(i, interval);
|
||||
assert!(
|
||||
(actual - expected).abs() < 1e-6,
|
||||
"cascade_delay({i}) = {actual}, expected {expected}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ use solitaire_core::KlondikePile;
|
||||
use solitaire_core::game_state::GameMode;
|
||||
use solitaire_core::{Card, Suit};
|
||||
use solitaire_data::AchievementRecord;
|
||||
use solitaire_sync::SyncResponse;
|
||||
|
||||
/// Request to move `count` cards from `from` to `to`. Fired by input systems,
|
||||
/// consumed by `GamePlugin`.
|
||||
@@ -248,16 +247,6 @@ pub struct ToggleLeaderboardRequestEvent;
|
||||
#[derive(Message, Debug, Clone, Copy, Default)]
|
||||
pub struct ToggleHomeRequestEvent;
|
||||
|
||||
/// Fired by `SyncPlugin` after a pull task resolves and the merged result has
|
||||
/// been persisted to disk. `Ok(SyncResponse)` carries the merged payload plus
|
||||
/// any `ConflictReport`s the merge produced. `Err(String)` carries a
|
||||
/// human-readable failure message (network, auth, serialization, etc.).
|
||||
///
|
||||
/// UI systems listen for this to refresh views without polling
|
||||
/// `SyncStatusResource`. See [ARCHITECTURE.md §4](../../ARCHITECTURE.md).
|
||||
#[derive(Message, Debug, Clone)]
|
||||
pub struct SyncCompleteEvent(pub Result<SyncResponse, String>);
|
||||
|
||||
/// Generic informational toast message. Any system can fire this to display
|
||||
/// a short string to the player, e.g. "Locked — reach level 5".
|
||||
#[derive(Message, Debug, Clone)]
|
||||
|
||||
@@ -83,9 +83,8 @@ pub use avatar_plugin::{AvatarFetchEvent, AvatarPlugin, AvatarResource};
|
||||
pub use card_animation::{
|
||||
AnimationChain, AnimationTuning, BufferedInput, CardAnimation, CardAnimationPlugin,
|
||||
DEAL_INTERVAL_SECS, DIAG_WINDOW_SIZE, FrameTimeDiagnostics, HoverState, InputBuffer,
|
||||
InputPlatform, MAX_DURATION_SECS, MIN_DURATION_SECS, MotionCurve, WIN_CASCADE_INTERVAL_SECS,
|
||||
WinCascadePlugin, cascade_delay, compute_duration, micro_vary, sample_curve,
|
||||
win_scatter_targets,
|
||||
InputPlatform, MAX_DURATION_SECS, MIN_DURATION_SECS, MotionCurve, compute_duration, micro_vary,
|
||||
sample_curve,
|
||||
};
|
||||
pub use card_plugin::{
|
||||
CardEntity, CardImageSet, CardLabel, CardPlugin, HintHighlight, HintHighlightTimer,
|
||||
@@ -107,7 +106,7 @@ pub use events::{
|
||||
HintVisualEvent, InfoToastEvent, ManualSyncRequestEvent, MoveRejectedEvent, MoveRequestEvent,
|
||||
NewGameRequestEvent, PauseRequestEvent, StartChallengeRequestEvent,
|
||||
StartDailyChallengeRequestEvent, StartDifficultyRequestEvent, StartPlayBySeedRequestEvent,
|
||||
StartTimeAttackRequestEvent, StartZenRequestEvent, StateChangedEvent, SyncCompleteEvent,
|
||||
StartTimeAttackRequestEvent, StartZenRequestEvent, StateChangedEvent,
|
||||
ToggleAchievementsRequestEvent, ToggleLeaderboardRequestEvent, ToggleProfileRequestEvent,
|
||||
ToggleSettingsRequestEvent, ToggleStatsRequestEvent, UndoRequestEvent, WinStreakMilestoneEvent,
|
||||
XpAwardedEvent,
|
||||
|
||||
@@ -22,12 +22,11 @@ use solitaire_data::{
|
||||
AchievementRecord, PlayerProgress, Replay, StatsSnapshot, SyncError, SyncProvider,
|
||||
save_achievements_to, save_progress_to, save_replay_history_to, save_stats_to,
|
||||
};
|
||||
use solitaire_sync::{SyncPayload, SyncResponse, merge};
|
||||
use solitaire_sync::{SyncPayload, merge};
|
||||
|
||||
use crate::achievement_plugin::{AchievementsResource, AchievementsStoragePath};
|
||||
use crate::events::{
|
||||
GameWonEvent, ManualSyncRequestEvent, SyncCompleteEvent, SyncConfigureRequestEvent,
|
||||
WarningToastEvent,
|
||||
GameWonEvent, ManualSyncRequestEvent, SyncConfigureRequestEvent, WarningToastEvent,
|
||||
};
|
||||
use crate::game_plugin::RecordingReplay;
|
||||
use crate::progress_plugin::{ProgressResource, ProgressStoragePath};
|
||||
@@ -108,7 +107,6 @@ impl Plugin for SyncPlugin {
|
||||
.init_resource::<PullTask>()
|
||||
.init_resource::<PendingReplayUpload>()
|
||||
.add_message::<ManualSyncRequestEvent>()
|
||||
.add_message::<SyncCompleteEvent>()
|
||||
.add_message::<SyncConfigureRequestEvent>()
|
||||
.add_message::<WarningToastEvent>();
|
||||
|
||||
@@ -198,7 +196,6 @@ fn poll_pull_result(
|
||||
achievements_path: Res<AchievementsStoragePath>,
|
||||
mut progress: ResMut<ProgressResource>,
|
||||
progress_path: Res<ProgressStoragePath>,
|
||||
mut complete_writer: MessageWriter<SyncCompleteEvent>,
|
||||
mut configure_sync: MessageWriter<SyncConfigureRequestEvent>,
|
||||
mut warning_toast: MessageWriter<WarningToastEvent>,
|
||||
) {
|
||||
@@ -213,7 +210,7 @@ fn poll_pull_result(
|
||||
match result {
|
||||
Ok(remote) => {
|
||||
let local = build_payload(&stats.0, &achievements.0, &progress.0);
|
||||
let (merged, conflicts) = merge(&local, &remote);
|
||||
let (merged, _conflicts) = merge(&local, &remote);
|
||||
|
||||
// Persist merged state atomically.
|
||||
if let Some(p) = &stats_path.0
|
||||
@@ -233,17 +230,10 @@ fn poll_pull_result(
|
||||
}
|
||||
|
||||
// Update in-world resources.
|
||||
let now = Utc::now();
|
||||
stats.0 = merged.stats.clone();
|
||||
achievements.0 = merged.achievements.clone();
|
||||
progress.0 = merged.progress.clone();
|
||||
status.0 = SyncStatus::LastSynced(now);
|
||||
|
||||
complete_writer.write(SyncCompleteEvent(Ok(SyncResponse {
|
||||
merged,
|
||||
server_time: now,
|
||||
conflicts,
|
||||
})));
|
||||
stats.0 = merged.stats;
|
||||
achievements.0 = merged.achievements;
|
||||
progress.0 = merged.progress;
|
||||
status.0 = SyncStatus::LastSynced(Utc::now());
|
||||
}
|
||||
Err(SyncError::UnsupportedPlatform) => {
|
||||
// No backend configured — not an error, just leave status as Idle.
|
||||
@@ -266,8 +256,7 @@ fn poll_pull_result(
|
||||
if matches!(e, SyncError::Auth(_)) {
|
||||
configure_sync.write(SyncConfigureRequestEvent);
|
||||
}
|
||||
status.0 = SyncStatus::Error(msg.clone());
|
||||
complete_writer.write(SyncCompleteEvent(Err(msg)));
|
||||
status.0 = SyncStatus::Error(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ pub mod stats;
|
||||
pub mod theme_store;
|
||||
|
||||
pub use achievements::AchievementRecord;
|
||||
pub use merge::{merge, merge_at};
|
||||
pub use merge::merge;
|
||||
pub use progress::{PlayerProgress, level_for_xp};
|
||||
pub use stats::StatsSnapshot;
|
||||
pub use theme_store::{ThemeCatalogEntry, ThemeCatalogResponse};
|
||||
@@ -98,30 +98,3 @@ pub struct LeaderboardEntry {
|
||||
/// When this entry was last recorded.
|
||||
pub recorded_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Error types
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Errors returned by the sync server in `application/json` error bodies.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, thiserror::Error)]
|
||||
pub enum ApiError {
|
||||
/// The request could not be authenticated (missing or invalid JWT).
|
||||
#[error("unauthorized")]
|
||||
Unauthorized,
|
||||
/// The supplied credentials were incorrect.
|
||||
#[error("invalid credentials")]
|
||||
InvalidCredentials,
|
||||
/// A username that was requested for registration is already taken.
|
||||
#[error("username already taken")]
|
||||
UsernameTaken,
|
||||
/// The request payload was too large (> 1 MB).
|
||||
#[error("payload too large")]
|
||||
PayloadTooLarge,
|
||||
/// The request body could not be parsed.
|
||||
#[error("bad request: {0}")]
|
||||
BadRequest(String),
|
||||
/// An unexpected server-side error occurred.
|
||||
#[error("internal server error")]
|
||||
Internal,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user