@@ -516,6 +516,30 @@ void addClosureMotionSystems(
516516 .triggerPolicy = safecrowd::engine::TriggerPolicy::EveryFrame});
517517}
518518
519+ void addHazardClosureMotionSystems (
520+ safecrowd::engine::EngineRuntime& runtime,
521+ const safecrowd::domain::FacilityLayout2D& layout,
522+ std::vector<safecrowd::domain::EnvironmentHazardDraft> hazards,
523+ std::vector<safecrowd::domain::ConnectionBlockDraft> blocks) {
524+ runtime.addSystem (
525+ safecrowd::domain::makeScenarioControlSystem (layout, std::move (blocks)),
526+ {.phase = safecrowd::engine::UpdatePhase::PreSimulation,
527+ .triggerPolicy = safecrowd::engine::TriggerPolicy::EveryFrame});
528+ runtime.addSystem (
529+ safecrowd::domain::makeScenarioEnvironmentHazardSystem (layout, std::move (hazards)),
530+ {.phase = safecrowd::engine::UpdatePhase::PostSimulation,
531+ .order = -20 ,
532+ .triggerPolicy = safecrowd::engine::TriggerPolicy::EveryFrame});
533+ runtime.addSystem (
534+ safecrowd::domain::makeScenarioSimulationMotionSystem (layout),
535+ {.phase = safecrowd::engine::UpdatePhase::PostSimulation,
536+ .triggerPolicy = safecrowd::engine::TriggerPolicy::EveryFrame});
537+ runtime.addSystem (
538+ std::make_unique<safecrowd::domain::ScenarioFrameSyncSystem>(),
539+ {.phase = safecrowd::engine::UpdatePhase::RenderSync,
540+ .triggerPolicy = safecrowd::engine::TriggerPolicy::EveryFrame});
541+ }
542+
519543void stepScenarioRuntime (safecrowd::engine::EngineRuntime& runtime, double deltaSeconds) {
520544 runtime.world ().resources ().set (safecrowd::domain::ScenarioSimulationStepResource{.deltaSeconds = deltaSeconds});
521545 runtime.stepFrame (0.0 );
@@ -2349,6 +2373,91 @@ SC_TEST(ScenarioSimulationMotionSystem_RetriesNoExitAfterFiniteDoorClosureReopen
23492373 SC_EXPECT_TRUE (routeRecovered);
23502374}
23512375
2376+ SC_TEST (ScenarioSimulationMotionSystem_CombinesHazardExposureWithDoorClosureReroute) {
2377+ auto layout = wideTwoExitHazardRouteLayout ();
2378+ safecrowd::domain::ConnectionBlockDraft block;
2379+ block.id = " block-near-exit" ;
2380+ block.connectionId = " room-near-exit" ;
2381+
2382+ auto seed = doorRouteSeed (
2383+ {.x = 8.4 , .y = 1.0 },
2384+ " near-exit" ,
2385+ " room-near-exit" ,
2386+ {{.x = 10.0 , .y = 0.7 }, {.x = 10.0 , .y = 1.3 }},
2387+ 1.0 ,
2388+ 0.2 );
2389+ seed.agent .reactionDelaySeconds = 10.0 ;
2390+ seed.agent .hazardSensitivity = 1.0 ;
2391+ seed.agent .smokeSensitivity = 1.0 ;
2392+
2393+ auto fire = hazardDraft (
2394+ " combined-fire" ,
2395+ safecrowd::domain::EnvironmentHazardKind::Fire,
2396+ safecrowd::domain::ScenarioElementSeverity::Low,
2397+ {.x = 8.4 , .y = 1.0 },
2398+ " room" );
2399+ auto smoke = hazardDraft (
2400+ " combined-smoke" ,
2401+ safecrowd::domain::EnvironmentHazardKind::Smoke,
2402+ safecrowd::domain::ScenarioElementSeverity::Low,
2403+ {.x = 8.6 , .y = 1.0 },
2404+ " room" );
2405+
2406+ safecrowd::engine::EngineRuntime runtime ({
2407+ .fixedDeltaTime = 0.1 ,
2408+ .maxCatchUpSteps = 1 ,
2409+ .baseSeed = 94 ,
2410+ });
2411+ runtime.addSystem (std::make_unique<safecrowd::domain::ScenarioAgentSpawnSystem>(
2412+ std::vector<safecrowd::domain::ScenarioAgentSeed>{seed},
2413+ 5.0 ));
2414+ addHazardClosureMotionSystems (runtime, layout, {fire, smoke}, {block});
2415+
2416+ runtime.play ();
2417+ stepScenarioRuntime (runtime, 0.1 );
2418+
2419+ auto & query = runtime.world ().query ();
2420+ const auto entities = query.view <
2421+ safecrowd::domain::Position,
2422+ safecrowd::domain::Velocity,
2423+ safecrowd::domain::EvacuationRoute>();
2424+ SC_EXPECT_EQ (entities.size (), std::size_t {1 });
2425+ const auto entity = entities.front ();
2426+
2427+ const auto & firstState =
2428+ runtime.world ().resources ().get <safecrowd::domain::ScenarioEnvironmentReactionResource>().agentsById .at (entity.index );
2429+ SC_EXPECT_TRUE (firstState.hazardDetected );
2430+ SC_EXPECT_TRUE (!firstState.hazardAware );
2431+ SC_EXPECT_TRUE (firstState.closureDetected );
2432+ SC_EXPECT_TRUE (!firstState.closureAware );
2433+ SC_EXPECT_EQ (firstState.blockedConnectionId , std::string{" room-near-exit" });
2434+
2435+ const auto & activeHazards =
2436+ runtime.world ().resources ().get <safecrowd::domain::ScenarioActiveEnvironmentHazardsResource>();
2437+ SC_EXPECT_EQ (activeHazards.hazards .size (), std::size_t {2 });
2438+
2439+ for (int i = 0 ; i < 4 ; ++i) {
2440+ stepScenarioRuntime (runtime, 0.1 );
2441+ }
2442+
2443+ const auto & route = query.get <safecrowd::domain::EvacuationRoute>(entity);
2444+ SC_EXPECT_EQ (route.destinationZoneId , std::string{" far-exit" });
2445+ SC_EXPECT_TRUE (!route.noExitAvailable );
2446+ SC_EXPECT_TRUE (std::none_of (
2447+ route.waypointConnectionIds .begin (),
2448+ route.waypointConnectionIds .end (),
2449+ [](const auto & connectionId) {
2450+ return connectionId == " room-near-exit" ;
2451+ }));
2452+
2453+ const auto & exposure =
2454+ runtime.world ().resources ().get <safecrowd::domain::ScenarioHazardExposureResource>();
2455+ SC_EXPECT_TRUE (exposure.hazardsById .at (" combined-fire" ).exposedAgentSeconds > 0.0 );
2456+ SC_EXPECT_TRUE (exposure.hazardsById .at (" combined-smoke" ).exposedAgentSeconds > 0.0 );
2457+ SC_EXPECT_EQ (exposure.hazardsById .at (" combined-fire" ).peakExposedAgentCount , std::size_t {1 });
2458+ SC_EXPECT_EQ (exposure.hazardsById .at (" combined-smoke" ).peakExposedAgentCount , std::size_t {1 });
2459+ }
2460+
23522461SC_TEST (ScenarioRiskMetricsSystem_PublishesStalledHotspotAndBottleneckMetrics) {
23532462 std::vector<safecrowd::domain::ScenarioAgentSeed> seeds;
23542463 for (int index = 0 ; index < 5 ; ++index) {
0 commit comments