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:
+4
-3
@@ -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);
|
||||
|
||||
@@ -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()))
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ mod gauntlet_integration {
|
||||
/// Build a minimal Gauntlet app with the given archetype and run one tick.
|
||||
fn boot_gauntlet(archetype: CharacterArchetype) -> App {
|
||||
let mut app = App::new();
|
||||
app.add_plugins(SimulationPlugin);
|
||||
app.add_plugins(SimulationPlugin { seed: 0 });
|
||||
test_world::setup_gauntlet(&mut app, archetype);
|
||||
app.update();
|
||||
app
|
||||
|
||||
@@ -221,7 +221,7 @@ fn t3_pause_mid_corridor_discards_movement_and_resumes() {
|
||||
use settled_reach_server::simulation::SimulationPlugin;
|
||||
|
||||
let mut app = App::new();
|
||||
app.add_plugins(SimulationPlugin);
|
||||
app.add_plugins(SimulationPlugin { seed: 0 });
|
||||
// 200×200 walkability map covers the full gauntlet coordinate space.
|
||||
app.insert_resource(WalkabilityMap::new(200, 200, 1));
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ use settled_reach_server::simulation::SimulationPlugin;
|
||||
/// Snapshots are written to SnapshotBuffer for direct inspection.
|
||||
fn build_deterministic_app(seed: u64) -> App {
|
||||
let mut app = App::new();
|
||||
app.add_plugins(SimulationPlugin);
|
||||
app.add_plugins(SimulationPlugin { seed });
|
||||
app.add_plugins(BridgePlugin);
|
||||
app.add_plugins(KnowledgePlugin);
|
||||
app.add_plugins(NpcPlugin);
|
||||
|
||||
@@ -46,7 +46,7 @@ fn malformed_input_produces_sim_error_and_server_continues() {
|
||||
let bridge = TcpBridge::accept_on(listener).expect("accept connection");
|
||||
|
||||
let mut app = App::new();
|
||||
app.add_plugins(SimulationPlugin);
|
||||
app.add_plugins(SimulationPlugin { seed: 0 });
|
||||
app.add_plugins(BridgePlugin);
|
||||
app.add_plugins(KnowledgePlugin);
|
||||
app.init_resource::<TrustEventQueue>();
|
||||
@@ -177,7 +177,7 @@ fn state_hash_populated_in_snapshot() {
|
||||
let bridge = TcpBridge::accept_on(listener).expect("accept connection");
|
||||
|
||||
let mut app = App::new();
|
||||
app.add_plugins(SimulationPlugin);
|
||||
app.add_plugins(SimulationPlugin { seed: 0 });
|
||||
app.add_plugins(BridgePlugin);
|
||||
app.add_plugins(KnowledgePlugin);
|
||||
app.init_resource::<TrustEventQueue>();
|
||||
|
||||
@@ -28,7 +28,7 @@ fn player_moves_north_through_full_pipeline() {
|
||||
|
||||
// Build app
|
||||
let mut app = App::new();
|
||||
app.add_plugins(SimulationPlugin);
|
||||
app.add_plugins(SimulationPlugin { seed: 0 });
|
||||
app.add_plugins(BridgePlugin);
|
||||
app.add_plugins(KnowledgePlugin);
|
||||
app.init_resource::<TrustEventQueue>();
|
||||
|
||||
@@ -39,7 +39,7 @@ const FIXTURE_DIR: &str = "../tests/fixtures/gauntlet";
|
||||
/// Identical to what the server runs in --test-mode.
|
||||
fn build_gauntlet(seed: u64) -> App {
|
||||
let mut app = App::new();
|
||||
app.add_plugins(SimulationPlugin);
|
||||
app.add_plugins(SimulationPlugin { seed: 0 });
|
||||
app.add_plugins(BridgePlugin);
|
||||
app.add_plugins(KnowledgePlugin);
|
||||
app.add_plugins(NpcPlugin);
|
||||
|
||||
@@ -46,7 +46,7 @@ const NUM_TICKS: usize = 10;
|
||||
/// Mirrors the setup in determinism.rs / main.rs.
|
||||
fn build_app(seed: u64) -> App {
|
||||
let mut app = App::new();
|
||||
app.add_plugins(SimulationPlugin);
|
||||
app.add_plugins(SimulationPlugin { seed: 0 });
|
||||
app.add_plugins(BridgePlugin);
|
||||
app.add_plugins(KnowledgePlugin);
|
||||
app.add_plugins(NpcPlugin);
|
||||
|
||||
@@ -6,7 +6,7 @@ use settled_reach_server::simulation::SimulationPlugin;
|
||||
#[test]
|
||||
fn movement_validated_within_app() {
|
||||
let mut app = App::new();
|
||||
app.add_plugins(SimulationPlugin);
|
||||
app.add_plugins(SimulationPlugin { seed: 0 });
|
||||
|
||||
// Insert a walkability map with one blocked tile
|
||||
let mut map = WalkabilityMap::new(10, 10, 1);
|
||||
@@ -56,7 +56,7 @@ fn movement_validated_within_app() {
|
||||
#[test]
|
||||
fn entity_collision_blocks_movement() {
|
||||
let mut app = App::new();
|
||||
app.add_plugins(SimulationPlugin);
|
||||
app.add_plugins(SimulationPlugin { seed: 0 });
|
||||
app.insert_resource(WalkabilityMap::new(10, 10, 1));
|
||||
|
||||
// Stationary entity at (5,4)
|
||||
|
||||
@@ -66,7 +66,7 @@ fn perf_tick_timing() {
|
||||
let bridge = TcpBridge::accept_on(listener).expect("accept connection");
|
||||
|
||||
let mut app = App::new();
|
||||
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);
|
||||
|
||||
@@ -7,7 +7,7 @@ use settled_reach_server::simulation::SimulationPlugin;
|
||||
#[test]
|
||||
fn world_boots_and_ticks() {
|
||||
let mut app = App::new();
|
||||
app.add_plugins(SimulationPlugin);
|
||||
app.add_plugins(SimulationPlugin { seed: 0 });
|
||||
|
||||
// Verify initial state
|
||||
let time = app.world().resource::<SimulationTime>();
|
||||
|
||||
@@ -112,7 +112,7 @@ fn build_storyteller_app() -> App {
|
||||
bridge::types::CharacterArchetype, simulation::SimulationPlugin, test_world,
|
||||
};
|
||||
let mut app = App::new();
|
||||
app.add_plugins(SimulationPlugin);
|
||||
app.add_plugins(SimulationPlugin { seed: 0 });
|
||||
test_world::setup_gauntlet(&mut app, CharacterArchetype::default());
|
||||
app
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user