From 58b94223f77da36aa272aff2ca9cb76b80f123b6 Mon Sep 17 00:00:00 2001 From: mlangkabel Date: Wed, 5 Aug 2026 06:45:49 +0200 Subject: [PATCH] migrate every factory query off BuildingSystem onto the free functions --- src/balancing/ArenaSimulation.cpp | 5 + src/balancing/ArenaSimulation.h | 1 + src/balancing/ArenaView.cpp | 3 +- src/lib/sim/BuildingConfig.cpp | 9 +- src/lib/sim/BuildingSystem.cpp | 65 +---------- src/lib/sim/BuildingSystem.h | 12 -- src/lib/sim/FactoryQueries.cpp | 6 + src/lib/sim/FactoryQueries.h | 3 + src/lib/sim/Simulation.cpp | 11 +- src/lib/sim/Simulation.h | 3 + src/test/BehaviorSystemTest.cpp | 11 +- src/test/BlueprintTest.cpp | 35 +++--- src/test/BuildingConfigTest.cpp | 5 +- src/test/BuildingTest.cpp | 177 +++++++++++++++--------------- src/test/ShipModuleTest.cpp | 15 +-- src/test/ShipyardTest.cpp | 3 +- src/test/WaveSystemTest.cpp | 5 +- src/ui/GameWorldView.cpp | 63 +++++------ src/ui/MainWindow.cpp | 9 +- src/ui/SelectedBuildingPanel.cpp | 15 +-- 20 files changed, 212 insertions(+), 244 deletions(-) diff --git a/src/balancing/ArenaSimulation.cpp b/src/balancing/ArenaSimulation.cpp index f31232d..fc5da88 100644 --- a/src/balancing/ArenaSimulation.cpp +++ b/src/balancing/ArenaSimulation.cpp @@ -488,6 +488,11 @@ const ArenaConfig& ArenaSimulation::getArenaConfig() const return m_arenaConfig; } +const FactoryState& ArenaSimulation::getFactoryState() const +{ + return m_factoryState; +} + const BuildingSystem& ArenaSimulation::getBuildings() const { return *m_buildingSystem; diff --git a/src/balancing/ArenaSimulation.h b/src/balancing/ArenaSimulation.h index 3fc8edc..fa23cf8 100644 --- a/src/balancing/ArenaSimulation.h +++ b/src/balancing/ArenaSimulation.h @@ -86,6 +86,7 @@ public: const ArenaConfig& getArenaConfig() const; const BuildingSystem& getBuildings() const; + const FactoryState& getFactoryState() const; const ShipSystem& getShips() const; const DebrisSystem& getDebrisSystem() const; EntityAdmin& getAdmin(); diff --git a/src/balancing/ArenaView.cpp b/src/balancing/ArenaView.cpp index aa3b838..e9c453d 100644 --- a/src/balancing/ArenaView.cpp +++ b/src/balancing/ArenaView.cpp @@ -1,4 +1,5 @@ #include "ArenaView.h" +#include "FactoryQueries.h" #include #include @@ -306,7 +307,7 @@ void ArenaView::drawTiles(QPainter& painter) void ArenaView::drawBuildings(QPainter& painter) { - for (const Building& b : m_sim->getBuildings().getAllBuildings()) + for (const Building& b : getAllBuildings(m_sim->getFactoryState())) { const std::map::const_iterator it = m_visuals->buildings.find(b.type); diff --git a/src/lib/sim/BuildingConfig.cpp b/src/lib/sim/BuildingConfig.cpp index 0e31ebd..6c4d110 100644 --- a/src/lib/sim/BuildingConfig.cpp +++ b/src/lib/sim/BuildingConfig.cpp @@ -1,4 +1,5 @@ #include "BuildingConfig.h" +#include "FactoryQueries.h" #include #include @@ -26,8 +27,8 @@ struct SelectedBuilding // (the HQ and defence stations, per REQ-UI-BLUEPRINT-CREATE). std::optional resolvePlaceable(const Simulation& sim, BuildingId id) { - const Building* building = sim.getBuildings().findBuilding(id); - const ConstructionSite* site = building ? nullptr : sim.getBuildings().findSite(id); + const Building* building = findBuilding(sim.getFactoryState(), id); + const ConstructionSite* site = building ? nullptr : findSite(sim.getFactoryState(), id); if (!building && !site) { return std::nullopt; @@ -52,8 +53,8 @@ std::optional resolvePlaceable(const Simulation& sim, Building std::optional readBuildingConfig(const Simulation& sim, BuildingId id) { - const Building* building = sim.getBuildings().findBuilding(id); - const ConstructionSite* site = building ? nullptr : sim.getBuildings().findSite(id); + const Building* building = findBuilding(sim.getFactoryState(), id); + const ConstructionSite* site = building ? nullptr : findSite(sim.getFactoryState(), id); if (!building && !site) { return std::nullopt; diff --git a/src/lib/sim/BuildingSystem.cpp b/src/lib/sim/BuildingSystem.cpp index 1e77834..9761702 100644 --- a/src/lib/sim/BuildingSystem.cpp +++ b/src/lib/sim/BuildingSystem.cpp @@ -241,11 +241,11 @@ std::vector BuildingSystem::computeInputPorts( std::vector BuildingSystem::getInputPorts(BuildingId id) const { - if (const Building* building = findBuilding(id)) + if (const Building* building = findBuilding(m_state, id)) { return building->inputPorts; } - if (const ConstructionSite* site = findSite(id)) + if (const ConstructionSite* site = findSite(m_state, id)) { // A site stores no ports; derive its output ports from the mask (absolute) // and run the same input-edge scan (REQ-BLD-BELT-DRAG, REQ-MAT-INPUT-PORTS). @@ -825,7 +825,7 @@ void BuildingSystem::cancelDeconstruction(BuildingId id) // Resume operation: clear the flag and re-register belt/tunnel/splitter // tiles that were unregistered at enqueue (which re-pairs tunnels, // REQ-BLD-TUNNEL-PAIR). Deconstruction progress is discarded; no refund. - if (Building* building = findBuildingMutable(id)) + if (Building* building = findBuilding(m_state, id)) { building->queuedForDeconstruction = false; reregisterBeltTile(*building, it->splitterFilterA, it->splitterFilterB); @@ -838,12 +838,6 @@ void BuildingSystem::cancelDeconstruction(BuildingId id) } } -bool BuildingSystem::isQueuedForDeconstruction(BuildingId id) const -{ - const Building* building = findBuilding(id); - return building && building->queuedForDeconstruction; -} - void BuildingSystem::tickBeltPull() { TRACE(); @@ -941,7 +935,7 @@ bool BuildingSystem::tryDirectCoupleDeposit(BuildingId producerId, return false; } - Building* consumer = findBuildingMutable(*ownerId); + Building* consumer = findBuilding(m_state, *ownerId); if (!consumer) { return false; // an unbuilt construction site, or not an operational building @@ -1263,41 +1257,6 @@ void BuildingSystem::forEachIncomingItem( // Queries // --------------------------------------------------------------------------- -const Building* BuildingSystem::findBuilding(BuildingId id) const -{ - return ::findBuilding(m_state, id); -} - -Building* BuildingSystem::findBuildingMutable(BuildingId id) -{ - return ::findBuilding(m_state, id); -} - -const ConstructionSite* BuildingSystem::findSite(BuildingId id) const -{ - return ::findSite(m_state, id); -} - -std::vector BuildingSystem::getAllBuildings() const -{ - return ::getAllBuildings(m_state); -} - -std::vector BuildingSystem::getAllSites() const -{ - return ::getAllSites(m_state); -} - -int BuildingSystem::getProductionBuildingCount() const -{ - return ::getProductionBuildingCount(m_state); -} - -int BuildingSystem::getActiveProductionBuildingCount() const -{ - return ::getActiveProductionBuildingCount(m_state); -} - std::vector BuildingSystem::gatherCandidateRecipes(const Building& b) const { @@ -1471,11 +1430,6 @@ std::vector BuildingSystem::getAllBeltTiles() cons return result; } -bool BuildingSystem::isTileOccupied(QPoint tile) const -{ - return ::isTileOccupied(m_state, tile); -} - std::optional BuildingSystem::findRotateInPlaceTarget( BuildingType type, QPoint anchor, Rotation rot) const { @@ -1591,17 +1545,6 @@ void BuildingSystem::rotateInPlace(BuildingId id, Rotation newRotation) } } -const Building* BuildingSystem::findNearestBuilding(QVector2D worldPos, - BuildingType type) const -{ - return ::findNearestBuilding(m_state, worldPos, type); -} - -bool BuildingSystem::deliverScrapToSalvageBay(BuildingId bayId) -{ - return ::deliverScrapToSalvageBay(m_state, bayId); -} - BuildingId BuildingSystem::placeImmediate(BuildingType type, const std::vector& surfaceMask, QPoint anchor, Rotation rotation) diff --git a/src/lib/sim/BuildingSystem.h b/src/lib/sim/BuildingSystem.h index 469c841..4e70846 100644 --- a/src/lib/sim/BuildingSystem.h +++ b/src/lib/sim/BuildingSystem.h @@ -94,7 +94,6 @@ public: void cancelDeconstruction(BuildingId id); // True if the building is currently in the deconstruction queue. - bool isQueuedForDeconstruction(BuildingId id) const; // Set the recipe (or schematic id for shipyard) on a building or queued // construction site. Clears both buffers on an operational building. @@ -140,18 +139,12 @@ public: Rotation directionB; // Splitter: second output; Belt: same as directionA }; - const Building* findBuilding(BuildingId id) const; - const ConstructionSite* findSite(BuildingId id) const; - std::vector getAllBuildings() const; - std::vector getAllSites() const; // REQ-UI-DEBUG-OVERLAY "Max Factory Production": count of completed // (operational) Miner/Smelter/Assembler/ReprocessingPlant/Shipyard buildings. - int getProductionBuildingCount() const; // REQ-UI-DEBUG-OVERLAY "Current Factory Production": subset of the above // that currently has an active production cycle. - int getActiveProductionBuildingCount() const; // Production state for the UI status light (REQ-UI-STATUS-LIGHT). Returns // nullopt for building types that show no light (belts, splitters, tunnels, @@ -159,7 +152,6 @@ public: // Producing while its output buffer holds scrap, Starved when empty. std::optional getProductionStatus(const Building& building) const; std::vector getAllBeltTiles() const; - bool isTileOccupied(QPoint tile) const; // Visits every item currently emerging from a building output port on its // virtual output belt (REQ-MAT-OUTPUT-EMERGE), passing the item type and its @@ -186,8 +178,6 @@ public: // currently on the tile are discarded by BeltSystem::removeTile). void rotateInPlace(BuildingId id, Rotation newRotation); - // Find nearest operational building of the given type; nullptr if none. - const Building* findNearestBuilding(QVector2D worldPos, BuildingType type) const; // Input-capable adjacent tiles for a building or construction site // (REQ-BLD-BELT-DRAG, REQ-MAT-INPUT-PORTS): each returned Port.tile is the @@ -201,7 +191,6 @@ public: // Place one "scrap" item into a SalvageBay's output buffer. // Returns false if bay not found, wrong type, or output buffer is full. - bool deliverScrapToSalvageBay(BuildingId bayId); // Bypass the construction queue and create a fully-operational Building // immediately. Used for pre-placed structures (HQ, defence stations). @@ -234,7 +223,6 @@ private: const std::vector& splitterFilterA, const std::vector& splitterFilterB); - Building* findBuildingMutable(BuildingId id); // True if the consumer would accept `type` at the given input port right now: // it is a required input (or a building block for the HQ), the reservation-aware // buffer has room, and the input belt entry is free (REQ-MAT-INPUT-INTAKE). diff --git a/src/lib/sim/FactoryQueries.cpp b/src/lib/sim/FactoryQueries.cpp index cee4286..2d9eaf2 100644 --- a/src/lib/sim/FactoryQueries.cpp +++ b/src/lib/sim/FactoryQueries.cpp @@ -77,6 +77,12 @@ bool isTileOccupied(const FactoryState& state, QPoint tile) return state.grid.isOccupied(tile); } +bool isQueuedForDeconstruction(const FactoryState& state, BuildingId id) +{ + const Building* building = findBuilding(state, id); + return building && building->queuedForDeconstruction; +} + const Building* findNearestBuilding(const FactoryState& state, QVector2D worldPos, BuildingType type) { diff --git a/src/lib/sim/FactoryQueries.h b/src/lib/sim/FactoryQueries.h index 518a60f..442e0ba 100644 --- a/src/lib/sim/FactoryQueries.h +++ b/src/lib/sim/FactoryQueries.h @@ -39,6 +39,9 @@ int getActiveProductionBuildingCount(const FactoryState& state); bool isTileOccupied(const FactoryState& state, QPoint tile); +// True while the building is in the deconstruction queue (REQ-BLD-DECON-QUEUE). +bool isQueuedForDeconstruction(const FactoryState& state, BuildingId id); + // 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, diff --git a/src/lib/sim/Simulation.cpp b/src/lib/sim/Simulation.cpp index f5aba55..37ff2af 100644 --- a/src/lib/sim/Simulation.cpp +++ b/src/lib/sim/Simulation.cpp @@ -1,5 +1,7 @@ #include "Simulation.h" +#include "FactoryQueries.h" + #include #include #include @@ -811,12 +813,12 @@ double Simulation::getThreatAccumulationRate() const double Simulation::getMaxFactoryProductionThreatRate() const { - return static_cast(m_buildingSystem->getProductionBuildingCount()); + return static_cast(getProductionBuildingCount(m_factoryState)); } double Simulation::getCurrentFactoryProductionThreatRate() const { - return static_cast(m_buildingSystem->getActiveProductionBuildingCount()); + return static_cast(getActiveProductionBuildingCount(m_factoryState)); } int Simulation::getBossWaveCounter() const @@ -895,6 +897,11 @@ BuildingSystem& Simulation::getBuildingsMutable() return *m_buildingSystem; } +const FactoryState& Simulation::getFactoryState() const +{ + return m_factoryState; +} + const BuildingSystem& Simulation::getBuildings() const { return *m_buildingSystem; diff --git a/src/lib/sim/Simulation.h b/src/lib/sim/Simulation.h index e38e728..db01c94 100644 --- a/src/lib/sim/Simulation.h +++ b/src/lib/sim/Simulation.h @@ -119,6 +119,9 @@ public: // chokepoint) or, in tests, SimulationTestAccess — so production code cannot // mutate the factory outside the recorded command path (docs/replay_design.md). const BuildingSystem& getBuildings() const; + + // The factory's world data, for the free queries in FactoryQueries.h. + const FactoryState& getFactoryState() const; const BeltSystem& getBelts() const; ShipSystem& getShips(); const ShipSystem& getShips() const; diff --git a/src/test/BehaviorSystemTest.cpp b/src/test/BehaviorSystemTest.cpp index 2e23fc6..4a09b4f 100644 --- a/src/test/BehaviorSystemTest.cpp +++ b/src/test/BehaviorSystemTest.cpp @@ -1,4 +1,5 @@ #include "catch.hpp" +#include "FactoryQueries.h" #include #include @@ -957,12 +958,12 @@ TEST_CASE("BehaviorSystem: full-cargo salvage ship moves toward SalvageBay", "[b for (int i = 0; i < 500; ++i) { f.buildings.tickConstruction(t++); - if (f.buildings.findBuilding(bayId) != nullptr) + if (findBuilding(f.state, bayId) != nullptr) { break; } } - REQUIRE(f.buildings.findBuilding(bayId) != nullptr); + REQUIRE(findBuilding(f.state, bayId) != nullptr); const ShipLayoutConfig salvageLayout = makeSingleModuleLayout("salvager"); const entt::entity ship = f.ships.spawn("salvage_ship", QVector2D(5.0f, 0.0f), @@ -992,9 +993,9 @@ TEST_CASE("SalvagerSystem: full-cargo ship at its SalvageBay hands over cargo", for (int i = 0; i < 500; ++i) { f.buildings.tickConstruction(t++); - if (f.buildings.findBuilding(bayId) != nullptr) { break; } + if (findBuilding(f.state, bayId) != nullptr) { break; } } - const Building* bay = f.buildings.findBuilding(bayId); + const Building* bay = findBuilding(f.state, bayId); REQUIRE(bay != nullptr); // Config-driven output-buffer capacity is applied on placement (REQ-BLD-SALVAGE-BAY). REQUIRE(bay->outputBuffer.capacity == 20); @@ -1015,7 +1016,7 @@ TEST_CASE("SalvagerSystem: full-cargo ship at its SalvageBay hands over cargo", // One unit handed over from cargo into the bay's output buffer. REQUIRE(f.admin.get(ship).current == before - 1); - const Building* bayAfter = f.buildings.findBuilding(bayId); + const Building* bayAfter = findBuilding(f.state, bayId); REQUIRE(bayAfter != nullptr); REQUIRE(bayAfter->outputBuffer.items.size() == 1); REQUIRE(bayAfter->outputBuffer.items.front().type.id == "scrap"); diff --git a/src/test/BlueprintTest.cpp b/src/test/BlueprintTest.cpp index 472067f..4a84483 100644 --- a/src/test/BlueprintTest.cpp +++ b/src/test/BlueprintTest.cpp @@ -1,4 +1,5 @@ #include "catch.hpp" +#include "FactoryQueries.h" #include #include @@ -529,9 +530,9 @@ TEST_CASE("Blueprint placement: buildings land at anchor + offset from cursor", REQUIRE(idA != kInvalidBuildingId); REQUIRE(idB != kInvalidBuildingId); - REQUIRE(sim.getBuildings().isTileOccupied(cursor + offsetA)); // (-6, 0) - REQUIRE(sim.getBuildings().isTileOccupied(cursor + offsetB)); // (-4, 0) - REQUIRE_FALSE(sim.getBuildings().isTileOccupied(cursor)); // center not occupied + REQUIRE(isTileOccupied(sim.getFactoryState(), cursor + offsetA)); // (-6, 0) + REQUIRE(isTileOccupied(sim.getFactoryState(), cursor + offsetB)); // (-4, 0) + REQUIRE_FALSE(isTileOccupied(sim.getFactoryState(), cursor)); // center not occupied } TEST_CASE("Blueprint placement: cost is deducted for each building in sequence", "[blueprint]") @@ -600,7 +601,7 @@ TEST_CASE("Simulation: tryPlaceBuilding rejects terrain-invalid placement and ch REQUIRE_FALSE(id.has_value()); REQUIRE(sim.getBuildingBlocksStock() == startBlocks); - REQUIRE(sim.getBuildings().getAllSites().empty()); + REQUIRE(getAllSites(sim.getFactoryState()).empty()); } TEST_CASE("Simulation: tryPlaceBuilding accepts a valid asteroid spot, occupies tiles, charges cost", @@ -623,11 +624,11 @@ TEST_CASE("Simulation: tryPlaceBuilding accepts a valid asteroid spot, occupies REQUIRE(id != kInvalidBuildingId); REQUIRE(sim.getBuildingBlocksStock() == startBlocks - minerCost); - REQUIRE(sim.getBuildings().isTileOccupied(QPoint(-3, 0))); - REQUIRE(sim.getBuildings().isTileOccupied(QPoint(-2, 0))); - REQUIRE(sim.getBuildings().isTileOccupied(QPoint(-3, 1))); + REQUIRE(isTileOccupied(sim.getFactoryState(), QPoint(-3, 0))); + REQUIRE(isTileOccupied(sim.getFactoryState(), QPoint(-2, 0))); + REQUIRE(isTileOccupied(sim.getFactoryState(), QPoint(-3, 1))); // The output-port tile (1,1)+anchor = (-2,1) is not a body cell. - REQUIRE_FALSE(sim.getBuildings().isTileOccupied(QPoint(-2, 1))); + REQUIRE_FALSE(isTileOccupied(sim.getFactoryState(), QPoint(-2, 1))); } // --------------------------------------------------------------------------- @@ -667,7 +668,7 @@ TEST_CASE("Blueprint placement: setRecipe on construction site stores recipe", " SimulationTestAccess::buildings(sim).setRecipe(id, "mine_iron_ore"); - const ConstructionSite* site = sim.getBuildings().findSite(id); + const ConstructionSite* site = findSite(sim.getFactoryState(), id); REQUIRE(site != nullptr); REQUIRE(site->recipeId == "mine_iron_ore"); } @@ -688,7 +689,7 @@ TEST_CASE("Blueprint placement: recipe transfers to building after construction sim.tick(); } - const Building* b = sim.getBuildings().findBuilding(id); + const Building* b = findBuilding(sim.getFactoryState(), id); REQUIRE(b != nullptr); REQUIRE(b->recipeId == "mine_copper_ore"); } @@ -708,8 +709,8 @@ TEST_CASE("Blueprint creation: a construction site is captured", "[blueprint]") const BuildingId id = SimulationTestAccess::place(sim, BuildingType::Belt, QPoint(-2, 0), Rotation::East).value(); REQUIRE(id != kInvalidBuildingId); - REQUIRE(sim.getBuildings().findSite(id) != nullptr); - REQUIRE(sim.getBuildings().findBuilding(id) == nullptr); + REQUIRE(findSite(sim.getFactoryState(), id) != nullptr); + REQUIRE(findBuilding(sim.getFactoryState(), id) == nullptr); const Blueprint bp = captureBlueprintFromSelection(sim, { id }); @@ -744,14 +745,14 @@ TEST_CASE("Blueprint creation: mixed operational building and construction site REQUIRE(idA != kInvalidBuildingId); SimulationTestAccess::buildings(sim).setRecipe(idA, "mine_iron_ore"); for (int i = 0; i <= static_cast(secondsToTicks(10.0)); ++i) { sim.tick(); } - REQUIRE(sim.getBuildings().findBuilding(idA) != nullptr); + REQUIRE(findBuilding(sim.getFactoryState(), idA) != nullptr); // Building B: place and configure, but leave as a construction site. const BuildingId idB = SimulationTestAccess::place(sim, BuildingType::Miner, QPoint(-6, 0), Rotation::East).value(); REQUIRE(idB != kInvalidBuildingId); SimulationTestAccess::buildings(sim).setRecipe(idB, "mine_copper_ore"); - REQUIRE(sim.getBuildings().findSite(idB) != nullptr); + REQUIRE(findSite(sim.getFactoryState(), idB) != nullptr); const Blueprint bp = captureBlueprintFromSelection(sim, { idA, idB }); @@ -775,7 +776,7 @@ TEST_CASE("Blueprint creation: selectionHasPlaceableBuilding sees a construction const BuildingId id = SimulationTestAccess::place(sim, BuildingType::Miner, QPoint(-2, 0), Rotation::East).value(); REQUIRE(id != kInvalidBuildingId); - REQUIRE(sim.getBuildings().findSite(id) != nullptr); + REQUIRE(findSite(sim.getFactoryState(), id) != nullptr); REQUIRE(selectionHasPlaceableBuilding(sim, { id })); } @@ -855,7 +856,7 @@ TEST_CASE("Blueprint placement: setShipLayout on construction site stores layout SimulationTestAccess::buildings(sim).setShipLayout(id, layout); - const ConstructionSite* site = sim.getBuildings().findSite(id); + const ConstructionSite* site = findSite(sim.getFactoryState(), id); REQUIRE(site != nullptr); REQUIRE(site->shipLayout.has_value()); REQUIRE(site->shipLayout->placedModules.size() == 1); @@ -892,7 +893,7 @@ TEST_CASE("Blueprint placement: ship layout transfers to building after construc sim.tick(); } - const Building* b = sim.getBuildings().findBuilding(id); + const Building* b = findBuilding(sim.getFactoryState(), id); REQUIRE(b != nullptr); REQUIRE(b->shipLayout.has_value()); REQUIRE(b->shipLayout->placedModules.size() == 1); diff --git a/src/test/BuildingConfigTest.cpp b/src/test/BuildingConfigTest.cpp index 7a86bb7..bdce076 100644 --- a/src/test/BuildingConfigTest.cpp +++ b/src/test/BuildingConfigTest.cpp @@ -1,4 +1,5 @@ #include "catch.hpp" +#include "FactoryQueries.h" #include @@ -122,8 +123,8 @@ TEST_CASE("readBuildingConfig reads a queued construction site", "[copyconfig]") const BuildingId id = SimulationTestAccess::place(sim, BuildingType::Miner, QPoint(-2, 0), Rotation::East).value(); REQUIRE(id != kInvalidBuildingId); - REQUIRE(sim.getBuildings().findBuilding(id) == nullptr); - REQUIRE(sim.getBuildings().findSite(id) != nullptr); + REQUIRE(findBuilding(sim.getFactoryState(), id) == nullptr); + REQUIRE(findSite(sim.getFactoryState(), id) != nullptr); SimulationTestAccess::buildings(sim).setRecipe(id, "mine_iron_ore"); diff --git a/src/test/BuildingTest.cpp b/src/test/BuildingTest.cpp index 51c547e..d557c51 100644 --- a/src/test/BuildingTest.cpp +++ b/src/test/BuildingTest.cpp @@ -1,4 +1,5 @@ #include "catch.hpp" +#include "FactoryQueries.h" #include #include @@ -125,11 +126,11 @@ TEST_CASE("BuildingSystem: place miner occupies expected body tiles", "[building REQUIRE(id != kInvalidBuildingId); // Miner mask ["AA","A>"] with East rotation → body at (0,0),(1,0),(0,1). - REQUIRE(bs.isTileOccupied(QPoint(0, 0))); - REQUIRE(bs.isTileOccupied(QPoint(1, 0))); - REQUIRE(bs.isTileOccupied(QPoint(0, 1))); + REQUIRE(isTileOccupied(state_bs, QPoint(0, 0))); + REQUIRE(isTileOccupied(state_bs, QPoint(1, 0))); + REQUIRE(isTileOccupied(state_bs, QPoint(0, 1))); // (1,1) is the output-port tile, NOT a body cell. - REQUIRE_FALSE(bs.isTileOccupied(QPoint(1, 1))); + REQUIRE_FALSE(isTileOccupied(state_bs, QPoint(1, 1))); } // -- World-bounds rejection (REQ-BLD-PLACE-VALID) --------------------------- @@ -142,8 +143,8 @@ TEST_CASE("BuildingSystem: place rejects a building above the world (y < 0)", "[ // row sits above the world. const std::optional id = f.bs.place(BuildingType::Miner, QPoint(0, -1), Rotation::East, 0); REQUIRE_FALSE(id.has_value()); - REQUIRE(f.bs.getAllSites().empty()); - REQUIRE_FALSE(f.bs.isTileOccupied(QPoint(0, 0))); + REQUIRE(getAllSites(f.state).empty()); + REQUIRE_FALSE(isTileOccupied(f.state, QPoint(0, 0))); } TEST_CASE("BuildingSystem: place rejects a building below the world (y >= height)", "[building]") @@ -156,7 +157,7 @@ TEST_CASE("BuildingSystem: place rejects a building below the world (y >= height const std::optional id = f.bs.place(BuildingType::Miner, QPoint(0, heightTiles - 1), Rotation::East, 0); REQUIRE_FALSE(id.has_value()); - REQUIRE(f.bs.getAllSites().empty()); + REQUIRE(getAllSites(f.state).empty()); } TEST_CASE("BuildingSystem: place rejects a building left of the asteroid edge", "[building]") @@ -167,7 +168,7 @@ TEST_CASE("BuildingSystem: place rejects a building left of the asteroid edge", const std::optional id = f.bs.place(BuildingType::Miner, QPoint(leftEdgeX - 1, 0), Rotation::East, 0); REQUIRE_FALSE(id.has_value()); - REQUIRE(f.bs.getAllSites().empty()); + REQUIRE(getAllSites(f.state).empty()); } TEST_CASE("BuildingSystem: place accepts a building flush against the world's left edge", @@ -180,7 +181,7 @@ TEST_CASE("BuildingSystem: place accepts a building flush against the world's le const BuildingId id = f.bs.place(BuildingType::Miner, QPoint(leftEdgeX, 0), Rotation::East, 0).value(); REQUIRE(id != kInvalidBuildingId); - REQUIRE(f.bs.isTileOccupied(QPoint(leftEdgeX, 0))); + REQUIRE(isTileOccupied(f.state, QPoint(leftEdgeX, 0))); } TEST_CASE("BuildingSystem: place imposes no right-side bound (space extends rightward)", @@ -240,9 +241,9 @@ TEST_CASE("BuildingSystem: placing a belt registers it with BeltSystem after con runTicks(bs, belts, static_cast(secondsToTicks(1.0)) + 1, tick); REQUIRE(belts.tryPutItem(QPoint(5, 5), makeItem("iron_ore"), Rotation::East)); - REQUIRE(bs.getAllBuildings().size() == 1); - REQUIRE(bs.getAllBuildings()[0].type == BuildingType::Belt); - REQUIRE(bs.getAllBuildings()[0].anchor == QPoint(5, 5)); + REQUIRE(getAllBuildings(state_bs).size() == 1); + REQUIRE(getAllBuildings(state_bs)[0].type == BuildingType::Belt); + REQUIRE(getAllBuildings(state_bs)[0].anchor == QPoint(5, 5)); } TEST_CASE("BuildingSystem: placed building enters construction queue", "[building]") @@ -262,9 +263,9 @@ TEST_CASE("BuildingSystem: placed building enters construction queue", "[buildin const BuildingId id = bs.place(BuildingType::Miner, QPoint(0, 0), Rotation::East, 0).value(); - REQUIRE(bs.getAllSites().size() == 1); - REQUIRE(bs.getAllBuildings().empty()); - REQUIRE(bs.findSite(id) != nullptr); + REQUIRE(getAllSites(state_bs).size() == 1); + REQUIRE(getAllBuildings(state_bs).empty()); + REQUIRE(findSite(state_bs, id) != nullptr); } TEST_CASE("BuildingSystem: deconstructing a construction site removes it instantly with full refund", @@ -279,8 +280,8 @@ TEST_CASE("BuildingSystem: deconstructing a construction site removes it instant const int refund = f.bs.deconstruct(id, 0); REQUIRE(refund == 15); // Miner cost = 15 - REQUIRE_FALSE(f.bs.isTileOccupied(QPoint(0, 0))); - REQUIRE(f.bs.getAllSites().empty()); + REQUIRE_FALSE(isTileOccupied(f.state, QPoint(0, 0))); + REQUIRE(getAllSites(f.state).empty()); } // --------------------------------------------------------------------------- @@ -304,7 +305,7 @@ TEST_CASE("BuildingSystem: first queued building starts construction immediately rng); bs.place(BuildingType::Miner, QPoint(0, 0), Rotation::East, 0); - REQUIRE(bs.getAllSites().front().completesAt > 0); + REQUIRE(getAllSites(state_bs).front().completesAt > 0); } TEST_CASE("BuildingSystem: second queued building waits (completesAt == 0)", "[building]") @@ -325,9 +326,9 @@ TEST_CASE("BuildingSystem: second queued building waits (completesAt == 0)", "[b bs.place(BuildingType::Miner, QPoint(0, 0), Rotation::East, 0); bs.place(BuildingType::Miner, QPoint(5, 5), Rotation::East, 0); - REQUIRE(bs.getAllSites().size() == 2); - REQUIRE(bs.getAllSites()[0].completesAt > 0); - REQUIRE(bs.getAllSites()[1].completesAt == 0); + REQUIRE(getAllSites(state_bs).size() == 2); + REQUIRE(getAllSites(state_bs)[0].completesAt > 0); + REQUIRE(getAllSites(state_bs)[1].completesAt == 0); } TEST_CASE("BuildingSystem: construction completes after configured duration", "[building]") @@ -352,8 +353,8 @@ TEST_CASE("BuildingSystem: construction completes after configured duration", "[ Tick tick = 0; runTicks(bs, belts, static_cast(secondsToTicks(10.0)) + 1, tick); - REQUIRE(bs.getAllSites().empty()); - REQUIRE(bs.findBuilding(id) != nullptr); + REQUIRE(getAllSites(state_bs).empty()); + REQUIRE(findBuilding(state_bs, id) != nullptr); } // --------------------------------------------------------------------------- @@ -363,11 +364,11 @@ TEST_CASE("BuildingSystem: construction completes after configured duration", "[ // Runs ticks until the building with the given id is operational, or fails. static void runUntilBuilt(PlacementFixture& f, BuildingId id, Tick& tick) { - for (int i = 0; i < 100000 && f.bs.findBuilding(id) == nullptr; ++i) + for (int i = 0; i < 100000 && findBuilding(f.state, id) == nullptr; ++i) { runTicks(f.bs, f.belts, 1, tick); } - REQUIRE(f.bs.findBuilding(id) != nullptr); + REQUIRE(findBuilding(f.state, id) != nullptr); } TEST_CASE("BuildingSystem: deconstructing a built building queues it; refund credited on completion", @@ -384,15 +385,15 @@ TEST_CASE("BuildingSystem: deconstructing a built building queues it; refund cre // stopping it operating while its tiles stay occupied (REQ-BLD-DECON-QUEUE). const int refund = f.bs.deconstruct(id, tick); REQUIRE(refund == 0); - REQUIRE(f.bs.isQueuedForDeconstruction(id)); - REQUIRE(f.bs.isTileOccupied(QPoint(0, 0))); + REQUIRE(isQueuedForDeconstruction(f.state, id)); + REQUIRE(isTileOccupied(f.state, QPoint(0, 0))); REQUIRE(f.stock == 0); // After the deconstruction time (0.1s = 3 ticks) it is removed and the partial // refund (15 * 75 / 100 = 11) is credited exactly once. runTicks(f.bs, f.belts, static_cast(secondsToTicks(0.1)) + 1, tick); - REQUIRE(f.bs.findBuilding(id) == nullptr); - REQUIRE_FALSE(f.bs.isTileOccupied(QPoint(0, 0))); + REQUIRE(findBuilding(f.state, id) == nullptr); + REQUIRE_FALSE(isTileOccupied(f.state, QPoint(0, 0))); REQUIRE(f.stock == 15 * f.cfg.world.refundPercentage / 100); } @@ -409,20 +410,20 @@ TEST_CASE("BuildingSystem: deconstruction queue removes one building at a time", // Queue both in one tick; 'a' is at the front of the deconstruction queue. f.bs.deconstruct(a, tick); f.bs.deconstruct(b, tick); - REQUIRE(f.bs.isQueuedForDeconstruction(a)); - REQUIRE(f.bs.isQueuedForDeconstruction(b)); + REQUIRE(isQueuedForDeconstruction(f.state, a)); + REQUIRE(isQueuedForDeconstruction(f.state, b)); // After one deconstruction interval only the front building is gone; the // second is still queued and its refund not yet credited. runTicks(f.bs, f.belts, static_cast(secondsToTicks(0.1)) + 1, tick); - REQUIRE(f.bs.findBuilding(a) == nullptr); - REQUIRE(f.bs.findBuilding(b) != nullptr); - REQUIRE(f.bs.isQueuedForDeconstruction(b)); + REQUIRE(findBuilding(f.state, a) == nullptr); + REQUIRE(findBuilding(f.state, b) != nullptr); + REQUIRE(isQueuedForDeconstruction(f.state, b)); REQUIRE(f.stock == 15 * f.cfg.world.refundPercentage / 100); // The second drains next. runTicks(f.bs, f.belts, static_cast(secondsToTicks(0.1)) + 2, tick); - REQUIRE(f.bs.findBuilding(b) == nullptr); + REQUIRE(findBuilding(f.state, b) == nullptr); REQUIRE(f.stock == 2 * (15 * f.cfg.world.refundPercentage / 100)); } @@ -437,18 +438,18 @@ TEST_CASE("BuildingSystem: cancelling deconstruction resumes the building with n runUntilBuilt(f, id, tick); f.bs.deconstruct(id, tick); - REQUIRE(f.bs.isQueuedForDeconstruction(id)); + REQUIRE(isQueuedForDeconstruction(f.state, id)); // Un-queue before it drains: it operates again, no refund, tiles still occupied. f.bs.cancelDeconstruction(id); - REQUIRE_FALSE(f.bs.isQueuedForDeconstruction(id)); - REQUIRE(f.bs.findBuilding(id) != nullptr); - REQUIRE(f.bs.isTileOccupied(QPoint(0, 0))); + REQUIRE_FALSE(isQueuedForDeconstruction(f.state, id)); + REQUIRE(findBuilding(f.state, id) != nullptr); + REQUIRE(isTileOccupied(f.state, QPoint(0, 0))); REQUIRE(f.stock == 0); // It is never removed even after more than a deconstruction interval passes. runTicks(f.bs, f.belts, static_cast(secondsToTicks(0.1)) + 5, tick); - REQUIRE(f.bs.findBuilding(id) != nullptr); + REQUIRE(findBuilding(f.state, id) != nullptr); REQUIRE(f.stock == 0); } @@ -466,7 +467,7 @@ TEST_CASE("BuildingSystem: queued belt stops transporting; cancel restores it", // accepts or transports items, though the tile stays occupied (REQ-BLD-DECON-QUEUE). f.bs.deconstruct(id, tick); REQUIRE_FALSE(f.belts.tryPutItem(QPoint(0, 0), makeItem("iron_ore"), Rotation::East)); - REQUIRE(f.bs.isTileOccupied(QPoint(0, 0))); + REQUIRE(isTileOccupied(f.state, QPoint(0, 0))); // Un-queuing re-registers the belt tile so it transports again. f.bs.cancelDeconstruction(id); @@ -519,9 +520,9 @@ TEST_CASE("BuildingSystem: second building starts after first completes", "[buil Tick tick = 0; runTicks(bs, belts, static_cast(secondsToTicks(10.0)) + 1, tick); - REQUIRE(bs.getAllSites().size() == 1); - REQUIRE(bs.getAllSites().front().id == id2); - REQUIRE(bs.getAllSites().front().completesAt > 0); + REQUIRE(getAllSites(state_bs).size() == 1); + REQUIRE(getAllSites(state_bs).front().id == id2); + REQUIRE(getAllSites(state_bs).front().completesAt > 0); } // --------------------------------------------------------------------------- @@ -553,7 +554,7 @@ TEST_CASE("BuildingSystem: miner produces iron_ore after recipe duration", "[bui static_cast(secondsToTicks(10.0)) + static_cast(secondsToTicks(1.0)) + 1, tick); - const Building* b = bs.findBuilding(id); + const Building* b = findBuilding(state_bs, id); REQUIRE(b != nullptr); // No belt at the output port, so the produced item emerges and stays on the // building's virtual output belt (REQ-MAT-OUTPUT-EMERGE). @@ -591,7 +592,7 @@ TEST_CASE("BuildingSystem: miner output buffer stalls when full", "[building]") + 2 * static_cast(secondsToTicks(1.0)) + 2, tick); - const Building* b = bs.findBuilding(id); + const Building* b = findBuilding(state_bs, id); REQUIRE(b != nullptr); // Both produced items are held on the output side (buffer + emerging lane), // which is what the capacity rule counts (REQ-MAT-OUTPUT-EMERGE). @@ -624,23 +625,23 @@ TEST_CASE("BuildingSystem: productionBuildingCount excludes construction sites", Tick tick = 0; // Both still under construction. - REQUIRE(bs.getProductionBuildingCount() == 0); + REQUIRE(getProductionBuildingCount(state_bs) == 0); // The queue builds one at a time: miner (10s) completes at tick 300, then // the smelter (15s) starts and completes at tick 300 + 450 = 750. runTicks(bs, belts, static_cast(secondsToTicks(10.0)) + 1, tick); - REQUIRE(bs.getProductionBuildingCount() == 1); + REQUIRE(getProductionBuildingCount(state_bs) == 1); runTicks(bs, belts, static_cast(secondsToTicks(15.0)), tick); - REQUIRE(bs.getProductionBuildingCount() == 2); + REQUIRE(getProductionBuildingCount(state_bs) == 2); // Neither is producing yet: the miner has no recipe selected, and the // smelter (auto-recipe, REQ-BLD-SMELTER) has no input feeding it. - REQUIRE(bs.getActiveProductionBuildingCount() == 0); + REQUIRE(getActiveProductionBuildingCount(state_bs) == 0); bs.setRecipe(minerId, "mine_iron_ore"); runTicks(bs, belts, 1, tick); - REQUIRE(bs.getActiveProductionBuildingCount() == 1); + REQUIRE(getActiveProductionBuildingCount(state_bs) == 1); } TEST_CASE("BuildingSystem: activeProductionBuildingCount tracks production cycle state", @@ -664,21 +665,21 @@ TEST_CASE("BuildingSystem: activeProductionBuildingCount tracks production cycle Tick tick = 0; // Not yet operational while under construction. - REQUIRE(bs.getActiveProductionBuildingCount() == 0); + REQUIRE(getActiveProductionBuildingCount(state_bs) == 0); // Construction completes at tick 300; cycle 1 starts the same tick (completesAt=330). runTicks(bs, belts, static_cast(secondsToTicks(10.0)) + 1, tick); - REQUIRE(bs.getActiveProductionBuildingCount() == 1); + REQUIRE(getActiveProductionBuildingCount(state_bs) == 1); // Run cycles 1 and 2 to completion (1s each); cycle 3 stalls once the // output buffer (capacity 2) is full (REQ-MAT-OUTPUT-BUFFER). runTicks(bs, belts, 2 * static_cast(secondsToTicks(1.0)) + 1, tick); - const Building* b = bs.findBuilding(id); + const Building* b = findBuilding(state_bs, id); REQUIRE(b != nullptr); REQUIRE(b->getOutputItemCount() == 2); REQUIRE_FALSE(b->production.has_value()); - REQUIRE(bs.getActiveProductionBuildingCount() == 0); + REQUIRE(getActiveProductionBuildingCount(state_bs) == 0); } // --------------------------------------------------------------------------- @@ -719,7 +720,7 @@ TEST_CASE("BuildingSystem: smelter input buffer fills from adjacent west-flowing bs.tickBeltPull(); - const Building* b = bs.findBuilding(sid); + const Building* b = findBuilding(state_bs, sid); REQUIRE(b != nullptr); // The item was accepted; it may still be travelling inward on the input belt, // so count buffered + in-transit (REQ-MAT-INPUT-INTAKE). @@ -754,7 +755,7 @@ TEST_CASE("BuildingSystem: accepted input travels inward before entering the buf belts.tick(); bs.tickBeltPull(); // accepts the item onto the input belt at progress 0.0 - const Building* b = bs.findBuilding(sid); + const Building* b = findBuilding(state_bs, sid); REQUIRE(b != nullptr); // Reserved but not yet consumable: nothing in the buffer, but it counts against // the cap via pendingInputCount. @@ -802,7 +803,7 @@ TEST_CASE("BuildingSystem: input reservation caps buffered plus in-transit at th bs.tickBeltPull(); } - const Building* b = bs.findBuilding(id); + const Building* b = findBuilding(state_bs, id); REQUIRE(b != nullptr); const int cap = b->inputBuffer.caps.at(ItemType{"scrap"}); REQUIRE(cap > 0); @@ -848,7 +849,7 @@ TEST_CASE("BuildingSystem: smelter auto-smelts ore without a recipe selection", // iron_ingot recipe cycle is 2s; run to completion. runTicks(bs, belts, static_cast(secondsToTicks(2.0)) + 2, tick); - const Building* b = bs.findBuilding(sid); + const Building* b = findBuilding(state_bs, sid); REQUIRE(b != nullptr); bool hasIronIngot = false; for (const Item& item : outputSideItems(*b)) @@ -896,7 +897,7 @@ TEST_CASE("BuildingSystem: smelter runs a satisfiable recipe while an incomplete // copper_ingot cycle is 2.5s; run to completion. runTicks(bs, belts, static_cast(secondsToTicks(2.5)) + 2, tick); - const Building* b = bs.findBuilding(sid); + const Building* b = findBuilding(state_bs, sid); REQUIRE(b != nullptr); // Copper was smelted; the lone iron_ore still waits for a second unit. @@ -984,7 +985,7 @@ TEST_CASE("BuildingSystem: output port couples directly into an adjacent input p // Smelter build (15s) + margin for coupling and a smelt cycle. runTicks(bs, belts, static_cast(secondsToTicks(30.0)), tick); - const Building* smelter = bs.findBuilding(smelterId); + const Building* smelter = findBuilding(state_bs, smelterId); REQUIRE(smelter != nullptr); // iron_ore reached the smelter over the direct coupling and was smelted. bool hasIronIngot = false; @@ -1024,8 +1025,8 @@ TEST_CASE("BuildingSystem: direct coupling to a non-consumer leaves the item stu // Both miners build sequentially (10s each), then the producer runs and jams. runTicks(bs, belts, static_cast(secondsToTicks(25.0)), tick); - const Building* miner = bs.findBuilding(minerId); - const Building* sink = bs.findBuilding(sinkId); + const Building* miner = findBuilding(state_bs, minerId); + const Building* sink = findBuilding(state_bs, sinkId); REQUIRE(miner != nullptr); REQUIRE(sink != nullptr); // Nothing was delivered, and the producer's output side has backed up to its cap. @@ -1063,14 +1064,14 @@ TEST_CASE("BuildingSystem: setRecipe clears output buffer and active production" tick); { - const Building* b = bs.findBuilding(id); + const Building* b = findBuilding(state_bs, id); REQUIRE(b != nullptr); REQUIRE(b->getOutputItemCount() > 0); } bs.setRecipe(id, "mine_copper_ore"); - const Building* b = bs.findBuilding(id); + const Building* b = findBuilding(state_bs, id); // Clearing the output buffer on a recipe change also discards emerging items // (REQ-MAT-OUTPUT-EMERGE). REQUIRE(b->getOutputItemCount() == 0); @@ -1106,7 +1107,7 @@ TEST_CASE("BuildingSystem: reprocessing plant output buffer capacity equals max Tick tick = 0; runTicks(bs, belts, static_cast(secondsToTicks(25.0)) + 1, tick); - const Building* b = bs.findBuilding(id); + const Building* b = findBuilding(state_bs, id); REQUIRE(b != nullptr); // reprocessing_cycle outputs: 2 iron_ingot (60%), 1 circuit_board (30%), // 1 advanced_alloy (10%). Max per roll = 2. Capacity = 2 (1× max). @@ -1153,7 +1154,7 @@ TEST_CASE("BuildingSystem: reprocessing plant produces one cycle output then sta // Verify all five scrap were accepted; some may still be travelling inward on // the input belt (REQ-MAT-INPUT-INTAKE), so count buffered + in-transit. { - const Building* b = bs.findBuilding(id); + const Building* b = findBuilding(state_bs, id); REQUIRE(b != nullptr); REQUIRE(b->pendingInputCount(ItemType{"scrap"}) == 5); } @@ -1161,7 +1162,7 @@ TEST_CASE("BuildingSystem: reprocessing plant produces one cycle output then sta // Run production cycle (3s = 90 ticks + 1 for the completion tick). runTicks(bs, belts, static_cast(secondsToTicks(3.0)) + 1, tick); - const Building* b = bs.findBuilding(id); + const Building* b = findBuilding(state_bs, id); REQUIRE(b != nullptr); // Cycle should have completed and output deposited. REQUIRE_FALSE(b->outputBuffer.items.empty()); @@ -1237,7 +1238,7 @@ TEST_CASE("BuildingSystem: findRotateInPlaceTarget returns the building id for a Tick tick = 0; runTicks(bs, belts, static_cast(secondsToTicks(1.0)) + 1, tick); - REQUIRE(bs.getAllSites().empty()); + REQUIRE(getAllSites(state_bs).empty()); const std::optional result = bs.findRotateInPlaceTarget(BuildingType::Belt, QPoint(0, 0), Rotation::South); @@ -1367,11 +1368,11 @@ TEST_CASE("BuildingSystem: rotateInPlace updates the rotation field of a constru rng); const BuildingId id = bs.place(BuildingType::Belt, QPoint(0, 0), Rotation::East, 0).value(); - REQUIRE(bs.findSite(id)->rotation == Rotation::East); + REQUIRE(findSite(state_bs, id)->rotation == Rotation::East); bs.rotateInPlace(id, Rotation::North); - REQUIRE(bs.findSite(id)->rotation == Rotation::North); + REQUIRE(findSite(state_bs, id)->rotation == Rotation::North); } TEST_CASE("BuildingSystem: rotateInPlace preserves the construction progress of a queued site", @@ -1391,12 +1392,12 @@ TEST_CASE("BuildingSystem: rotateInPlace preserves the construction progress of rng); const BuildingId id = bs.place(BuildingType::Belt, QPoint(0, 0), Rotation::East, 0).value(); - const Tick completesAt = bs.findSite(id)->completesAt; + const Tick completesAt = findSite(state_bs, id)->completesAt; REQUIRE(completesAt > 0); bs.rotateInPlace(id, Rotation::South); - REQUIRE(bs.findSite(id)->completesAt == completesAt); + REQUIRE(findSite(state_bs, id)->completesAt == completesAt); } TEST_CASE("BuildingSystem: rotateInPlace updates rotation and output port direction on an operational building", @@ -1419,14 +1420,14 @@ TEST_CASE("BuildingSystem: rotateInPlace updates rotation and output port direct Tick tick = 0; runTicks(bs, belts, static_cast(secondsToTicks(1.0)) + 1, tick); - REQUIRE(bs.findBuilding(id) != nullptr); + REQUIRE(findBuilding(state_bs, id) != nullptr); - const Building& before = *bs.findBuilding(id); + const Building& before = *findBuilding(state_bs, id); REQUIRE(before.outputPorts[0].direction == Rotation::East); bs.rotateInPlace(id, Rotation::North); - const Building& after = *bs.findBuilding(id); + const Building& after = *findBuilding(state_bs, id); REQUIRE(after.rotation == Rotation::North); REQUIRE(after.outputPorts[0].direction == Rotation::North); } @@ -1468,11 +1469,11 @@ TEST_CASE("BuildingSystem: rotateInPlace preserves the output filters of a split // Run until construction completes, so the splitter is registered with BeltSystem. Tick tick = 0; - while (f.bs.getAllBuildings().empty() && tick < 100000) + while (getAllBuildings(f.state).empty() && tick < 100000) { runTicks(f.bs, f.belts, 1, tick); } - REQUIRE(f.bs.getAllBuildings().size() == 1); + REQUIRE(getAllBuildings(f.state).size() == 1); const std::vector filterA{ ItemType{"iron_ore"} }; const std::vector filterB{ ItemType{"copper_ore"} }; @@ -1496,7 +1497,7 @@ TEST_CASE("BuildingSystem: splitter filters configured on a construction site ca const QPoint tile(5, 5); const BuildingId id = f.bs.place(BuildingType::Splitter, tile, Rotation::East, 0).value(); REQUIRE(id != kInvalidBuildingId); - REQUIRE(f.bs.findSite(id) != nullptr); + REQUIRE(findSite(f.state, id) != nullptr); // Configure an output filter on the still-queued splitter site. const std::vector filterA{ ItemType{"iron_ore"} }; @@ -1513,12 +1514,12 @@ TEST_CASE("BuildingSystem: splitter filters configured on a construction site ca // Run until construction completes. Tick tick = 0; - while (f.bs.getAllBuildings().empty() && tick < 100000) + while (getAllBuildings(f.state).empty() && tick < 100000) { runTicks(f.bs, f.belts, 1, tick); } - REQUIRE(f.bs.getAllBuildings().size() == 1); - REQUIRE(f.bs.getAllBuildings()[0].type == BuildingType::Splitter); + REQUIRE(getAllBuildings(f.state).size() == 1); + REQUIRE(getAllBuildings(f.state)[0].type == BuildingType::Splitter); // The built splitter is registered with BeltSystem carrying the filters. const std::optional builtInfo = f.belts.getSplitterInfo(tile); @@ -1669,10 +1670,10 @@ namespace // Advances the sim until the given site becomes an operational building, or a // safety cap is reached. - void buildToCompletion(BuildingSystem& bs, BeltSystem& belts, BuildingId id, - Tick& tick) + void buildToCompletion(BuildingSystem& bs, const FactoryState& state, + BeltSystem& belts, BuildingId id, Tick& tick) { - for (int i = 0; i < 20000 && bs.findBuilding(id) == nullptr; ++i) + for (int i = 0; i < 20000 && findBuilding(state, id) == nullptr; ++i) { runTicks(bs, belts, 1, tick); } @@ -1709,8 +1710,8 @@ TEST_CASE("BuildingSystem: getInputPorts matches between a site and the built bu const BuildingId id = f.bs.place(BuildingType::Miner, QPoint(0, 0), Rotation::East, 0).value(); const std::vector sitePorts = f.bs.getInputPorts(id); - buildToCompletion(f.bs, f.belts, id, tick); - REQUIRE(f.bs.findBuilding(id) != nullptr); + buildToCompletion(f.bs, f.state, f.belts, id, tick); + REQUIRE(findBuilding(f.state, id) != nullptr); const std::vector builtPorts = f.bs.getInputPorts(id); // The operational path (stored inputPorts) agrees with the site path (mask-derived). @@ -1725,7 +1726,7 @@ TEST_CASE("BuildingSystem: getInputPorts invariants hold for a rotated site", "[ { PlacementFixture f; const BuildingId id = f.bs.place(BuildingType::Miner, QPoint(0, 0), Rotation::South, 0).value(); - const ConstructionSite* site = f.bs.findSite(id); + const ConstructionSite* site = findSite(f.state, id); REQUIRE(site != nullptr); std::set> bodySet; diff --git a/src/test/ShipModuleTest.cpp b/src/test/ShipModuleTest.cpp index 3f83e1d..ff09e15 100644 --- a/src/test/ShipModuleTest.cpp +++ b/src/test/ShipModuleTest.cpp @@ -1,4 +1,5 @@ #include "catch.hpp" +#include "FactoryQueries.h" #include "Building.h" #include "BuildingSystem.h" @@ -218,7 +219,7 @@ TEST_CASE("Shipyard: setShipLayout reinitializes buffers with module materials", SimulationTestAccess::buildings(sim).setShipLayout(yardId, layout); - const Building* b = sim.getBuildings().findBuilding(yardId); + const Building* b = findBuilding(sim.getFactoryState(), yardId); REQUIRE(b != nullptr); // armor_plate needs 2 iron_ingot; interceptor needs 3 iron_ingot + 1 circuit_board // Total iron_ingot = 5, buffer cap = 2 * 5 = 10 @@ -243,7 +244,7 @@ TEST_CASE("Shipyard: setShipLayout cancels in-progress production", fillMaterials(sim, yardId, *def, emptyLayout); sim.tick(); - const Building* b1 = sim.getBuildings().findBuilding(yardId); + const Building* b1 = findBuilding(sim.getFactoryState(), yardId); REQUIRE(b1 != nullptr); REQUIRE(b1->production.has_value()); @@ -257,7 +258,7 @@ TEST_CASE("Shipyard: setShipLayout cancels in-progress production", SimulationTestAccess::buildings(sim).setShipLayout(yardId, layout); - const Building* b2 = sim.getBuildings().findBuilding(yardId); + const Building* b2 = findBuilding(sim.getFactoryState(), yardId); REQUIRE(b2 != nullptr); CHECK_FALSE(b2->production.has_value()); } @@ -323,13 +324,13 @@ TEST_CASE("Shipyard: setRecipe clears ship layout", "[modules][shipyard]") layout.placedModules.push_back(pm); SimulationTestAccess::buildings(sim).setShipLayout(yardId, layout); - const Building* b1 = sim.getBuildings().findBuilding(yardId); + const Building* b1 = findBuilding(sim.getFactoryState(), yardId); REQUIRE(b1 != nullptr); REQUIRE(b1->shipLayout.has_value()); SimulationTestAccess::buildings(sim).setRecipe(yardId,"destroyer"); - const Building* b2 = sim.getBuildings().findBuilding(yardId); + const Building* b2 = findBuilding(sim.getFactoryState(), yardId); REQUIRE(b2 != nullptr); CHECK_FALSE(b2->shipLayout.has_value()); } @@ -352,14 +353,14 @@ TEST_CASE("Shipyard: setRecipe with unchanged recipe keeps ship layout", layout.placedModules.push_back(pm); SimulationTestAccess::buildings(sim).setShipLayout(yardId, layout); - const Building* b1 = sim.getBuildings().findBuilding(yardId); + const Building* b1 = findBuilding(sim.getFactoryState(), yardId); REQUIRE(b1 != nullptr); REQUIRE(b1->shipLayout.has_value()); // Re-selecting the same recipe must be a no-op and preserve the layout. SimulationTestAccess::buildings(sim).setRecipe(yardId,"interceptor"); - const Building* b2 = sim.getBuildings().findBuilding(yardId); + const Building* b2 = findBuilding(sim.getFactoryState(), yardId); REQUIRE(b2 != nullptr); REQUIRE(b2->shipLayout.has_value()); REQUIRE(b2->shipLayout->placedModules.size() == 1); diff --git a/src/test/ShipyardTest.cpp b/src/test/ShipyardTest.cpp index d60f7a8..e62a026 100644 --- a/src/test/ShipyardTest.cpp +++ b/src/test/ShipyardTest.cpp @@ -1,4 +1,5 @@ #include "catch.hpp" +#include "FactoryQueries.h" #include @@ -214,7 +215,7 @@ TEST_CASE("Shipyard: spawns a second ship after materials replenished", "[shipya // Verify the shipyard production field cleared (i.e. the cycle completed // and is not still running). bool productionCleared = false; - for (const Building& b : sim.getBuildings().getAllBuildings()) + for (const Building& b : getAllBuildings(sim.getFactoryState())) { if (b.id == yardId) { diff --git a/src/test/WaveSystemTest.cpp b/src/test/WaveSystemTest.cpp index 1b189bb..6009536 100644 --- a/src/test/WaveSystemTest.cpp +++ b/src/test/WaveSystemTest.cpp @@ -1,4 +1,5 @@ #include "catch.hpp" +#include "FactoryQueries.h" #include @@ -100,7 +101,7 @@ TEST_CASE("WaveSystem: Simulation pre-places HQ + 2 player + 2 enemy stations", // HQ is still a Building (for belt integration). int hqCount = 0; - for (const Building& b : sim.getBuildings().getAllBuildings()) + for (const Building& b : getAllBuildings(sim.getFactoryState())) { if (b.type == BuildingType::Hq) { ++hqCount; } } @@ -143,7 +144,7 @@ TEST_CASE("WaveSystem: HQ anchor is at asteroid right edge", "[wave]") { const Simulation sim(loadTestConfig(), 42); - for (const Building& b : sim.getBuildings().getAllBuildings()) + for (const Building& b : getAllBuildings(sim.getFactoryState())) { if (b.type != BuildingType::Hq) { continue; } // Rightmost body cell must be at x = -1 (asteroid right edge). diff --git a/src/ui/GameWorldView.cpp b/src/ui/GameWorldView.cpp index ed47171..f07f7cb 100644 --- a/src/ui/GameWorldView.cpp +++ b/src/ui/GameWorldView.cpp @@ -1,4 +1,5 @@ #include "GameWorldView.h" +#include "FactoryQueries.h" #include #include @@ -577,7 +578,7 @@ QRect GameWorldView::getViewportRect() const float GameWorldView::getAsteroidLeftEdge() const { float leftX = -static_cast(m_sim->getCurrentAsteroidWidth_tiles()); - for (const Building& b : m_sim->getBuildings().getAllBuildings()) + for (const Building& b : getAllBuildings(m_sim->getFactoryState())) { for (const QPoint& cell : b.bodyCells) { @@ -672,7 +673,7 @@ bool GameWorldView::isValidPlacement(BuildingType type, QPoint anchor, bool anyOccupied = false; for (const QPoint& relCell : parsed.bodyCells) { - if (m_sim->getBuildings().isTileOccupied(anchor + relCell)) + if (isTileOccupied(m_sim->getFactoryState(), anchor + relCell)) { anyOccupied = true; break; @@ -688,7 +689,7 @@ bool GameWorldView::isValidPlacement(BuildingType type, QPoint anchor, std::optional GameWorldView::buildingAtTile(QPoint tile) const { - for (const Building& b : m_sim->getBuildings().getAllBuildings()) + for (const Building& b : getAllBuildings(m_sim->getFactoryState())) { for (const QPoint& cell : b.bodyCells) { @@ -703,7 +704,7 @@ std::optional GameWorldView::buildingAtTile(QPoint tile) const std::optional GameWorldView::siteAtTile(QPoint tile) const { - for (const ConstructionSite& s : m_sim->getBuildings().getAllSites()) + for (const ConstructionSite& s : getAllSites(m_sim->getFactoryState())) { for (const QPoint& cell : s.bodyCells) { @@ -725,7 +726,7 @@ std::vector GameWorldView::buildingsInBox(QPoint cornerA, QPoint cor const int y1 = std::max(cornerA.y(), cornerB.y()); std::vector ids; - for (const Building& b : m_sim->getBuildings().getAllBuildings()) + for (const Building& b : getAllBuildings(m_sim->getFactoryState())) { for (const QPoint& cell : b.bodyCells) { @@ -737,7 +738,7 @@ std::vector GameWorldView::buildingsInBox(QPoint cornerA, QPoint cor } } } - for (const ConstructionSite& s : m_sim->getBuildings().getAllSites()) + for (const ConstructionSite& s : getAllSites(m_sim->getFactoryState())) { for (const QPoint& cell : s.bodyCells) { @@ -951,14 +952,14 @@ TunnelTileMap GameWorldView::collectTunnelTiles() const // single-cell tile, so a just-placed tunnel (not yet constructed) is matchable // (REQ-BLD-TUNNEL-MODE, REQ-BLD-TUNNEL-SELECT-HIGHLIGHT). TunnelTileMap tunnels; - for (const Building& b : m_sim->getBuildings().getAllBuildings()) + for (const Building& b : getAllBuildings(m_sim->getFactoryState())) { if (b.type == BuildingType::TunnelEntry || b.type == BuildingType::TunnelExit) { tunnels[b.anchor] = TunnelTileInfo{b.type, b.rotation}; } } - for (const ConstructionSite& s : m_sim->getBuildings().getAllSites()) + for (const ConstructionSite& s : getAllSites(m_sim->getFactoryState())) { if (s.type == BuildingType::TunnelEntry || s.type == BuildingType::TunnelExit) { @@ -1034,7 +1035,7 @@ void GameWorldView::placeAtTile(QPoint tile) || type == BuildingType::TunnelEntry || type == BuildingType::TunnelExit) { - if (!m_sim->getBuildings().isTileOccupied(tile) && canAfford(type)) + if (!isTileOccupied(m_sim->getFactoryState(), tile) && canAfford(type)) { enqueuePlaceBuilding(type, tile, m_ghostRotation); } @@ -1061,7 +1062,7 @@ void GameWorldView::recomputeBeltDragPath(QPoint cursorTile) std::optional targetType; if (targetId.has_value()) { - if (const Building* building = m_sim->getBuildings().findBuilding(*targetId)) + if (const Building* building = findBuilding(m_sim->getFactoryState(), *targetId)) { targetType = building->type; } @@ -1069,7 +1070,7 @@ void GameWorldView::recomputeBeltDragPath(QPoint cursorTile) else if (std::optional siteId = siteAtTile(cursorTile); siteId.has_value()) { targetId = siteId; - if (const ConstructionSite* site = m_sim->getBuildings().findSite(*siteId)) + if (const ConstructionSite* site = findSite(m_sim->getFactoryState(), *siteId)) { targetType = site->type; } @@ -1298,7 +1299,7 @@ bool GameWorldView::drawBuildingIcon(QPainter& painter, BuildingType type, void GameWorldView::drawBuildings(QPainter& painter) { - for (const Building& b : m_sim->getBuildings().getAllBuildings()) + for (const Building& b : getAllBuildings(m_sim->getFactoryState())) { const std::map::const_iterator it = m_visuals->buildings.find(b.type); @@ -1365,7 +1366,7 @@ void GameWorldView::drawBuildings(QPainter& painter) } painter.setOpacity(0.5); - for (const ConstructionSite& s : m_sim->getBuildings().getAllSites()) + for (const ConstructionSite& s : getAllSites(m_sim->getFactoryState())) { const std::map::const_iterator it = m_visuals->buildings.find(s.type); @@ -1442,7 +1443,7 @@ void GameWorldView::drawBuildings(QPainter& painter) // after every building and construction site fill so a belt (or other tile) // placed directly below the HQ cannot overpaint the bar (REQ-UI-STATUS-LIGHT // neighbours case, same rationale as the selection highlights below). - for (const Building& b : m_sim->getBuildings().getAllBuildings()) + for (const Building& b : getAllBuildings(m_sim->getFactoryState())) { if (b.type != BuildingType::Hq) { continue; } const QPointF tl = tileToWidget(b.anchor); @@ -1473,12 +1474,12 @@ std::optional GameWorldView::footprintWidgetRect(BuildingId id) const std::optional anchor; std::optional footprint; - if (const Building* b = m_sim->getBuildings().findBuilding(id)) + if (const Building* b = findBuilding(m_sim->getFactoryState(), id)) { anchor = b->anchor; footprint = b->footprint; } - else if (const ConstructionSite* s = m_sim->getBuildings().findSite(id)) + else if (const ConstructionSite* s = findSite(m_sim->getFactoryState(), id)) { anchor = s->anchor; footprint = s->footprint; @@ -1530,13 +1531,13 @@ void GameWorldView::drawCopyConfigFeedback(QPainter& painter) painter.setPen(Qt::NoPen); painter.setBrush(color); const BuildingType type = m_copiedConfig->type; - for (const Building& b : m_sim->getBuildings().getAllBuildings()) + for (const Building& b : getAllBuildings(m_sim->getFactoryState())) { if (b.type != type) { continue; } const std::optional rect = footprintWidgetRect(b.id); if (rect.has_value()) { painter.drawRect(*rect); } } - for (const ConstructionSite& s : m_sim->getBuildings().getAllSites()) + for (const ConstructionSite& s : getAllSites(m_sim->getFactoryState())) { if (s.type != type) { continue; } const std::optional rect = footprintWidgetRect(s.id); @@ -1571,7 +1572,7 @@ void GameWorldView::drawPortItems(QPainter& painter) const double margin = kPortMarginTiles * static_cast(getTilePx()); QRegion clip(rect()); - for (const Building& b : m_sim->getBuildings().getAllBuildings()) + for (const Building& b : getAllBuildings(m_sim->getFactoryState())) { if (b.type == BuildingType::Belt || b.type == BuildingType::Splitter || b.type == BuildingType::TunnelEntry || b.type == BuildingType::TunnelExit) @@ -1982,11 +1983,11 @@ void GameWorldView::drawSelectedTunnelConnections(QPainter& painter) std::optional anchor; std::optional type; Rotation rotation = Rotation::East; - if (const Building* b = m_sim->getBuildings().findBuilding(id)) + if (const Building* b = findBuilding(m_sim->getFactoryState(), id)) { anchor = b->anchor; type = b->type; rotation = b->rotation; } - else if (const ConstructionSite* s = m_sim->getBuildings().findSite(id)) + else if (const ConstructionSite* s = findSite(m_sim->getFactoryState(), id)) { anchor = s->anchor; type = s->type; rotation = s->rotation; } @@ -2091,7 +2092,7 @@ void GameWorldView::drawOverlays(QPainter& painter) // Queued for deconstruction: tint every building currently in the // deconstruction queue, regardless of mode (REQ-BLD-DECON-QUEUE). - for (const Building& b : m_sim->getBuildings().getAllBuildings()) + for (const Building& b : getAllBuildings(m_sim->getFactoryState())) { if (!b.queuedForDeconstruction) { continue; } for (const QPoint& cell : b.bodyCells) @@ -2106,12 +2107,12 @@ void GameWorldView::drawOverlays(QPainter& painter) { for (BuildingId id : buildingsInBox(m_boxStartTile, m_boxCurrentTile)) { - const Building* b = m_sim->getBuildings().findBuilding(id); + const Building* b = findBuilding(m_sim->getFactoryState(), id); if (b && b->type == BuildingType::Hq) { continue; } const std::vector* cells = nullptr; const ConstructionSite* s = nullptr; if (b) { cells = &b->bodyCells; } - else if ((s = m_sim->getBuildings().findSite(id))) { cells = &s->bodyCells; } + else if ((s = findSite(m_sim->getFactoryState(), id))) { cells = &s->bodyCells; } if (cells) { for (const QPoint& cell : *cells) @@ -2123,7 +2124,7 @@ void GameWorldView::drawOverlays(QPainter& painter) } else if (m_deconstructMode && m_deconstructHoverBuildingId.has_value()) { - const Building* b = m_sim->getBuildings().findBuilding(*m_deconstructHoverBuildingId); + const Building* b = findBuilding(m_sim->getFactoryState(), *m_deconstructHoverBuildingId); if (b) { for (const QPoint& cell : b->bodyCells) @@ -2748,7 +2749,7 @@ void GameWorldView::mouseReleaseEvent(QMouseEvent* event) if (m_deconstructMode) { - const BuildingSystem& buildings = m_sim->getBuildings(); + const FactoryState& factory = m_sim->getFactoryState(); // Split covered ids into construction sites (removed instantly) and // operational deconstructible buildings (the HQ is protected; player @@ -2757,12 +2758,12 @@ void GameWorldView::mouseReleaseEvent(QMouseEvent* event) std::vector operational; for (BuildingId id : boxIds) { - if (const Building* b = buildings.findBuilding(id)) + if (const Building* b = findBuilding(factory, id)) { if (b->type == BuildingType::Hq) { continue; } operational.push_back(id); } - else if (buildings.findSite(id)) + else if (findSite(factory, id)) { sites.push_back(id); } @@ -2784,7 +2785,7 @@ void GameWorldView::mouseReleaseEvent(QMouseEvent* event) bool allQueued = !operational.empty(); for (BuildingId id : operational) { - if (!buildings.isQueuedForDeconstruction(id)) { allQueued = false; break; } + if (!isQueuedForDeconstruction(factory, id)) { allQueued = false; break; } } for (BuildingId id : operational) { @@ -2795,7 +2796,7 @@ void GameWorldView::mouseReleaseEvent(QMouseEvent* event) command->id = id; enqueueCommand(command); } - else if (!buildings.isQueuedForDeconstruction(id)) + else if (!isQueuedForDeconstruction(factory, id)) { std::shared_ptr command = std::make_shared(); @@ -2997,7 +2998,7 @@ void GameWorldView::pasteConfigTo(BuildingId id) { // Operational splitters are configured by tile; sites by BuildingId // (mirrors SelectedBuildingPanel::onSplitterFilterChanged). - if (const Building* building = m_sim->getBuildings().findBuilding(id)) + if (const Building* building = findBuilding(m_sim->getFactoryState(), id)) { std::shared_ptr command = std::make_shared(); diff --git a/src/ui/MainWindow.cpp b/src/ui/MainWindow.cpp index 2dedab7..d55d4e5 100644 --- a/src/ui/MainWindow.cpp +++ b/src/ui/MainWindow.cpp @@ -1,4 +1,5 @@ #include "MainWindow.h" +#include "FactoryQueries.h" #include #include @@ -277,9 +278,9 @@ void MainWindow::handleEvent(std::shared_ptr e { // A construction site has no Building yet; fall back to its site record so // the shipyard layout can be configured before it is built (REQ-BLD-SITE-CONFIG). - const Building* b = m_sim->getBuildings().findBuilding(event->shipyardId); + const Building* b = findBuilding(m_sim->getFactoryState(), event->shipyardId); const ConstructionSite* s = - b ? nullptr : m_sim->getBuildings().findSite(event->shipyardId); + b ? nullptr : findSite(m_sim->getFactoryState(), event->shipyardId); if (!b && !s) { return; @@ -304,9 +305,9 @@ void MainWindow::handleEvent(std::shared_ptrgetBuildings().findBuilding(event->buildingId); + const Building* b = findBuilding(m_sim->getFactoryState(), event->buildingId); const ConstructionSite* s = - b ? nullptr : m_sim->getBuildings().findSite(event->buildingId); + b ? nullptr : findSite(m_sim->getFactoryState(), event->buildingId); if (!b && !s) { return; diff --git a/src/ui/SelectedBuildingPanel.cpp b/src/ui/SelectedBuildingPanel.cpp index 74ad01b..e262c15 100644 --- a/src/ui/SelectedBuildingPanel.cpp +++ b/src/ui/SelectedBuildingPanel.cpp @@ -1,4 +1,5 @@ #include "SelectedBuildingPanel.h" +#include "FactoryQueries.h" #include #include @@ -234,8 +235,8 @@ void SelectedBuildingPanel::buildSingle(BuildingId id) m_singleBuildingId = id; hideAllWidgets(); - const Building* b = m_sim->getBuildings().findBuilding(id); - const ConstructionSite* s = b ? nullptr : m_sim->getBuildings().findSite(id); + const Building* b = findBuilding(m_sim->getFactoryState(), id); + const ConstructionSite* s = b ? nullptr : findSite(m_sim->getFactoryState(), id); if (!b && !s) { buildEmpty(); @@ -602,7 +603,7 @@ void SelectedBuildingPanel::refreshSelectionDisplay(RefreshReason reason) // category owns the panel there is none: yieldToFieldSelection() has cleared it, so // this returns immediately and the field panel refreshes itself off the same events. if (!m_singleBuildingId.has_value()) { return; } - const Building* b = m_sim->getBuildings().findBuilding(*m_singleBuildingId); + const Building* b = findBuilding(m_sim->getFactoryState(), *m_singleBuildingId); if (b) { if (m_titleLabel->text().startsWith(tr("(Building) "))) @@ -615,7 +616,7 @@ void SelectedBuildingPanel::refreshSelectionDisplay(RefreshReason reason) } return; } - const ConstructionSite* s = m_sim->getBuildings().findSite(*m_singleBuildingId); + const ConstructionSite* s = findSite(m_sim->getFactoryState(), *m_singleBuildingId); if (s) { // A periodic tick only advances construction progress, so update just the @@ -649,13 +650,13 @@ void SelectedBuildingPanel::buildMulti(const std::vector& ids) std::map counts; for (BuildingId id : ids) { - const Building* b = m_sim->getBuildings().findBuilding(id); + const Building* b = findBuilding(m_sim->getFactoryState(), id); if (b) { counts[b->type]++; continue; } - const ConstructionSite* s = m_sim->getBuildings().findSite(id); + const ConstructionSite* s = findSite(m_sim->getFactoryState(), id); if (s) { counts[s->type]++; @@ -817,7 +818,7 @@ void SelectedBuildingPanel::onClearBelt() std::vector tiles; for (BuildingId id : m_selectedBuildingIds) { - const Building* b = m_sim->getBuildings().findBuilding(id); + const Building* b = findBuilding(m_sim->getFactoryState(), id); if (b && isBeltSubsystemType(b->type)) { for (const QPoint& cell : b->bodyCells)