feat(simulation): thread world seed from StartupMessage into economy (#826)

Replaces the hardcoded seed=0 with the seed received in StartupMessage,
threading it through SimulationPlugin -> EconomyPlugin / SimRng. Integration
test fixtures updated for the new SimulationPlugin { seed } signature.
This commit is contained in:
2026-04-14 17:21:53 +02:00
parent 521c682175
commit 920ea0582f
16 changed files with 39 additions and 28 deletions
+4 -3
View File
@@ -147,7 +147,7 @@ fn main() {
let mut app = App::new();
settled_reach_server::tick_phases::TickPhase::configure(&mut app);
app.add_plugins(SimulationPlugin);
app.add_plugins(SimulationPlugin { seed });
app.add_plugins(BridgePlugin);
app.add_plugins(settled_reach_server::knowledge::KnowledgePlugin);
app.add_plugins(settled_reach_server::npc::NpcPlugin);
@@ -176,7 +176,8 @@ fn main() {
app.insert_resource(BridgeResource::new(bridge));
app.insert_resource(HandshakeState::Complete);
// Override SimRng with the chosen seed (SimulationPlugin defaults to seed 0)
// SimulationPlugin { seed } already inserts SimRng with the correct seed.
// Re-insert here as a defensive override in case plugin ordering shifts.
app.insert_resource(settled_reach_server::simulation::rng::SimRng::new(seed));
// Initialize empty line pool index (populated by generator pipeline in v0.2).
@@ -359,7 +360,7 @@ fn dump_schedule_graph() {
let mut app = App::new();
settled_reach_server::tick_phases::TickPhase::configure(&mut app);
app.add_plugins(SimulationPlugin);
app.add_plugins(SimulationPlugin { seed: 0 });
app.add_plugins(BridgePlugin);
app.add_plugins(settled_reach_server::knowledge::KnowledgePlugin);
app.add_plugins(settled_reach_server::npc::NpcPlugin);
+2 -1
View File
@@ -358,9 +358,10 @@ pub fn try_load_economy(run_seed: u64) -> Option<(EconSimResource, EconStateReso
match Simulation::load_auto(run_seed) {
Ok(sim) => {
tracing::info!(
econ_seed = run_seed,
commodities = sim.economy().commodities.len(),
active_nodes = sim.nodes.len(),
"Economy simulation loaded"
"Economy simulation loaded with seed"
);
Some((EconSimResource::new(sim), EconStateResource::default()))
}
+7 -3
View File
@@ -8,13 +8,17 @@ use bevy_ecs::schedule::IntoScheduleConfigs;
use crate::tick_phases::TickPhase;
pub struct EconomyPlugin;
pub struct EconomyPlugin {
/// World seed from `StartupMessage` (#826). Passed to `try_load_economy` so the
/// tâtonnement simulation is seeded deterministically from the client's world seed.
pub seed: u64,
}
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) {
// Seed comes from StartupMessage.world_seed, threaded via SimulationPlugin (#826).
if let Some((econ_sim, econ_state)) = super::economy::try_load_economy(self.seed) {
app.insert_resource(econ_sim).insert_resource(econ_state);
}
// EconQueryBuffer: always registered so EconStateQuery PlayerActions are accepted
+8 -3
View File
@@ -50,7 +50,12 @@ pub mod zone;
/// Each sub-plugin owns its domain's systems, resources, and phase assignment.
/// No system registration happens here — only sub-plugin composition and
/// shared resources needed by multiple sub-plugins.
pub struct SimulationPlugin;
///
/// `seed` is threaded through to [`economy_plugin::EconomyPlugin`] so the
/// economy simulation uses the world seed from `StartupMessage` (#826).
pub struct SimulationPlugin {
pub seed: u64,
}
impl Plugin for SimulationPlugin {
fn build(&self, app: &mut App) {
@@ -59,7 +64,7 @@ impl Plugin for SimulationPlugin {
// Shared resources needed by multiple sub-plugins.
// Each sub-plugin inits its own domain-specific resources.
app.insert_resource(rng::SimRng::new(0))
app.insert_resource(rng::SimRng::new(self.seed))
.init_resource::<save_io::SaveLoadPending>()
.init_resource::<crate::knowledge::EntityRegistry>()
// Triangle escalation event queue (#250) — consumed by storyteller + npc plugins
@@ -81,7 +86,7 @@ impl Plugin for SimulationPlugin {
app.add_plugins(input_plugin::InputPlugin);
app.add_plugins(movement_plugin::MovementPlugin);
app.add_plugins(social_plugin::SocialPlugin);
app.add_plugins(economy_plugin::EconomyPlugin);
app.add_plugins(economy_plugin::EconomyPlugin { seed: self.seed });
// Background worker tick integration (#843 Part C)
app.add_systems(
+5 -5
View File
@@ -695,7 +695,7 @@ mod tests {
#[test]
fn gauntlet_setup_creates_expected_entities() {
let mut app = App::new();
app.add_plugins(crate::simulation::SimulationPlugin);
app.add_plugins(crate::simulation::SimulationPlugin { seed: 0 });
app.add_plugins(crate::knowledge::KnowledgePlugin);
app.add_plugins(crate::npc::NpcPlugin);
@@ -718,7 +718,7 @@ mod tests {
#[test]
fn hub_center_is_walkable() {
let mut app = App::new();
app.add_plugins(crate::simulation::SimulationPlugin);
app.add_plugins(crate::simulation::SimulationPlugin { seed: 0 });
app.add_plugins(crate::knowledge::KnowledgePlugin);
app.add_plugins(crate::npc::NpcPlugin);
@@ -735,7 +735,7 @@ mod tests {
#[test]
fn occlusion_north_wall_blocks() {
let mut app = App::new();
app.add_plugins(crate::simulation::SimulationPlugin);
app.add_plugins(crate::simulation::SimulationPlugin { seed: 0 });
app.add_plugins(crate::knowledge::KnowledgePlugin);
app.add_plugins(crate::npc::NpcPlugin);
@@ -754,7 +754,7 @@ mod tests {
#[test]
fn corridor_connects_hub_to_occlusion() {
let mut app = App::new();
app.add_plugins(crate::simulation::SimulationPlugin);
app.add_plugins(crate::simulation::SimulationPlugin { seed: 0 });
app.add_plugins(crate::knowledge::KnowledgePlugin);
app.add_plugins(crate::npc::NpcPlugin);
@@ -771,7 +771,7 @@ mod tests {
#[test]
fn stable_id_ranges_match_spec() {
let mut app = App::new();
app.add_plugins(crate::simulation::SimulationPlugin);
app.add_plugins(crate::simulation::SimulationPlugin { seed: 0 });
app.add_plugins(crate::knowledge::KnowledgePlugin);
app.add_plugins(crate::npc::NpcPlugin);