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
+33
View File
@@ -0,0 +1,33 @@
//! Economy phase plugin — tâtonnement simulation tick and IPC query serving.
//!
//! All systems run in [`TickPhase::Economy`]. Intra-phase ordering:
//! - tick_economy_simulation → serve_econ_state_query (query reads fresh signals)
use bevy_app::prelude::*;
use bevy_ecs::schedule::IntoScheduleConfigs;
use crate::tick_phases::TickPhase;
pub struct EconomyPlugin;
impl Plugin for EconomyPlugin {
fn build(&self, app: &mut App) {
// Economy simulation (#821, D-031) — loaded once at startup, no-op when DB absent.
// Uses seed 0 for now; will be threaded through StartupMessage world seed (#826).
if let Some((econ_sim, econ_state)) = super::economy::try_load_economy(0) {
app.insert_resource(econ_sim).insert_resource(econ_state);
}
// EconQueryBuffer: always registered so EconStateQuery PlayerActions are accepted
// even when the economy DB is absent (queries just produce no response).
app.init_resource::<super::economy::EconQueryBuffer>()
.add_systems(
Update,
(
super::economy::tick_economy_simulation,
super::economy::serve_econ_state_query
.after(super::economy::tick_economy_simulation),
)
.in_set(TickPhase::Economy),
);
}
}