put the block stock where the blocks are
The global building block stock lived on Simulation while being factory data through and through: placement spends it, deconstruction refunds it, and blocks delivered to the HQ by belt add to it. Every system that credited it therefore held a std::function back into Simulation to do so -- BuildingSystem and DeconstructionSystem each carried one, and the arena and three test fixtures had to pass a stub. It moves into FactoryState, seeded by makeFactoryState from world.starting_building_blocks, and both callbacks are gone: the HQ's belt intake and the deconstruction refund now credit the state they are already holding. BuildingSystem::deconstruct stops returning a refund for its caller to remember to credit. It had grown asymmetric -- the queued path credits itself through DeconstructionSystem while the instant path handed a number back -- so it now credits the site's full cost directly and returns void. Checksum order is untouched: Simulation still folds the stock at exactly the point it always did, reading it from the state. The arena no longer discards refunds into a no-op sink; nothing there reads the stock either way. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ne3mejABZoLWKLh8fgpM3x
This commit is contained in:
@@ -52,7 +52,6 @@ ArenaSimulation::ArenaSimulation(const GameConfig& gameConfig,
|
|||||||
m_gameConfig,
|
m_gameConfig,
|
||||||
m_beltSystem,
|
m_beltSystem,
|
||||||
[this]() { return allocateBuildingId(); },
|
[this]() { return allocateBuildingId(); },
|
||||||
[](int) {},
|
|
||||||
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
|
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
|
||||||
[](const std::string&) -> bool { return true; },
|
[](const std::string&) -> bool { return true; },
|
||||||
m_rng);
|
m_rng);
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ bool inputLaneEntryFree(const std::vector<BeltItemSlot>& lane)
|
|||||||
BuildingSystem::BuildingSystem(const GameConfig& config,
|
BuildingSystem::BuildingSystem(const GameConfig& config,
|
||||||
BeltSystem& belts,
|
BeltSystem& belts,
|
||||||
std::function<BuildingId()> allocateBuildingId,
|
std::function<BuildingId()> allocateBuildingId,
|
||||||
std::function<void(int)> addBuildingBlocks,
|
|
||||||
std::function<void(const std::string&, QVector2D,
|
std::function<void(const std::string&, QVector2D,
|
||||||
const std::optional<ShipLayoutConfig>&)> spawnShip,
|
const std::optional<ShipLayoutConfig>&)> spawnShip,
|
||||||
std::function<bool(const std::string&)> isItemUnlocked,
|
std::function<bool(const std::string&)> isItemUnlocked,
|
||||||
@@ -36,7 +35,6 @@ BuildingSystem::BuildingSystem(const GameConfig& config,
|
|||||||
: m_config(config)
|
: m_config(config)
|
||||||
, m_belts(belts)
|
, m_belts(belts)
|
||||||
, m_allocateBuildingId(std::move(allocateBuildingId))
|
, m_allocateBuildingId(std::move(allocateBuildingId))
|
||||||
, m_addBuildingBlocks(std::move(addBuildingBlocks))
|
|
||||||
, m_spawnShip(std::move(spawnShip))
|
, m_spawnShip(std::move(spawnShip))
|
||||||
, m_isItemUnlocked(std::move(isItemUnlocked))
|
, m_isItemUnlocked(std::move(isItemUnlocked))
|
||||||
, m_rng(rng)
|
, m_rng(rng)
|
||||||
@@ -158,7 +156,7 @@ std::optional<BuildingId> BuildingSystem::place(FactoryState& state, BuildingTyp
|
|||||||
// Deconstruct
|
// Deconstruct
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
int BuildingSystem::deconstruct(FactoryState& state, BuildingId id, Tick currentTick)
|
void BuildingSystem::deconstruct(FactoryState& state, BuildingId id, Tick currentTick)
|
||||||
{
|
{
|
||||||
// Construction site? Removed instantly with the full refund; never queued
|
// Construction site? Removed instantly with the full refund; never queued
|
||||||
// for deconstruction (REQ-BLD-DECONSTRUCT).
|
// for deconstruction (REQ-BLD-DECONSTRUCT).
|
||||||
@@ -173,9 +171,9 @@ int BuildingSystem::deconstruct(FactoryState& state, BuildingId id, Tick current
|
|||||||
state.constructionQueue.erase(it);
|
state.constructionQueue.erase(it);
|
||||||
if (def)
|
if (def)
|
||||||
{
|
{
|
||||||
return def->cost;
|
state.buildingBlocksStock += def->cost;
|
||||||
}
|
}
|
||||||
return 0;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,7 +183,7 @@ int BuildingSystem::deconstruct(FactoryState& state, BuildingId id, Tick current
|
|||||||
for (Building& building : state.buildings)
|
for (Building& building : state.buildings)
|
||||||
{
|
{
|
||||||
if (building.id != id) { continue; }
|
if (building.id != id) { continue; }
|
||||||
if (building.queuedForDeconstruction) { return 0; } // already queued
|
if (building.queuedForDeconstruction) { return; } // already queued
|
||||||
|
|
||||||
building.queuedForDeconstruction = true;
|
building.queuedForDeconstruction = true;
|
||||||
|
|
||||||
@@ -216,10 +214,8 @@ int BuildingSystem::deconstruct(FactoryState& state, BuildingId id, Tick current
|
|||||||
{
|
{
|
||||||
startFrontDeconstruction(state, m_config, currentTick);
|
startFrontDeconstruction(state, m_config, currentTick);
|
||||||
}
|
}
|
||||||
return 0;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -411,7 +407,7 @@ void BuildingSystem::tickBeltPull(FactoryState& state)
|
|||||||
lane.erase(lane.begin());
|
lane.erase(lane.begin());
|
||||||
if (isHq)
|
if (isHq)
|
||||||
{
|
{
|
||||||
m_addBuildingBlocks(1);
|
state.buildingBlocksStock += 1;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -41,7 +41,6 @@ public:
|
|||||||
BuildingSystem(const GameConfig& config,
|
BuildingSystem(const GameConfig& config,
|
||||||
BeltSystem& belts,
|
BeltSystem& belts,
|
||||||
std::function<BuildingId()> allocateBuildingId,
|
std::function<BuildingId()> allocateBuildingId,
|
||||||
std::function<void(int)> addBuildingBlocks,
|
|
||||||
std::function<void(const std::string&, QVector2D,
|
std::function<void(const std::string&, QVector2D,
|
||||||
const std::optional<ShipLayoutConfig>&)> spawnShip,
|
const std::optional<ShipLayoutConfig>&)> spawnShip,
|
||||||
std::function<bool(const std::string&)> isItemUnlocked,
|
std::function<bool(const std::string&)> isItemUnlocked,
|
||||||
@@ -64,12 +63,12 @@ public:
|
|||||||
{ state.asteroidWidth_tiles = widthTiles; }
|
{ state.asteroidWidth_tiles = widthTiles; }
|
||||||
|
|
||||||
// Mark a building or construction site for demolition (REQ-BLD-DECONSTRUCT).
|
// Mark a building or construction site for demolition (REQ-BLD-DECONSTRUCT).
|
||||||
// A construction site is removed instantly and the full cost is returned.
|
// A construction site is removed instantly and its full cost credited back to the
|
||||||
// A fully-built building is instead appended to the deconstruction queue
|
// block stock. A fully-built building is instead appended to the deconstruction
|
||||||
// (REQ-BLD-DECON-QUEUE) and stops operating at once; its (partial) refund is
|
// queue (REQ-BLD-DECON-QUEUE) and stops operating at once; its (partial) refund is
|
||||||
// credited later, on completion in tickDeconstruction, so this returns 0 for
|
// credited later, on completion, by DeconstructionSystem. No-op for unknown ids and
|
||||||
// it. Returns 0 for unknown ids and for a building already queued.
|
// for a building already queued.
|
||||||
int deconstruct(FactoryState& state, BuildingId id, Tick currentTick);
|
void deconstruct(FactoryState& state, BuildingId id, Tick currentTick);
|
||||||
|
|
||||||
// Take a building back out of the deconstruction queue before it is removed
|
// Take a building back out of the deconstruction queue before it is removed
|
||||||
// (REQ-BLD-DECON-QUEUE). Clears its queued flag and resumes operation
|
// (REQ-BLD-DECON-QUEUE). Clears its queued flag and resumes operation
|
||||||
@@ -180,7 +179,6 @@ private:
|
|||||||
const GameConfig& m_config;
|
const GameConfig& m_config;
|
||||||
BeltSystem& m_belts;
|
BeltSystem& m_belts;
|
||||||
std::function<BuildingId()> m_allocateBuildingId;
|
std::function<BuildingId()> m_allocateBuildingId;
|
||||||
std::function<void(int)> m_addBuildingBlocks;
|
|
||||||
std::function<void(const std::string&, QVector2D,
|
std::function<void(const std::string&, QVector2D,
|
||||||
const std::optional<ShipLayoutConfig>&)> m_spawnShip;
|
const std::optional<ShipLayoutConfig>&)> m_spawnShip;
|
||||||
std::function<bool(const std::string&)> m_isItemUnlocked;
|
std::function<bool(const std::string&)> m_isItemUnlocked;
|
||||||
|
|||||||
@@ -54,7 +54,8 @@ void DeconstructionSystem::tick(FactoryState& state, Tick currentTick)
|
|||||||
state.buildings.erase(it);
|
state.buildings.erase(it);
|
||||||
if (def)
|
if (def)
|
||||||
{
|
{
|
||||||
m_addBuildingBlocks(def->cost * m_config.world.refundPercentage / 100);
|
state.buildingBlocksStock +=
|
||||||
|
def->cost * m_config.world.refundPercentage / 100;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <functional>
|
|
||||||
|
|
||||||
#include "FactoryState.h"
|
#include "FactoryState.h"
|
||||||
#include "GameConfig.h"
|
#include "GameConfig.h"
|
||||||
#include "Tick.h"
|
#include "Tick.h"
|
||||||
@@ -14,19 +12,17 @@
|
|||||||
// It needs no BeltSystem: a belt, splitter or tunnel end is unregistered the moment
|
// It needs no BeltSystem: a belt, splitter or tunnel end is unregistered the moment
|
||||||
// it is queued (see BuildingSystem::deconstruct), not when the timer completes.
|
// it is queued (see BuildingSystem::deconstruct), not when the timer completes.
|
||||||
//
|
//
|
||||||
// Holds the config and the refund sink; the world arrives per tick.
|
// Holds the config alone; the world arrives per tick, the refund going straight into
|
||||||
|
// the block stock it carries (FactoryState.h).
|
||||||
class DeconstructionSystem
|
class DeconstructionSystem
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
DeconstructionSystem(const GameConfig& config,
|
explicit DeconstructionSystem(const GameConfig& config) : m_config(config) {}
|
||||||
std::function<void(int)> addBuildingBlocks)
|
|
||||||
: m_config(config), m_addBuildingBlocks(std::move(addBuildingBlocks)) {}
|
|
||||||
|
|
||||||
void tick(FactoryState& state, Tick currentTick);
|
void tick(FactoryState& state, Tick currentTick);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const GameConfig& m_config;
|
const GameConfig& m_config;
|
||||||
std::function<void(int)> m_addBuildingBlocks;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Starts the timer on the front entry of the deconstruction queue, if it has one and
|
// Starts the timer on the front entry of the deconstruction queue, if it has one and
|
||||||
|
|||||||
@@ -57,15 +57,26 @@ struct FactoryState
|
|||||||
// derived from config and Simulation's expansion count, which is folded already.
|
// derived from config and Simulation's expansion count, which is folded already.
|
||||||
// Seeded from config by BuildingSystem's constructor.
|
// Seeded from config by BuildingSystem's constructor.
|
||||||
int asteroidWidth_tiles = 0;
|
int asteroidWidth_tiles = 0;
|
||||||
|
|
||||||
|
// The global building block stock (REQ-HQ-STARTING-BLOCKS, REQ-HQ-BELT-INPUT):
|
||||||
|
// what placement spends, what deconstruction refunds, and what blocks delivered to
|
||||||
|
// the HQ add to. Factory data, so it lives with the factory rather than being
|
||||||
|
// reached through a callback by every system that credits it.
|
||||||
|
//
|
||||||
|
// Folded into the checksum by Simulation, in the place it has always occupied
|
||||||
|
// (docs/replay_design.md).
|
||||||
|
int buildingBlocksStock = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
// A fresh factory for a new run: nothing built, and the asteroid bound seeded from
|
// A fresh factory for a new run: nothing built, the asteroid bound and the starting
|
||||||
// config. Every owner of a FactoryState creates it this way — the bound has no
|
// block stock seeded from config. Every owner of a FactoryState creates it this way —
|
||||||
// sensible default without the config, so a default-constructed state would refuse
|
// the bound has no sensible default without the config, so a default-constructed state
|
||||||
// every placement on the asteroid.
|
// would refuse every placement on the asteroid, and the player would start with nothing
|
||||||
|
// to build from.
|
||||||
inline FactoryState makeFactoryState(const GameConfig& config)
|
inline FactoryState makeFactoryState(const GameConfig& config)
|
||||||
{
|
{
|
||||||
FactoryState state;
|
FactoryState state;
|
||||||
state.asteroidWidth_tiles = config.world.regions.asteroidWidth_tiles;
|
state.asteroidWidth_tiles = config.world.regions.asteroidWidth_tiles;
|
||||||
|
state.buildingBlocksStock = config.world.startingBuildingBlocks;
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ Simulation::Simulation(GameConfig config, unsigned int seed)
|
|||||||
, m_currentTick(0)
|
, m_currentTick(0)
|
||||||
, m_nextDepartureTick(secondsToTicks(m_config.world.departureIntervalSeconds))
|
, m_nextDepartureTick(secondsToTicks(m_config.world.departureIntervalSeconds))
|
||||||
, m_nextBuildingId(1)
|
, m_nextBuildingId(1)
|
||||||
, m_buildingBlocksStock(m_config.world.startingBuildingBlocks)
|
|
||||||
, m_gameOver(false)
|
, m_gameOver(false)
|
||||||
, m_hqProxyEntity(entt::null)
|
, m_hqProxyEntity(entt::null)
|
||||||
, m_playerStation1Entity(entt::null)
|
, m_playerStation1Entity(entt::null)
|
||||||
@@ -85,7 +84,6 @@ void Simulation::reset(unsigned int seed)
|
|||||||
m_currentTick = 0;
|
m_currentTick = 0;
|
||||||
m_nextDepartureTick = secondsToTicks(m_config.world.departureIntervalSeconds);
|
m_nextDepartureTick = secondsToTicks(m_config.world.departureIntervalSeconds);
|
||||||
m_nextBuildingId = 1;
|
m_nextBuildingId = 1;
|
||||||
m_buildingBlocksStock = m_config.world.startingBuildingBlocks;
|
|
||||||
m_expansionsPurchased = 0;
|
m_expansionsPurchased = 0;
|
||||||
m_gameOver = false;
|
m_gameOver = false;
|
||||||
m_isWon = false;
|
m_isWon = false;
|
||||||
@@ -114,7 +112,6 @@ void Simulation::initializeSubsystems()
|
|||||||
m_config,
|
m_config,
|
||||||
m_beltSystem,
|
m_beltSystem,
|
||||||
[this]() { return allocateBuildingId(); },
|
[this]() { return allocateBuildingId(); },
|
||||||
[this](int amount) { m_buildingBlocksStock += amount; },
|
|
||||||
[this](const std::string& id, QVector2D pos,
|
[this](const std::string& id, QVector2D pos,
|
||||||
const std::optional<ShipLayoutConfig>& layout) {
|
const std::optional<ShipLayoutConfig>& layout) {
|
||||||
if (!isSchematicUnlocked(id))
|
if (!isSchematicUnlocked(id))
|
||||||
@@ -126,8 +123,7 @@ void Simulation::initializeSubsystems()
|
|||||||
[this](const std::string& itemId) -> bool { return isItemUnlocked(itemId); },
|
[this](const std::string& itemId) -> bool { return isItemUnlocked(itemId); },
|
||||||
m_rng);
|
m_rng);
|
||||||
m_constructionSystem = std::make_unique<ConstructionSystem>(m_config);
|
m_constructionSystem = std::make_unique<ConstructionSystem>(m_config);
|
||||||
m_deconstructionSystem = std::make_unique<DeconstructionSystem>(
|
m_deconstructionSystem = std::make_unique<DeconstructionSystem>(m_config);
|
||||||
m_config, [this](int amount) { m_buildingBlocksStock += amount; });
|
|
||||||
m_shipSystem = std::make_unique<ShipSystem>(m_config, m_admin);
|
m_shipSystem = std::make_unique<ShipSystem>(m_config, m_admin);
|
||||||
m_aiSystem = std::make_unique<AiSystem>(m_config);
|
m_aiSystem = std::make_unique<AiSystem>(m_config);
|
||||||
m_movementIntentSystem = std::make_unique<MovementIntentSystem>();
|
m_movementIntentSystem = std::make_unique<MovementIntentSystem>();
|
||||||
@@ -658,7 +654,7 @@ unsigned long long Simulation::computeStateChecksum() const
|
|||||||
hasher.append(m_currentTick);
|
hasher.append(m_currentTick);
|
||||||
hasher.append(m_nextDepartureTick);
|
hasher.append(m_nextDepartureTick);
|
||||||
hasher.append(m_nextBuildingId);
|
hasher.append(m_nextBuildingId);
|
||||||
hasher.append(m_buildingBlocksStock);
|
hasher.append(m_factoryState.buildingBlocksStock);
|
||||||
hasher.append(m_gameOver);
|
hasher.append(m_gameOver);
|
||||||
hasher.append(m_isWon);
|
hasher.append(m_isWon);
|
||||||
hasher.append(m_artifactCount);
|
hasher.append(m_artifactCount);
|
||||||
@@ -763,7 +759,7 @@ unsigned int Simulation::getSeed() const
|
|||||||
|
|
||||||
int Simulation::getBuildingBlocksStock() const
|
int Simulation::getBuildingBlocksStock() const
|
||||||
{
|
{
|
||||||
return m_buildingBlocksStock;
|
return m_factoryState.buildingBlocksStock;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Simulation::getCurrentAsteroidWidth_tiles() const
|
int Simulation::getCurrentAsteroidWidth_tiles() const
|
||||||
@@ -782,11 +778,11 @@ int Simulation::getCurrentExpansionCost() const
|
|||||||
void Simulation::tryExpandAsteroid()
|
void Simulation::tryExpandAsteroid()
|
||||||
{
|
{
|
||||||
const int cost = getCurrentExpansionCost();
|
const int cost = getCurrentExpansionCost();
|
||||||
if (m_buildingBlocksStock < cost)
|
if (m_factoryState.buildingBlocksStock < cost)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
m_buildingBlocksStock -= cost;
|
m_factoryState.buildingBlocksStock -= cost;
|
||||||
++m_expansionsPurchased;
|
++m_expansionsPurchased;
|
||||||
m_buildingSystem->setAsteroidWidth_tiles(m_factoryState, getCurrentAsteroidWidth_tiles());
|
m_buildingSystem->setAsteroidWidth_tiles(m_factoryState, getCurrentAsteroidWidth_tiles());
|
||||||
}
|
}
|
||||||
@@ -879,17 +875,17 @@ std::optional<BuildingId> Simulation::tryPlaceBuilding(BuildingType type, QPoint
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (m_buildingBlocksStock < cost)
|
if (m_factoryState.buildingBlocksStock < cost)
|
||||||
{
|
{
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
m_buildingBlocksStock -= cost;
|
m_factoryState.buildingBlocksStock -= cost;
|
||||||
return m_buildingSystem->place(m_factoryState, type, anchor, rotation, m_currentTick);
|
return m_buildingSystem->place(m_factoryState, type, anchor, rotation, m_currentTick);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Simulation::deconstruct(BuildingId id)
|
void Simulation::deconstruct(BuildingId id)
|
||||||
{
|
{
|
||||||
m_buildingBlocksStock += m_buildingSystem->deconstruct(m_factoryState, id, m_currentTick);
|
m_buildingSystem->deconstruct(m_factoryState, id, m_currentTick);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Simulation::cancelDeconstruction(BuildingId id)
|
void Simulation::cancelDeconstruction(BuildingId id)
|
||||||
|
|||||||
@@ -196,7 +196,6 @@ private:
|
|||||||
Tick m_currentTick;
|
Tick m_currentTick;
|
||||||
Tick m_nextDepartureTick;
|
Tick m_nextDepartureTick;
|
||||||
BuildingId m_nextBuildingId;
|
BuildingId m_nextBuildingId;
|
||||||
int m_buildingBlocksStock;
|
|
||||||
int m_expansionsPurchased = 0; // REQ-EXP-COST formula variable x
|
int m_expansionsPurchased = 0; // REQ-EXP-COST formula variable x
|
||||||
bool m_gameOver = false;
|
bool m_gameOver = false;
|
||||||
bool m_isWon = false;
|
bool m_isWon = false;
|
||||||
|
|||||||
@@ -60,7 +60,6 @@ struct Fixture
|
|||||||
FactoryState state = makeFactoryState(cfg);
|
FactoryState state = makeFactoryState(cfg);
|
||||||
BeltSystem belts;
|
BeltSystem belts;
|
||||||
BuildingId nextBuildingId;
|
BuildingId nextBuildingId;
|
||||||
int stock;
|
|
||||||
std::mt19937 rng;
|
std::mt19937 rng;
|
||||||
EntityAdmin admin;
|
EntityAdmin admin;
|
||||||
BuildingSystem buildings;
|
BuildingSystem buildings;
|
||||||
@@ -79,11 +78,9 @@ struct Fixture
|
|||||||
: cfg(loadTestConfig())
|
: cfg(loadTestConfig())
|
||||||
, belts(cfg.world.beltSpeed_tps)
|
, belts(cfg.world.beltSpeed_tps)
|
||||||
, nextBuildingId(1)
|
, nextBuildingId(1)
|
||||||
, stock(0)
|
|
||||||
, rng(42)
|
, rng(42)
|
||||||
, buildings(cfg, belts,
|
, buildings(cfg, belts,
|
||||||
[this]() { return nextBuildingId++; },
|
[this]() { return nextBuildingId++; },
|
||||||
[this](int n) { stock += n; },
|
|
||||||
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
|
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
|
||||||
[](const std::string&) -> bool { return true; },
|
[](const std::string&) -> bool { return true; },
|
||||||
rng)
|
rng)
|
||||||
|
|||||||
@@ -58,12 +58,12 @@ static Port westPort(QPoint tile)
|
|||||||
|
|
||||||
// Run N full sim ticks: construction, belt-pull, production, belt-push, belt tick.
|
// Run N full sim ticks: construction, belt-pull, production, belt-push, belt tick.
|
||||||
static void runTicks(BuildingSystem& bs, const GameConfig& cfg, FactoryState& state_bs,
|
static void runTicks(BuildingSystem& bs, const GameConfig& cfg, FactoryState& state_bs,
|
||||||
BeltSystem& belts, int& stock, int n, Tick& tick)
|
BeltSystem& belts, int n, Tick& tick)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < n; ++i)
|
for (int i = 0; i < n; ++i)
|
||||||
{
|
{
|
||||||
ConstructionSystem(cfg).tick(state_bs, belts, tick);
|
ConstructionSystem(cfg).tick(state_bs, belts, tick);
|
||||||
DeconstructionSystem(cfg, [&stock](int n) { stock += n; }).tick(state_bs, tick);
|
DeconstructionSystem(cfg).tick(state_bs, tick);
|
||||||
bs.tickBeltPull(state_bs);
|
bs.tickBeltPull(state_bs);
|
||||||
bs.tickProduction(state_bs, tick);
|
bs.tickProduction(state_bs, tick);
|
||||||
bs.tickOutputBelts(state_bs);
|
bs.tickOutputBelts(state_bs);
|
||||||
@@ -98,11 +98,17 @@ struct PlacementFixture
|
|||||||
GameConfig cfg = loadTestConfig();
|
GameConfig cfg = loadTestConfig();
|
||||||
FactoryState state = makeFactoryState(cfg);
|
FactoryState state = makeFactoryState(cfg);
|
||||||
BeltSystem belts;
|
BeltSystem belts;
|
||||||
int stock = 0;
|
|
||||||
std::mt19937 rng{0};
|
std::mt19937 rng{0};
|
||||||
BuildingId nextBuildingId = 1;
|
BuildingId nextBuildingId = 1;
|
||||||
BuildingSystem bs;
|
BuildingSystem bs;
|
||||||
|
|
||||||
|
// Blocks credited back since the run began. The state is seeded with the configured
|
||||||
|
// starting stock (FactoryState.h), so a refund reads as a delta rather than a total.
|
||||||
|
int getRefundedBlocks() const
|
||||||
|
{
|
||||||
|
return state.buildingBlocksStock - cfg.world.startingBuildingBlocks;
|
||||||
|
}
|
||||||
|
|
||||||
// Defaults to the configured belt speed; pass kFastBeltSpeed_tps where the test
|
// Defaults to the configured belt speed; pass kFastBeltSpeed_tps where the test
|
||||||
// needs items to arrive immediately. Everything counts as unlocked unless the test
|
// needs items to arrive immediately. Everything counts as unlocked unless the test
|
||||||
// says otherwise, which is what the output-group eligibility rule turns on
|
// says otherwise, which is what the output-group eligibility rule turns on
|
||||||
@@ -113,7 +119,6 @@ struct PlacementFixture
|
|||||||
: belts(beltSpeed_tps.value_or(cfg.world.beltSpeed_tps))
|
: belts(beltSpeed_tps.value_or(cfg.world.beltSpeed_tps))
|
||||||
, bs(cfg, belts,
|
, bs(cfg, belts,
|
||||||
[this]() { return nextBuildingId++; },
|
[this]() { return nextBuildingId++; },
|
||||||
[this](int n) { stock += n; },
|
|
||||||
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
|
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
|
||||||
isItemUnlocked ? std::move(isItemUnlocked)
|
isItemUnlocked ? std::move(isItemUnlocked)
|
||||||
: std::function<bool(const std::string&)>(
|
: std::function<bool(const std::string&)>(
|
||||||
@@ -255,7 +260,7 @@ TEST_CASE("BuildingSystem: placing a belt registers it with BeltSystem after con
|
|||||||
|
|
||||||
// Complete construction (1 s).
|
// Complete construction (1 s).
|
||||||
Tick tick = 0;
|
Tick tick = 0;
|
||||||
runTicks(f.bs, f.cfg, f.state, f.belts, f.stock, static_cast<int>(secondsToTicks(1.0)) + 1, tick);
|
runTicks(f.bs, f.cfg, f.state, f.belts, static_cast<int>(secondsToTicks(1.0)) + 1, tick);
|
||||||
|
|
||||||
REQUIRE(f.belts.tryPutItem(QPoint(5, 5), makeItem("iron_ore"), Rotation::East));
|
REQUIRE(f.belts.tryPutItem(QPoint(5, 5), makeItem("iron_ore"), Rotation::East));
|
||||||
REQUIRE(getAllBuildings(f.state).size() == 1);
|
REQUIRE(getAllBuildings(f.state).size() == 1);
|
||||||
@@ -283,9 +288,9 @@ TEST_CASE("BuildingSystem: deconstructing a construction site removes it instant
|
|||||||
|
|
||||||
// Still queued for construction (not yet built): instant removal, full cost
|
// Still queued for construction (not yet built): instant removal, full cost
|
||||||
// refunded immediately, never entering the deconstruction queue (REQ-BLD-DECONSTRUCT).
|
// refunded immediately, never entering the deconstruction queue (REQ-BLD-DECONSTRUCT).
|
||||||
const int refund = f.bs.deconstruct(f.state, id, 0);
|
f.bs.deconstruct(f.state, id, 0);
|
||||||
|
|
||||||
REQUIRE(refund == 15); // Miner cost = 15
|
REQUIRE(f.getRefundedBlocks() == 15); // Miner cost = 15
|
||||||
REQUIRE_FALSE(isTileOccupied(f.state, QPoint(0, 0)));
|
REQUIRE_FALSE(isTileOccupied(f.state, QPoint(0, 0)));
|
||||||
REQUIRE(getAllSites(f.state).empty());
|
REQUIRE(getAllSites(f.state).empty());
|
||||||
}
|
}
|
||||||
@@ -324,7 +329,7 @@ TEST_CASE("BuildingSystem: construction completes after configured duration", "[
|
|||||||
// Miner construction_time_seconds = 10. completesAt = secondsToTicks(10) = 300.
|
// Miner construction_time_seconds = 10. completesAt = secondsToTicks(10) = 300.
|
||||||
// We need to process tick 300 itself, so run 301 ticks (ticks 0..300).
|
// We need to process tick 300 itself, so run 301 ticks (ticks 0..300).
|
||||||
Tick tick = 0;
|
Tick tick = 0;
|
||||||
runTicks(f.bs, f.cfg, f.state, f.belts, f.stock, static_cast<int>(secondsToTicks(10.0)) + 1, tick);
|
runTicks(f.bs, f.cfg, f.state, f.belts, static_cast<int>(secondsToTicks(10.0)) + 1, tick);
|
||||||
|
|
||||||
REQUIRE(getAllSites(f.state).empty());
|
REQUIRE(getAllSites(f.state).empty());
|
||||||
REQUIRE(findBuilding(f.state, id) != nullptr);
|
REQUIRE(findBuilding(f.state, id) != nullptr);
|
||||||
@@ -339,7 +344,7 @@ static void runUntilBuilt(PlacementFixture& f, BuildingId id, Tick& tick)
|
|||||||
{
|
{
|
||||||
for (int i = 0; i < 100000 && findBuilding(f.state, id) == nullptr; ++i)
|
for (int i = 0; i < 100000 && findBuilding(f.state, id) == nullptr; ++i)
|
||||||
{
|
{
|
||||||
runTicks(f.bs, f.cfg, f.state, f.belts, f.stock, 1, tick);
|
runTicks(f.bs, f.cfg, f.state, f.belts, 1, tick);
|
||||||
}
|
}
|
||||||
REQUIRE(findBuilding(f.state, id) != nullptr);
|
REQUIRE(findBuilding(f.state, id) != nullptr);
|
||||||
}
|
}
|
||||||
@@ -356,18 +361,17 @@ TEST_CASE("BuildingSystem: deconstructing a built building queues it; refund cre
|
|||||||
|
|
||||||
// Deconstructing a built building returns nothing immediately and queues it,
|
// Deconstructing a built building returns nothing immediately and queues it,
|
||||||
// stopping it operating while its tiles stay occupied (REQ-BLD-DECON-QUEUE).
|
// stopping it operating while its tiles stay occupied (REQ-BLD-DECON-QUEUE).
|
||||||
const int refund = f.bs.deconstruct(f.state, id, tick);
|
f.bs.deconstruct(f.state, id, tick);
|
||||||
REQUIRE(refund == 0);
|
|
||||||
REQUIRE(isQueuedForDeconstruction(f.state, id));
|
REQUIRE(isQueuedForDeconstruction(f.state, id));
|
||||||
REQUIRE(isTileOccupied(f.state, QPoint(0, 0)));
|
REQUIRE(isTileOccupied(f.state, QPoint(0, 0)));
|
||||||
REQUIRE(f.stock == 0);
|
REQUIRE(f.getRefundedBlocks() == 0);
|
||||||
|
|
||||||
// After the deconstruction time (0.1s = 3 ticks) it is removed and the partial
|
// After the deconstruction time (0.1s = 3 ticks) it is removed and the partial
|
||||||
// refund (15 * 75 / 100 = 11) is credited exactly once.
|
// refund (15 * 75 / 100 = 11) is credited exactly once.
|
||||||
runTicks(f.bs, f.cfg, f.state, f.belts, f.stock, 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(findBuilding(f.state, id) == nullptr);
|
||||||
REQUIRE_FALSE(isTileOccupied(f.state, QPoint(0, 0)));
|
REQUIRE_FALSE(isTileOccupied(f.state, QPoint(0, 0)));
|
||||||
REQUIRE(f.stock == 15 * f.cfg.world.refundPercentage / 100);
|
REQUIRE(f.getRefundedBlocks() == 15 * f.cfg.world.refundPercentage / 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("BuildingSystem: deconstruction queue removes one building at a time", "[building][decon]")
|
TEST_CASE("BuildingSystem: deconstruction queue removes one building at a time", "[building][decon]")
|
||||||
@@ -388,16 +392,16 @@ TEST_CASE("BuildingSystem: deconstruction queue removes one building at a time",
|
|||||||
|
|
||||||
// After one deconstruction interval only the front building is gone; the
|
// After one deconstruction interval only the front building is gone; the
|
||||||
// second is still queued and its refund not yet credited.
|
// second is still queued and its refund not yet credited.
|
||||||
runTicks(f.bs, f.cfg, f.state, f.belts, f.stock, 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, a) == nullptr);
|
||||||
REQUIRE(findBuilding(f.state, b) != nullptr);
|
REQUIRE(findBuilding(f.state, b) != nullptr);
|
||||||
REQUIRE(isQueuedForDeconstruction(f.state, b));
|
REQUIRE(isQueuedForDeconstruction(f.state, b));
|
||||||
REQUIRE(f.stock == 15 * f.cfg.world.refundPercentage / 100);
|
REQUIRE(f.getRefundedBlocks() == 15 * f.cfg.world.refundPercentage / 100);
|
||||||
|
|
||||||
// The second drains next.
|
// The second drains next.
|
||||||
runTicks(f.bs, f.cfg, f.state, f.belts, f.stock, 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(findBuilding(f.state, b) == nullptr);
|
||||||
REQUIRE(f.stock == 2 * (15 * f.cfg.world.refundPercentage / 100));
|
REQUIRE(f.getRefundedBlocks() == 2 * (15 * f.cfg.world.refundPercentage / 100));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("BuildingSystem: cancelling deconstruction resumes the building with no refund",
|
TEST_CASE("BuildingSystem: cancelling deconstruction resumes the building with no refund",
|
||||||
@@ -418,12 +422,12 @@ TEST_CASE("BuildingSystem: cancelling deconstruction resumes the building with n
|
|||||||
REQUIRE_FALSE(isQueuedForDeconstruction(f.state, id));
|
REQUIRE_FALSE(isQueuedForDeconstruction(f.state, id));
|
||||||
REQUIRE(findBuilding(f.state, id) != nullptr);
|
REQUIRE(findBuilding(f.state, id) != nullptr);
|
||||||
REQUIRE(isTileOccupied(f.state, QPoint(0, 0)));
|
REQUIRE(isTileOccupied(f.state, QPoint(0, 0)));
|
||||||
REQUIRE(f.stock == 0);
|
REQUIRE(f.getRefundedBlocks() == 0);
|
||||||
|
|
||||||
// It is never removed even after more than a deconstruction interval passes.
|
// It is never removed even after more than a deconstruction interval passes.
|
||||||
runTicks(f.bs, f.cfg, f.state, f.belts, f.stock, 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(findBuilding(f.state, id) != nullptr);
|
||||||
REQUIRE(f.stock == 0);
|
REQUIRE(f.getRefundedBlocks() == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("BuildingSystem: queued belt stops transporting; cancel restores it", "[building][decon]")
|
TEST_CASE("BuildingSystem: queued belt stops transporting; cancel restores it", "[building][decon]")
|
||||||
@@ -480,7 +484,7 @@ TEST_CASE("BuildingSystem: second building starts after first completes", "[buil
|
|||||||
|
|
||||||
// Process through tick 300 to complete first miner's construction.
|
// Process through tick 300 to complete first miner's construction.
|
||||||
Tick tick = 0;
|
Tick tick = 0;
|
||||||
runTicks(f.bs, f.cfg, f.state, f.belts, f.stock, static_cast<int>(secondsToTicks(10.0)) + 1, tick);
|
runTicks(f.bs, f.cfg, f.state, f.belts, static_cast<int>(secondsToTicks(10.0)) + 1, tick);
|
||||||
|
|
||||||
REQUIRE(getAllSites(f.state).size() == 1);
|
REQUIRE(getAllSites(f.state).size() == 1);
|
||||||
REQUIRE(getAllSites(f.state).front().id == id2);
|
REQUIRE(getAllSites(f.state).front().id == id2);
|
||||||
@@ -501,7 +505,7 @@ TEST_CASE("BuildingSystem: miner produces iron_ore after recipe duration", "[bui
|
|||||||
Tick tick = 0;
|
Tick tick = 0;
|
||||||
// Construction completes on tick 300; production cycle starts tick 300,
|
// Construction completes on tick 300; production cycle starts tick 300,
|
||||||
// completes on tick 330. Process through tick 330: 331 ticks total.
|
// completes on tick 330. Process through tick 330: 331 ticks total.
|
||||||
runTicks(f.bs, f.cfg, f.state, f.belts, f.stock,
|
runTicks(f.bs, f.cfg, f.state, f.belts,
|
||||||
static_cast<int>(secondsToTicks(10.0)) + static_cast<int>(secondsToTicks(1.0)) + 1,
|
static_cast<int>(secondsToTicks(10.0)) + static_cast<int>(secondsToTicks(1.0)) + 1,
|
||||||
tick);
|
tick);
|
||||||
|
|
||||||
@@ -527,7 +531,7 @@ TEST_CASE("BuildingSystem: miner output buffer stalls when full", "[building]")
|
|||||||
// (completesAt=360). Cycle 2 completes at tick 360: deposit item -> 2 items held,
|
// (completesAt=360). Cycle 2 completes at tick 360: deposit item -> 2 items held,
|
||||||
// which fills the buffer (capacity 2), so cycle 3 cannot start.
|
// which fills the buffer (capacity 2), so cycle 3 cannot start.
|
||||||
// Need to process through tick 360: 361 ticks total.
|
// Need to process through tick 360: 361 ticks total.
|
||||||
runTicks(f.bs, f.cfg, f.state, f.belts, f.stock,
|
runTicks(f.bs, f.cfg, f.state, f.belts,
|
||||||
static_cast<int>(secondsToTicks(10.0))
|
static_cast<int>(secondsToTicks(10.0))
|
||||||
+ 2 * static_cast<int>(secondsToTicks(1.0)) + 1,
|
+ 2 * static_cast<int>(secondsToTicks(1.0)) + 1,
|
||||||
tick);
|
tick);
|
||||||
@@ -556,7 +560,7 @@ TEST_CASE("BuildingSystem: the next cycle starts on the tick the last one comple
|
|||||||
|
|
||||||
Tick tick = 0;
|
Tick tick = 0;
|
||||||
// Construction completes at tick 300 and cycle 1 starts in that same tick.
|
// Construction completes at tick 300 and cycle 1 starts in that same tick.
|
||||||
runTicks(f.bs, f.cfg, f.state, f.belts, f.stock,
|
runTicks(f.bs, f.cfg, f.state, f.belts,
|
||||||
static_cast<int>(secondsToTicks(10.0)) + 1, tick);
|
static_cast<int>(secondsToTicks(10.0)) + 1, tick);
|
||||||
const Building* b = findBuilding(f.state, id);
|
const Building* b = findBuilding(f.state, id);
|
||||||
REQUIRE(b != nullptr);
|
REQUIRE(b != nullptr);
|
||||||
@@ -565,7 +569,7 @@ TEST_CASE("BuildingSystem: the next cycle starts on the tick the last one comple
|
|||||||
|
|
||||||
// Process up to and including that completion tick: the next cycle is already
|
// Process up to and including that completion tick: the next cycle is already
|
||||||
// running, due exactly one duration later rather than one duration plus a tick.
|
// running, due exactly one duration later rather than one duration plus a tick.
|
||||||
runTicks(f.bs, f.cfg, f.state, f.belts, f.stock, static_cast<int>(cycleTicks), tick);
|
runTicks(f.bs, f.cfg, f.state, f.belts, static_cast<int>(cycleTicks), tick);
|
||||||
b = findBuilding(f.state, id);
|
b = findBuilding(f.state, id);
|
||||||
REQUIRE(b->getOutputItemCount() == 1);
|
REQUIRE(b->getOutputItemCount() == 1);
|
||||||
REQUIRE(b->production.has_value());
|
REQUIRE(b->production.has_value());
|
||||||
@@ -577,7 +581,7 @@ TEST_CASE("BuildingSystem: the next cycle starts on the tick the last one comple
|
|||||||
building.outputBuffer.items.clear();
|
building.outputBuffer.items.clear();
|
||||||
for (std::vector<BeltItemSlot>& lane : building.emergingItems) { lane.clear(); }
|
for (std::vector<BeltItemSlot>& lane : building.emergingItems) { lane.clear(); }
|
||||||
});
|
});
|
||||||
runTicks(f.bs, f.cfg, f.state, f.belts, f.stock, static_cast<int>(cycleTicks), tick);
|
runTicks(f.bs, f.cfg, f.state, f.belts, static_cast<int>(cycleTicks), tick);
|
||||||
b = findBuilding(f.state, id);
|
b = findBuilding(f.state, id);
|
||||||
REQUIRE(b->production.has_value());
|
REQUIRE(b->production.has_value());
|
||||||
REQUIRE(b->production->completesAt == firstCompletesAt + 2 * cycleTicks);
|
REQUIRE(b->production->completesAt == firstCompletesAt + 2 * cycleTicks);
|
||||||
@@ -601,10 +605,10 @@ TEST_CASE("BuildingSystem: productionBuildingCount excludes construction sites",
|
|||||||
|
|
||||||
// The queue builds one at a time: miner (10s) completes at tick 300, then
|
// 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.
|
// the smelter (15s) starts and completes at tick 300 + 450 = 750.
|
||||||
runTicks(f.bs, f.cfg, f.state, f.belts, f.stock, static_cast<int>(secondsToTicks(10.0)) + 1, tick);
|
runTicks(f.bs, f.cfg, f.state, f.belts, static_cast<int>(secondsToTicks(10.0)) + 1, tick);
|
||||||
REQUIRE(getProductionBuildingCount(f.state) == 1);
|
REQUIRE(getProductionBuildingCount(f.state) == 1);
|
||||||
|
|
||||||
runTicks(f.bs, f.cfg, f.state, f.belts, f.stock, static_cast<int>(secondsToTicks(15.0)), tick);
|
runTicks(f.bs, f.cfg, f.state, f.belts, static_cast<int>(secondsToTicks(15.0)), tick);
|
||||||
REQUIRE(getProductionBuildingCount(f.state) == 2);
|
REQUIRE(getProductionBuildingCount(f.state) == 2);
|
||||||
|
|
||||||
// Neither is producing yet: the miner has no recipe selected, and the
|
// Neither is producing yet: the miner has no recipe selected, and the
|
||||||
@@ -612,7 +616,7 @@ TEST_CASE("BuildingSystem: productionBuildingCount excludes construction sites",
|
|||||||
REQUIRE(getActiveProductionBuildingCount(f.state) == 0);
|
REQUIRE(getActiveProductionBuildingCount(f.state) == 0);
|
||||||
|
|
||||||
f.bs.setRecipe(f.state, minerId, "mine_iron_ore");
|
f.bs.setRecipe(f.state, minerId, "mine_iron_ore");
|
||||||
runTicks(f.bs, f.cfg, f.state, f.belts, f.stock, 1, tick);
|
runTicks(f.bs, f.cfg, f.state, f.belts, 1, tick);
|
||||||
REQUIRE(getActiveProductionBuildingCount(f.state) == 1);
|
REQUIRE(getActiveProductionBuildingCount(f.state) == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -629,12 +633,12 @@ TEST_CASE("BuildingSystem: activeProductionBuildingCount tracks production cycle
|
|||||||
REQUIRE(getActiveProductionBuildingCount(f.state) == 0);
|
REQUIRE(getActiveProductionBuildingCount(f.state) == 0);
|
||||||
|
|
||||||
// Construction completes at tick 300; cycle 1 starts the same tick (completesAt=330).
|
// Construction completes at tick 300; cycle 1 starts the same tick (completesAt=330).
|
||||||
runTicks(f.bs, f.cfg, f.state, f.belts, f.stock, static_cast<int>(secondsToTicks(10.0)) + 1, tick);
|
runTicks(f.bs, f.cfg, f.state, f.belts, static_cast<int>(secondsToTicks(10.0)) + 1, tick);
|
||||||
REQUIRE(getActiveProductionBuildingCount(f.state) == 1);
|
REQUIRE(getActiveProductionBuildingCount(f.state) == 1);
|
||||||
|
|
||||||
// Run cycles 1 and 2 to completion (1s each); cycle 3 stalls once the
|
// Run cycles 1 and 2 to completion (1s each); cycle 3 stalls once the
|
||||||
// output buffer (capacity 2) is full (REQ-MAT-OUTPUT-BUFFER).
|
// output buffer (capacity 2) is full (REQ-MAT-OUTPUT-BUFFER).
|
||||||
runTicks(f.bs, f.cfg, f.state, f.belts, f.stock, 2 * static_cast<int>(secondsToTicks(1.0)) + 1, tick);
|
runTicks(f.bs, f.cfg, f.state, f.belts, 2 * static_cast<int>(secondsToTicks(1.0)) + 1, tick);
|
||||||
|
|
||||||
const Building* b = findBuilding(f.state, id);
|
const Building* b = findBuilding(f.state, id);
|
||||||
REQUIRE(b != nullptr);
|
REQUIRE(b != nullptr);
|
||||||
@@ -661,7 +665,7 @@ TEST_CASE("BuildingSystem: smelter input buffer fills from adjacent west-flowing
|
|||||||
|
|
||||||
// Complete construction (15s → tick 450+1 = 451 ticks).
|
// Complete construction (15s → tick 450+1 = 451 ticks).
|
||||||
Tick tick = 0;
|
Tick tick = 0;
|
||||||
runTicks(f.bs, f.cfg, f.state, f.belts, f.stock, static_cast<int>(secondsToTicks(15.0)) + 1, tick);
|
runTicks(f.bs, f.cfg, f.state, f.belts, static_cast<int>(secondsToTicks(15.0)) + 1, tick);
|
||||||
REQUIRE(findBuilding(f.state, sid)->recipeId.empty());
|
REQUIRE(findBuilding(f.state, sid)->recipeId.empty());
|
||||||
|
|
||||||
// Place west-flowing belt at (2,0): belt flows West, delivers to smelter.
|
// Place west-flowing belt at (2,0): belt flows West, delivers to smelter.
|
||||||
@@ -685,9 +689,9 @@ TEST_CASE("BuildingSystem: smelter input buffer fills from adjacent west-flowing
|
|||||||
REQUIRE(b->pendingInputCount(ItemType{"iron_ore"}) >= 1);
|
REQUIRE(b->pendingInputCount(ItemType{"iron_ore"}) >= 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// An accepted input item travels inward on its input belt before it becomes usable
|
// An accepted input item travels inward on its input belt before it becomes usable:
|
||||||
// f.stock: it is reserved (counts against the cap) on entry and only enters the
|
// it is reserved (counts against the cap) on entry and only enters the buffer on
|
||||||
// buffer on reaching the tile centre (REQ-MAT-INPUT-INTAKE).
|
// reaching the tile centre (REQ-MAT-INPUT-INTAKE).
|
||||||
TEST_CASE("BuildingSystem: accepted input travels inward before entering the buffer",
|
TEST_CASE("BuildingSystem: accepted input travels inward before entering the buffer",
|
||||||
"[building]")
|
"[building]")
|
||||||
{
|
{
|
||||||
@@ -695,7 +699,7 @@ TEST_CASE("BuildingSystem: accepted input travels inward before entering the buf
|
|||||||
|
|
||||||
const BuildingId sid = f.bs.place(f.state, BuildingType::Smelter, QPoint(0, 0), Rotation::East, 0).value();
|
const BuildingId sid = f.bs.place(f.state, BuildingType::Smelter, QPoint(0, 0), Rotation::East, 0).value();
|
||||||
Tick tick = 0;
|
Tick tick = 0;
|
||||||
runTicks(f.bs, f.cfg, f.state, f.belts, f.stock, static_cast<int>(secondsToTicks(15.0)) + 1, tick);
|
runTicks(f.bs, f.cfg, f.state, f.belts, static_cast<int>(secondsToTicks(15.0)) + 1, tick);
|
||||||
|
|
||||||
f.belts.placeBelt(QPoint(2, 0), Rotation::West);
|
f.belts.placeBelt(QPoint(2, 0), Rotation::West);
|
||||||
f.belts.tryPutItem(QPoint(2, 0), makeItem("iron_ore"));
|
f.belts.tryPutItem(QPoint(2, 0), makeItem("iron_ore"));
|
||||||
@@ -727,7 +731,7 @@ TEST_CASE("BuildingSystem: input reservation caps buffered plus in-transit at th
|
|||||||
const BuildingId id = f.bs.place(f.state, BuildingType::ReprocessingPlant,
|
const BuildingId id = f.bs.place(f.state, BuildingType::ReprocessingPlant,
|
||||||
QPoint(0, 0), Rotation::East, 0).value();
|
QPoint(0, 0), Rotation::East, 0).value();
|
||||||
Tick tick = 0;
|
Tick tick = 0;
|
||||||
runTicks(f.bs, f.cfg, f.state, f.belts, f.stock, static_cast<int>(secondsToTicks(25.0)) + 1, tick);
|
runTicks(f.bs, f.cfg, f.state, f.belts, static_cast<int>(secondsToTicks(25.0)) + 1, tick);
|
||||||
|
|
||||||
// Feed scrap via an input belt without ever running production (only pull), so
|
// 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.
|
// the buffer fills and stays full. Try to over-fill it well past the cap.
|
||||||
@@ -759,7 +763,7 @@ TEST_CASE("BuildingSystem: smelter auto-smelts ore without a recipe selection",
|
|||||||
const BuildingId sid = f.bs.place(f.state, BuildingType::Smelter, QPoint(0, 0), Rotation::East, 0).value();
|
const BuildingId sid = f.bs.place(f.state, BuildingType::Smelter, QPoint(0, 0), Rotation::East, 0).value();
|
||||||
|
|
||||||
Tick tick = 0;
|
Tick tick = 0;
|
||||||
runTicks(f.bs, f.cfg, f.state, f.belts, f.stock, static_cast<int>(secondsToTicks(15.0)) + 1, tick);
|
runTicks(f.bs, f.cfg, f.state, f.belts, static_cast<int>(secondsToTicks(15.0)) + 1, tick);
|
||||||
|
|
||||||
// Feed 2 iron_ore (the test-config iron_ingot recipe needs 2) via a
|
// Feed 2 iron_ore (the test-config iron_ingot recipe needs 2) via a
|
||||||
// west-flowing belt at input port (2,0).
|
// west-flowing belt at input port (2,0).
|
||||||
@@ -772,7 +776,7 @@ TEST_CASE("BuildingSystem: smelter auto-smelts ore without a recipe selection",
|
|||||||
}
|
}
|
||||||
|
|
||||||
// iron_ingot recipe cycle is 2s; run to completion.
|
// iron_ingot recipe cycle is 2s; run to completion.
|
||||||
runTicks(f.bs, f.cfg, f.state, f.belts, f.stock, static_cast<int>(secondsToTicks(2.0)) + 2, tick);
|
runTicks(f.bs, f.cfg, f.state, f.belts, static_cast<int>(secondsToTicks(2.0)) + 2, tick);
|
||||||
|
|
||||||
const Building* b = findBuilding(f.state, sid);
|
const Building* b = findBuilding(f.state, sid);
|
||||||
REQUIRE(b != nullptr);
|
REQUIRE(b != nullptr);
|
||||||
@@ -795,7 +799,7 @@ TEST_CASE("BuildingSystem: mixed ore on one belt leaves the smelter on the first
|
|||||||
const BuildingId sid = f.bs.place(f.state, BuildingType::Smelter, QPoint(0, 0), Rotation::East, 0).value();
|
const BuildingId sid = f.bs.place(f.state, BuildingType::Smelter, QPoint(0, 0), Rotation::East, 0).value();
|
||||||
|
|
||||||
Tick tick = 0;
|
Tick tick = 0;
|
||||||
runTicks(f.bs, f.cfg, f.state, f.belts, f.stock, static_cast<int>(secondsToTicks(15.0)) + 1, tick);
|
runTicks(f.bs, f.cfg, f.state, f.belts, static_cast<int>(secondsToTicks(15.0)) + 1, tick);
|
||||||
|
|
||||||
// Feed 1 iron_ore, then 2 copper_ore, via the west-flowing input belt.
|
// Feed 1 iron_ore, then 2 copper_ore, via the west-flowing input belt.
|
||||||
f.belts.placeBelt(QPoint(2, 0), Rotation::West);
|
f.belts.placeBelt(QPoint(2, 0), Rotation::West);
|
||||||
@@ -807,7 +811,7 @@ TEST_CASE("BuildingSystem: mixed ore on one belt leaves the smelter on the first
|
|||||||
f.bs.tickBeltPull(f.state);
|
f.bs.tickBeltPull(f.state);
|
||||||
}
|
}
|
||||||
|
|
||||||
runTicks(f.bs, f.cfg, f.state, f.belts, f.stock, static_cast<int>(secondsToTicks(2.5)) + 2, tick);
|
runTicks(f.bs, f.cfg, f.state, f.belts, static_cast<int>(secondsToTicks(2.5)) + 2, tick);
|
||||||
|
|
||||||
const Building* b = findBuilding(f.state, sid);
|
const Building* b = findBuilding(f.state, sid);
|
||||||
REQUIRE(b != nullptr);
|
REQUIRE(b != nullptr);
|
||||||
@@ -845,13 +849,13 @@ TEST_CASE("BuildingSystem: miner output buffer drains onto adjacent belt", "[bui
|
|||||||
|
|
||||||
Tick tick = 0;
|
Tick tick = 0;
|
||||||
// Construction (10s) + 1 production cycle (1s) + 1 extra tick.
|
// Construction (10s) + 1 production cycle (1s) + 1 extra tick.
|
||||||
runTicks(f.bs, f.cfg, f.state, f.belts, f.stock,
|
runTicks(f.bs, f.cfg, f.state, f.belts,
|
||||||
static_cast<int>(secondsToTicks(10.0)) + static_cast<int>(secondsToTicks(1.0)) + 1,
|
static_cast<int>(secondsToTicks(10.0)) + static_cast<int>(secondsToTicks(1.0)) + 1,
|
||||||
tick);
|
tick);
|
||||||
|
|
||||||
// Item should have been pushed onto the belt this tick or a subsequent one.
|
// 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.
|
// Run one more tick to ensure tickBeltPush fires after the deposit tick.
|
||||||
runTicks(f.bs, f.cfg, f.state, f.belts, f.stock, 1, tick);
|
runTicks(f.bs, f.cfg, f.state, f.belts, 1, tick);
|
||||||
|
|
||||||
const std::optional<Item> item = f.belts.tryTakeItem(eastPort(QPoint(1, 1)));
|
const std::optional<Item> item = f.belts.tryTakeItem(eastPort(QPoint(1, 1)));
|
||||||
REQUIRE(item.has_value());
|
REQUIRE(item.has_value());
|
||||||
@@ -876,7 +880,7 @@ TEST_CASE("BuildingSystem: output port couples directly into an adjacent input p
|
|||||||
|
|
||||||
Tick tick = 0;
|
Tick tick = 0;
|
||||||
// Smelter build (15s) + margin for coupling and a smelt cycle.
|
// Smelter build (15s) + margin for coupling and a smelt cycle.
|
||||||
runTicks(f.bs, f.cfg, f.state, f.belts, f.stock, static_cast<int>(secondsToTicks(30.0)), tick);
|
runTicks(f.bs, f.cfg, f.state, f.belts, static_cast<int>(secondsToTicks(30.0)), tick);
|
||||||
|
|
||||||
const Building* smelter = findBuilding(f.state, smelterId);
|
const Building* smelter = findBuilding(f.state, smelterId);
|
||||||
REQUIRE(smelter != nullptr);
|
REQUIRE(smelter != nullptr);
|
||||||
@@ -905,7 +909,7 @@ TEST_CASE("BuildingSystem: direct coupling to a non-consumer leaves the item stu
|
|||||||
|
|
||||||
Tick tick = 0;
|
Tick tick = 0;
|
||||||
// Both miners build sequentially (10s each), then the producer runs and jams.
|
// Both miners build sequentially (10s each), then the producer runs and jams.
|
||||||
runTicks(f.bs, f.cfg, f.state, f.belts, f.stock, static_cast<int>(secondsToTicks(25.0)), tick);
|
runTicks(f.bs, f.cfg, f.state, f.belts, static_cast<int>(secondsToTicks(25.0)), tick);
|
||||||
|
|
||||||
const Building* miner = findBuilding(f.state, minerId);
|
const Building* miner = findBuilding(f.state, minerId);
|
||||||
const Building* sink = findBuilding(f.state, sinkId);
|
const Building* sink = findBuilding(f.state, sinkId);
|
||||||
@@ -931,7 +935,7 @@ TEST_CASE("BuildingSystem: setRecipe clears output buffer and active production"
|
|||||||
|
|
||||||
Tick tick = 0;
|
Tick tick = 0;
|
||||||
// Run until first item is in output buffer.
|
// Run until first item is in output buffer.
|
||||||
runTicks(f.bs, f.cfg, f.state, f.belts, f.stock,
|
runTicks(f.bs, f.cfg, f.state, f.belts,
|
||||||
static_cast<int>(secondsToTicks(10.0)) + static_cast<int>(secondsToTicks(1.0)) + 1,
|
static_cast<int>(secondsToTicks(10.0)) + static_cast<int>(secondsToTicks(1.0)) + 1,
|
||||||
tick);
|
tick);
|
||||||
|
|
||||||
@@ -996,9 +1000,8 @@ TEST_CASE("BuildingSystem: a single-group recipe consumes no randomness", "[buil
|
|||||||
advanced.bs.setRecipe(advanced.state, b, "mine_iron_ore");
|
advanced.bs.setRecipe(advanced.state, b, "mine_iron_ore");
|
||||||
|
|
||||||
const int ticks = static_cast<int>(secondsToTicks(10.0)) + 40;
|
const int ticks = static_cast<int>(secondsToTicks(10.0)) + 40;
|
||||||
runTicks(quiet.bs, quiet.cfg, quiet.state, quiet.belts, quiet.stock, ticks, tickA);
|
runTicks(quiet.bs, quiet.cfg, quiet.state, quiet.belts, ticks, tickA);
|
||||||
runTicks(advanced.bs, advanced.cfg, advanced.state, advanced.belts, advanced.stock,
|
runTicks(advanced.bs, advanced.cfg, advanced.state, advanced.belts, ticks, tickB);
|
||||||
ticks, tickB);
|
|
||||||
|
|
||||||
const Building* minerA = findBuilding(quiet.state, a);
|
const Building* minerA = findBuilding(quiet.state, a);
|
||||||
const Building* minerB = findBuilding(advanced.state, b);
|
const Building* minerB = findBuilding(advanced.state, b);
|
||||||
@@ -1055,7 +1058,7 @@ TEST_CASE("BuildingSystem: a group with a locked item is never picked", "[buildi
|
|||||||
Tick tick = 0;
|
Tick tick = 0;
|
||||||
const BuildingId id = f.bs.place(f.state, BuildingType::ReprocessingPlant,
|
const BuildingId id = f.bs.place(f.state, BuildingType::ReprocessingPlant,
|
||||||
QPoint(0, 0), Rotation::East, 0).value();
|
QPoint(0, 0), Rotation::East, 0).value();
|
||||||
runTicks(f.bs, f.cfg, f.state, f.belts, f.stock,
|
runTicks(f.bs, f.cfg, f.state, f.belts,
|
||||||
static_cast<int>(secondsToTicks(25.0)) + 1, tick);
|
static_cast<int>(secondsToTicks(25.0)) + 1, tick);
|
||||||
f.bs.setRecipe(f.state, id, "reprocessing_cycle");
|
f.bs.setRecipe(f.state, id, "reprocessing_cycle");
|
||||||
|
|
||||||
@@ -1071,7 +1074,7 @@ TEST_CASE("BuildingSystem: a group with a locked item is never picked", "[buildi
|
|||||||
building.outputBuffer.items.clear();
|
building.outputBuffer.items.clear();
|
||||||
for (std::vector<BeltItemSlot>& lane : building.emergingItems) { lane.clear(); }
|
for (std::vector<BeltItemSlot>& lane : building.emergingItems) { lane.clear(); }
|
||||||
});
|
});
|
||||||
runTicks(f.bs, f.cfg, f.state, f.belts, f.stock,
|
runTicks(f.bs, f.cfg, f.state, f.belts,
|
||||||
static_cast<int>(secondsToTicks(3.0)) + 1, tick);
|
static_cast<int>(secondsToTicks(3.0)) + 1, tick);
|
||||||
|
|
||||||
for (const Item& item : outputSideItems(*findBuilding(f.state, id)))
|
for (const Item& item : outputSideItems(*findBuilding(f.state, id)))
|
||||||
@@ -1093,7 +1096,7 @@ TEST_CASE("BuildingSystem: reprocessing plant sizes one output buffer per possib
|
|||||||
|
|
||||||
// Complete construction (25s).
|
// Complete construction (25s).
|
||||||
Tick tick = 0;
|
Tick tick = 0;
|
||||||
runTicks(f.bs, f.cfg, f.state, f.belts, f.stock, static_cast<int>(secondsToTicks(25.0)) + 1, tick);
|
runTicks(f.bs, f.cfg, f.state, f.belts, static_cast<int>(secondsToTicks(25.0)) + 1, tick);
|
||||||
|
|
||||||
// A plant holds no buffers until it has a recipe (REQ-BLD-AUTO-RECIPE); selecting
|
// A plant holds no buffers until it has a recipe (REQ-BLD-AUTO-RECIPE); selecting
|
||||||
// one sizes them, exactly as the first scrap offered to it would.
|
// one sizes them, exactly as the first scrap offered to it would.
|
||||||
@@ -1123,7 +1126,7 @@ TEST_CASE("BuildingSystem: one full output buffer stops the plant even when the
|
|||||||
const BuildingId id = f.bs.place(f.state, BuildingType::ReprocessingPlant,
|
const BuildingId id = f.bs.place(f.state, BuildingType::ReprocessingPlant,
|
||||||
QPoint(0, 0), Rotation::East, 0).value();
|
QPoint(0, 0), Rotation::East, 0).value();
|
||||||
Tick tick = 0;
|
Tick tick = 0;
|
||||||
runTicks(f.bs, f.cfg, f.state, f.belts, f.stock,
|
runTicks(f.bs, f.cfg, f.state, f.belts,
|
||||||
static_cast<int>(secondsToTicks(25.0)) + 1, tick);
|
static_cast<int>(secondsToTicks(25.0)) + 1, tick);
|
||||||
|
|
||||||
// Feed a full cycle's scrap (5) so only the output side can hold it back.
|
// Feed a full cycle's scrap (5) so only the output side can hold it back.
|
||||||
@@ -1145,7 +1148,7 @@ TEST_CASE("BuildingSystem: one full output buffer stops the plant even when the
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
runTicks(f.bs, f.cfg, f.state, f.belts, f.stock, 5, tick);
|
runTicks(f.bs, f.cfg, f.state, f.belts, 5, tick);
|
||||||
|
|
||||||
const Building* b = findBuilding(f.state, id);
|
const Building* b = findBuilding(f.state, id);
|
||||||
REQUIRE(b != nullptr);
|
REQUIRE(b != nullptr);
|
||||||
@@ -1170,7 +1173,7 @@ TEST_CASE("BuildingSystem: reprocessing plant runs a second cycle while holding
|
|||||||
const BuildingId id = f.bs.place(f.state, BuildingType::ReprocessingPlant,
|
const BuildingId id = f.bs.place(f.state, BuildingType::ReprocessingPlant,
|
||||||
QPoint(0, 0), Rotation::East, 0).value();
|
QPoint(0, 0), Rotation::East, 0).value();
|
||||||
Tick tick = 0;
|
Tick tick = 0;
|
||||||
runTicks(f.bs, f.cfg, f.state, f.belts, f.stock,
|
runTicks(f.bs, f.cfg, f.state, f.belts,
|
||||||
static_cast<int>(secondsToTicks(25.0)) + 1, tick);
|
static_cast<int>(secondsToTicks(25.0)) + 1, tick);
|
||||||
|
|
||||||
// Two cycles' worth of scrap (5 each), which is exactly the input cap.
|
// Two cycles' worth of scrap (5 each), which is exactly the input cap.
|
||||||
@@ -1185,7 +1188,7 @@ TEST_CASE("BuildingSystem: reprocessing plant runs a second cycle while holding
|
|||||||
|
|
||||||
// No belt carries the output away, so the first cycle's result is still held.
|
// No belt carries the output away, so the first cycle's result is still held.
|
||||||
// reprocessing_cycle runs 3s; run through the completion tick.
|
// reprocessing_cycle runs 3s; run through the completion tick.
|
||||||
runTicks(f.bs, f.cfg, f.state, f.belts, f.stock,
|
runTicks(f.bs, f.cfg, f.state, f.belts,
|
||||||
static_cast<int>(secondsToTicks(3.0)) + 1, tick);
|
static_cast<int>(secondsToTicks(3.0)) + 1, tick);
|
||||||
|
|
||||||
const Building* b = findBuilding(f.state, id);
|
const Building* b = findBuilding(f.state, id);
|
||||||
@@ -1206,7 +1209,7 @@ static BuildingId buildSmelter(PlacementFixture& f, QPoint anchor, Tick& tick)
|
|||||||
{
|
{
|
||||||
const BuildingId id =
|
const BuildingId id =
|
||||||
f.bs.place(f.state, BuildingType::Smelter, anchor, Rotation::East, 0).value();
|
f.bs.place(f.state, BuildingType::Smelter, anchor, Rotation::East, 0).value();
|
||||||
runTicks(f.bs, f.cfg, f.state, f.belts, f.stock,
|
runTicks(f.bs, f.cfg, f.state, f.belts,
|
||||||
static_cast<int>(secondsToTicks(15.0)) + 1, tick);
|
static_cast<int>(secondsToTicks(15.0)) + 1, tick);
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
@@ -1291,7 +1294,7 @@ TEST_CASE("BuildingSystem: selecting a different recipe frees a stuck auto-recip
|
|||||||
f.belts.tryPutItem(QPoint(2, 0), makeItem("iron_ore"));
|
f.belts.tryPutItem(QPoint(2, 0), makeItem("iron_ore"));
|
||||||
f.belts.tick();
|
f.belts.tick();
|
||||||
f.bs.tickBeltPull(f.state);
|
f.bs.tickBeltPull(f.state);
|
||||||
runTicks(f.bs, f.cfg, f.state, f.belts, f.stock, 30, tick);
|
runTicks(f.bs, f.cfg, f.state, f.belts, 30, tick);
|
||||||
|
|
||||||
const Building* stuck = findBuilding(f.state, id);
|
const Building* stuck = findBuilding(f.state, id);
|
||||||
REQUIRE(stuck->recipeId == "iron_ingot");
|
REQUIRE(stuck->recipeId == "iron_ingot");
|
||||||
@@ -1339,7 +1342,7 @@ TEST_CASE("BuildingSystem: reprocessing plant produces one cycle output then sta
|
|||||||
|
|
||||||
// Complete construction (25s).
|
// Complete construction (25s).
|
||||||
Tick tick = 0;
|
Tick tick = 0;
|
||||||
runTicks(f.bs, f.cfg, f.state, f.belts, f.stock, static_cast<int>(secondsToTicks(25.0)) + 1, tick);
|
runTicks(f.bs, f.cfg, f.state, f.belts, static_cast<int>(secondsToTicks(25.0)) + 1, tick);
|
||||||
|
|
||||||
// Feed 5 scrap into the building via a belt at an input port.
|
// Feed 5 scrap into the building via a belt at an input port.
|
||||||
// Reprocessing plant body (East rotation) = 3×3 at (0,0).
|
// Reprocessing plant body (East rotation) = 3×3 at (0,0).
|
||||||
@@ -1361,7 +1364,7 @@ TEST_CASE("BuildingSystem: reprocessing plant produces one cycle output then sta
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Run production cycle (3s = 90 ticks + 1 for the completion tick).
|
// Run production cycle (3s = 90 ticks + 1 for the completion tick).
|
||||||
runTicks(f.bs, f.cfg, f.state, f.belts, f.stock, static_cast<int>(secondsToTicks(3.0)) + 1, tick);
|
runTicks(f.bs, f.cfg, f.state, f.belts, static_cast<int>(secondsToTicks(3.0)) + 1, tick);
|
||||||
|
|
||||||
const Building* b = findBuilding(f.state, id);
|
const Building* b = findBuilding(f.state, id);
|
||||||
REQUIRE(b != nullptr);
|
REQUIRE(b != nullptr);
|
||||||
@@ -1405,7 +1408,7 @@ TEST_CASE("BuildingSystem: findRotateInPlaceTarget returns the building id for a
|
|||||||
const BuildingId id = f.bs.place(f.state, BuildingType::Belt, QPoint(0, 0), Rotation::East, 0).value();
|
const BuildingId id = f.bs.place(f.state, BuildingType::Belt, QPoint(0, 0), Rotation::East, 0).value();
|
||||||
|
|
||||||
Tick tick = 0;
|
Tick tick = 0;
|
||||||
runTicks(f.bs, f.cfg, f.state, f.belts, f.stock, static_cast<int>(secondsToTicks(1.0)) + 1, tick);
|
runTicks(f.bs, f.cfg, f.state, f.belts, static_cast<int>(secondsToTicks(1.0)) + 1, tick);
|
||||||
REQUIRE(getAllSites(f.state).empty());
|
REQUIRE(getAllSites(f.state).empty());
|
||||||
|
|
||||||
const std::optional<BuildingId> result =
|
const std::optional<BuildingId> result =
|
||||||
@@ -1805,7 +1808,7 @@ TEST_CASE("BuildingSystem: rotateInPlace updates rotation and output port direct
|
|||||||
const BuildingId id = f.bs.place(f.state, BuildingType::Belt, QPoint(0, 0), Rotation::East, 0).value();
|
const BuildingId id = f.bs.place(f.state, BuildingType::Belt, QPoint(0, 0), Rotation::East, 0).value();
|
||||||
|
|
||||||
Tick tick = 0;
|
Tick tick = 0;
|
||||||
runTicks(f.bs, f.cfg, f.state, f.belts, f.stock, static_cast<int>(secondsToTicks(1.0)) + 1, tick);
|
runTicks(f.bs, f.cfg, f.state, f.belts, static_cast<int>(secondsToTicks(1.0)) + 1, tick);
|
||||||
REQUIRE(findBuilding(f.state, id) != nullptr);
|
REQUIRE(findBuilding(f.state, id) != nullptr);
|
||||||
|
|
||||||
const Building& before = *findBuilding(f.state, id);
|
const Building& before = *findBuilding(f.state, id);
|
||||||
@@ -1826,7 +1829,7 @@ TEST_CASE("BuildingSystem: rotateInPlace re-registers a belt tile with BeltSyste
|
|||||||
const BuildingId id = f.bs.place(f.state, BuildingType::Belt, QPoint(0, 0), Rotation::East, 0).value();
|
const BuildingId id = f.bs.place(f.state, BuildingType::Belt, QPoint(0, 0), Rotation::East, 0).value();
|
||||||
|
|
||||||
Tick tick = 0;
|
Tick tick = 0;
|
||||||
runTicks(f.bs, f.cfg, f.state, f.belts, f.stock, static_cast<int>(secondsToTicks(1.0)) + 1, tick);
|
runTicks(f.bs, f.cfg, f.state, f.belts, static_cast<int>(secondsToTicks(1.0)) + 1, tick);
|
||||||
|
|
||||||
f.bs.rotateInPlace(f.state, id, Rotation::North);
|
f.bs.rotateInPlace(f.state, id, Rotation::North);
|
||||||
|
|
||||||
@@ -1846,7 +1849,7 @@ TEST_CASE("BuildingSystem: rotateInPlace preserves the output filters of a split
|
|||||||
Tick tick = 0;
|
Tick tick = 0;
|
||||||
while (getAllBuildings(f.state).empty() && tick < 100000)
|
while (getAllBuildings(f.state).empty() && tick < 100000)
|
||||||
{
|
{
|
||||||
runTicks(f.bs, f.cfg, f.state, f.belts, f.stock, 1, tick);
|
runTicks(f.bs, f.cfg, f.state, f.belts, 1, tick);
|
||||||
}
|
}
|
||||||
REQUIRE(getAllBuildings(f.state).size() == 1);
|
REQUIRE(getAllBuildings(f.state).size() == 1);
|
||||||
|
|
||||||
@@ -1891,7 +1894,7 @@ TEST_CASE("BuildingSystem: splitter filters configured on a construction site ca
|
|||||||
Tick tick = 0;
|
Tick tick = 0;
|
||||||
while (getAllBuildings(f.state).empty() && tick < 100000)
|
while (getAllBuildings(f.state).empty() && tick < 100000)
|
||||||
{
|
{
|
||||||
runTicks(f.bs, f.cfg, f.state, f.belts, f.stock, 1, tick);
|
runTicks(f.bs, f.cfg, f.state, f.belts, 1, tick);
|
||||||
}
|
}
|
||||||
REQUIRE(getAllBuildings(f.state).size() == 1);
|
REQUIRE(getAllBuildings(f.state).size() == 1);
|
||||||
REQUIRE(getAllBuildings(f.state)[0].type == BuildingType::Splitter);
|
REQUIRE(getAllBuildings(f.state)[0].type == BuildingType::Splitter);
|
||||||
@@ -2199,11 +2202,11 @@ namespace
|
|||||||
// Advances the sim until the given site becomes an operational building, or a
|
// Advances the sim until the given site becomes an operational building, or a
|
||||||
// safety cap is reached.
|
// safety cap is reached.
|
||||||
void buildToCompletion(BuildingSystem& bs, const GameConfig& cfg, FactoryState& state,
|
void buildToCompletion(BuildingSystem& bs, const GameConfig& cfg, FactoryState& state,
|
||||||
BeltSystem& belts, int& stock, BuildingId id, Tick& tick)
|
BeltSystem& belts, BuildingId id, Tick& tick)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < 20000 && findBuilding(state, id) == nullptr; ++i)
|
for (int i = 0; i < 20000 && findBuilding(state, id) == nullptr; ++i)
|
||||||
{
|
{
|
||||||
runTicks(bs, cfg, state, belts, stock, 1, tick);
|
runTicks(bs, cfg, state, belts, 1, tick);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2238,7 +2241,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 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);
|
const std::vector<Port> sitePorts = getInputPorts(f.state, f.cfg, id);
|
||||||
buildToCompletion(f.bs, f.cfg, f.state, f.belts, f.stock, id, tick);
|
buildToCompletion(f.bs, f.cfg, f.state, f.belts, id, tick);
|
||||||
REQUIRE(findBuilding(f.state, id) != nullptr);
|
REQUIRE(findBuilding(f.state, id) != nullptr);
|
||||||
const std::vector<Port> builtPorts = getInputPorts(f.state, f.cfg, id);
|
const std::vector<Port> builtPorts = getInputPorts(f.state, f.cfg, id);
|
||||||
|
|
||||||
|
|||||||
@@ -69,7 +69,6 @@ struct CombatFixture
|
|||||||
, ships(cfg, admin)
|
, ships(cfg, admin)
|
||||||
, buildings(cfg, belts,
|
, buildings(cfg, belts,
|
||||||
[this]() { return nextBuildingId++; },
|
[this]() { return nextBuildingId++; },
|
||||||
[](int){},
|
|
||||||
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
|
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
|
||||||
[](const std::string&) -> bool { return true; },
|
[](const std::string&) -> bool { return true; },
|
||||||
rng)
|
rng)
|
||||||
|
|||||||
Reference in New Issue
Block a user