refactor(simulation): replace ad-hoc system ordering with TickPhase pipeline (#843)

10-phase linear pipeline: PreInput → Input → Movement → Simulation →
Economy → Storyteller → Snapshot → PostSnapshot → Knowledge → TickAdvance.

Each system assigned to exactly one phase via .in_set(TickPhase::X).
Cross-phase .after()/.before() eliminated — only intra-phase ordering
remains. Prevents schedule cycles by construction.

SimulationPlugin refactored into sub-plugins by domain:
  - InputPlugin (player actions, interactions, dialogue dispatch)
  - MovementPlugin (pathfinding, movement validation, spatial indexing)
  - SocialPlugin (conversations, sound, voice enrichment, follow state)
  - EconomyPlugin (tâtonnement tick, IPC query serving)
  - TimePlugin (chunk streaming, news ticker, tick advancement)

All other plugins (NPC, Knowledge, Perception, Storyteller, Settings,
Bridge) updated to use TickPhase assignments instead of cross-plugin
ordering constraints. BridgePlugin trimmed to bridge I/O concerns only.

Part A of #843. Parts B (multi-threaded executor) and C (background
workers) follow.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-10 23:51:06 +02:00
co-authored by Claude Opus 4.6
parent d434235985
commit 47d4918cc8
15 changed files with 443 additions and 274 deletions
+44 -35
View File
@@ -213,31 +213,36 @@ pub struct BridgePlugin;
impl Plugin for BridgePlugin {
fn build(&self, app: &mut App) {
use crate::tick_phases::TickPhase;
app.init_resource::<SnapshotBuffer>()
.init_resource::<ServerRunning>()
.init_resource::<HandshakeState>()
.init_resource::<SimErrorBuffer>()
.init_resource::<debug::DebugCommandBuffer>()
.init_resource::<DebugEnabled>()
.init_resource::<crate::perception::query::VisibilityGeometry>()
.init_resource::<crate::perception::query::ActivePerceptionMode>()
// Bridge I/O — PreInput (receive) and PostSnapshot (send)
.add_systems(
Update,
receive_bridge_inputs.in_set(TickPhase::PreInput),
)
.add_systems(
Update,
send_bridge_snapshot.in_set(TickPhase::PostSnapshot),
)
// Debug commands — Snapshot phase
.add_systems(
Update,
debug::handle_debug_commands
.in_set(TickPhase::Snapshot),
)
// Monologue chain — Simulation phase, strict intra-phase sequence.
// trigger_event_monologue must run after conversations + sound (also Simulation).
.add_systems(
Update,
(
receive_bridge_inputs.before(crate::simulation::input::process_player_input),
debug::handle_debug_commands
.after(crate::simulation::input::process_player_input)
// Note: NOT ordered after tick_economy_simulation — that creates a
// schedule cycle (debug → observer → advance_tick → econ → debug).
// GetEconState reads from EconStateResource which may be one tick
// stale on economy-tick boundaries. Acceptable for debug tooling.
.before(crate::perception::observer::compute_observer_snapshot),
crate::perception::observer::compute_visibility_geometry
.after(crate::simulation::movement::validate_movement),
crate::simulation::interaction::compute_nearby_interactions
.after(crate::simulation::movement::validate_movement),
crate::simulation::monologue::trigger_monologue
.after(crate::simulation::movement::validate_movement),
crate::simulation::monologue::trigger_monologue,
crate::simulation::monologue::trigger_recognition_monologue
.after(crate::simulation::monologue::trigger_monologue)
.after(crate::perception::anomaly::detect_anomalies),
@@ -248,29 +253,33 @@ impl Plugin for BridgePlugin {
.after(crate::simulation::sound::collect_sound_events)
.after(crate::simulation::conversation::run_npc_conversations)
.after(crate::simulation::dialogue::process_walk_away),
// Contradiction monologue fires from queue populated by prior tick's
// process_knowledge_events (which runs after the snapshot).
crate::simulation::monologue::process_contradiction_monologue
.after(crate::simulation::monologue::trigger_event_monologue),
crate::simulation::follow::update_follow_state
.after(crate::perception::observer::compute_visibility_geometry)
.after(crate::simulation::movement::validate_movement)
.before(crate::perception::observer::compute_observer_snapshot),
crate::perception::observer::compute_observer_snapshot
.after(crate::perception::observer::compute_visibility_geometry)
.after(crate::simulation::interaction::compute_nearby_interactions)
.after(crate::simulation::monologue::process_contradiction_monologue)
.after(crate::simulation::dialogue::process_talk_interaction)
.after(crate::simulation::dialogue::process_confrontation_response)
.after(crate::simulation::dialogue::process_dialogue_response)
.before(crate::simulation::time::advance_tick),
crate::perception::observation::emit_observation_events
.after(crate::perception::observer::compute_observer_snapshot)
.before(crate::simulation::time::advance_tick),
send_bridge_snapshot
.after(crate::perception::observer::compute_observer_snapshot),
),
)
.in_set(TickPhase::Simulation),
)
// Observation systems — Simulation phase (reads positions, feeds snapshot)
.add_systems(
Update,
(
crate::perception::observer::compute_visibility_geometry,
crate::simulation::interaction::compute_nearby_interactions,
)
.in_set(TickPhase::Simulation),
)
// Observer snapshot assembly — Snapshot phase
.add_systems(
Update,
crate::perception::observer::compute_observer_snapshot
.in_set(TickPhase::Snapshot),
)
// Post-snapshot: emit observation events
.add_systems(
Update,
crate::perception::observation::emit_observation_events
.in_set(TickPhase::PostSnapshot),
);
tracing::debug!("BridgePlugin initialized");
}
}