migrate every factory query off BuildingSystem onto the free functions
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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YHcUerKAZKWNvSKJxYKbnG
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
#include "catch.hpp"
|
||||
#include "FactoryQueries.h"
|
||||
|
||||
#include <map>
|
||||
#include <random>
|
||||
@@ -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<BuildingId> 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<BuildingId> 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<BuildingId> 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<int>(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<int>(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<int>(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<int>(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<int>(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<int>(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<int>(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<int>(secondsToTicks(10.0)) + static_cast<int>(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<int>(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<int>(secondsToTicks(10.0)) + 1, tick);
|
||||
REQUIRE(bs.getProductionBuildingCount() == 1);
|
||||
REQUIRE(getProductionBuildingCount(state_bs) == 1);
|
||||
|
||||
runTicks(bs, belts, static_cast<int>(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<int>(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<int>(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<int>(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<int>(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<int>(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<int>(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<int>(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<int>(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<int>(secondsToTicks(1.0)) + 1, tick);
|
||||
REQUIRE(bs.getAllSites().empty());
|
||||
REQUIRE(getAllSites(state_bs).empty());
|
||||
|
||||
const std::optional<BuildingId> 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<int>(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<ItemType> filterA{ ItemType{"iron_ore"} };
|
||||
const std::vector<ItemType> 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<ItemType> 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<BeltSystem::SplitterInfo> 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<Port> 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<Port> 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<std::pair<int, int>> bodySet;
|
||||
|
||||
Reference in New Issue
Block a user