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
+46 -38
View File
@@ -140,11 +140,7 @@ impl Plugin for UiFocusPlugin {
// resource.
.add_systems(
PostUpdate,
(
attach_focusable_to_modal_buttons,
auto_focus_on_modal_open,
)
.chain(),
(attach_focusable_to_modal_buttons, auto_focus_on_modal_open).chain(),
)
.add_systems(
Update,
@@ -433,11 +429,7 @@ fn handle_focus_keys(
// it matches the visual left → right layout.
let row_cycle: Vec<Entity> = siblings
.iter()
.filter(|e| {
focusables
.get(*e)
.is_ok_and(|(_, disabled)| !disabled)
})
.filter(|e| focusables.get(*e).is_ok_and(|(_, disabled)| !disabled))
.collect();
if !row_cycle.is_empty()
&& let Some(idx) = row_cycle.iter().position(|e| *e == target)
@@ -465,23 +457,23 @@ fn handle_focus_keys(
// 1. Any modal open ⇒ Modal(topmost scrim)
// 2. Any Hud-grouped focusable hovered ⇒ Hud
// 3. Otherwise ⇒ no-op
let active_group: FocusGroup = if let Some(active_scrim) = scrims.iter().max_by_key(|e| e.index()) {
// Pick the topmost modal as the active group. With multiple
// modals stacked (Pause + Forfeit confirm) the most-recently-
// spawned scrim has the highest entity index — same heuristic
// Phase 1 used.
FocusGroup::Modal(active_scrim)
} else if hud_interactions.iter().any(|(_, interaction, focusable)| {
matches!(interaction, Interaction::Hovered) && focusable.group == FocusGroup::Hud
}) {
FocusGroup::Hud
} else {
return;
};
let active_group: FocusGroup =
if let Some(active_scrim) = scrims.iter().max_by_key(|e| e.index()) {
// Pick the topmost modal as the active group. With multiple
// modals stacked (Pause + Forfeit confirm) the most-recently-
// spawned scrim has the highest entity index — same heuristic
// Phase 1 used.
FocusGroup::Modal(active_scrim)
} else if hud_interactions.iter().any(|(_, interaction, focusable)| {
matches!(interaction, Interaction::Hovered) && focusable.group == FocusGroup::Hud
}) {
FocusGroup::Hud
} else {
return;
};
let tab_pressed = keys.just_pressed(KeyCode::Tab);
let activate_pressed =
keys.just_pressed(KeyCode::Enter) || keys.just_pressed(KeyCode::Space);
let activate_pressed = keys.just_pressed(KeyCode::Enter) || keys.just_pressed(KeyCode::Space);
if !tab_pressed && !activate_pressed {
return;
@@ -657,28 +649,37 @@ fn update_focus_overlay(
mod tests {
use super::*;
use crate::ui_modal::{
spawn_modal, spawn_modal_actions, spawn_modal_button, ButtonVariant, UiModalPlugin,
ButtonVariant, UiModalPlugin, spawn_modal, spawn_modal_actions, spawn_modal_button,
};
#[test]
fn focus_ring_pulse_factor_at_zero_is_mid_point() {
// sin(0) = 0 → factor = 0.825 (mid of [0.65, 1.0]).
let f = focus_ring_pulse_factor(0.0);
assert!((f - 0.825).abs() < 1e-5, "factor at t=0 should be 0.825, got {f}");
assert!(
(f - 0.825).abs() < 1e-5,
"factor at t=0 should be 0.825, got {f}"
);
}
#[test]
fn focus_ring_pulse_factor_peaks_at_quarter_period() {
// sin(τ/4) = 1 → factor = 1.0.
let f = focus_ring_pulse_factor(MOTION_FOCUS_PULSE_SECS / 4.0);
assert!((f - 1.0).abs() < 1e-4, "factor at peak should be 1.0, got {f}");
assert!(
(f - 1.0).abs() < 1e-4,
"factor at peak should be 1.0, got {f}"
);
}
#[test]
fn focus_ring_pulse_factor_troughs_at_three_quarter_period() {
// sin(3τ/4) = -1 → factor = 0.65.
let f = focus_ring_pulse_factor(MOTION_FOCUS_PULSE_SECS * 3.0 / 4.0);
assert!((f - 0.65).abs() < 1e-4, "factor at trough should be 0.65, got {f}");
assert!(
(f - 0.65).abs() < 1e-4,
"factor at trough should be 0.65, got {f}"
);
}
#[test]
@@ -755,12 +756,16 @@ mod tests {
// `auto_focus_on_modal_open` execute.
app.update();
let mut a_query = app.world_mut().query_filtered::<Entity, With<TestButtonA>>();
let mut a_query = app
.world_mut()
.query_filtered::<Entity, With<TestButtonA>>();
let a = a_query
.iter(app.world())
.next()
.expect("button A should have been spawned");
let mut b_query = app.world_mut().query_filtered::<Entity, With<TestButtonB>>();
let mut b_query = app
.world_mut()
.query_filtered::<Entity, With<TestButtonB>>();
let b = b_query
.iter(app.world())
.next()
@@ -807,11 +812,17 @@ mod tests {
};
app.update();
let mut q_a = app.world_mut().query_filtered::<Entity, With<TestButtonA>>();
let mut q_a = app
.world_mut()
.query_filtered::<Entity, With<TestButtonA>>();
let a = q_a.iter(app.world()).next().expect("A spawned");
let mut q_b = app.world_mut().query_filtered::<Entity, With<TestButtonB>>();
let mut q_b = app
.world_mut()
.query_filtered::<Entity, With<TestButtonB>>();
let b = q_b.iter(app.world()).next().expect("B spawned");
let mut q_c = app.world_mut().query_filtered::<Entity, With<TestButtonC>>();
let mut q_c = app
.world_mut()
.query_filtered::<Entity, With<TestButtonC>>();
let c = q_c.iter(app.world()).next().expect("C spawned");
(scrim, a, b, c)
}
@@ -864,10 +875,7 @@ mod tests {
/// Crucially this system has **no** ordering relationship with
/// `UiFocusPlugin`'s chain — exactly the situation that surfaces the
/// "focus arrives one frame late" bug in production.
fn spawn_modal_via_system(
mut commands: Commands,
mut trigger: ResMut<SpawnModalTrigger>,
) {
fn spawn_modal_via_system(mut commands: Commands, mut trigger: ResMut<SpawnModalTrigger>) {
if !trigger.0 {
return;
}