From df7f60c8982c5e207255fd88f65d20cd8b92e850 Mon Sep 17 00:00:00 2001 From: Malte Langkabel Date: Tue, 4 Aug 2026 21:52:49 +0200 Subject: [PATCH] migrate every factory query off BuildingSystem onto the free functions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The eleven forwarding members added last commit are gone; callers now read the data directly through FactoryQueries.h. Simulation and ArenaSimulation expose getFactoryState() so the UI, the balancing view and the tests can reach it. isQueuedForDeconstruction joined the free functions along the way — it only reaches findBuilding, so it was state-pure too. No facade was introduced. The chained form was the reason one looked attractive, but rewriting sim.getBuildings().findBuilding(id) to findBuilding(sim.getFactoryState(), id) turned out to be mechanical, and the result says which data is read rather than which system happens to own it. BuildingSystem.cpp is down to 1735 lines and no longer answers questions about the factory — it only changes it. What remains on it are the mutators, the tick phases, and the queries that also need GameConfig. 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 | 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 | 9 -- 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(+), 241 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..4068da8 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, @@ -201,7 +194,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 +226,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)