From 0b0d59b6ed2cb154b4c902b05ceba7aaa5d3c82b Mon Sep 17 00:00:00 2001 From: Silversupplier Date: Mon, 11 May 2026 16:13:56 +0900 Subject: [PATCH] [Domain] Add environment reaction state contract --- src/domain/AgentComponents.h | 4 ++ src/domain/ScenarioSimulationSystems.h | 17 ++++++++ tests/ScenarioSimulationRunnerTests.cpp | 21 ++++++++++ tests/ScenarioSimulationSystemsTests.cpp | 50 ++++++++++++++++++++++++ 4 files changed, 92 insertions(+) diff --git a/src/domain/AgentComponents.h b/src/domain/AgentComponents.h index e3c6495..27a2acd 100644 --- a/src/domain/AgentComponents.h +++ b/src/domain/AgentComponents.h @@ -19,6 +19,10 @@ struct Agent { std::string sourcePlacementId{}; std::string sourceZoneId{}; double guidancePropensity{0.5}; + double hazardSensitivity{1.0}; + double smokeSensitivity{1.0}; + double reactionDelaySeconds{0.0}; + double closurePatienceSeconds{0.0}; }; struct Velocity { diff --git a/src/domain/ScenarioSimulationSystems.h b/src/domain/ScenarioSimulationSystems.h index 355d72e..9d17847 100644 --- a/src/domain/ScenarioSimulationSystems.h +++ b/src/domain/ScenarioSimulationSystems.h @@ -79,6 +79,23 @@ struct ScenarioPressureFeedbackResource { std::size_t criticalAgentCount{0}; }; +struct ScenarioEnvironmentReactionAgentState { + bool hazardDetected{false}; + bool hazardAware{false}; + std::string hazardKey{}; + double hazardDetectedAtSeconds{0.0}; + double hazardReactionReadySeconds{0.0}; + bool closureDetected{false}; + bool closureAware{false}; + std::string blockedConnectionId{}; + double closureDetectedAtSeconds{0.0}; + double closureReactionReadySeconds{0.0}; +}; + +struct ScenarioEnvironmentReactionResource { + std::unordered_map agentsById{}; +}; + struct ScenarioResultArtifactsResource { ScenarioResultArtifacts artifacts{}; std::size_t lastRecordedEvacuatedCount{static_cast(-1)}; diff --git a/tests/ScenarioSimulationRunnerTests.cpp b/tests/ScenarioSimulationRunnerTests.cpp index 63b0465..778e3d9 100644 --- a/tests/ScenarioSimulationRunnerTests.cpp +++ b/tests/ScenarioSimulationRunnerTests.cpp @@ -1794,3 +1794,24 @@ SC_TEST(ScenarioSimulationRunnerRoutesAroundClosedObstructions) { SC_EXPECT_EQ(runner.frame().totalAgentCount, static_cast(1)); SC_EXPECT_EQ(runner.frame().evacuatedAgentCount, static_cast(1)); } + +SC_TEST(ScenarioSimulationRunnerNoHazardNoBlockBaselineStillEvacuates) { + safecrowd::domain::InitialPlacement2D placement; + placement.id = "baseline-crowd"; + placement.zoneId = "room"; + placement.targetAgentCount = 4; + placement.initialVelocity = {.x = 1.0, .y = 0.0}; + placement.area.outline = {{.x = 0.8, .y = 1.0}, {.x = 1.6, .y = 1.0}, {.x = 1.6, .y = 3.0}, {.x = 0.8, .y = 3.0}}; + + safecrowd::domain::ScenarioDraft scenario; + scenario.execution.timeLimitSeconds = 12.0; + scenario.population.initialPlacements.push_back(placement); + + safecrowd::domain::ScenarioSimulationRunner runner(wideDoorLayout(), scenario); + for (int i = 0; i < 48 && !runner.complete(); ++i) { + runner.step(0.25); + } + + SC_EXPECT_EQ(runner.frame().totalAgentCount, static_cast(4)); + SC_EXPECT_EQ(runner.frame().evacuatedAgentCount, static_cast(4)); +} diff --git a/tests/ScenarioSimulationSystemsTests.cpp b/tests/ScenarioSimulationSystemsTests.cpp index 363a835..f3ba86a 100644 --- a/tests/ScenarioSimulationSystemsTests.cpp +++ b/tests/ScenarioSimulationSystemsTests.cpp @@ -520,6 +520,51 @@ std::vector pressureFeedbackMotionSeeds() } // namespace +SC_TEST(Agent_DefaultsIncludeEnvironmentReactionTraits) { + const safecrowd::domain::Agent agent{}; + + SC_EXPECT_NEAR(agent.hazardSensitivity, 1.0, 1e-9); + SC_EXPECT_NEAR(agent.smokeSensitivity, 1.0, 1e-9); + SC_EXPECT_NEAR(agent.reactionDelaySeconds, 0.0, 1e-9); + SC_EXPECT_NEAR(agent.closurePatienceSeconds, 0.0, 1e-9); +} + +SC_TEST(ScenarioEnvironmentReactionResource_DefaultsEmptyAndStoresAgentState) { + safecrowd::engine::EngineRuntime runtime({ + .fixedDeltaTime = 1.0 / 30.0, + .maxCatchUpSteps = 1, + .baseSeed = 2, + }); + + auto& resources = runtime.world().resources(); + resources.set(safecrowd::domain::ScenarioEnvironmentReactionResource{}); + + SC_EXPECT_TRUE(resources.contains()); + auto& reactions = resources.get(); + SC_EXPECT_TRUE(reactions.agentsById.empty()); + + reactions.agentsById.emplace( + 7, + safecrowd::domain::ScenarioEnvironmentReactionAgentState{ + .hazardDetected = true, + .hazardAware = false, + .hazardKey = "fire-a", + .hazardDetectedAtSeconds = 1.25, + .hazardReactionReadySeconds = 2.0, + .closureDetected = true, + .closureAware = false, + .blockedConnectionId = "door-a", + .closureDetectedAtSeconds = 1.5, + .closureReactionReadySeconds = 3.0, + }); + + const auto& state = reactions.agentsById.at(7); + SC_EXPECT_TRUE(state.hazardDetected); + SC_EXPECT_EQ(state.hazardKey, std::string{"fire-a"}); + SC_EXPECT_TRUE(state.closureDetected); + SC_EXPECT_EQ(state.blockedConnectionId, std::string{"door-a"}); +} + SC_TEST(ScenarioAgentSpawnSystem_ConfiguresClockAndSpawnsAgentSeeds) { std::vector seeds; seeds.push_back({ @@ -551,6 +596,11 @@ SC_TEST(ScenarioAgentSpawnSystem_ConfiguresClockAndSpawnsAgentSeeds) { safecrowd::domain::EvacuationRoute, safecrowd::domain::EvacuationStatus>(); SC_EXPECT_EQ(entities.size(), std::size_t{1}); + const auto& agent = runtime.world().query().get(entities.front()); + SC_EXPECT_NEAR(agent.hazardSensitivity, 1.0, 1e-9); + SC_EXPECT_NEAR(agent.smokeSensitivity, 1.0, 1e-9); + SC_EXPECT_NEAR(agent.reactionDelaySeconds, 0.0, 1e-9); + SC_EXPECT_NEAR(agent.closurePatienceSeconds, 0.0, 1e-9); const auto& clock = runtime.world().resources().get(); SC_EXPECT_NEAR(clock.timeLimitSeconds, 15.0, 1e-9); const auto& frame = runtime.world().resources().get().frame;