Stats panel can open on top of another modal (missing scrim guard) #75

Closed
opened 2026-05-28 22:12:07 +00:00 by funman300 · 1 comment
Owner

Bug

toggle_stats_screen in stats_plugin.rs does not check whether another ModalScrim is already open before spawning the Stats panel.

All other modal-spawning systems (Settings, Profile, Home, Achievements, Help, Leaderboard, Sync Setup) guard with:

other_modal_scrims: Query<(), (With<ModalScrim>, Without<MyScreen>)>,
// ...
if !other_modal_scrims.is_empty() { return; }

toggle_stats_screen only checks screens.single() for its own screen — it does not check for other open modals. As a result, pressing S (or the Stats HUD button) while the Settings, Profile, Leaderboard, or any other modal is open will spawn a second ModalScrim on top of the existing one.

Steps to reproduce

  1. Open the Settings panel
  2. Press S (Stats keyboard shortcut)
  3. Stats panel spawns on top of Settings — two ModalScrim entities exist simultaneously

Fix

Add other_modal_scrims: Query<(), (With<ModalScrim>, Without<StatsScreen>)> to toggle_stats_screen and return early if !other_modal_scrims.is_empty(), matching the pattern used by every other modal plugin.

## Bug `toggle_stats_screen` in `stats_plugin.rs` does not check whether another `ModalScrim` is already open before spawning the Stats panel. All other modal-spawning systems (Settings, Profile, Home, Achievements, Help, Leaderboard, Sync Setup) guard with: ```rust other_modal_scrims: Query<(), (With<ModalScrim>, Without<MyScreen>)>, // ... if !other_modal_scrims.is_empty() { return; } ``` `toggle_stats_screen` only checks `screens.single()` for *its own* screen — it does not check for other open modals. As a result, pressing `S` (or the Stats HUD button) while the Settings, Profile, Leaderboard, or any other modal is open will spawn a second `ModalScrim` on top of the existing one. ## Steps to reproduce 1. Open the Settings panel 2. Press `S` (Stats keyboard shortcut) 3. Stats panel spawns on top of Settings — two `ModalScrim` entities exist simultaneously ## Fix Add `other_modal_scrims: Query<(), (With<ModalScrim>, Without<StatsScreen>)>` to `toggle_stats_screen` and return early if `!other_modal_scrims.is_empty()`, matching the pattern used by every other modal plugin.
Author
Owner

Fixed in commit f1d9601

Root cause

toggle_stats_screen only checked screens.single() to see if the Stats panel itself was already open. It never checked whether another modal (Settings, Profile, Leaderboard, etc.) was already open. Pressing S while any other modal was visible would spawn a second ModalScrim, violating the one-scrim-at-a-time invariant.

Fix

Added other_modal_scrims: Query<(), (With<ModalScrim>, Without<StatsScreen>)> as a system parameter and added an early-return guard:

} else {
    if !other_modal_scrims.is_empty() {
        return;
    }
    spawn_stats_screen(...);
}

This matches the identical pattern already used by Settings, Profile, Home, Achievements, Help, Leaderboard, and Sync Setup. Also imported ModalScrim which was missing from this file.

Audit method: a Python script scanned all spawn_modal() call sites across the codebase and verified each had a scrim guard within scope. stats_plugin.rs was the only production file that lacked it.

## Fixed in commit f1d9601 ### Root cause `toggle_stats_screen` only checked `screens.single()` to see if the Stats panel itself was already open. It never checked whether *another* modal (Settings, Profile, Leaderboard, etc.) was already open. Pressing `S` while any other modal was visible would spawn a second `ModalScrim`, violating the one-scrim-at-a-time invariant. ### Fix Added `other_modal_scrims: Query<(), (With<ModalScrim>, Without<StatsScreen>)>` as a system parameter and added an early-return guard: ```rust } else { if !other_modal_scrims.is_empty() { return; } spawn_stats_screen(...); } ``` This matches the identical pattern already used by Settings, Profile, Home, Achievements, Help, Leaderboard, and Sync Setup. Also imported `ModalScrim` which was missing from this file. Audit method: a Python script scanned all `spawn_modal()` call sites across the codebase and verified each had a scrim guard within scope. `stats_plugin.rs` was the only production file that lacked it.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: funman300/Ferrous-Solitaire#75