From 7540c21d5c8eae1145840c1257245d19adb1645d Mon Sep 17 00:00:00 2001 From: Malte Langkabel Date: Tue, 4 Aug 2026 21:26:49 +0200 Subject: [PATCH] depend on factory data instead of BuildingSystem in the AI path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ten queries that read nothing but FactoryState become free functions in FactoryQueries.h; the BuildingSystem methods stay as one-line forwards, so no existing caller moves yet. That lets the AI path drop its dependency on the system entirely. AiSystem, SalvagerSystem, DeliverScrapEvaluator and DeliverScrapExecutor took a BuildingSystem& purely to call findBuilding, findNearestBuilding and deliverScrapToSalvageBay — all three are state-pure — so they now take FactoryState& and say what they actually read. Four forward declarations of BuildingSystem go with them. No facade: the queries are plain free functions over the data. A facade was considered to spare the ~180 UI call sites, but the AI needed only the data and would have been given GameConfig it has no use for. isProductionBuildingType moves to BuildingType.h beside isAutoRecipeBuildingType and isBeltSubsystemType rather than being copied into the new file. Verified with a golden-checksum capture before and after — all four sample ticks identical. Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_01YHcUerKAZKWNvSKJxYKbnG --- src/balancing/ArenaSimulation.cpp | 4 +- src/lib/core/BuildingType.cpp | 15 +++ src/lib/core/BuildingType.h | 4 + src/lib/ecs/system/AiSystem.cpp | 7 +- src/lib/ecs/system/AiSystem.h | 5 +- src/lib/ecs/system/SalvagerSystem.cpp | 7 +- src/lib/ecs/system/SalvagerSystem.h | 5 +- .../ecs/system/ai/DeliverScrapEvaluator.cpp | 5 +- src/lib/ecs/system/ai/DeliverScrapEvaluator.h | 5 +- .../ecs/system/ai/DeliverScrapExecutor.cpp | 5 +- src/lib/ecs/system/ai/DeliverScrapExecutor.h | 5 +- src/lib/sim/BuildingSystem.cpp | 112 ++-------------- src/lib/sim/CMakeLists.txt | 2 + src/lib/sim/FactoryQueries.cpp | 122 ++++++++++++++++++ src/lib/sim/FactoryQueries.h | 51 ++++++++ src/lib/sim/Simulation.cpp | 4 +- src/test/BehaviorSystemTest.cpp | 6 +- 17 files changed, 238 insertions(+), 126 deletions(-) create mode 100644 src/lib/sim/FactoryQueries.cpp create mode 100644 src/lib/sim/FactoryQueries.h diff --git a/src/balancing/ArenaSimulation.cpp b/src/balancing/ArenaSimulation.cpp index 901e77a..6075ed9 100644 --- a/src/balancing/ArenaSimulation.cpp +++ b/src/balancing/ArenaSimulation.cpp @@ -323,9 +323,9 @@ void ArenaSimulation::tick() // Ship behavior systems (tick step 7): evaluate, select winner, execute. // Module + combat systems emit their tool beams into a shared buffer. m_shipSystem->clearMovementIntents(); - m_aiSystem->tick(m_admin, *m_buildingSystem, *m_debrisSystem); + m_aiSystem->tick(m_admin, m_factoryState, *m_debrisSystem); std::vector beamFiredEvents; - m_salvagerSystem->tick(m_currentTick, *m_debrisSystem, *m_buildingSystem, beamFiredEvents); + m_salvagerSystem->tick(m_currentTick, *m_debrisSystem, m_factoryState, beamFiredEvents); m_repairSystem->tick(m_currentTick, beamFiredEvents); // Combat resolution (tick step 8). diff --git a/src/lib/core/BuildingType.cpp b/src/lib/core/BuildingType.cpp index 3725c3f..bbbb819 100644 --- a/src/lib/core/BuildingType.cpp +++ b/src/lib/core/BuildingType.cpp @@ -52,3 +52,18 @@ bool isBeltSubsystemType(BuildingType type) || type == BuildingType::TunnelEntry || type == BuildingType::TunnelExit; } + +bool isProductionBuildingType(BuildingType type) +{ + switch (type) + { + case BuildingType::Miner: + case BuildingType::Smelter: + case BuildingType::Assembler: + case BuildingType::ReprocessingPlant: + case BuildingType::Shipyard: + return true; + default: + return false; + } +} diff --git a/src/lib/core/BuildingType.h b/src/lib/core/BuildingType.h index cec4259..a54e696 100644 --- a/src/lib/core/BuildingType.h +++ b/src/lib/core/BuildingType.h @@ -35,6 +35,10 @@ std::string buildingTypeId(BuildingType type); // they receive, matching against every recipe of their building type. bool isAutoRecipeBuildingType(BuildingType type); +// Buildings that run a production cycle: Miner, Smelter, Assembler, Reprocessing +// Plant and Shipyard (REQ-UI-DEBUG-OVERLAY counts these). +bool isProductionBuildingType(BuildingType type); + // Belts, splitters, and tunnel ends keep their runtime data in the belt subsystem // rather than in the Building instance, so placing/removing them must register or // unregister a tile with BeltSystem. diff --git a/src/lib/ecs/system/AiSystem.cpp b/src/lib/ecs/system/AiSystem.cpp index 4fc8f91..fbaf86f 100644 --- a/src/lib/ecs/system/AiSystem.cpp +++ b/src/lib/ecs/system/AiSystem.cpp @@ -1,4 +1,5 @@ #include "AiSystem.h" +#include "FactoryQueries.h" #include @@ -42,7 +43,7 @@ AiSystem::AiSystem(const GameConfig& config) { } -void AiSystem::tick(EntityAdmin& admin, const BuildingSystem& buildings, +void AiSystem::tick(EntityAdmin& admin, const FactoryState& state, const DebrisSystem& debris) { TRACE(); @@ -55,7 +56,7 @@ void AiSystem::tick(EntityAdmin& admin, const BuildingSystem& buildings, m_attackEvaluator.evaluate(admin); m_repairEvaluator.evaluate(admin); m_salvageScrapEvaluator.evaluate(admin, debris); - m_deliverScrapEvaluator.evaluate(admin, buildings); + m_deliverScrapEvaluator.evaluate(admin, state); // Phase 2: pick the highest-scoring behavior per ship. selectWinningBehaviors(admin); @@ -68,7 +69,7 @@ void AiSystem::tick(EntityAdmin& admin, const BuildingSystem& buildings, m_attackExecutor.execute(admin); m_repairExecutor.execute(admin); m_salvageScrapExecutor.execute(admin); - m_deliverScrapExecutor.execute(admin, buildings); + m_deliverScrapExecutor.execute(admin, state); } void AiSystem::selectWinningBehaviors(EntityAdmin& admin) diff --git a/src/lib/ecs/system/AiSystem.h b/src/lib/ecs/system/AiSystem.h index 044c0c3..a020f7b 100644 --- a/src/lib/ecs/system/AiSystem.h +++ b/src/lib/ecs/system/AiSystem.h @@ -1,5 +1,7 @@ #pragma once +#include "FactoryQueries.h" + #include "AdvanceEvaluator.h" #include "AdvanceExecutor.h" #include "AttackEvaluator.h" @@ -17,7 +19,6 @@ #include "StandbyEvaluator.h" #include "StandbyExecutor.h" -class BuildingSystem; class EntityAdmin; class DebrisSystem; struct GameConfig; @@ -34,7 +35,7 @@ class AiSystem public: explicit AiSystem(const GameConfig& config); - void tick(EntityAdmin& admin, const BuildingSystem& buildings, const DebrisSystem& debris); + void tick(EntityAdmin& admin, const FactoryState& state, const DebrisSystem& debris); private: void selectWinningBehaviors(EntityAdmin& admin); diff --git a/src/lib/ecs/system/SalvagerSystem.cpp b/src/lib/ecs/system/SalvagerSystem.cpp index 33e65e8..5574b39 100644 --- a/src/lib/ecs/system/SalvagerSystem.cpp +++ b/src/lib/ecs/system/SalvagerSystem.cpp @@ -1,4 +1,5 @@ #include "SalvagerSystem.h" +#include "FactoryQueries.h" #include @@ -23,7 +24,7 @@ SalvagerSystem::SalvagerSystem(EntityAdmin& admin) { } -void SalvagerSystem::tick(Tick currentTick, DebrisSystem& debris, BuildingSystem& buildings, +void SalvagerSystem::tick(Tick currentTick, DebrisSystem& debris, FactoryState& state, std::vector& outBeamFiredEvents) { TRACE(); @@ -89,7 +90,7 @@ void SalvagerSystem::tick(Tick currentTick, DebrisSystem& debris, BuildingSystem [&](entt::entity ship, const DeliverScrapBehavior& deliver, const PositionComponent& pos) { if (!deliver.deliveryBay.has_value()) { return; } - const Building* bay = buildings.findBuilding(*deliver.deliveryBay); + const Building* bay = findBuilding(state, *deliver.deliveryBay); if (!bay) { return; } const QVector2D bayCenter(bay->anchor.x() + bay->footprint.width() / 2.0f, @@ -100,7 +101,7 @@ void SalvagerSystem::tick(Tick currentTick, DebrisSystem& debris, BuildingSystem if (!m_admin.hasAll(ship)) { return; } CargoComponent& cargo = m_admin.get(ship); if (cargo.current <= 0) { return; } - if (buildings.deliverScrapToSalvageBay(*deliver.deliveryBay)) + if (deliverScrapToSalvageBay(state, *deliver.deliveryBay)) { --cargo.current; } diff --git a/src/lib/ecs/system/SalvagerSystem.h b/src/lib/ecs/system/SalvagerSystem.h index a977652..97a5526 100644 --- a/src/lib/ecs/system/SalvagerSystem.h +++ b/src/lib/ecs/system/SalvagerSystem.h @@ -1,5 +1,7 @@ #pragma once +#include "FactoryQueries.h" + #include #include "BeamFiredEvent.h" @@ -7,7 +9,6 @@ #include "entt/entity/entity.hpp" -class BuildingSystem; class EntityAdmin; class DebrisSystem; @@ -21,7 +22,7 @@ class SalvagerSystem public: explicit SalvagerSystem(EntityAdmin& admin); - void tick(Tick currentTick, DebrisSystem& debris, BuildingSystem& buildings, + void tick(Tick currentTick, DebrisSystem& debris, FactoryState& state, std::vector& outBeamFiredEvents); private: diff --git a/src/lib/ecs/system/ai/DeliverScrapEvaluator.cpp b/src/lib/ecs/system/ai/DeliverScrapEvaluator.cpp index e354c88..d525f4d 100644 --- a/src/lib/ecs/system/ai/DeliverScrapEvaluator.cpp +++ b/src/lib/ecs/system/ai/DeliverScrapEvaluator.cpp @@ -1,4 +1,5 @@ #include "DeliverScrapEvaluator.h" +#include "FactoryQueries.h" #include @@ -12,7 +13,7 @@ #include "PositionComponent.h" #include "tracing.h" -void DeliverScrapEvaluator::evaluate(EntityAdmin& admin, const BuildingSystem& buildings) +void DeliverScrapEvaluator::evaluate(EntityAdmin& admin, const FactoryState& state) { TRACE(); const std::unordered_map cargoByShip = buildCargoByShip(admin); @@ -34,7 +35,7 @@ void DeliverScrapEvaluator::evaluate(EntityAdmin& admin, const BuildingSystem& b if (!deliver.deliveryBay.has_value()) { const Building* bay = - buildings.findNearestBuilding(pos.value, BuildingType::SalvageBay); + findNearestBuilding(state, pos.value, BuildingType::SalvageBay); if (bay) { deliver.deliveryBay = bay->id; } } diff --git a/src/lib/ecs/system/ai/DeliverScrapEvaluator.h b/src/lib/ecs/system/ai/DeliverScrapEvaluator.h index a2046fe..54cc7b2 100644 --- a/src/lib/ecs/system/ai/DeliverScrapEvaluator.h +++ b/src/lib/ecs/system/ai/DeliverScrapEvaluator.h @@ -1,12 +1,13 @@ #pragma once +#include "FactoryQueries.h" + class EntityAdmin; -class BuildingSystem; // Scores high only when the ship's cargo is full, and assigns the nearest // SalvageBay as the delivery destination. class DeliverScrapEvaluator { public: - void evaluate(EntityAdmin& admin, const BuildingSystem& buildings); + void evaluate(EntityAdmin& admin, const FactoryState& state); }; diff --git a/src/lib/ecs/system/ai/DeliverScrapExecutor.cpp b/src/lib/ecs/system/ai/DeliverScrapExecutor.cpp index b92073b..f03f071 100644 --- a/src/lib/ecs/system/ai/DeliverScrapExecutor.cpp +++ b/src/lib/ecs/system/ai/DeliverScrapExecutor.cpp @@ -1,4 +1,5 @@ #include "DeliverScrapExecutor.h" +#include "FactoryQueries.h" #include @@ -12,7 +13,7 @@ #include "SelectedBehaviorComponent.h" #include "tracing.h" -void DeliverScrapExecutor::execute(EntityAdmin& admin, const BuildingSystem& buildings) +void DeliverScrapExecutor::execute(EntityAdmin& admin, const FactoryState& state) { TRACE(); admin.forEachanchor.x() + bay->footprint.width() / 2.0f, diff --git a/src/lib/ecs/system/ai/DeliverScrapExecutor.h b/src/lib/ecs/system/ai/DeliverScrapExecutor.h index a47f659..866c1bd 100644 --- a/src/lib/ecs/system/ai/DeliverScrapExecutor.h +++ b/src/lib/ecs/system/ai/DeliverScrapExecutor.h @@ -1,12 +1,13 @@ #pragma once +#include "FactoryQueries.h" + class EntityAdmin; -class BuildingSystem; // Moves a ship toward its delivery bay when DeliverScrap is the winning // behavior. Never decrements cargo — SalvagerSystem performs the delivery. class DeliverScrapExecutor { public: - void execute(EntityAdmin& admin, const BuildingSystem& buildings); + void execute(EntityAdmin& admin, const FactoryState& state); }; diff --git a/src/lib/sim/BuildingSystem.cpp b/src/lib/sim/BuildingSystem.cpp index 45c462c..b5f16f7 100644 --- a/src/lib/sim/BuildingSystem.cpp +++ b/src/lib/sim/BuildingSystem.cpp @@ -6,6 +6,7 @@ #include #include +#include "FactoryQueries.h" #include "PortGeometry.h" #include "StateChecksum.h" #include "SurfaceMask.h" @@ -1264,87 +1265,37 @@ void BuildingSystem::forEachIncomingItem( const Building* BuildingSystem::findBuilding(BuildingId id) const { - for (const Building& building : m_state.buildings) - { - if (building.id == id) - { - return &building; - } - } - return nullptr; + return ::findBuilding(m_state, id); } Building* BuildingSystem::findBuildingMutable(BuildingId id) { - for (Building& building : m_state.buildings) - { - if (building.id == id) - { - return &building; - } - } - return nullptr; + return ::findBuilding(m_state, id); } const ConstructionSite* BuildingSystem::findSite(BuildingId id) const { - for (const ConstructionSite& site : m_state.constructionQueue) - { - if (site.id == id) - { - return &site; - } - } - return nullptr; + return ::findSite(m_state, id); } std::vector BuildingSystem::getAllBuildings() const { - return m_state.buildings; + return ::getAllBuildings(m_state); } std::vector BuildingSystem::getAllSites() const { - return std::vector(m_state.constructionQueue.begin(), - m_state.constructionQueue.end()); + return ::getAllSites(m_state); } -namespace -{ -bool isProductionBuildingType(BuildingType type) -{ - switch (type) - { - case BuildingType::Miner: - case BuildingType::Smelter: - case BuildingType::Assembler: - case BuildingType::ReprocessingPlant: - case BuildingType::Shipyard: - return true; - default: - return false; - } -} -} // namespace - int BuildingSystem::getProductionBuildingCount() const { - int count = 0; - for (const Building& b : m_state.buildings) - { - if (isProductionBuildingType(b.type)) { ++count; } - } - return count; + return ::getProductionBuildingCount(m_state); } int BuildingSystem::getActiveProductionBuildingCount() const { - int count = 0; - for (const Building& b : m_state.buildings) - { - if (isProductionBuildingType(b.type) && b.production.has_value()) { ++count; } - } - return count; + return ::getActiveProductionBuildingCount(m_state); } std::vector @@ -1522,7 +1473,7 @@ std::vector BuildingSystem::getAllBeltTiles() cons bool BuildingSystem::isTileOccupied(QPoint tile) const { - return m_state.grid.isOccupied(tile); + return ::isTileOccupied(m_state, tile); } std::optional BuildingSystem::findRotateInPlaceTarget( @@ -1643,53 +1594,12 @@ void BuildingSystem::rotateInPlace(BuildingId id, Rotation newRotation) const Building* BuildingSystem::findNearestBuilding(QVector2D worldPos, BuildingType type) const { - const Building* best = nullptr; - float bestDist = std::numeric_limits::max(); - for (const Building& b : m_state.buildings) - { - if (b.type != type) - { - continue; - } - QVector2D center(b.anchor.x() + b.footprint.width() / 2.0f, - b.anchor.y() + b.footprint.height() / 2.0f); - float dist = (center - worldPos).length(); - if (dist < bestDist) - { - bestDist = dist; - best = &b; - } - } - return best; + return ::findNearestBuilding(m_state, worldPos, type); } bool BuildingSystem::deliverScrapToSalvageBay(BuildingId bayId) { - Building* bay = nullptr; - for (Building& b : m_state.buildings) - { - if (b.id == bayId) - { - bay = &b; - break; - } - } - if (!bay || bay->type != BuildingType::SalvageBay) - { - return false; - } - if (bay->queuedForDeconstruction) - { - return false; // queued for deconstruction: stopped operating (REQ-BLD-DECON-QUEUE) - } - // Emerging scrap still counts against the bay's holding capacity - // (REQ-MAT-OUTPUT-EMERGE). - if (bay->getOutputItemCount() >= bay->outputBuffer.capacity) - { - return false; - } - bay->outputBuffer.items.push_back(Item{ItemType{"scrap"}}); - return true; + return ::deliverScrapToSalvageBay(m_state, bayId); } BuildingId BuildingSystem::placeImmediate(BuildingType type, diff --git a/src/lib/sim/CMakeLists.txt b/src/lib/sim/CMakeLists.txt index 6c2f0ed..793e500 100644 --- a/src/lib/sim/CMakeLists.txt +++ b/src/lib/sim/CMakeLists.txt @@ -14,6 +14,7 @@ SET(HDRS ${CMAKE_CURRENT_SOURCE_DIR}/BuildingConfig.h ${CMAKE_CURRENT_SOURCE_DIR}/BuildingGrid.h ${CMAKE_CURRENT_SOURCE_DIR}/FactoryState.h + ${CMAKE_CURRENT_SOURCE_DIR}/FactoryQueries.h ${CMAKE_CURRENT_SOURCE_DIR}/BuildingSystem.h ${CMAKE_CURRENT_SOURCE_DIR}/EntityHitTest.h ${CMAKE_CURRENT_SOURCE_DIR}/ShipLayout.h @@ -39,6 +40,7 @@ SET(SRCS ${CMAKE_CURRENT_SOURCE_DIR}/BeltSystem.cpp ${CMAKE_CURRENT_SOURCE_DIR}/BuildingConfig.cpp ${CMAKE_CURRENT_SOURCE_DIR}/BuildingGrid.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/FactoryQueries.cpp ${CMAKE_CURRENT_SOURCE_DIR}/BuildingSystem.cpp ${CMAKE_CURRENT_SOURCE_DIR}/EntityHitTest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ShipStatsCalculator.cpp diff --git a/src/lib/sim/FactoryQueries.cpp b/src/lib/sim/FactoryQueries.cpp new file mode 100644 index 0000000..cee4286 --- /dev/null +++ b/src/lib/sim/FactoryQueries.cpp @@ -0,0 +1,122 @@ +#include "FactoryQueries.h" + +#include + +#include "Item.h" +#include "ItemType.h" + +const Building* findBuilding(const FactoryState& state, BuildingId id) +{ + for (const Building& building : state.buildings) + { + if (building.id == id) + { + return &building; + } + } + return nullptr; +} + +Building* findBuilding(FactoryState& state, BuildingId id) +{ + for (Building& building : state.buildings) + { + if (building.id == id) + { + return &building; + } + } + return nullptr; +} + +const ConstructionSite* findSite(const FactoryState& state, BuildingId id) +{ + for (const ConstructionSite& site : state.constructionQueue) + { + if (site.id == id) + { + return &site; + } + } + return nullptr; +} + +std::vector getAllBuildings(const FactoryState& state) +{ + return state.buildings; +} + +std::vector getAllSites(const FactoryState& state) +{ + return std::vector(state.constructionQueue.begin(), + state.constructionQueue.end()); +} + +int getProductionBuildingCount(const FactoryState& state) +{ + int count = 0; + for (const Building& b : state.buildings) + { + if (isProductionBuildingType(b.type)) { ++count; } + } + return count; +} + +int getActiveProductionBuildingCount(const FactoryState& state) +{ + int count = 0; + for (const Building& b : state.buildings) + { + if (isProductionBuildingType(b.type) && b.production.has_value()) { ++count; } + } + return count; +} + +bool isTileOccupied(const FactoryState& state, QPoint tile) +{ + return state.grid.isOccupied(tile); +} + +const Building* findNearestBuilding(const FactoryState& state, QVector2D worldPos, + BuildingType type) +{ + const Building* best = nullptr; + float bestDist = std::numeric_limits::max(); + for (const Building& b : state.buildings) + { + if (b.type != type) + { + continue; + } + QVector2D center(b.anchor.x() + b.footprint.width() / 2.0f, + b.anchor.y() + b.footprint.height() / 2.0f); + float dist = (center - worldPos).length(); + if (dist < bestDist) + { + bestDist = dist; + best = &b; + } + } + return best; +} + +bool deliverScrapToSalvageBay(FactoryState& state, BuildingId bayId) +{ + Building* bay = findBuilding(state, bayId); + if (!bay || bay->type != BuildingType::SalvageBay) + { + return false; + } + if (bay->queuedForDeconstruction) + { + return false; // queued for deconstruction: stopped operating (REQ-BLD-DECON-QUEUE) + } + // Emerging scrap still counts against the bay's holding capacity + // (REQ-MAT-OUTPUT-EMERGE). + if (bay->getOutputItemCount() >= bay->outputBuffer.capacity) + { + return false; + } + bay->outputBuffer.items.push_back(Item{ItemType{"scrap"}}); + return true; +} diff --git a/src/lib/sim/FactoryQueries.h b/src/lib/sim/FactoryQueries.h new file mode 100644 index 0000000..518a60f --- /dev/null +++ b/src/lib/sim/FactoryQueries.h @@ -0,0 +1,51 @@ +#pragma once + +#include + +#include +#include + +#include "Building.h" +#include "BuildingId.h" +#include "BuildingType.h" +#include "FactoryState.h" + +// Queries and operations over the factory's world data that need nothing but that +// data — no config, no belts, no RNG. Free functions rather than BuildingSystem +// methods so that callers depend on the data they read instead of on the system +// that happens to tick it (see FactoryState.h). +// +// A helper belongs here only if it is a pure function of FactoryState. Anything +// needing GameConfig or the asteroid bound stays on BuildingSystem for now. + +// The building with the given id, or nullptr when no building has it. Construction +// sites are not buildings yet — use findSite for those. +const Building* findBuilding(const FactoryState& state, BuildingId id); +Building* findBuilding(FactoryState& state, BuildingId id); + +// The queued construction site with the given id, or nullptr. +const ConstructionSite* findSite(const FactoryState& state, BuildingId id); + +std::vector getAllBuildings(const FactoryState& state); +std::vector getAllSites(const FactoryState& state); + +// REQ-UI-DEBUG-OVERLAY "Max Factory Production": count of completed +// (operational) Miner/Smelter/Assembler/ReprocessingPlant/Shipyard buildings. +int getProductionBuildingCount(const FactoryState& state); + +// REQ-UI-DEBUG-OVERLAY "Current Factory Production": subset of the above that +// currently has an active production cycle. +int getActiveProductionBuildingCount(const FactoryState& state); + +bool isTileOccupied(const FactoryState& state, QPoint tile); + +// The nearest building of the given type to a world position, or nullptr when +// none exists. Distance is measured to the building's footprint centre. +const Building* findNearestBuilding(const FactoryState& state, QVector2D worldPos, + BuildingType type); + +// Hands one scrap to a Salvage Bay's output buffer (REQ-BLD-SALVAGE-BAY). Fails +// if the id is not a Salvage Bay, it is queued for deconstruction +// (REQ-BLD-DECON-QUEUE), or its holding capacity is already taken — emerging +// scrap counts against that capacity (REQ-MAT-OUTPUT-EMERGE). +bool deliverScrapToSalvageBay(FactoryState& state, BuildingId bayId); diff --git a/src/lib/sim/Simulation.cpp b/src/lib/sim/Simulation.cpp index 4bc36af..adc6102 100644 --- a/src/lib/sim/Simulation.cpp +++ b/src/lib/sim/Simulation.cpp @@ -260,10 +260,10 @@ void Simulation::tick() m_shipSystem->clearMovementIntents(); // Score-based behavior selection: evaluate, select winner, execute (sets // movement intent + preferred module targets only — no world mutation). - m_aiSystem->tick(m_admin, *m_buildingSystem, *m_debrisSystem); + m_aiSystem->tick(m_admin, m_factoryState, *m_debrisSystem); // Module systems perform the world mutation (collection/delivery, healing). // Each emits its tool beams and applies its own delayed (mid-beam) effects. - m_salvagerSystem->tick(m_currentTick, *m_debrisSystem, *m_buildingSystem, m_beamFiredEvents); + m_salvagerSystem->tick(m_currentTick, *m_debrisSystem, m_factoryState, m_beamFiredEvents); m_repairSystem->tick(m_currentTick, m_beamFiredEvents); // Step 8: combat resolution diff --git a/src/test/BehaviorSystemTest.cpp b/src/test/BehaviorSystemTest.cpp index 055e9c2..2e23fc6 100644 --- a/src/test/BehaviorSystemTest.cpp +++ b/src/test/BehaviorSystemTest.cpp @@ -97,14 +97,14 @@ struct Fixture void decide() { ships.clearMovementIntents(); - ai.tick(admin, buildings, scraps); + ai.tick(admin, state, scraps); } // World mutation: collection/delivery and healing. void runModules() { beamEvents.clear(); - salvager.tick(tick, scraps, buildings, beamEvents); + salvager.tick(tick, scraps, state, beamEvents); repair.tick(tick, beamEvents); } @@ -137,7 +137,7 @@ struct Fixture void salvageTick() { beamEvents.clear(); - salvager.tick(tick, scraps, buildings, beamEvents); + salvager.tick(tick, scraps, state, beamEvents); ++tick; }