refactor(engine): ambiguity gate covers the full cluster — batches 1-3, 585 pairs burned to zero
Port the July 7 gate-extender branch onto current master (the You-hub, touch action bar, glass tab bar, Phase L, and hint-ghost work all landed since) and finish batch 3. cluster_app now includes the mode, replay, input, radial, tooltip, cursor, touch-selection, and safe-area plugins on top of batches 1-2; the 585 ambiguous pairs the expansion exposed are annotated down to zero and the gate stays assert_eq!(count, 0). New ordering machinery: - ReplayPlayback: playback driver chain on the pre-mutation spine (PointerInput < ReplayPlayback < overlay chain < GameMutation) - ModeStart: mode-start handlers between the stats abandon-recorder and GameMutation; ChallengeCompletion < DailyCompletion < WeeklyGoalsEval pinned inside ProgressUpdate so `.after(ProgressUpdate)` readers see settled progress - PointerInput: public set wrapping the input chain; AbandonRecord on the stats abandon-recorder; PendingHint around the async hint pipeline; HintGhostFx for the ghost chain (after the full visual spine) - Clubs: MoveRequestWriters, WarningToastWriters join the existing request/toast writer clubs; UiTextFx grows the overlay/tooltip/safe-area chrome painters; SettingsAccess grows the lag-tolerant settings readers Gate: cargo test --workspace green (993 engine tests), clippy --all-targets -D warnings clean, rustfmt applied. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -46,6 +46,14 @@ pub struct StatsStoragePath(pub Option<PathBuf>);
|
||||
#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct StatsUpdate;
|
||||
|
||||
/// Set holding the abandon-recorder (`update_stats_on_new_game`). It must
|
||||
/// read the outgoing game's `move_count` before any mode-start handler or
|
||||
/// `GameMutation` clobbers the game state — mode plugins order their reset
|
||||
/// handlers `.after(AbandonRecord)` via `crate::game_plugin::ModeStart`
|
||||
/// (#143).
|
||||
#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct AbandonRecord;
|
||||
|
||||
/// Marker component on the stats overlay root node.
|
||||
#[derive(Component, Debug)]
|
||||
pub struct StatsScreen;
|
||||
@@ -205,10 +213,34 @@ impl Plugin for StatsPlugin {
|
||||
// constraints (win_summary_plugin: cache_win_data.before(StatsUpdate)),
|
||||
// and a system cannot be both inside a set and individually before a
|
||||
// set-level ordering constraint.
|
||||
.add_systems(Update, update_stats_on_new_game.before(GameMutation))
|
||||
.add_systems(
|
||||
Update,
|
||||
update_stats_on_win.after(GameMutation).in_set(StatsUpdate),
|
||||
update_stats_on_new_game
|
||||
.in_set(AbandonRecord)
|
||||
.before(GameMutation)
|
||||
// Reads the shared new-game request stream via its own
|
||||
// cursor — append order is irrelevant, so it joins the
|
||||
// self-ambiguous writer club (NewGameRequestWriters must
|
||||
// not carry set-level edges; see its doc). The explicit
|
||||
// edges pin it after the pause/settings/selection
|
||||
// mutators and the pre-mutation game-state spine it
|
||||
// reads `move_count` from (#143).
|
||||
.in_set(crate::game_plugin::NewGameRequestWriters)
|
||||
.ambiguous_with(crate::game_plugin::NewGameRequestWriters)
|
||||
.after(crate::game_plugin::PreMutation)
|
||||
.after(crate::pause_plugin::PauseMutation)
|
||||
.after(crate::settings_plugin::SettingsMutation)
|
||||
.after(crate::selection_plugin::SelectionKeySet),
|
||||
)
|
||||
.add_systems(
|
||||
Update,
|
||||
update_stats_on_win
|
||||
.after(GameMutation)
|
||||
.in_set(StatsUpdate)
|
||||
// Also emits toasts (streak milestones) — join the
|
||||
// self-ambiguous writer club (#143).
|
||||
.in_set(crate::game_plugin::InfoToastWriters)
|
||||
.ambiguous_with(crate::game_plugin::InfoToastWriters),
|
||||
)
|
||||
.add_systems(
|
||||
Update,
|
||||
@@ -216,29 +248,69 @@ impl Plugin for StatsPlugin {
|
||||
// the NewGameRequestEvent it emits is not visible to
|
||||
// update_stats_on_new_game in the same frame — otherwise
|
||||
// record_abandoned() fires twice on every forfeit (#21).
|
||||
// It drains the ForfeitEvent the pause/forfeit modal chain
|
||||
// emits, and shares the new-game request stream with the
|
||||
// other writers (#143).
|
||||
handle_forfeit
|
||||
.before(GameMutation)
|
||||
.before(update_stats_on_new_game),
|
||||
.before(update_stats_on_new_game)
|
||||
.after(crate::pause_plugin::PauseMutation)
|
||||
.after(crate::selection_plugin::SelectionKeySet)
|
||||
.in_set(crate::game_plugin::NewGameRequestWriters)
|
||||
.ambiguous_with(crate::game_plugin::NewGameRequestWriters),
|
||||
)
|
||||
// Open/close/tab handling moved to `you_hub_plugin`
|
||||
// (Phase E) — this plugin now owns body content, replay
|
||||
// selector behavior, and scroll.
|
||||
.add_systems(Update, refresh_replay_history_on_win.after(GameMutation))
|
||||
.add_systems(Update, handle_watch_replay_button)
|
||||
.add_systems(Update, handle_copy_share_link_button)
|
||||
//
|
||||
// Stats overlay UI: one deterministic chain — every member
|
||||
// touches the replay-selector state or the overlay's nodes, and
|
||||
// handle_watch_replay_button resets the live game, so the chain
|
||||
// sits between GameMutation and the auto-complete driver on the
|
||||
// game-state timeline (#143). The toast-emitting members join
|
||||
// the self-ambiguous InfoToastWriters set so the toast pipeline
|
||||
// (AnimFx) drains them this frame.
|
||||
.add_systems(
|
||||
Update,
|
||||
(
|
||||
refresh_replay_history_on_win,
|
||||
handle_replay_selector_buttons,
|
||||
repaint_replay_selector_caption,
|
||||
repaint_replay_selector_detail,
|
||||
// Marker-filtered Text repaints — disjoint from the
|
||||
// HUD text painters (UiTextFx club, #143).
|
||||
repaint_replay_selector_caption
|
||||
.in_set(crate::ui_theme::UiTextFx)
|
||||
.ambiguous_with(crate::ui_theme::UiTextFx),
|
||||
repaint_replay_selector_detail
|
||||
.in_set(crate::ui_theme::UiTextFx)
|
||||
.ambiguous_with(crate::ui_theme::UiTextFx),
|
||||
handle_watch_replay_button
|
||||
.in_set(crate::game_plugin::InfoToastWriters)
|
||||
.ambiguous_with(crate::game_plugin::InfoToastWriters),
|
||||
handle_copy_share_link_button
|
||||
.in_set(crate::game_plugin::InfoToastWriters)
|
||||
.ambiguous_with(crate::game_plugin::InfoToastWriters),
|
||||
)
|
||||
.chain(),
|
||||
.chain()
|
||||
.after(GameMutation)
|
||||
// The overlay repaints read this frame's settled stats,
|
||||
// progress, and achievement evaluation (level line,
|
||||
// streak).
|
||||
.after(StatsUpdate)
|
||||
.after(crate::progress_plugin::ProgressUpdate)
|
||||
.after(crate::achievement_plugin::AchievementEval)
|
||||
.before(crate::auto_complete_plugin::AutoComplete),
|
||||
)
|
||||
.add_systems(Update, scroll_stats_panel)
|
||||
.add_systems(
|
||||
Update,
|
||||
crate::ui_modal::touch_scroll_panel::<StatsScrollable>,
|
||||
scroll_stats_panel
|
||||
.in_set(crate::ui_modal::PanelScroll)
|
||||
.ambiguous_with(crate::ui_modal::PanelScroll),
|
||||
)
|
||||
.add_systems(
|
||||
Update,
|
||||
crate::ui_modal::touch_scroll_panel::<StatsScrollable>
|
||||
.in_set(crate::ui_modal::PanelScroll)
|
||||
.ambiguous_with(crate::ui_modal::PanelScroll),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user