fix(engine,server): safe area clamp, analytics batch, achievement save order, daily rollover, replay validation, leaderboard opt-in (#56, #60, #61, #62, #66, #68)
Build and Deploy / build-and-push (push) Successful in 3m54s
Build and Deploy / build-and-push (push) Successful in 3m54s
- #66: Clamp safe-area insets to 25% of window height with warn!() on excess - #68: Move fire_flush outside per-event loop in analytics (batch flush once) - #56: Persist progress before marking reward_granted to prevent XP loss on crash - #60: Add DateRolloverTimer + check_date_rollover system for midnight seed refresh - #62: Add validate_header() in replay upload with mode/draw_mode allowlists - #61: Restore two-query leaderboard opt-in check (SELECT then UPDATE); original queries already in .sqlx cache; EXISTS variant would require sqlx prepare Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -9,7 +9,7 @@ use std::path::PathBuf;
|
||||
|
||||
use bevy::prelude::*;
|
||||
use solitaire_data::{
|
||||
load_progress_from, progress_file_path, save_progress_to, xp_for_win, PlayerProgress,
|
||||
PlayerProgress, load_progress_from, progress_file_path, save_progress_to, xp_for_win,
|
||||
};
|
||||
|
||||
use crate::events::{GameWonEvent, XpAwardedEvent};
|
||||
@@ -74,9 +74,7 @@ impl Plugin for ProgressPlugin {
|
||||
.add_message::<GameWonEvent>()
|
||||
.add_systems(
|
||||
Update,
|
||||
award_xp_on_win
|
||||
.after(GameMutation)
|
||||
.in_set(ProgressUpdate),
|
||||
award_xp_on_win.after(GameMutation).in_set(ProgressUpdate),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -102,9 +100,10 @@ fn award_xp_on_win(
|
||||
});
|
||||
}
|
||||
if let Some(target) = &path.0
|
||||
&& let Err(e) = save_progress_to(target, &progress.0) {
|
||||
warn!("failed to save progress: {e}");
|
||||
}
|
||||
&& let Err(e) = save_progress_to(target, &progress.0)
|
||||
{
|
||||
warn!("failed to save progress: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,7 +182,10 @@ mod tests {
|
||||
fn crossing_500_xp_fires_levelup_event() {
|
||||
let mut app = headless_app();
|
||||
// Pre-load 480 XP so a 75-XP win pushes us over the 500 boundary.
|
||||
app.world_mut().resource_mut::<ProgressResource>().0.total_xp = 480;
|
||||
app.world_mut()
|
||||
.resource_mut::<ProgressResource>()
|
||||
.0
|
||||
.total_xp = 480;
|
||||
|
||||
app.world_mut().write_message(GameWonEvent {
|
||||
score: 500,
|
||||
@@ -233,7 +235,10 @@ mod tests {
|
||||
#[test]
|
||||
fn levelup_event_total_xp_matches_progress_resource() {
|
||||
let mut app = headless_app();
|
||||
app.world_mut().resource_mut::<ProgressResource>().0.total_xp = 480;
|
||||
app.world_mut()
|
||||
.resource_mut::<ProgressResource>()
|
||||
.0
|
||||
.total_xp = 480;
|
||||
|
||||
app.world_mut().write_message(GameWonEvent {
|
||||
score: 500,
|
||||
@@ -255,13 +260,11 @@ mod tests {
|
||||
// score=0 in the event (Zen keeps score at 0), time=300 (no speed bonus),
|
||||
// undo_count=0 so no-undo bonus applies: expected 50+25=75.
|
||||
let mut app = headless_app();
|
||||
app.world_mut()
|
||||
.resource_mut::<GameStateResource>()
|
||||
.0
|
||||
.mode = solitaire_core::game_state::GameMode::Zen;
|
||||
app.world_mut().resource_mut::<GameStateResource>().0.mode =
|
||||
solitaire_core::game_state::GameMode::Zen;
|
||||
|
||||
app.world_mut().write_message(GameWonEvent {
|
||||
score: 0, // Zen mode keeps score at 0
|
||||
score: 0, // Zen mode keeps score at 0
|
||||
time_seconds: 300,
|
||||
});
|
||||
app.update();
|
||||
|
||||
Reference in New Issue
Block a user