//! Integration test: contamination activation mechanic (#254). //! //! Verifies that the storyteller activates contamination after the configured //! delay and that all ActiveFork triangles receive pressure. use bevy_ecs::prelude::*; use bevy_ecs::schedule::Schedule; use std::collections::BTreeMap; use settled_reach_server::knowledge::types::StableId; use settled_reach_server::simulation::tier::ActiveSim; use settled_reach_server::simulation::time::SimulationTime; use settled_reach_server::simulation::triangle::{ RoleId, TemplateId, TriangleClassification, TriangleId, TrianglePhase, TriangleState, }; use settled_reach_server::storyteller::{ tick_contamination_activation, ContaminationActive, ContaminationEventQueue, CONTAMINATION_DELAY_TICKS, CONTAMINATION_PRESSURE_DELTA, }; fn make_triangle( slug: &str, classification: TriangleClassification, base_ids: [u64; 3], ) -> TriangleState { let mut role_assignments = BTreeMap::new(); role_assignments.insert(RoleId::new("role-a"), StableId(base_ids[0])); role_assignments.insert(RoleId::new("role-b"), StableId(base_ids[1])); role_assignments.insert(RoleId::new("role-c"), StableId(base_ids[2])); TriangleState { triangle_id: TriangleId::from_seed_and_slug(0, slug), role_assignments, tension: 0, phase: match classification { TriangleClassification::ActiveFork => TrianglePhase::Simmering, TriangleClassification::PassiveTension => TrianglePhase::Dormant, }, tension_rate: 1, template_id: TemplateId::from_seed_and_slug(0, "authored"), classification, } } /// Acceptance test: run past CONTAMINATION_DELAY_TICKS, assert ContaminationActive /// is set and all ActiveFork TriangleState entities have tension > 0. #[test] fn contamination_activates_after_delay() { let mut world = World::new(); world.init_resource::(); world.init_resource::(); world.init_resource::(); // Spawn 3 ActiveFork + 2 PassiveTension triangles (matching real content) let fork1 = world .spawn(( make_triangle("hub-power", TriangleClassification::ActiveFork, [1, 2, 3]), ActiveSim, )) .id(); let fork2 = world .spawn(( make_triangle( "bar-tensions", TriangleClassification::ActiveFork, [4, 5, 6], ), ActiveSim, )) .id(); let fork3 = world .spawn(( make_triangle( "informant-question", TriangleClassification::ActiveFork, [7, 8, 9], ), ActiveSim, )) .id(); let passive1 = world .spawn(( make_triangle( "worried-knowledge", TriangleClassification::PassiveTension, [10, 11, 12], ), ActiveSim, )) .id(); let passive2 = world .spawn(( make_triangle( "worried-partner", TriangleClassification::PassiveTension, [13, 14, 15], ), ActiveSim, )) .id(); let mut schedule = Schedule::default(); schedule.add_systems(tick_contamination_activation); // Simulate past the delay threshold (CONTAMINATION_DELAY_TICKS = 300) for tick in 0..=CONTAMINATION_DELAY_TICKS { world.resource_mut::().tick = tick; schedule.run(&mut world); } // Assert ContaminationActive is set assert!( world.resource::().0, "ContaminationActive must be true after CONTAMINATION_DELAY_TICKS ({}) ticks", CONTAMINATION_DELAY_TICKS ); // Assert all ActiveFork triangles have tension > 0 for (label, entity) in [ ("hub-power", fork1), ("bar-tensions", fork2), ("informant-question", fork3), ] { let state = world .get::(entity) .unwrap_or_else(|| panic!("{} triangle entity should exist", label)); assert!( state.tension > 0, "ActiveFork triangle '{}' should have tension > 0 after contamination, got {}", label, state.tension ); assert_eq!( state.tension, CONTAMINATION_PRESSURE_DELTA, "ActiveFork triangle '{}' tension should be exactly {} (contamination delta)", label, CONTAMINATION_PRESSURE_DELTA ); } // Assert PassiveTension triangles were NOT pressured for (label, entity) in [ ("worried-knowledge", passive1), ("worried-partner", passive2), ] { let state = world .get::(entity) .unwrap_or_else(|| panic!("{} triangle entity should exist", label)); assert_eq!( state.tension, 0, "PassiveTension triangle '{}' should have tension 0 (not affected by contamination)", label ); } // Assert exactly one ContaminationEvent was emitted let events = world.resource_mut::().drain(); assert_eq!(events.len(), 1, "exactly one ContaminationEvent expected"); assert_eq!(events[0].tick, CONTAMINATION_DELAY_TICKS); assert_eq!(events[0].triangles_affected, 3); }