refactor(engine): first ambiguity burn-down batch — 302 → 198 pairs #146

Merged
funman300 merged 1 commits from refactor/ambiguity-burndown into master 2026-07-07 03:37:53 +00:00
5 changed files with 70 additions and 22 deletions
+5 -3
View File
@@ -570,8 +570,8 @@ impl Plugin for CardPlugin {
tick_flip_anim, tick_flip_anim,
update_drag_shadow, update_drag_shadow,
update_card_shadows_on_drag.after(sync_cards_on_change), update_card_shadows_on_drag.after(sync_cards_on_change),
tick_hint_highlight, tick_hint_highlight.after(GameMutation),
handle_right_click, handle_right_click.after(GameMutation),
tick_right_click_highlights, tick_right_click_highlights,
clear_right_click_highlights_on_state_change.after(GameMutation), clear_right_click_highlights_on_state_change.after(GameMutation),
clear_right_click_highlights_on_pause, clear_right_click_highlights_on_pause,
@@ -580,7 +580,9 @@ impl Plugin for CardPlugin {
.after(GameMutation) .after(GameMutation)
.run_if(resource_changed::<GameStateResource>), .run_if(resource_changed::<GameStateResource>),
collect_resize_events.after(LayoutSystem::UpdateOnResize), collect_resize_events.after(LayoutSystem::UpdateOnResize),
snap_cards_on_window_resize.after(collect_resize_events), snap_cards_on_window_resize
.after(collect_resize_events)
.after(GameMutation),
), ),
); );
+48 -9
View File
@@ -63,6 +63,18 @@ pub struct GameOverScreen;
#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)] #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
pub struct GameMutation; pub struct GameMutation;
/// System set for every writer of [`crate::events::NewGameRequestEvent`].
///
/// Many UI entry points fire this trigger (buttons, keyboard, modals,
/// mode pickers). Their relative append order within a frame is
/// meaningless — consumers drain the whole queue — so members are
/// registered `.in_set(NewGameRequestWriters).ambiguous_with(NewGameRequestWriters)`
/// to declare writer-vs-writer order irrelevant instead of leaving it as an
/// ambiguity (#143). Only ever combine with `.ambiguous_with` on the same
/// set; do NOT hang ordering edges off this set.
#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
pub struct NewGameRequestWriters;
/// Persistence path for the in-progress game state file. `None` disables I/O. /// Persistence path for the in-progress game state file. `None` disables I/O.
#[derive(Resource, Debug, Clone)] #[derive(Resource, Debug, Clone)]
pub struct GameStatePath(pub Option<PathBuf>); pub struct GameStatePath(pub Option<PathBuf>);
@@ -208,7 +220,13 @@ impl Plugin for GamePlugin {
.add_message::<AppLifecycle>() .add_message::<AppLifecycle>()
// add_message is idempotent; SettingsPlugin also registers this. // add_message is idempotent; SettingsPlugin also registers this.
.add_message::<crate::settings_plugin::SettingsChangedEvent>() .add_message::<crate::settings_plugin::SettingsChangedEvent>()
.add_systems(Update, poll_pending_new_game_seed.before(GameMutation)) .add_systems(
Update,
poll_pending_new_game_seed
.before(GameMutation)
.in_set(NewGameRequestWriters)
.ambiguous_with(NewGameRequestWriters),
)
.add_systems( .add_systems(
Update, Update,
(handle_new_game, handle_draw, handle_move, handle_undo) (handle_new_game, handle_draw, handle_move, handle_undo)
@@ -217,19 +235,40 @@ impl Plugin for GamePlugin {
) )
.add_systems(Update, check_no_moves.after(GameMutation)) .add_systems(Update, check_no_moves.after(GameMutation))
.add_systems(Update, record_replay_on_win.after(GameMutation)) .add_systems(Update, record_replay_on_win.after(GameMutation))
.add_systems(Update, handle_confirm_input.after(GameMutation)) .add_systems(
.add_systems(Update, handle_confirm_button_input.after(GameMutation)) Update,
.add_systems(Update, handle_game_over_input.after(GameMutation)) (
.add_systems(Update, handle_game_over_button_input.after(GameMutation)) handle_confirm_input,
handle_confirm_button_input,
handle_game_over_input,
handle_game_over_button_input,
)
.after(GameMutation)
.in_set(NewGameRequestWriters)
.ambiguous_with(NewGameRequestWriters),
)
// Restore prompt: spawn the modal once the splash is gone, // Restore prompt: spawn the modal once the splash is gone,
// route Continue / New Game intents back into the existing // route Continue / New Game intents back into the existing
// GameMutation flow. // GameMutation flow.
.add_systems(Update, spawn_restore_prompt_if_pending) .add_systems(Update, spawn_restore_prompt_if_pending)
.add_systems(Update, handle_restore_prompt.before(GameMutation)) // All three pre-mutation game-state writers are chained: elapsed
.add_systems(Update, sync_settings_to_game.before(GameMutation)) // time ticks first, settings sync next, then the restore prompt —
// a deterministic spine instead of three unordered ResMut holders
// (ambiguity burn-down, #143).
.add_systems(
Update,
(
tick_elapsed_time,
sync_settings_to_game,
handle_restore_prompt
.in_set(NewGameRequestWriters)
.ambiguous_with(NewGameRequestWriters),
)
.chain()
.before(GameMutation),
)
.init_resource::<AutoSaveTimer>() .init_resource::<AutoSaveTimer>()
.add_systems(Update, tick_elapsed_time) .add_systems(Update, auto_save_game_state.after(GameMutation))
.add_systems(Update, auto_save_game_state)
.add_systems(Last, save_game_state_on_exit); .add_systems(Last, save_game_state_on_exit);
} }
} }
+14 -8
View File
@@ -36,7 +36,7 @@ use crate::events::{
UndoRequestEvent, WinStreakMilestoneEvent, UndoRequestEvent, WinStreakMilestoneEvent,
}; };
use crate::font_plugin::FontResource; use crate::font_plugin::FontResource;
use crate::game_plugin::GameMutation; use crate::game_plugin::{GameMutation, NewGameRequestWriters};
#[cfg(target_os = "android")] #[cfg(target_os = "android")]
use crate::input_plugin::TouchDragSet; use crate::input_plugin::TouchDragSet;
use crate::layout::HUD_BAND_HEIGHT; use crate::layout::HUD_BAND_HEIGHT;
@@ -478,10 +478,12 @@ impl Plugin for HudPlugin {
.add_systems(Update, announce_auto_complete.after(GameMutation)) .add_systems(Update, announce_auto_complete.after(GameMutation))
.add_systems( .add_systems(
Update, Update,
update_selection_hud.run_if( update_selection_hud
resource_exists_and_changed::<SelectionState> .after(GameMutation)
.or(resource_exists_and_changed::<GameStateResource>), .run_if(
), resource_exists_and_changed::<SelectionState>
.or(resource_exists_and_changed::<GameStateResource>),
),
) )
.add_systems(Update, update_hud_typography) .add_systems(Update, update_hud_typography)
.add_systems( .add_systems(
@@ -503,13 +505,17 @@ impl Plugin for HudPlugin {
.add_systems( .add_systems(
Update, Update,
( (
handle_new_game_button, handle_new_game_button
.in_set(NewGameRequestWriters)
.ambiguous_with(NewGameRequestWriters),
handle_undo_button, handle_undo_button,
handle_pause_button, handle_pause_button,
handle_help_button, handle_help_button,
handle_hint_button, handle_hint_button.after(GameMutation),
handle_modes_button, handle_modes_button,
handle_mode_option_click, handle_mode_option_click
.in_set(NewGameRequestWriters)
.ambiguous_with(NewGameRequestWriters),
handle_modes_backdrop_click, handle_modes_backdrop_click,
close_modes_popover_on_escape, close_modes_popover_on_escape,
handle_menu_button, handle_menu_button,
+1 -1
View File
@@ -38,7 +38,7 @@ mod tests {
/// ordering edge — add `.before`/`.after` (order matters) or /// ordering edge — add `.before`/`.after` (order matters) or
/// `.ambiguous_with` (provably order-independent) at the registration /// `.ambiguous_with` (provably order-independent) at the registration
/// site. When triage lowers the real count, lower this constant too. /// site. When triage lowers the real count, lower this constant too.
const AMBIGUITY_BASELINE: usize = 302; const AMBIGUITY_BASELINE: usize = 198;
fn cluster_app() -> App { fn cluster_app() -> App {
let mut app = App::new(); let mut app = App::new();
+2 -1
View File
@@ -11,6 +11,7 @@ use solitaire_core::{FOUNDATIONS, TABLEAUS};
use solitaire_core::Suit; use solitaire_core::Suit;
use crate::events::{HintVisualEvent, StateChangedEvent}; use crate::events::{HintVisualEvent, StateChangedEvent};
use crate::game_plugin::GameMutation;
use crate::hud_plugin::HudVisibility; use crate::hud_plugin::HudVisibility;
use crate::layout::{ use crate::layout::{
Layout, LayoutResource, LayoutSystem, TABLE_COLOUR, apply_dynamic_tableau_fan, compute_layout, Layout, LayoutResource, LayoutSystem, TABLE_COLOUR, apply_dynamic_tableau_fan, compute_layout,
@@ -103,7 +104,7 @@ impl Plugin for TablePlugin {
apply_theme_on_settings_change, apply_theme_on_settings_change,
apply_hint_pile_highlight, apply_hint_pile_highlight,
tick_hint_pile_highlights, tick_hint_pile_highlights,
sync_pile_marker_visibility, sync_pile_marker_visibility.after(GameMutation),
), ),
); );
} }