make construction its own system (extracted from BuildingSystem)
This commit is contained in:
@@ -334,109 +334,6 @@ void BuildingSystem::setSiteSplitterFilters(FactoryState& state, BuildingId id,
|
||||
// Tick hooks
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void BuildingSystem::tickConstruction(FactoryState& state, Tick currentTick)
|
||||
{
|
||||
TRACE();
|
||||
if (state.constructionQueue.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ConstructionSite& front = state.constructionQueue.front();
|
||||
|
||||
// Guard: if somehow the front site was never started, start it now.
|
||||
if (front.completesAt == 0)
|
||||
{
|
||||
const BuildingDef* def = m_config.buildings.findBuildingDef(front.type);
|
||||
if (def)
|
||||
{
|
||||
front.completesAt = currentTick + secondsToTicks(def->constructionTimeSeconds);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentTick < front.completesAt)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Promote construction site to an operational Building.
|
||||
const BuildingDef* def = m_config.buildings.findBuildingDef(front.type);
|
||||
const ParsedSurfaceMask mask = parseSurfaceMask(
|
||||
def ? def->surfaceMask : std::vector<std::string>{},
|
||||
front.rotation);
|
||||
|
||||
Building building;
|
||||
building.id = front.id;
|
||||
building.anchor = front.anchor;
|
||||
building.footprint = front.footprint;
|
||||
building.rotation = front.rotation;
|
||||
building.type = front.type;
|
||||
building.recipeId = front.recipeId;
|
||||
building.shipLayout = front.shipLayout;
|
||||
|
||||
for (const QPoint& cell : mask.bodyCells)
|
||||
{
|
||||
building.bodyCells.push_back(front.anchor + cell);
|
||||
}
|
||||
for (const Port& port : mask.outputPorts)
|
||||
{
|
||||
Port absPort;
|
||||
absPort.tile = front.anchor + port.tile;
|
||||
absPort.direction = port.direction;
|
||||
building.outputPorts.push_back(absPort);
|
||||
}
|
||||
building.emergingItems.resize(building.outputPorts.size());
|
||||
building.inputPorts = computeInputPorts(building.bodyCells, building.outputPorts);
|
||||
building.incomingItems.assign(building.inputPorts.size(), {});
|
||||
|
||||
if (building.type == BuildingType::SalvageBay)
|
||||
{
|
||||
initSalvageBayBuffer(m_config, building);
|
||||
}
|
||||
else if (isAutoRecipeBuildingType(building.type))
|
||||
{
|
||||
// Smelter/Reprocessing Plant need no recipe selection; buffers are set
|
||||
// up from all recipes of the type (REQ-BLD-SMELTER, REQ-BLD-REPROCESSING).
|
||||
initAutoBuffers(m_config, building);
|
||||
}
|
||||
else if (!building.recipeId.empty())
|
||||
{
|
||||
if (building.type == BuildingType::Shipyard)
|
||||
{
|
||||
initShipyardBuffers(m_config, building);
|
||||
}
|
||||
else
|
||||
{
|
||||
const RecipeDef* recipe = m_config.recipes.findRecipeDef(building.recipeId, building.type);
|
||||
if (recipe)
|
||||
{
|
||||
initBuffers(building, *recipe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Register with BeltSystem before the move (mask/building stays valid). Any
|
||||
// filters configured while under construction carry over (REQ-BLD-SITE-CONFIG).
|
||||
reregisterBeltTile(m_belts, m_config, building, front.splitterFilterA, front.splitterFilterB);
|
||||
|
||||
state.buildings.push_back(std::move(building));
|
||||
|
||||
state.constructionQueue.pop_front();
|
||||
|
||||
// Start next queued site if present.
|
||||
if (!state.constructionQueue.empty() && state.constructionQueue.front().completesAt == 0)
|
||||
{
|
||||
const BuildingDef* nextDef =
|
||||
m_config.buildings.findBuildingDef(state.constructionQueue.front().type);
|
||||
if (nextDef)
|
||||
{
|
||||
state.constructionQueue.front().completesAt =
|
||||
currentTick + secondsToTicks(nextDef->constructionTimeSeconds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BuildingSystem::tickDeconstruction(FactoryState& state, Tick currentTick)
|
||||
{
|
||||
TRACE();
|
||||
|
||||
@@ -104,7 +104,6 @@ public:
|
||||
const std::vector<ItemType>& filterB);
|
||||
|
||||
// -- Tick hooks (called from Simulation::tick in the documented order) ---
|
||||
void tickConstruction(FactoryState& state, Tick currentTick);
|
||||
// Advances the deconstruction queue (REQ-BLD-DECON-QUEUE): one building at a
|
||||
// time, in parallel with tickConstruction. Removes the front building and
|
||||
// credits its refund when its timer elapses.
|
||||
|
||||
@@ -13,6 +13,7 @@ SET(HDRS
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Building.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/BuildingConfig.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/BuildingGrid.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ConstructionSystem.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/BuildingBuffers.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/FactoryState.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/FactoryQueries.h
|
||||
@@ -43,6 +44,7 @@ SET(SRCS
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/BeltSystem.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/BuildingConfig.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/BuildingGrid.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ConstructionSystem.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/BuildingBuffers.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/FactoryQueries.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ProductionRules.cpp
|
||||
|
||||
112
src/lib/sim/ConstructionSystem.cpp
Normal file
112
src/lib/sim/ConstructionSystem.cpp
Normal file
@@ -0,0 +1,112 @@
|
||||
#include "ConstructionSystem.h"
|
||||
|
||||
#include "BuildingBuffers.h"
|
||||
#include "BuildingType.h"
|
||||
#include "FactoryQueries.h"
|
||||
#include "PortGeometry.h"
|
||||
#include "SurfaceMask.h"
|
||||
#include "tracing.h"
|
||||
|
||||
void ConstructionSystem::tick(FactoryState& state, BeltSystem& belts, Tick currentTick)
|
||||
{
|
||||
TRACE();
|
||||
if (state.constructionQueue.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ConstructionSite& front = state.constructionQueue.front();
|
||||
|
||||
// Guard: if somehow the front site was never started, start it now.
|
||||
if (front.completesAt == 0)
|
||||
{
|
||||
const BuildingDef* def = m_config.buildings.findBuildingDef(front.type);
|
||||
if (def)
|
||||
{
|
||||
front.completesAt = currentTick + secondsToTicks(def->constructionTimeSeconds);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentTick < front.completesAt)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Promote construction site to an operational Building.
|
||||
const BuildingDef* def = m_config.buildings.findBuildingDef(front.type);
|
||||
const ParsedSurfaceMask mask = parseSurfaceMask(
|
||||
def ? def->surfaceMask : std::vector<std::string>{},
|
||||
front.rotation);
|
||||
|
||||
Building building;
|
||||
building.id = front.id;
|
||||
building.anchor = front.anchor;
|
||||
building.footprint = front.footprint;
|
||||
building.rotation = front.rotation;
|
||||
building.type = front.type;
|
||||
building.recipeId = front.recipeId;
|
||||
building.shipLayout = front.shipLayout;
|
||||
|
||||
for (const QPoint& cell : mask.bodyCells)
|
||||
{
|
||||
building.bodyCells.push_back(front.anchor + cell);
|
||||
}
|
||||
for (const Port& port : mask.outputPorts)
|
||||
{
|
||||
Port absPort;
|
||||
absPort.tile = front.anchor + port.tile;
|
||||
absPort.direction = port.direction;
|
||||
building.outputPorts.push_back(absPort);
|
||||
}
|
||||
building.emergingItems.resize(building.outputPorts.size());
|
||||
building.inputPorts = computeInputPorts(building.bodyCells, building.outputPorts);
|
||||
building.incomingItems.assign(building.inputPorts.size(), {});
|
||||
|
||||
if (building.type == BuildingType::SalvageBay)
|
||||
{
|
||||
initSalvageBayBuffer(m_config, building);
|
||||
}
|
||||
else if (isAutoRecipeBuildingType(building.type))
|
||||
{
|
||||
// Smelter/Reprocessing Plant need no recipe selection; buffers are set
|
||||
// up from all recipes of the type (REQ-BLD-SMELTER, REQ-BLD-REPROCESSING).
|
||||
initAutoBuffers(m_config, building);
|
||||
}
|
||||
else if (!building.recipeId.empty())
|
||||
{
|
||||
if (building.type == BuildingType::Shipyard)
|
||||
{
|
||||
initShipyardBuffers(m_config, building);
|
||||
}
|
||||
else
|
||||
{
|
||||
const RecipeDef* recipe = m_config.recipes.findRecipeDef(building.recipeId, building.type);
|
||||
if (recipe)
|
||||
{
|
||||
initBuffers(building, *recipe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Register with BeltSystem before the move (mask/building stays valid). Any
|
||||
// filters configured while under construction carry over (REQ-BLD-SITE-CONFIG).
|
||||
reregisterBeltTile(belts, m_config, building, front.splitterFilterA, front.splitterFilterB);
|
||||
|
||||
state.buildings.push_back(std::move(building));
|
||||
|
||||
state.constructionQueue.pop_front();
|
||||
|
||||
// Start next queued site if present.
|
||||
if (!state.constructionQueue.empty() && state.constructionQueue.front().completesAt == 0)
|
||||
{
|
||||
const BuildingDef* nextDef =
|
||||
m_config.buildings.findBuildingDef(state.constructionQueue.front().type);
|
||||
if (nextDef)
|
||||
{
|
||||
state.constructionQueue.front().completesAt =
|
||||
currentTick + secondsToTicks(nextDef->constructionTimeSeconds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
30
src/lib/sim/ConstructionSystem.h
Normal file
30
src/lib/sim/ConstructionSystem.h
Normal file
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#include "BeltSystem.h"
|
||||
#include "FactoryState.h"
|
||||
#include "GameConfig.h"
|
||||
#include "Tick.h"
|
||||
|
||||
// Advances the construction queue and turns a finished site into an operational
|
||||
// building (REQ-BLD-CONSTRUCTION). One site is built at a time, in queue order:
|
||||
// the front site's timer runs, and when it elapses the site becomes a Building —
|
||||
// its ports and buffers are derived from its definition, its belt tile is handed
|
||||
// back to BeltSystem, and the next queued site starts.
|
||||
//
|
||||
// It completes the building itself rather than handing the finished site back to
|
||||
// BuildingSystem: everything materialisation needs is either in FactoryState, the
|
||||
// config, or a free function (see BuildingBuffers.h, PortGeometry.h), so there is
|
||||
// no intermediate value to pass and no ordering rule between two calls.
|
||||
//
|
||||
// Holds only the config; the world it works on arrives per tick, like the other
|
||||
// systems in lib/ecs/system.
|
||||
class ConstructionSystem
|
||||
{
|
||||
public:
|
||||
explicit ConstructionSystem(const GameConfig& config) : m_config(config) {}
|
||||
|
||||
void tick(FactoryState& state, BeltSystem& belts, Tick currentTick);
|
||||
|
||||
private:
|
||||
const GameConfig& m_config;
|
||||
};
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "Simulation.h"
|
||||
|
||||
#include "FactoryQueries.h"
|
||||
#include "ConstructionSystem.h"
|
||||
#include "PlacementRules.h"
|
||||
|
||||
#include <algorithm>
|
||||
@@ -123,6 +124,7 @@ void Simulation::initializeSubsystems()
|
||||
},
|
||||
[this](const std::string& itemId) -> bool { return isItemUnlocked(itemId); },
|
||||
m_rng);
|
||||
m_constructionSystem = std::make_unique<ConstructionSystem>(m_config);
|
||||
m_shipSystem = std::make_unique<ShipSystem>(m_config, m_admin);
|
||||
m_aiSystem = std::make_unique<AiSystem>(m_config);
|
||||
m_movementIntentSystem = std::make_unique<MovementIntentSystem>();
|
||||
@@ -243,7 +245,7 @@ void Simulation::tick()
|
||||
m_waveSystem->tickThreatAccumulation();
|
||||
|
||||
// Construction + production pipeline
|
||||
m_buildingSystem->tickConstruction(m_factoryState, m_currentTick);
|
||||
m_constructionSystem->tick(m_factoryState, m_beltSystem, m_currentTick);
|
||||
m_buildingSystem->tickDeconstruction(m_factoryState, m_currentTick); // parallel to construction
|
||||
m_buildingSystem->tickBeltPull(m_factoryState); // step 3
|
||||
m_buildingSystem->tickProduction(m_factoryState, m_currentTick); // step 4
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
class AiSystem;
|
||||
class BuildingSystem;
|
||||
class ConstructionSystem;
|
||||
struct Command;
|
||||
class Hasher;
|
||||
class CombatSystem;
|
||||
@@ -218,6 +219,7 @@ private:
|
||||
FactoryState m_factoryState;
|
||||
BeltSystem m_beltSystem;
|
||||
std::unique_ptr<BuildingSystem> m_buildingSystem;
|
||||
std::unique_ptr<ConstructionSystem> m_constructionSystem;
|
||||
std::unique_ptr<ShipSystem> m_shipSystem;
|
||||
std::unique_ptr<AiSystem> m_aiSystem;
|
||||
std::unique_ptr<MovementIntentSystem> m_movementIntentSystem;
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "BeltSystem.h"
|
||||
#include "Building.h"
|
||||
#include "BuildingSystem.h"
|
||||
#include "ConstructionSystem.h"
|
||||
#include "FactoryState.h"
|
||||
#include "BuildingType.h"
|
||||
#include "ConfigLoader.h"
|
||||
@@ -63,6 +64,7 @@ struct Fixture
|
||||
std::mt19937 rng;
|
||||
EntityAdmin admin;
|
||||
BuildingSystem buildings;
|
||||
ConstructionSystem construction;
|
||||
ShipSystem ships;
|
||||
AiSystem ai;
|
||||
SalvagerSystem salvager;
|
||||
@@ -85,6 +87,7 @@ struct Fixture
|
||||
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
|
||||
[](const std::string&) -> bool { return true; },
|
||||
rng)
|
||||
, construction(cfg)
|
||||
, ships(cfg, admin)
|
||||
, ai(cfg)
|
||||
, salvager(admin)
|
||||
@@ -957,7 +960,7 @@ TEST_CASE("BehaviorSystem: full-cargo salvage ship moves toward SalvageBay", "[b
|
||||
Tick t = 0;
|
||||
for (int i = 0; i < 500; ++i)
|
||||
{
|
||||
f.buildings.tickConstruction(f.state, t++);
|
||||
f.construction.tick(f.state, f.belts, t++);
|
||||
if (findBuilding(f.state, bayId) != nullptr)
|
||||
{
|
||||
break;
|
||||
@@ -992,7 +995,7 @@ TEST_CASE("SalvagerSystem: full-cargo ship at its SalvageBay hands over cargo",
|
||||
Tick t = 0;
|
||||
for (int i = 0; i < 500; ++i)
|
||||
{
|
||||
f.buildings.tickConstruction(f.state, t++);
|
||||
f.construction.tick(f.state, f.belts, t++);
|
||||
if (findBuilding(f.state, bayId) != nullptr) { break; }
|
||||
}
|
||||
const Building* bay = findBuilding(f.state, bayId);
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "BeltSystem.h"
|
||||
#include "Building.h"
|
||||
#include "BuildingSystem.h"
|
||||
#include "ConstructionSystem.h"
|
||||
#include "FactoryState.h"
|
||||
#include "BuildingType.h"
|
||||
#include "ConfigLoader.h"
|
||||
@@ -53,12 +54,12 @@ static Port westPort(QPoint tile)
|
||||
}
|
||||
|
||||
// Run N full sim ticks: construction, belt-pull, production, belt-push, belt tick.
|
||||
static void runTicks(BuildingSystem& bs, FactoryState& state_bs, BeltSystem& belts,
|
||||
int n, Tick& tick)
|
||||
static void runTicks(BuildingSystem& bs, const GameConfig& cfg, FactoryState& state_bs,
|
||||
BeltSystem& belts, int n, Tick& tick)
|
||||
{
|
||||
for (int i = 0; i < n; ++i)
|
||||
{
|
||||
bs.tickConstruction(state_bs, tick);
|
||||
ConstructionSystem(cfg).tick(state_bs, belts, tick);
|
||||
bs.tickDeconstruction(state_bs, tick);
|
||||
bs.tickBeltPull(state_bs);
|
||||
bs.tickProduction(state_bs, tick);
|
||||
@@ -240,7 +241,7 @@ TEST_CASE("BuildingSystem: placing a belt registers it with BeltSystem after con
|
||||
|
||||
// Complete construction (1 s).
|
||||
Tick tick = 0;
|
||||
runTicks(bs, state_bs, belts, static_cast<int>(secondsToTicks(1.0)) + 1, tick);
|
||||
runTicks(bs, cfg, state_bs, belts, static_cast<int>(secondsToTicks(1.0)) + 1, tick);
|
||||
|
||||
REQUIRE(belts.tryPutItem(QPoint(5, 5), makeItem("iron_ore"), Rotation::East));
|
||||
REQUIRE(getAllBuildings(state_bs).size() == 1);
|
||||
@@ -353,7 +354,7 @@ TEST_CASE("BuildingSystem: construction completes after configured duration", "[
|
||||
// Miner construction_time_seconds = 10. completesAt = secondsToTicks(10) = 300.
|
||||
// We need to process tick 300 itself, so run 301 ticks (ticks 0..300).
|
||||
Tick tick = 0;
|
||||
runTicks(bs, state_bs, belts, static_cast<int>(secondsToTicks(10.0)) + 1, tick);
|
||||
runTicks(bs, cfg, state_bs, belts, static_cast<int>(secondsToTicks(10.0)) + 1, tick);
|
||||
|
||||
REQUIRE(getAllSites(state_bs).empty());
|
||||
REQUIRE(findBuilding(state_bs, id) != nullptr);
|
||||
@@ -368,7 +369,7 @@ static void runUntilBuilt(PlacementFixture& f, BuildingId id, Tick& tick)
|
||||
{
|
||||
for (int i = 0; i < 100000 && findBuilding(f.state, id) == nullptr; ++i)
|
||||
{
|
||||
runTicks(f.bs, f.state, f.belts, 1, tick);
|
||||
runTicks(f.bs, f.cfg, f.state, f.belts, 1, tick);
|
||||
}
|
||||
REQUIRE(findBuilding(f.state, id) != nullptr);
|
||||
}
|
||||
@@ -393,7 +394,7 @@ TEST_CASE("BuildingSystem: deconstructing a built building queues it; refund cre
|
||||
|
||||
// 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.state, f.belts, static_cast<int>(secondsToTicks(0.1)) + 1, tick);
|
||||
runTicks(f.bs, f.cfg, f.state, f.belts, static_cast<int>(secondsToTicks(0.1)) + 1, tick);
|
||||
REQUIRE(findBuilding(f.state, id) == nullptr);
|
||||
REQUIRE_FALSE(isTileOccupied(f.state, QPoint(0, 0)));
|
||||
REQUIRE(f.stock == 15 * f.cfg.world.refundPercentage / 100);
|
||||
@@ -417,14 +418,14 @@ TEST_CASE("BuildingSystem: deconstruction queue removes one building at a time",
|
||||
|
||||
// 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.state, f.belts, static_cast<int>(secondsToTicks(0.1)) + 1, tick);
|
||||
runTicks(f.bs, f.cfg, f.state, f.belts, static_cast<int>(secondsToTicks(0.1)) + 1, tick);
|
||||
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.state, f.belts, static_cast<int>(secondsToTicks(0.1)) + 2, tick);
|
||||
runTicks(f.bs, f.cfg, f.state, f.belts, static_cast<int>(secondsToTicks(0.1)) + 2, tick);
|
||||
REQUIRE(findBuilding(f.state, b) == nullptr);
|
||||
REQUIRE(f.stock == 2 * (15 * f.cfg.world.refundPercentage / 100));
|
||||
}
|
||||
@@ -450,7 +451,7 @@ TEST_CASE("BuildingSystem: cancelling deconstruction resumes the building with n
|
||||
REQUIRE(f.stock == 0);
|
||||
|
||||
// It is never removed even after more than a deconstruction interval passes.
|
||||
runTicks(f.bs, f.state, f.belts, static_cast<int>(secondsToTicks(0.1)) + 5, tick);
|
||||
runTicks(f.bs, f.cfg, f.state, f.belts, static_cast<int>(secondsToTicks(0.1)) + 5, tick);
|
||||
REQUIRE(findBuilding(f.state, id) != nullptr);
|
||||
REQUIRE(f.stock == 0);
|
||||
}
|
||||
@@ -520,7 +521,7 @@ TEST_CASE("BuildingSystem: second building starts after first completes", "[buil
|
||||
|
||||
// Process through tick 300 to complete first miner's construction.
|
||||
Tick tick = 0;
|
||||
runTicks(bs, state_bs, belts, static_cast<int>(secondsToTicks(10.0)) + 1, tick);
|
||||
runTicks(bs, cfg, state_bs, belts, static_cast<int>(secondsToTicks(10.0)) + 1, tick);
|
||||
|
||||
REQUIRE(getAllSites(state_bs).size() == 1);
|
||||
REQUIRE(getAllSites(state_bs).front().id == id2);
|
||||
@@ -552,7 +553,7 @@ TEST_CASE("BuildingSystem: miner produces iron_ore after recipe duration", "[bui
|
||||
Tick tick = 0;
|
||||
// Construction completes on tick 300; production cycle starts tick 300,
|
||||
// completes on tick 330. Process through tick 330: 331 ticks total.
|
||||
runTicks(bs, state_bs, belts,
|
||||
runTicks(bs, cfg, state_bs, belts,
|
||||
static_cast<int>(secondsToTicks(10.0)) + static_cast<int>(secondsToTicks(1.0)) + 1,
|
||||
tick);
|
||||
|
||||
@@ -589,7 +590,7 @@ TEST_CASE("BuildingSystem: miner output buffer stalls when full", "[building]")
|
||||
// Cycle 2 starts at tick 331 (completesAt=361).
|
||||
// Cycle 2 completes at tick 361: deposit item → buffer=2, cycle 3 stalls.
|
||||
// Need to process through tick 361: 362 ticks total.
|
||||
runTicks(bs, state_bs, belts,
|
||||
runTicks(bs, cfg, state_bs, belts,
|
||||
static_cast<int>(secondsToTicks(10.0))
|
||||
+ 2 * static_cast<int>(secondsToTicks(1.0)) + 2,
|
||||
tick);
|
||||
@@ -631,10 +632,10 @@ TEST_CASE("BuildingSystem: productionBuildingCount excludes construction sites",
|
||||
|
||||
// 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, state_bs, belts, static_cast<int>(secondsToTicks(10.0)) + 1, tick);
|
||||
runTicks(bs, cfg, state_bs, belts, static_cast<int>(secondsToTicks(10.0)) + 1, tick);
|
||||
REQUIRE(getProductionBuildingCount(state_bs) == 1);
|
||||
|
||||
runTicks(bs, state_bs, belts, static_cast<int>(secondsToTicks(15.0)), tick);
|
||||
runTicks(bs, cfg, state_bs, belts, static_cast<int>(secondsToTicks(15.0)), tick);
|
||||
REQUIRE(getProductionBuildingCount(state_bs) == 2);
|
||||
|
||||
// Neither is producing yet: the miner has no recipe selected, and the
|
||||
@@ -642,7 +643,7 @@ TEST_CASE("BuildingSystem: productionBuildingCount excludes construction sites",
|
||||
REQUIRE(getActiveProductionBuildingCount(state_bs) == 0);
|
||||
|
||||
bs.setRecipe(state_bs, minerId, "mine_iron_ore");
|
||||
runTicks(bs, state_bs, belts, 1, tick);
|
||||
runTicks(bs, cfg, state_bs, belts, 1, tick);
|
||||
REQUIRE(getActiveProductionBuildingCount(state_bs) == 1);
|
||||
}
|
||||
|
||||
@@ -670,12 +671,12 @@ TEST_CASE("BuildingSystem: activeProductionBuildingCount tracks production cycle
|
||||
REQUIRE(getActiveProductionBuildingCount(state_bs) == 0);
|
||||
|
||||
// Construction completes at tick 300; cycle 1 starts the same tick (completesAt=330).
|
||||
runTicks(bs, state_bs, belts, static_cast<int>(secondsToTicks(10.0)) + 1, tick);
|
||||
runTicks(bs, cfg, state_bs, belts, static_cast<int>(secondsToTicks(10.0)) + 1, tick);
|
||||
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, state_bs, belts, 2 * static_cast<int>(secondsToTicks(1.0)) + 1, tick);
|
||||
runTicks(bs, cfg, state_bs, belts, 2 * static_cast<int>(secondsToTicks(1.0)) + 1, tick);
|
||||
|
||||
const Building* b = findBuilding(state_bs, id);
|
||||
REQUIRE(b != nullptr);
|
||||
@@ -713,7 +714,7 @@ TEST_CASE("BuildingSystem: smelter input buffer fills from adjacent west-flowing
|
||||
|
||||
// Complete construction (15s → tick 450+1 = 451 ticks).
|
||||
Tick tick = 0;
|
||||
runTicks(bs, state_bs, belts, static_cast<int>(secondsToTicks(15.0)) + 1, tick);
|
||||
runTicks(bs, cfg, state_bs, belts, static_cast<int>(secondsToTicks(15.0)) + 1, tick);
|
||||
|
||||
// Place west-flowing belt at (2,0): belt flows West, delivers to smelter.
|
||||
belts.placeBelt(QPoint(2, 0), Rotation::West);
|
||||
@@ -750,7 +751,7 @@ TEST_CASE("BuildingSystem: accepted input travels inward before entering the buf
|
||||
|
||||
const BuildingId sid = bs.place(state_bs, BuildingType::Smelter, QPoint(0, 0), Rotation::East, 0).value();
|
||||
Tick tick = 0;
|
||||
runTicks(bs, state_bs, belts, static_cast<int>(secondsToTicks(15.0)) + 1, tick);
|
||||
runTicks(bs, cfg, state_bs, belts, static_cast<int>(secondsToTicks(15.0)) + 1, tick);
|
||||
|
||||
belts.placeBelt(QPoint(2, 0), Rotation::West);
|
||||
belts.tryPutItem(QPoint(2, 0), makeItem("iron_ore"));
|
||||
@@ -793,7 +794,7 @@ TEST_CASE("BuildingSystem: input reservation caps buffered plus in-transit at th
|
||||
const BuildingId id = bs.place(state_bs, BuildingType::ReprocessingPlant,
|
||||
QPoint(0, 0), Rotation::East, 0).value();
|
||||
Tick tick = 0;
|
||||
runTicks(bs, state_bs, belts, static_cast<int>(secondsToTicks(25.0)) + 1, tick);
|
||||
runTicks(bs, cfg, state_bs, belts, static_cast<int>(secondsToTicks(25.0)) + 1, tick);
|
||||
|
||||
// Feed scrap via an input belt without ever running production (only pull), so
|
||||
// the buffer fills and stays full. Try to over-fill it well past the cap.
|
||||
@@ -836,7 +837,7 @@ TEST_CASE("BuildingSystem: smelter auto-smelts ore without a recipe selection",
|
||||
const BuildingId sid = bs.place(state_bs, BuildingType::Smelter, QPoint(0, 0), Rotation::East, 0).value();
|
||||
|
||||
Tick tick = 0;
|
||||
runTicks(bs, state_bs, belts, static_cast<int>(secondsToTicks(15.0)) + 1, tick);
|
||||
runTicks(bs, cfg, state_bs, belts, static_cast<int>(secondsToTicks(15.0)) + 1, tick);
|
||||
|
||||
// Feed 2 iron_ore (the test-config iron_ingot recipe needs 2) via a
|
||||
// west-flowing belt at input port (2,0).
|
||||
@@ -849,7 +850,7 @@ TEST_CASE("BuildingSystem: smelter auto-smelts ore without a recipe selection",
|
||||
}
|
||||
|
||||
// iron_ingot recipe cycle is 2s; run to completion.
|
||||
runTicks(bs, state_bs, belts, static_cast<int>(secondsToTicks(2.0)) + 2, tick);
|
||||
runTicks(bs, cfg, state_bs, belts, static_cast<int>(secondsToTicks(2.0)) + 2, tick);
|
||||
|
||||
const Building* b = findBuilding(state_bs, sid);
|
||||
REQUIRE(b != nullptr);
|
||||
@@ -883,7 +884,7 @@ TEST_CASE("BuildingSystem: smelter runs a satisfiable recipe while an incomplete
|
||||
const BuildingId sid = bs.place(state_bs, BuildingType::Smelter, QPoint(0, 0), Rotation::East, 0).value();
|
||||
|
||||
Tick tick = 0;
|
||||
runTicks(bs, state_bs, belts, static_cast<int>(secondsToTicks(15.0)) + 1, tick);
|
||||
runTicks(bs, cfg, state_bs, belts, static_cast<int>(secondsToTicks(15.0)) + 1, tick);
|
||||
|
||||
// Feed 1 iron_ore (iron_ingot needs 2 — incomplete) then 2 copper_ore
|
||||
// (copper_ingot needs 2 — satisfiable) via the west-flowing input belt.
|
||||
@@ -897,7 +898,7 @@ TEST_CASE("BuildingSystem: smelter runs a satisfiable recipe while an incomplete
|
||||
}
|
||||
|
||||
// copper_ingot cycle is 2.5s; run to completion.
|
||||
runTicks(bs, state_bs, belts, static_cast<int>(secondsToTicks(2.5)) + 2, tick);
|
||||
runTicks(bs, cfg, state_bs, belts, static_cast<int>(secondsToTicks(2.5)) + 2, tick);
|
||||
|
||||
const Building* b = findBuilding(state_bs, sid);
|
||||
REQUIRE(b != nullptr);
|
||||
@@ -943,13 +944,13 @@ TEST_CASE("BuildingSystem: miner output buffer drains onto adjacent belt", "[bui
|
||||
|
||||
Tick tick = 0;
|
||||
// Construction (10s) + 1 production cycle (1s) + 1 extra tick.
|
||||
runTicks(bs, state_bs, belts,
|
||||
runTicks(bs, cfg, state_bs, belts,
|
||||
static_cast<int>(secondsToTicks(10.0)) + static_cast<int>(secondsToTicks(1.0)) + 1,
|
||||
tick);
|
||||
|
||||
// Item should have been pushed onto the belt this tick or a subsequent one.
|
||||
// Run one more tick to ensure tickBeltPush fires after the deposit tick.
|
||||
runTicks(bs, state_bs, belts, 1, tick);
|
||||
runTicks(bs, cfg, state_bs, belts, 1, tick);
|
||||
|
||||
const std::optional<Item> item = belts.tryTakeItem(eastPort(QPoint(1, 1)));
|
||||
REQUIRE(item.has_value());
|
||||
@@ -985,7 +986,7 @@ TEST_CASE("BuildingSystem: output port couples directly into an adjacent input p
|
||||
|
||||
Tick tick = 0;
|
||||
// Smelter build (15s) + margin for coupling and a smelt cycle.
|
||||
runTicks(bs, state_bs, belts, static_cast<int>(secondsToTicks(30.0)), tick);
|
||||
runTicks(bs, cfg, state_bs, belts, static_cast<int>(secondsToTicks(30.0)), tick);
|
||||
|
||||
const Building* smelter = findBuilding(state_bs, smelterId);
|
||||
REQUIRE(smelter != nullptr);
|
||||
@@ -1025,7 +1026,7 @@ TEST_CASE("BuildingSystem: direct coupling to a non-consumer leaves the item stu
|
||||
|
||||
Tick tick = 0;
|
||||
// Both miners build sequentially (10s each), then the producer runs and jams.
|
||||
runTicks(bs, state_bs, belts, static_cast<int>(secondsToTicks(25.0)), tick);
|
||||
runTicks(bs, cfg, state_bs, belts, static_cast<int>(secondsToTicks(25.0)), tick);
|
||||
|
||||
const Building* miner = findBuilding(state_bs, minerId);
|
||||
const Building* sink = findBuilding(state_bs, sinkId);
|
||||
@@ -1061,7 +1062,7 @@ TEST_CASE("BuildingSystem: setRecipe clears output buffer and active production"
|
||||
|
||||
Tick tick = 0;
|
||||
// Run until first item is in output buffer.
|
||||
runTicks(bs, state_bs, belts,
|
||||
runTicks(bs, cfg, state_bs, belts,
|
||||
static_cast<int>(secondsToTicks(10.0)) + static_cast<int>(secondsToTicks(1.0)) + 1,
|
||||
tick);
|
||||
|
||||
@@ -1107,7 +1108,7 @@ TEST_CASE("BuildingSystem: reprocessing plant output buffer capacity equals max
|
||||
|
||||
// Complete construction (25s).
|
||||
Tick tick = 0;
|
||||
runTicks(bs, state_bs, belts, static_cast<int>(secondsToTicks(25.0)) + 1, tick);
|
||||
runTicks(bs, cfg, state_bs, belts, static_cast<int>(secondsToTicks(25.0)) + 1, tick);
|
||||
|
||||
const Building* b = findBuilding(state_bs, id);
|
||||
REQUIRE(b != nullptr);
|
||||
@@ -1140,7 +1141,7 @@ TEST_CASE("BuildingSystem: reprocessing plant produces one cycle output then sta
|
||||
|
||||
// Complete construction (25s).
|
||||
Tick tick = 0;
|
||||
runTicks(bs, state_bs, belts, static_cast<int>(secondsToTicks(25.0)) + 1, tick);
|
||||
runTicks(bs, cfg, state_bs, belts, static_cast<int>(secondsToTicks(25.0)) + 1, tick);
|
||||
|
||||
// Feed 5 scrap into the building via a belt at an input port.
|
||||
// Reprocessing plant body (East rotation) = 3×3 at (0,0).
|
||||
@@ -1162,7 +1163,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, state_bs, belts, static_cast<int>(secondsToTicks(3.0)) + 1, tick);
|
||||
runTicks(bs, cfg, state_bs, belts, static_cast<int>(secondsToTicks(3.0)) + 1, tick);
|
||||
|
||||
const Building* b = findBuilding(state_bs, id);
|
||||
REQUIRE(b != nullptr);
|
||||
@@ -1239,7 +1240,7 @@ TEST_CASE("BuildingSystem: findRotateInPlaceTarget returns the building id for a
|
||||
const BuildingId id = bs.place(state_bs, BuildingType::Belt, QPoint(0, 0), Rotation::East, 0).value();
|
||||
|
||||
Tick tick = 0;
|
||||
runTicks(bs, state_bs, belts, static_cast<int>(secondsToTicks(1.0)) + 1, tick);
|
||||
runTicks(bs, cfg, state_bs, belts, static_cast<int>(secondsToTicks(1.0)) + 1, tick);
|
||||
REQUIRE(getAllSites(state_bs).empty());
|
||||
|
||||
const std::optional<BuildingId> result =
|
||||
@@ -1421,7 +1422,7 @@ TEST_CASE("BuildingSystem: rotateInPlace updates rotation and output port direct
|
||||
const BuildingId id = bs.place(state_bs, BuildingType::Belt, QPoint(0, 0), Rotation::East, 0).value();
|
||||
|
||||
Tick tick = 0;
|
||||
runTicks(bs, state_bs, belts, static_cast<int>(secondsToTicks(1.0)) + 1, tick);
|
||||
runTicks(bs, cfg, state_bs, belts, static_cast<int>(secondsToTicks(1.0)) + 1, tick);
|
||||
REQUIRE(findBuilding(state_bs, id) != nullptr);
|
||||
|
||||
const Building& before = *findBuilding(state_bs, id);
|
||||
@@ -1453,7 +1454,7 @@ TEST_CASE("BuildingSystem: rotateInPlace re-registers a belt tile with BeltSyste
|
||||
const BuildingId id = bs.place(state_bs, BuildingType::Belt, QPoint(0, 0), Rotation::East, 0).value();
|
||||
|
||||
Tick tick = 0;
|
||||
runTicks(bs, state_bs, belts, static_cast<int>(secondsToTicks(1.0)) + 1, tick);
|
||||
runTicks(bs, cfg, state_bs, belts, static_cast<int>(secondsToTicks(1.0)) + 1, tick);
|
||||
|
||||
bs.rotateInPlace(state_bs, id, Rotation::North);
|
||||
|
||||
@@ -1473,7 +1474,7 @@ TEST_CASE("BuildingSystem: rotateInPlace preserves the output filters of a split
|
||||
Tick tick = 0;
|
||||
while (getAllBuildings(f.state).empty() && tick < 100000)
|
||||
{
|
||||
runTicks(f.bs, f.state, f.belts, 1, tick);
|
||||
runTicks(f.bs, f.cfg, f.state, f.belts, 1, tick);
|
||||
}
|
||||
REQUIRE(getAllBuildings(f.state).size() == 1);
|
||||
|
||||
@@ -1518,7 +1519,7 @@ TEST_CASE("BuildingSystem: splitter filters configured on a construction site ca
|
||||
Tick tick = 0;
|
||||
while (getAllBuildings(f.state).empty() && tick < 100000)
|
||||
{
|
||||
runTicks(f.bs, f.state, f.belts, 1, tick);
|
||||
runTicks(f.bs, f.cfg, f.state, f.belts, 1, tick);
|
||||
}
|
||||
REQUIRE(getAllBuildings(f.state).size() == 1);
|
||||
REQUIRE(getAllBuildings(f.state)[0].type == BuildingType::Splitter);
|
||||
@@ -1672,12 +1673,12 @@ namespace
|
||||
|
||||
// Advances the sim until the given site becomes an operational building, or a
|
||||
// safety cap is reached.
|
||||
void buildToCompletion(BuildingSystem& bs, FactoryState& state,
|
||||
void buildToCompletion(BuildingSystem& bs, const GameConfig& cfg, FactoryState& state,
|
||||
BeltSystem& belts, BuildingId id, Tick& tick)
|
||||
{
|
||||
for (int i = 0; i < 20000 && findBuilding(state, id) == nullptr; ++i)
|
||||
{
|
||||
runTicks(bs, state, belts, 1, tick);
|
||||
runTicks(bs, cfg, state, belts, 1, tick);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1712,7 +1713,7 @@ TEST_CASE("BuildingSystem: getInputPorts matches between a site and the built bu
|
||||
const BuildingId id = f.bs.place(f.state, BuildingType::Miner, QPoint(0, 0), Rotation::East, 0).value();
|
||||
|
||||
const std::vector<Port> sitePorts = getInputPorts(f.state, f.cfg, id);
|
||||
buildToCompletion(f.bs, f.state, f.belts, id, tick);
|
||||
buildToCompletion(f.bs, f.cfg, f.state, f.belts, id, tick);
|
||||
REQUIRE(findBuilding(f.state, id) != nullptr);
|
||||
const std::vector<Port> builtPorts = getInputPorts(f.state, f.cfg, id);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user