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:
@@ -22,8 +22,8 @@
|
||||
use std::io::Cursor;
|
||||
|
||||
use bevy::prelude::*;
|
||||
use kira::sound::static_sound::{StaticSoundData, StaticSoundHandle};
|
||||
use kira::sound::Region;
|
||||
use kira::sound::static_sound::{StaticSoundData, StaticSoundHandle};
|
||||
use kira::track::{TrackBuilder, TrackHandle};
|
||||
use kira::{AudioManager, AudioManagerSettings, Decibels, DefaultBackend, Tween, Value};
|
||||
|
||||
@@ -178,8 +178,7 @@ fn build_library() -> Option<SoundLibrary> {
|
||||
let place = decode(include_bytes!("../../assets/audio/card_place.wav"))?;
|
||||
let invalid = decode(include_bytes!("../../assets/audio/card_invalid.wav"))?;
|
||||
let fanfare = decode(include_bytes!("../../assets/audio/win_fanfare.wav"))?;
|
||||
let foundation_complete =
|
||||
decode(include_bytes!("../../assets/audio/foundation_complete.wav"))?;
|
||||
let foundation_complete = decode(include_bytes!("../../assets/audio/foundation_complete.wav"))?;
|
||||
Some(SoundLibrary {
|
||||
deal,
|
||||
flip,
|
||||
@@ -212,8 +211,7 @@ fn start_ambient_loop(
|
||||
) -> Option<StaticSoundHandle> {
|
||||
let manager = manager?;
|
||||
|
||||
let ambient_bytes: &'static [u8] =
|
||||
include_bytes!("../../assets/audio/ambient_loop.wav");
|
||||
let ambient_bytes: &'static [u8] = include_bytes!("../../assets/audio/ambient_loop.wav");
|
||||
let mut data = match StaticSoundData::from_cursor(Cursor::new(ambient_bytes.to_vec())) {
|
||||
Ok(d) => d,
|
||||
Err(e) => {
|
||||
@@ -280,13 +278,19 @@ impl AudioState {
|
||||
|
||||
fn set_sfx_volume(audio: &mut AudioState, volume: f32) {
|
||||
if let Some(track) = audio.sfx_track.as_mut() {
|
||||
track.set_volume(amplitude_to_decibels(volume.clamp(0.0, 1.0)), Tween::default());
|
||||
track.set_volume(
|
||||
amplitude_to_decibels(volume.clamp(0.0, 1.0)),
|
||||
Tween::default(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn set_music_volume(audio: &mut AudioState, volume: f32) {
|
||||
if let Some(track) = audio.music_track.as_mut() {
|
||||
track.set_volume(amplitude_to_decibels(volume.clamp(0.0, 1.0)), Tween::default());
|
||||
track.set_volume(
|
||||
amplitude_to_decibels(volume.clamp(0.0, 1.0)),
|
||||
Tween::default(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -319,7 +323,10 @@ fn apply_volume_on_change(
|
||||
let sfx_muted = mute.as_ref().is_some_and(|m| m.sfx_muted);
|
||||
let music_muted = mute.as_ref().is_some_and(|m| m.music_muted);
|
||||
set_sfx_volume(&mut audio, if sfx_muted { 0.0 } else { ev.0.sfx_volume });
|
||||
set_music_volume(&mut audio, if music_muted { 0.0 } else { ev.0.music_volume });
|
||||
set_music_volume(
|
||||
&mut audio,
|
||||
if music_muted { 0.0 } else { ev.0.music_volume },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -374,8 +381,7 @@ fn play_on_draw(
|
||||
|
||||
if is_recycle(stock_len) {
|
||||
let mut data = lib.flip.clone();
|
||||
data.settings.volume =
|
||||
Value::Fixed(amplitude_to_decibels(RECYCLE_VOLUME as f32));
|
||||
data.settings.volume = Value::Fixed(amplitude_to_decibels(RECYCLE_VOLUME as f32));
|
||||
let result = if let Some(track) = audio.sfx_track.as_mut() {
|
||||
track.play(data)
|
||||
} else if let Some(manager) = audio.manager.as_mut() {
|
||||
@@ -516,7 +522,10 @@ mod tests {
|
||||
toggle_all(&mut m);
|
||||
assert!(m.sfx_muted && m.music_muted, "M should mute both channels");
|
||||
toggle_all(&mut m);
|
||||
assert!(!m.sfx_muted && !m.music_muted, "second M should unmute both channels");
|
||||
assert!(
|
||||
!m.sfx_muted && !m.music_muted,
|
||||
"second M should unmute both channels"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -537,14 +546,23 @@ mod tests {
|
||||
assert!(m.music_muted && !m.sfx_muted);
|
||||
// M should mute sfx (not-all-muted → mute-all).
|
||||
toggle_all(&mut m);
|
||||
assert!(m.sfx_muted && m.music_muted, "M unmutes neither — it mutes all when sfx was audible");
|
||||
assert!(
|
||||
m.sfx_muted && m.music_muted,
|
||||
"M unmutes neither — it mutes all when sfx was audible"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mute_all_when_both_already_muted_unmutes_both() {
|
||||
let mut m = MuteState { sfx_muted: true, music_muted: true };
|
||||
let mut m = MuteState {
|
||||
sfx_muted: true,
|
||||
music_muted: true,
|
||||
};
|
||||
toggle_all(&mut m);
|
||||
assert!(!m.sfx_muted && !m.music_muted, "M should unmute both when all were muted");
|
||||
assert!(
|
||||
!m.sfx_muted && !m.music_muted,
|
||||
"M should unmute both when all were muted"
|
||||
);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user