feat(engine): skip splash on subsequent launches
The 1.6 s brand beat is delightful on first launch and tedious on
every subsequent one. spawn_splash now reads SettingsResource and
returns early when first_run_complete is true — the player has
already seen the splash at least once and the onboarding flow that
follows it, so dropping straight into gameplay is the right move.
Reuses the existing first_run_complete signal rather than introducing
a separate splash_seen field; the two states ("I've been here") line
up naturally and avoid carrying a one-shot flag forever.
The first run, a save reset (settings.json deleted), or a headless
test fixture that doesn't register SettingsResource all still see the
full splash — Option<Res<SettingsResource>> defaults to "show" when
absent, so the existing test fixture observes the same spawn it
always did.
Two new tests pin the split: splash_skipped_when_first_run_complete
asserts no SplashRoot spawns when settings say so, and
splash_still_shows_when_first_run_incomplete asserts the first-run
path is unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -42,6 +42,7 @@ use bevy::input::touch::Touches;
|
|||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
|
|
||||||
use crate::font_plugin::FontResource;
|
use crate::font_plugin::FontResource;
|
||||||
|
use crate::settings_plugin::SettingsResource;
|
||||||
use crate::ui_theme::{
|
use crate::ui_theme::{
|
||||||
ACCENT_PRIMARY, BG_BASE, MOTION_SPLASH_FADE_SECS, MOTION_SPLASH_TOTAL_SECS, TEXT_SECONDARY,
|
ACCENT_PRIMARY, BG_BASE, MOTION_SPLASH_FADE_SECS, MOTION_SPLASH_TOTAL_SECS, TEXT_SECONDARY,
|
||||||
TYPE_CAPTION, TYPE_DISPLAY, VAL_SPACE_2, Z_SPLASH,
|
TYPE_CAPTION, TYPE_DISPLAY, VAL_SPACE_2, Z_SPLASH,
|
||||||
@@ -108,7 +109,25 @@ struct SplashSubtitle;
|
|||||||
/// at full alpha (the first `advance_splash` tick will overwrite the
|
/// at full alpha (the first `advance_splash` tick will overwrite the
|
||||||
/// alpha based on age), centres a "Solitaire Quest" title in
|
/// alpha based on age), centres a "Solitaire Quest" title in
|
||||||
/// [`ACCENT_PRIMARY`], and pins a small build-version line below.
|
/// [`ACCENT_PRIMARY`], and pins a small build-version line below.
|
||||||
fn spawn_splash(mut commands: Commands, font_res: Option<Res<FontResource>>) {
|
///
|
||||||
|
/// **Skipped on subsequent launches.** If `SettingsResource` reports
|
||||||
|
/// `first_run_complete == true`, the player has already seen the brand
|
||||||
|
/// beat at least once and we go straight to gameplay — having to wait
|
||||||
|
/// 1.6 s on every launch wears thin fast. The splash still shows on
|
||||||
|
/// first run, after a save reset (settings.json deleted), and under
|
||||||
|
/// `MinimalPlugins` (no `SettingsResource` registered) so the test
|
||||||
|
/// fixture observes the same spawn it always did.
|
||||||
|
fn spawn_splash(
|
||||||
|
mut commands: Commands,
|
||||||
|
font_res: Option<Res<FontResource>>,
|
||||||
|
settings: Option<Res<SettingsResource>>,
|
||||||
|
) {
|
||||||
|
if let Some(settings) = settings.as_deref()
|
||||||
|
&& settings.0.first_run_complete
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let font_handle = font_res.map(|f| f.0.clone()).unwrap_or_default();
|
let font_handle = font_res.map(|f| f.0.clone()).unwrap_or_default();
|
||||||
let title_font = TextFont {
|
let title_font = TextFont {
|
||||||
font: font_handle.clone(),
|
font: font_handle.clone(),
|
||||||
@@ -371,6 +390,47 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn splash_skipped_when_first_run_complete() {
|
||||||
|
use solitaire_data::Settings;
|
||||||
|
let mut app = App::new();
|
||||||
|
app.add_plugins(MinimalPlugins).add_plugins(SplashPlugin);
|
||||||
|
app.init_resource::<ButtonInput<KeyCode>>();
|
||||||
|
app.init_resource::<ButtonInput<MouseButton>>();
|
||||||
|
// Insert a SettingsResource that says "I've been here before"
|
||||||
|
// before any Startup system runs. spawn_splash should observe
|
||||||
|
// first_run_complete and decline to spawn the overlay.
|
||||||
|
app.insert_resource(SettingsResource(Settings {
|
||||||
|
first_run_complete: true,
|
||||||
|
..Settings::default()
|
||||||
|
}));
|
||||||
|
app.update();
|
||||||
|
assert_eq!(
|
||||||
|
count_splash_roots(&mut app),
|
||||||
|
0,
|
||||||
|
"SplashRoot must NOT spawn on subsequent launches"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn splash_still_shows_when_first_run_incomplete() {
|
||||||
|
use solitaire_data::Settings;
|
||||||
|
let mut app = App::new();
|
||||||
|
app.add_plugins(MinimalPlugins).add_plugins(SplashPlugin);
|
||||||
|
app.init_resource::<ButtonInput<KeyCode>>();
|
||||||
|
app.init_resource::<ButtonInput<MouseButton>>();
|
||||||
|
app.insert_resource(SettingsResource(Settings {
|
||||||
|
first_run_complete: false,
|
||||||
|
..Settings::default()
|
||||||
|
}));
|
||||||
|
app.update();
|
||||||
|
assert_eq!(
|
||||||
|
count_splash_roots(&mut app),
|
||||||
|
1,
|
||||||
|
"SplashRoot must spawn for first-run players (first_run_complete = false)"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn splash_despawns_after_total_duration() {
|
fn splash_despawns_after_total_duration() {
|
||||||
let mut app = headless_app();
|
let mut app = headless_app();
|
||||||
|
|||||||
Reference in New Issue
Block a user