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

- #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:
funman300
2026-05-28 13:07:22 -07:00
parent 8cb4c9808e
commit 6e407a3ea7
104 changed files with 6356 additions and 3092 deletions
+17 -8
View File
@@ -58,12 +58,15 @@ fn advance_on_challenge_win(
let prev = progress.0.challenge_index;
progress.0.challenge_index = prev.saturating_add(1);
if let Some(target) = &path.0
&& let Err(e) = save_progress_to(target, &progress.0) {
warn!("failed to save progress after challenge advance: {e}");
}
&& let Err(e) = save_progress_to(target, &progress.0)
{
warn!("failed to save progress after challenge advance: {e}");
}
// Human-readable level is 1-based (index 0 → "Challenge 1").
let level_number = prev.saturating_add(1);
toast.write(InfoToastEvent(format!("Challenge {level_number} complete!")));
toast.write(InfoToastEvent(format!(
"Challenge {level_number} complete!"
)));
advanced.write(ChallengeAdvancedEvent {
previous_index: prev,
new_index: progress.0.challenge_index,
@@ -184,8 +187,7 @@ mod tests {
#[test]
fn pressing_x_at_unlock_level_fires_new_game_with_challenge_seed() {
let mut app = headless_app();
app.world_mut().resource_mut::<ProgressResource>().0.level =
CHALLENGE_UNLOCK_LEVEL;
app.world_mut().resource_mut::<ProgressResource>().0.level = CHALLENGE_UNLOCK_LEVEL;
app.world_mut()
.resource_mut::<ProgressResource>()
.0
@@ -215,7 +217,10 @@ mod tests {
fn challenge_win_fires_complete_toast_with_level_number() {
let mut app = headless_app();
// Set challenge_index to 2 so the completed level is "Challenge 3".
app.world_mut().resource_mut::<ProgressResource>().0.challenge_index = 2;
app.world_mut()
.resource_mut::<ProgressResource>()
.0
.challenge_index = 2;
app.world_mut().resource_mut::<GameStateResource>().0 =
GameState::new_with_mode(1, DrawMode::DrawOne, GameMode::Challenge);
@@ -228,7 +233,11 @@ mod tests {
let events = app.world().resource::<Messages<InfoToastEvent>>();
let mut cursor = events.get_cursor();
let fired: Vec<_> = cursor.read(events).collect();
assert_eq!(fired.len(), 1, "exactly one toast must fire on challenge win");
assert_eq!(
fired.len(),
1,
"exactly one toast must fire on challenge win"
);
assert!(
fired[0].0.contains("Challenge 3"),
"toast must name the 1-based level that was just completed"