2 Commits

Author SHA1 Message Date
2522a8c974 move FactoryState ownership out of BuildingSystem to Simulation
Simulation (and ArenaSimulation in the balancing tool) now owns the factory's
world data; BuildingSystem holds a reference to it. This is what lets the systems
that operate on the data be handed the same state — phase 3's construction and
deconstruction systems, and later the ecs/system/ classes that today take a
BuildingSystem& only to query it.

reset() clears the state alongside m_admin and m_beltSystem, matching how the
subsystems were already rebuilt from scratch.

Falls short of the tick-argument form I sketched: BuildingSystem still reaches
the data through a member reference rather than a parameter. Making it truly
stateless means the const query surface has to find the data some other way, and
that surface is large — findBuilding alone has 64 call sites, with findSite,
getAllBuildings, getAllSites, isTileOccupied and the rest behind it. Doing that
needs a queries facade behind Simulation::getBuildings() so the callers do not
all move, which is its own decision rather than a side effect of this one.

The constructor gains a parameter, so the four owners and the three test fixtures
that build a BuildingSystem directly are updated; the 33 files that only use one
are untouched.

Verified with a golden-checksum capture before and after — all four sample ticks
identical.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YHcUerKAZKWNvSKJxYKbnG
2026-08-04 20:55:32 +02:00
e39b81eb22 gather the factory's world data into FactoryState
BuildingSystem is the one system in the codebase that owns the world data it
operates on. The ecs/system/ classes already do the opposite — AiSystem and
SalvagerSystem hold config and their own scratch, and take EntityAdmin and the
other systems as tick arguments — so this is bringing the outlier in line, not
inventing a pattern.

Phase 1 of that: the buildings vector, both work queues and the tile grid move
into a FactoryState struct, still owned by BuildingSystem. Every method reaches
through m_state. The public API is untouched, so none of the 33 files that
reference BuildingSystem needed a change.

DeconstructionEntry moves out of BuildingSystem's private section into
FactoryState.h, since the queue that holds it lives there now.

m_asteroidWidth_tiles stays on the system: it is not checksummed and is a cached
placement bound derived from config and the expansion count, not factory data.

The intent is for Simulation to own FactoryState and pass it into the tick
methods, leaving the systems stateless over it. FactoryState.h notes explicitly
that this is a data/behaviour split and not a step toward putting buildings in
the entity model, which architecture.md rules out.

Verified with a golden-checksum capture before and after — all four sample
ticks identical.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YHcUerKAZKWNvSKJxYKbnG
2026-08-04 20:39:34 +02:00
11 changed files with 219 additions and 134 deletions

View File

@@ -48,6 +48,7 @@ ArenaSimulation::ArenaSimulation(const GameConfig& gameConfig,
{
m_buildingSystem = std::make_unique<BuildingSystem>(
m_gameConfig,
m_factoryState,
m_beltSystem,
[this]() { return allocateBuildingId(); },
[](int) {},

View File

@@ -10,6 +10,7 @@
#include "BalancingConfig.h"
#include "BeltSystem.h"
#include "FactoryState.h"
#include "EntityAdmin.h"
#include "BuildingId.h"
@@ -107,6 +108,7 @@ private:
BuildingId m_nextBuildingId;
EntityAdmin m_admin;
FactoryState m_factoryState;
BeltSystem m_beltSystem;
std::unique_ptr<BuildingSystem> m_buildingSystem;
std::unique_ptr<ShipSystem> m_shipSystem;

View File

@@ -23,6 +23,7 @@ bool inputLaneEntryFree(const std::vector<BeltItemSlot>& lane)
} // namespace
BuildingSystem::BuildingSystem(const GameConfig& config,
FactoryState& state,
BeltSystem& belts,
std::function<BuildingId()> allocateBuildingId,
std::function<void(int)> addBuildingBlocks,
@@ -31,6 +32,7 @@ BuildingSystem::BuildingSystem(const GameConfig& config,
std::function<bool(const std::string&)> isItemUnlocked,
std::mt19937& rng)
: m_config(config)
, m_state(state)
, m_belts(belts)
, m_allocateBuildingId(std::move(allocateBuildingId))
, m_addBuildingBlocks(std::move(addBuildingBlocks))
@@ -308,7 +310,7 @@ std::optional<BuildingId> BuildingSystem::place(BuildingType type, QPoint anchor
for (const QPoint& cell : mask.bodyCells)
{
const QPoint absCell = anchor + cell;
m_grid.occupy(absCell, id);
m_state.grid.occupy(absCell, id);
}
// Build construction site.
@@ -323,13 +325,13 @@ std::optional<BuildingId> BuildingSystem::place(BuildingType type, QPoint anchor
site.bodyCells.push_back(anchor + cell);
}
if (m_constructionQueue.empty())
if (m_state.constructionQueue.empty())
{
site.completesAt = currentTick + secondsToTicks(def->constructionTimeSeconds);
}
// else: completesAt remains 0 (queued, not yet started).
m_constructionQueue.push_back(std::move(site));
m_state.constructionQueue.push_back(std::move(site));
return id;
}
@@ -405,15 +407,15 @@ int BuildingSystem::deconstruct(BuildingId id, Tick currentTick)
{
// Construction site? Removed instantly with the full refund; never queued
// for deconstruction (REQ-BLD-DECONSTRUCT).
for (std::deque<ConstructionSite>::iterator it = m_constructionQueue.begin();
it != m_constructionQueue.end();
for (std::deque<ConstructionSite>::iterator it = m_state.constructionQueue.begin();
it != m_state.constructionQueue.end();
++it)
{
if (it->id == id)
{
const BuildingDef* def = m_config.buildings.findBuildingDef(it->type);
m_grid.release(it->bodyCells);
m_constructionQueue.erase(it);
m_state.grid.release(it->bodyCells);
m_state.constructionQueue.erase(it);
if (def)
{
return def->cost;
@@ -425,7 +427,7 @@ int BuildingSystem::deconstruct(BuildingId id, Tick currentTick)
// Operational building? Append it to the deconstruction queue rather than
// removing it now; the partial refund is credited on completion in
// tickDeconstruction (REQ-BLD-DECON-QUEUE).
for (Building& building : m_buildings)
for (Building& building : m_state.buildings)
{
if (building.id != id) { continue; }
if (building.queuedForDeconstruction) { return 0; } // already queued
@@ -453,8 +455,8 @@ int BuildingSystem::deconstruct(BuildingId id, Tick currentTick)
m_belts.removeTile(building.anchor);
}
const bool wasEmpty = m_deconstructionQueue.empty();
m_deconstructionQueue.push_back(std::move(entry));
const bool wasEmpty = m_state.deconstructionQueue.empty();
m_state.deconstructionQueue.push_back(std::move(entry));
if (wasEmpty)
{
startFrontDeconstruction(currentTick);
@@ -467,8 +469,8 @@ int BuildingSystem::deconstruct(BuildingId id, Tick currentTick)
void BuildingSystem::startFrontDeconstruction(Tick currentTick)
{
if (m_deconstructionQueue.empty()) { return; }
DeconstructionEntry& front = m_deconstructionQueue.front();
if (m_state.deconstructionQueue.empty()) { return; }
DeconstructionEntry& front = m_state.deconstructionQueue.front();
if (front.completesAt == 0)
{
front.completesAt =
@@ -483,7 +485,7 @@ void BuildingSystem::startFrontDeconstruction(Tick currentTick)
void BuildingSystem::setRecipe(BuildingId id, const std::string& recipeId)
{
// Construction site: store recipe for when building completes.
for (ConstructionSite& site : m_constructionQueue)
for (ConstructionSite& site : m_state.constructionQueue)
{
if (site.id == id)
{
@@ -506,7 +508,7 @@ void BuildingSystem::setRecipe(BuildingId id, const std::string& recipeId)
}
// Operational building: clear buffers and re-init.
for (Building& building : m_buildings)
for (Building& building : m_state.buildings)
{
if (building.id == id)
{
@@ -558,7 +560,7 @@ void BuildingSystem::setRecipe(BuildingId id, const std::string& recipeId)
void BuildingSystem::setShipLayout(BuildingId id, const ShipLayoutConfig& layout)
{
for (ConstructionSite& site : m_constructionQueue)
for (ConstructionSite& site : m_state.constructionQueue)
{
if (site.id == id)
{
@@ -567,7 +569,7 @@ void BuildingSystem::setShipLayout(BuildingId id, const ShipLayoutConfig& layout
}
}
for (Building& building : m_buildings)
for (Building& building : m_state.buildings)
{
if (building.id == id)
{
@@ -594,7 +596,7 @@ void BuildingSystem::setShipLayout(BuildingId id, const ShipLayoutConfig& layout
std::optional<BeltSystem::SplitterInfo>
BuildingSystem::getSiteSplitterInfo(BuildingId id) const
{
for (const ConstructionSite& site : m_constructionQueue)
for (const ConstructionSite& site : m_state.constructionQueue)
{
if (site.id != id) { continue; }
if (site.type != BuildingType::Splitter) { return std::nullopt; }
@@ -618,7 +620,7 @@ void BuildingSystem::setSiteSplitterFilters(BuildingId id,
const std::vector<ItemType>& filterA,
const std::vector<ItemType>& filterB)
{
for (ConstructionSite& site : m_constructionQueue)
for (ConstructionSite& site : m_state.constructionQueue)
{
if (site.id == id && site.type == BuildingType::Splitter)
{
@@ -636,12 +638,12 @@ void BuildingSystem::setSiteSplitterFilters(BuildingId id,
void BuildingSystem::tickConstruction(Tick currentTick)
{
TRACE();
if (m_constructionQueue.empty())
if (m_state.constructionQueue.empty())
{
return;
}
ConstructionSite& front = m_constructionQueue.front();
ConstructionSite& front = m_state.constructionQueue.front();
// Guard: if somehow the front site was never started, start it now.
if (front.completesAt == 0)
@@ -719,18 +721,18 @@ void BuildingSystem::tickConstruction(Tick currentTick)
// filters configured while under construction carry over (REQ-BLD-SITE-CONFIG).
reregisterBeltTile(building, front.splitterFilterA, front.splitterFilterB);
m_buildings.push_back(std::move(building));
m_state.buildings.push_back(std::move(building));
m_constructionQueue.pop_front();
m_state.constructionQueue.pop_front();
// Start next queued site if present.
if (!m_constructionQueue.empty() && m_constructionQueue.front().completesAt == 0)
if (!m_state.constructionQueue.empty() && m_state.constructionQueue.front().completesAt == 0)
{
const BuildingDef* nextDef =
m_config.buildings.findBuildingDef(m_constructionQueue.front().type);
m_config.buildings.findBuildingDef(m_state.constructionQueue.front().type);
if (nextDef)
{
m_constructionQueue.front().completesAt =
m_state.constructionQueue.front().completesAt =
currentTick + secondsToTicks(nextDef->constructionTimeSeconds);
}
}
@@ -767,12 +769,12 @@ void BuildingSystem::reregisterBeltTile(const Building& building,
void BuildingSystem::tickDeconstruction(Tick currentTick)
{
TRACE();
if (m_deconstructionQueue.empty())
if (m_state.deconstructionQueue.empty())
{
return;
}
DeconstructionEntry& front = m_deconstructionQueue.front();
DeconstructionEntry& front = m_state.deconstructionQueue.front();
// Guard: if the front entry's timer was never started, start it now.
if (front.completesAt == 0)
@@ -789,15 +791,15 @@ void BuildingSystem::tickDeconstruction(Tick currentTick)
// Remove the building from the world and credit its refund (REQ-BLD-DECONSTRUCT).
// Belt/tunnel/splitter tiles were already unregistered when the building was
// queued (see deconstruct), so only tile occupancy and the record remain.
for (std::vector<Building>::iterator it = m_buildings.begin();
it != m_buildings.end();
for (std::vector<Building>::iterator it = m_state.buildings.begin();
it != m_state.buildings.end();
++it)
{
if (it->id != front.id) { continue; }
const BuildingDef* def = m_config.buildings.findBuildingDef(it->type);
m_grid.release(it->bodyCells);
m_buildings.erase(it);
m_state.grid.release(it->bodyCells);
m_state.buildings.erase(it);
if (def)
{
m_addBuildingBlocks(def->cost * m_config.world.refundPercentage / 100);
@@ -805,7 +807,7 @@ void BuildingSystem::tickDeconstruction(Tick currentTick)
break;
}
m_deconstructionQueue.pop_front();
m_state.deconstructionQueue.pop_front();
// Start the next queued deconstruction, if any.
startFrontDeconstruction(currentTick);
@@ -813,8 +815,8 @@ void BuildingSystem::tickDeconstruction(Tick currentTick)
void BuildingSystem::cancelDeconstruction(BuildingId id)
{
for (std::deque<DeconstructionEntry>::iterator it = m_deconstructionQueue.begin();
it != m_deconstructionQueue.end();
for (std::deque<DeconstructionEntry>::iterator it = m_state.deconstructionQueue.begin();
it != m_state.deconstructionQueue.end();
++it)
{
if (it->id != id) { continue; }
@@ -828,7 +830,7 @@ void BuildingSystem::cancelDeconstruction(BuildingId id)
reregisterBeltTile(*building, it->splitterFilterA, it->splitterFilterB);
}
m_deconstructionQueue.erase(it);
m_state.deconstructionQueue.erase(it);
// If the running front was removed, the new front (completesAt == 0) has
// its timer started by the next tickDeconstruction guard.
return;
@@ -848,7 +850,7 @@ void BuildingSystem::tickBeltPull()
// (REQ-GW-BELT-SPEED, REQ-MAT-INPUT-INTAKE).
const double progressPerTick = m_belts.getProgressPerTick_tpt();
for (Building& building : m_buildings)
for (Building& building : m_state.buildings)
{
// A building queued for deconstruction stops operating (REQ-BLD-DECON-QUEUE).
if (building.queuedForDeconstruction) { continue; }
@@ -932,7 +934,7 @@ bool BuildingSystem::tryDirectCoupleDeposit(BuildingId producerId,
const Port& outputPort,
const Item& item)
{
const std::optional<BuildingId> ownerId = m_grid.findOwner(outputPort.tile);
const std::optional<BuildingId> ownerId = m_state.grid.findOwner(outputPort.tile);
if (!ownerId.has_value() || *ownerId == producerId)
{
return false;
@@ -966,7 +968,7 @@ bool BuildingSystem::tryDirectCoupleDeposit(BuildingId producerId,
void BuildingSystem::tickProduction(Tick currentTick)
{
TRACE();
for (Building& building : m_buildings)
for (Building& building : m_state.buildings)
{
// A building queued for deconstruction stops operating (REQ-BLD-DECON-QUEUE).
if (building.queuedForDeconstruction) { continue; }
@@ -1069,7 +1071,7 @@ void BuildingSystem::tickProduction(Tick currentTick)
void BuildingSystem::tickShipyardProduction(Tick currentTick)
{
TRACE();
for (Building& building : m_buildings)
for (Building& building : m_state.buildings)
{
// A building queued for deconstruction stops operating (REQ-BLD-DECON-QUEUE).
if (building.queuedForDeconstruction) { continue; }
@@ -1169,7 +1171,7 @@ void BuildingSystem::tickOutputBelts()
// same speed as real belts (REQ-GW-BELT-SPEED, REQ-MAT-OUTPUT-EMERGE).
const double progressPerTick = m_belts.getProgressPerTick_tpt();
for (Building& building : m_buildings)
for (Building& building : m_state.buildings)
{
// A building queued for deconstruction stops operating (REQ-BLD-DECON-QUEUE).
if (building.queuedForDeconstruction) { continue; }
@@ -1215,7 +1217,7 @@ void BuildingSystem::tickOutputBelts()
void BuildingSystem::forEachEmergingItem(
const std::function<void(const ItemType&, QPointF)>& visit) const
{
for (const Building& building : m_buildings)
for (const Building& building : m_state.buildings)
{
for (std::size_t p = 0; p < building.outputPorts.size(); ++p)
{
@@ -1237,7 +1239,7 @@ void BuildingSystem::forEachEmergingItem(
void BuildingSystem::forEachIncomingItem(
const std::function<void(const ItemType&, QPointF)>& visit) const
{
for (const Building& building : m_buildings)
for (const Building& building : m_state.buildings)
{
for (std::size_t p = 0; p < building.inputPorts.size(); ++p)
{
@@ -1262,7 +1264,7 @@ void BuildingSystem::forEachIncomingItem(
const Building* BuildingSystem::findBuilding(BuildingId id) const
{
for (const Building& building : m_buildings)
for (const Building& building : m_state.buildings)
{
if (building.id == id)
{
@@ -1274,7 +1276,7 @@ const Building* BuildingSystem::findBuilding(BuildingId id) const
Building* BuildingSystem::findBuildingMutable(BuildingId id)
{
for (Building& building : m_buildings)
for (Building& building : m_state.buildings)
{
if (building.id == id)
{
@@ -1286,7 +1288,7 @@ Building* BuildingSystem::findBuildingMutable(BuildingId id)
const ConstructionSite* BuildingSystem::findSite(BuildingId id) const
{
for (const ConstructionSite& site : m_constructionQueue)
for (const ConstructionSite& site : m_state.constructionQueue)
{
if (site.id == id)
{
@@ -1298,13 +1300,13 @@ const ConstructionSite* BuildingSystem::findSite(BuildingId id) const
std::vector<Building> BuildingSystem::getAllBuildings() const
{
return m_buildings;
return m_state.buildings;
}
std::vector<ConstructionSite> BuildingSystem::getAllSites() const
{
return std::vector<ConstructionSite>(m_constructionQueue.begin(),
m_constructionQueue.end());
return std::vector<ConstructionSite>(m_state.constructionQueue.begin(),
m_state.constructionQueue.end());
}
namespace
@@ -1328,7 +1330,7 @@ bool isProductionBuildingType(BuildingType type)
int BuildingSystem::getProductionBuildingCount() const
{
int count = 0;
for (const Building& b : m_buildings)
for (const Building& b : m_state.buildings)
{
if (isProductionBuildingType(b.type)) { ++count; }
}
@@ -1338,7 +1340,7 @@ int BuildingSystem::getProductionBuildingCount() const
int BuildingSystem::getActiveProductionBuildingCount() const
{
int count = 0;
for (const Building& b : m_buildings)
for (const Building& b : m_state.buildings)
{
if (isProductionBuildingType(b.type) && b.production.has_value()) { ++count; }
}
@@ -1489,7 +1491,7 @@ BuildingSystem::getProductionStatus(const Building& building) const
std::vector<BuildingSystem::BeltTileInfo> BuildingSystem::getAllBeltTiles() const
{
std::vector<BeltTileInfo> result;
for (const Building& b : m_buildings)
for (const Building& b : m_state.buildings)
{
if (b.type != BuildingType::Belt && b.type != BuildingType::Splitter)
{
@@ -1520,7 +1522,7 @@ std::vector<BuildingSystem::BeltTileInfo> BuildingSystem::getAllBeltTiles() cons
bool BuildingSystem::isTileOccupied(QPoint tile) const
{
return m_grid.isOccupied(tile);
return m_state.grid.isOccupied(tile);
}
std::optional<BuildingId> BuildingSystem::findRotateInPlaceTarget(
@@ -1541,13 +1543,13 @@ std::optional<BuildingId> BuildingSystem::findRotateInPlaceTarget(
// All body cells must be occupied by the same entity.
const QPoint firstAbs = anchor + mask.bodyCells[0];
const std::optional<BuildingId> firstOwner = m_grid.findOwner(firstAbs);
const std::optional<BuildingId> firstOwner = m_state.grid.findOwner(firstAbs);
if (!firstOwner.has_value()) { return std::nullopt; }
const BuildingId candidateId = *firstOwner;
for (const QPoint& rel : mask.bodyCells)
{
const std::optional<BuildingId> owner = m_grid.findOwner(anchor + rel);
const std::optional<BuildingId> owner = m_state.grid.findOwner(anchor + rel);
if (!owner.has_value() || *owner != candidateId)
{
return std::nullopt;
@@ -1555,14 +1557,14 @@ std::optional<BuildingId> BuildingSystem::findRotateInPlaceTarget(
}
// Verify the candidate is the same building type with the same cell count.
for (const ConstructionSite& site : m_constructionQueue)
for (const ConstructionSite& site : m_state.constructionQueue)
{
if (site.id != candidateId) { continue; }
if (site.type != type) { return std::nullopt; }
if (site.bodyCells.size() != mask.bodyCells.size()) { return std::nullopt; }
return candidateId;
}
for (const Building& b : m_buildings)
for (const Building& b : m_state.buildings)
{
if (b.id != candidateId) { continue; }
if (b.type != type) { return std::nullopt; }
@@ -1576,7 +1578,7 @@ std::optional<BuildingId> BuildingSystem::findRotateInPlaceTarget(
void BuildingSystem::rotateInPlace(BuildingId id, Rotation newRotation)
{
// Construction site path — just update rotation; no ports to recompute.
for (ConstructionSite& site : m_constructionQueue)
for (ConstructionSite& site : m_state.constructionQueue)
{
if (site.id == id)
{
@@ -1586,7 +1588,7 @@ void BuildingSystem::rotateInPlace(BuildingId id, Rotation newRotation)
}
// Operational building path.
for (Building& b : m_buildings)
for (Building& b : m_state.buildings)
{
if (b.id != id) { continue; }
@@ -1643,7 +1645,7 @@ const Building* BuildingSystem::findNearestBuilding(QVector2D worldPos,
{
const Building* best = nullptr;
float bestDist = std::numeric_limits<float>::max();
for (const Building& b : m_buildings)
for (const Building& b : m_state.buildings)
{
if (b.type != type)
{
@@ -1664,7 +1666,7 @@ const Building* BuildingSystem::findNearestBuilding(QVector2D worldPos,
bool BuildingSystem::deliverScrapToSalvageBay(BuildingId bayId)
{
Building* bay = nullptr;
for (Building& b : m_buildings)
for (Building& b : m_state.buildings)
{
if (b.id == bayId)
{
@@ -1708,7 +1710,7 @@ BuildingId BuildingSystem::placeImmediate(BuildingType type,
{
const QPoint absCell = anchor + cell;
building.bodyCells.push_back(absCell);
m_grid.occupy(absCell, id);
m_state.grid.occupy(absCell, id);
}
for (const Port& port : mask.outputPorts)
{
@@ -1726,14 +1728,14 @@ BuildingId BuildingSystem::placeImmediate(BuildingType type,
initSalvageBayBuffer(building);
}
m_buildings.push_back(std::move(building));
m_state.buildings.push_back(std::move(building));
return id;
}
bool BuildingSystem::removeBuilding(BuildingId id)
{
for (std::vector<Building>::iterator it = m_buildings.begin();
it != m_buildings.end();
for (std::vector<Building>::iterator it = m_state.buildings.begin();
it != m_state.buildings.end();
++it)
{
if (it->id == id)
@@ -1743,8 +1745,8 @@ bool BuildingSystem::removeBuilding(BuildingId id)
{
m_belts.removeTile(it->anchor);
}
m_grid.release(it->bodyCells);
m_buildings.erase(it);
m_state.grid.release(it->bodyCells);
m_state.buildings.erase(it);
return true;
}
}
@@ -1753,7 +1755,7 @@ bool BuildingSystem::removeBuilding(BuildingId id)
void BuildingSystem::forEachBuilding(std::function<void(Building&)> fn)
{
for (Building& b : m_buildings)
for (Building& b : m_state.buildings)
{
fn(b);
}
@@ -1762,12 +1764,12 @@ void BuildingSystem::forEachBuilding(std::function<void(Building&)> fn)
void BuildingSystem::registerTileOccupancy(const std::vector<QPoint>& cells,
BuildingId ownerPlaceholder)
{
m_grid.occupy(cells, ownerPlaceholder);
m_state.grid.occupy(cells, ownerPlaceholder);
}
void BuildingSystem::unregisterTileOccupancy(const std::vector<QPoint>& cells)
{
m_grid.release(cells);
m_state.grid.release(cells);
}
namespace
@@ -1801,10 +1803,10 @@ void appendInputBuffer(Hasher& hasher, const InputBuffer& buffer)
void BuildingSystem::appendChecksum(Hasher& hasher) const
{
// m_buildings keeps a stable, deterministic order (append on build, swap-free
// m_state.buildings keeps a stable, deterministic order (append on build, swap-free
// erase aside — both runs perform identical operations, so order matches).
hasher.append(m_buildings.size());
for (const Building& b : m_buildings)
hasher.append(m_state.buildings.size());
for (const Building& b : m_state.buildings)
{
hasher.append(b.id);
hasher.append(b.anchor);
@@ -1847,8 +1849,8 @@ void BuildingSystem::appendChecksum(Hasher& hasher) const
hasher.append(b.queuedForDeconstruction);
}
hasher.append(m_constructionQueue.size());
for (const ConstructionSite& s : m_constructionQueue)
hasher.append(m_state.constructionQueue.size());
for (const ConstructionSite& s : m_state.constructionQueue)
{
hasher.append(s.id);
hasher.append(s.anchor);
@@ -1865,8 +1867,8 @@ void BuildingSystem::appendChecksum(Hasher& hasher) const
for (const ItemType& type : s.splitterFilterB) { hasher.append(type.id); }
}
hasher.append(m_deconstructionQueue.size());
for (const DeconstructionEntry& e : m_deconstructionQueue)
hasher.append(m_state.deconstructionQueue.size());
for (const DeconstructionEntry& e : m_state.deconstructionQueue)
{
hasher.append(e.id);
hasher.append(e.completesAt);
@@ -1876,5 +1878,5 @@ void BuildingSystem::appendChecksum(Hasher& hasher) const
for (const ItemType& type : e.splitterFilterB) { hasher.append(type.id); }
}
m_grid.appendChecksum(hasher);
m_state.grid.appendChecksum(hasher);
}

View File

@@ -15,7 +15,7 @@
#include "BeltSystem.h"
#include "Building.h"
#include "BuildingGrid.h"
#include "FactoryState.h"
#include "BuildingType.h"
#include "BuildingId.h"
#include "GameConfig.h"
@@ -47,6 +47,7 @@ class BuildingSystem
{
public:
BuildingSystem(const GameConfig& config,
FactoryState& state,
BeltSystem& belts,
std::function<BuildingId()> allocateBuildingId,
std::function<void(int)> addBuildingBlocks,
@@ -285,6 +286,11 @@ private:
QPoint anchor) const;
const GameConfig& m_config;
// The factory's world data — buildings, queued work, tile ownership. Owned by
// Simulation, not by this system (see FactoryState.h).
FactoryState& m_state;
BeltSystem& m_belts;
std::function<BuildingId()> m_allocateBuildingId;
std::function<void(int)> m_addBuildingBlocks;
@@ -293,24 +299,4 @@ private:
std::function<bool(const std::string&)> m_isItemUnlocked;
std::mt19937& m_rng;
int m_asteroidWidth_tiles;
std::vector<Building> m_buildings;
std::deque<ConstructionSite> m_constructionQueue;
// One pending demolition of a fully-built building (REQ-BLD-DECON-QUEUE).
// completesAt == 0 means "queued but its timer has not started yet"
// (mirrors ConstructionSite). For a Splitter, the filters it had are captured
// here so cancelDeconstruction can restore them on re-registration.
struct DeconstructionEntry
{
BuildingId id = kInvalidBuildingId;
Tick completesAt = 0;
std::vector<ItemType> splitterFilterA;
std::vector<ItemType> splitterFilterB;
};
std::deque<DeconstructionEntry> m_deconstructionQueue;
// The authority on which building owns which tile; every placement and removal
// path claims and releases its body cells here.
BuildingGrid m_grid;
};

View File

@@ -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}/FactoryState.h
${CMAKE_CURRENT_SOURCE_DIR}/BuildingSystem.h
${CMAKE_CURRENT_SOURCE_DIR}/EntityHitTest.h
${CMAKE_CURRENT_SOURCE_DIR}/ShipLayout.h

View File

@@ -0,0 +1,48 @@
#pragma once
#include <deque>
#include <vector>
#include "Building.h"
#include "BuildingGrid.h"
#include "BuildingId.h"
#include "ItemType.h"
#include "Tick.h"
// One pending demolition of a fully-built building (REQ-BLD-DECON-QUEUE).
// completesAt == 0 means "queued but its timer has not started yet"
// (mirrors ConstructionSite). For a Splitter, the filters it had are captured
// here so cancelDeconstruction can restore them on re-registration.
struct DeconstructionEntry
{
BuildingId id = kInvalidBuildingId;
Tick completesAt = 0;
std::vector<ItemType> splitterFilterA;
std::vector<ItemType> splitterFilterB;
};
// The factory's world data: every building, the work queued on them, and the
// tile ownership index. This is the buildings-side counterpart to EntityAdmin —
// data with no behaviour of its own beyond what BuildingGrid encapsulates.
//
// Buildings deliberately stay a plain vector rather than becoming EnTT entities
// (see docs/architecture.md). Separating this data from the systems that operate
// on it is not a step toward putting them in the entity model; it is the same
// data/behaviour split the ecs/system/ classes already follow, where world data
// arrives as a tick argument instead of being owned by the system.
//
// Owned by Simulation (and by ArenaSimulation in the balancing tool), not by the
// systems that operate on it. BuildingSystem holds a reference. The remaining step
// is to pass this into the tick methods instead, so the systems become stateless
// over it — that one is gated on the query surface, which today reaches the data
// through BuildingSystem's ~180 const call sites.
struct FactoryState
{
std::vector<Building> buildings;
std::deque<ConstructionSite> constructionQueue;
std::deque<DeconstructionEntry> deconstructionQueue;
// The authority on which building owns which tile; every placement and removal
// path claims and releases its body cells here.
BuildingGrid grid;
};

View File

@@ -94,6 +94,7 @@ void Simulation::reset(unsigned int seed)
m_pendingSchematicChoices.clear();
m_admin.clear();
m_factoryState = FactoryState{};
m_beltSystem = BeltSystem(m_config.world.beltSpeed_tps);
initializeSubsystems();
@@ -105,6 +106,7 @@ void Simulation::initializeSubsystems()
{
m_buildingSystem = std::make_unique<BuildingSystem>(
m_config,
m_factoryState,
m_beltSystem,
[this]() { return allocateBuildingId(); },
[this](int amount) { m_buildingBlocksStock += amount; },

View File

@@ -9,6 +9,7 @@
#include <QPoint>
#include "BeltSystem.h"
#include "FactoryState.h"
#include "EntityAdmin.h"
#include "entt/entity/entity.hpp"
#include "SchematicChoiceOption.h"
@@ -209,6 +210,9 @@ private:
UnlockState m_unlockState;
EntityAdmin m_admin;
// The factory's world data. Owned here, not by BuildingSystem, so the systems
// that operate on it can be handed the same state (see FactoryState.h).
FactoryState m_factoryState;
BeltSystem m_beltSystem;
std::unique_ptr<BuildingSystem> m_buildingSystem;
std::unique_ptr<ShipSystem> m_shipSystem;

View File

@@ -14,6 +14,7 @@
#include "BeltSystem.h"
#include "Building.h"
#include "BuildingSystem.h"
#include "FactoryState.h"
#include "BuildingType.h"
#include "ConfigLoader.h"
#include "DeliverScrapBehavior.h"
@@ -54,6 +55,7 @@
struct Fixture
{
GameConfig cfg;
FactoryState state;
BeltSystem belts;
BuildingId nextBuildingId;
int stock;
@@ -76,7 +78,7 @@ struct Fixture
, nextBuildingId(1)
, stock(0)
, rng(42)
, buildings(cfg, belts,
, buildings(cfg, state, belts,
[this]() { return nextBuildingId++; },
[this](int n) { stock += n; },
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},

View File

@@ -12,6 +12,7 @@
#include "BeltSystem.h"
#include "Building.h"
#include "BuildingSystem.h"
#include "FactoryState.h"
#include "BuildingType.h"
#include "ConfigLoader.h"
#include "Item.h"
@@ -83,6 +84,7 @@ static std::vector<Item> outputSideItems(const Building& b)
struct PlacementFixture
{
GameConfig cfg = loadTestConfig();
FactoryState state;
BeltSystem belts{cfg.world.beltSpeed_tps};
int stock = 0;
std::mt19937 rng{0};
@@ -90,7 +92,7 @@ struct PlacementFixture
BuildingSystem bs;
PlacementFixture()
: bs(cfg, belts,
: bs(cfg, state, belts,
[this]() { return nextBuildingId++; },
[this](int n) { stock += n; },
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
@@ -111,7 +113,8 @@ TEST_CASE("BuildingSystem: place miner occupies expected body tiles", "[building
int stock = 0;
std::mt19937 rng(0);
BuildingId nextBuildingId = 1;
BuildingSystem bs(cfg, belts,
FactoryState state_bs;
BuildingSystem bs(cfg, state_bs, belts,
[&nextBuildingId]() { return nextBuildingId++; },
[&stock](int n) { stock += n; },
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
@@ -219,7 +222,8 @@ TEST_CASE("BuildingSystem: placing a belt registers it with BeltSystem after con
int stock = 0;
std::mt19937 rng(0);
BuildingId nextBuildingId = 1;
BuildingSystem bs(cfg, belts,
FactoryState state_bs;
BuildingSystem bs(cfg, state_bs, belts,
[&nextBuildingId]() { return nextBuildingId++; },
[&stock](int n) { stock += n; },
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
@@ -248,7 +252,8 @@ TEST_CASE("BuildingSystem: placed building enters construction queue", "[buildin
int stock = 0;
std::mt19937 rng(0);
BuildingId nextBuildingId = 1;
BuildingSystem bs(cfg, belts,
FactoryState state_bs;
BuildingSystem bs(cfg, state_bs, belts,
[&nextBuildingId]() { return nextBuildingId++; },
[&stock](int n) { stock += n; },
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
@@ -290,7 +295,8 @@ TEST_CASE("BuildingSystem: first queued building starts construction immediately
int stock = 0;
std::mt19937 rng(0);
BuildingId nextBuildingId = 1;
BuildingSystem bs(cfg, belts,
FactoryState state_bs;
BuildingSystem bs(cfg, state_bs, belts,
[&nextBuildingId]() { return nextBuildingId++; },
[&stock](int n) { stock += n; },
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
@@ -308,7 +314,8 @@ TEST_CASE("BuildingSystem: second queued building waits (completesAt == 0)", "[b
int stock = 0;
std::mt19937 rng(0);
BuildingId nextBuildingId = 1;
BuildingSystem bs(cfg, belts,
FactoryState state_bs;
BuildingSystem bs(cfg, state_bs, belts,
[&nextBuildingId]() { return nextBuildingId++; },
[&stock](int n) { stock += n; },
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
@@ -330,7 +337,8 @@ TEST_CASE("BuildingSystem: construction completes after configured duration", "[
int stock = 0;
std::mt19937 rng(0);
BuildingId nextBuildingId = 1;
BuildingSystem bs(cfg, belts,
FactoryState state_bs;
BuildingSystem bs(cfg, state_bs, belts,
[&nextBuildingId]() { return nextBuildingId++; },
[&stock](int n) { stock += n; },
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
@@ -496,7 +504,8 @@ TEST_CASE("BuildingSystem: second building starts after first completes", "[buil
int stock = 0;
std::mt19937 rng(0);
BuildingId nextBuildingId = 1;
BuildingSystem bs(cfg, belts,
FactoryState state_bs;
BuildingSystem bs(cfg, state_bs, belts,
[&nextBuildingId]() { return nextBuildingId++; },
[&stock](int n) { stock += n; },
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
@@ -526,7 +535,8 @@ TEST_CASE("BuildingSystem: miner produces iron_ore after recipe duration", "[bui
int stock = 0;
std::mt19937 rng(0);
BuildingId nextBuildingId = 1;
BuildingSystem bs(cfg, belts,
FactoryState state_bs;
BuildingSystem bs(cfg, state_bs, belts,
[&nextBuildingId]() { return nextBuildingId++; },
[&stock](int n) { stock += n; },
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
@@ -559,7 +569,8 @@ TEST_CASE("BuildingSystem: miner output buffer stalls when full", "[building]")
int stock = 0;
std::mt19937 rng(0);
BuildingId nextBuildingId = 1;
BuildingSystem bs(cfg, belts,
FactoryState state_bs;
BuildingSystem bs(cfg, state_bs, belts,
[&nextBuildingId]() { return nextBuildingId++; },
[&stock](int n) { stock += n; },
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
@@ -599,7 +610,8 @@ TEST_CASE("BuildingSystem: productionBuildingCount excludes construction sites",
int stock = 0;
std::mt19937 rng(0);
BuildingId nextBuildingId = 1;
BuildingSystem bs(cfg, belts,
FactoryState state_bs;
BuildingSystem bs(cfg, state_bs, belts,
[&nextBuildingId]() { return nextBuildingId++; },
[&stock](int n) { stock += n; },
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
@@ -639,7 +651,8 @@ TEST_CASE("BuildingSystem: activeProductionBuildingCount tracks production cycle
int stock = 0;
std::mt19937 rng(0);
BuildingId nextBuildingId = 1;
BuildingSystem bs(cfg, belts,
FactoryState state_bs;
BuildingSystem bs(cfg, state_bs, belts,
[&nextBuildingId]() { return nextBuildingId++; },
[&stock](int n) { stock += n; },
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
@@ -681,7 +694,8 @@ TEST_CASE("BuildingSystem: smelter input buffer fills from adjacent west-flowing
int stock = 0;
std::mt19937 rng(0);
BuildingId nextBuildingId = 1;
BuildingSystem bs(cfg, belts,
FactoryState state_bs;
BuildingSystem bs(cfg, state_bs, belts,
[&nextBuildingId]() { return nextBuildingId++; },
[&stock](int n) { stock += n; },
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
@@ -723,7 +737,8 @@ TEST_CASE("BuildingSystem: accepted input travels inward before entering the buf
int stock = 0;
std::mt19937 rng(0);
BuildingId nextBuildingId = 1;
BuildingSystem bs(cfg, belts,
FactoryState state_bs;
BuildingSystem bs(cfg, state_bs, belts,
[&nextBuildingId]() { return nextBuildingId++; },
[&stock](int n) { stock += n; },
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
@@ -764,7 +779,8 @@ TEST_CASE("BuildingSystem: input reservation caps buffered plus in-transit at th
int stock = 0;
std::mt19937 rng(0);
BuildingId nextBuildingId = 1;
BuildingSystem bs(cfg, belts,
FactoryState state_bs;
BuildingSystem bs(cfg, state_bs, belts,
[&nextBuildingId]() { return nextBuildingId++; },
[&stock](int n) { stock += n; },
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
@@ -806,7 +822,8 @@ TEST_CASE("BuildingSystem: smelter auto-smelts ore without a recipe selection",
int stock = 0;
std::mt19937 rng(0);
BuildingId nextBuildingId = 1;
BuildingSystem bs(cfg, belts,
FactoryState state_bs;
BuildingSystem bs(cfg, state_bs, belts,
[&nextBuildingId]() { return nextBuildingId++; },
[&stock](int n) { stock += n; },
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
@@ -852,7 +869,8 @@ TEST_CASE("BuildingSystem: smelter runs a satisfiable recipe while an incomplete
int stock = 0;
std::mt19937 rng(0);
BuildingId nextBuildingId = 1;
BuildingSystem bs(cfg, belts,
FactoryState state_bs;
BuildingSystem bs(cfg, state_bs, belts,
[&nextBuildingId]() { return nextBuildingId++; },
[&stock](int n) { stock += n; },
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
@@ -906,7 +924,8 @@ TEST_CASE("BuildingSystem: miner output buffer drains onto adjacent belt", "[bui
int stock = 0;
std::mt19937 rng(0);
BuildingId nextBuildingId = 1;
BuildingSystem bs(cfg, belts,
FactoryState state_bs;
BuildingSystem bs(cfg, state_bs, belts,
[&nextBuildingId]() { return nextBuildingId++; },
[&stock](int n) { stock += n; },
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
@@ -945,7 +964,8 @@ TEST_CASE("BuildingSystem: output port couples directly into an adjacent input p
int stock = 0;
std::mt19937 rng(0);
BuildingId nextBuildingId = 1;
BuildingSystem bs(cfg, belts,
FactoryState state_bs;
BuildingSystem bs(cfg, state_bs, belts,
[&nextBuildingId]() { return nextBuildingId++; },
[&stock](int n) { stock += n; },
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
@@ -985,7 +1005,8 @@ TEST_CASE("BuildingSystem: direct coupling to a non-consumer leaves the item stu
int stock = 0;
std::mt19937 rng(0);
BuildingId nextBuildingId = 1;
BuildingSystem bs(cfg, belts,
FactoryState state_bs;
BuildingSystem bs(cfg, state_bs, belts,
[&nextBuildingId]() { return nextBuildingId++; },
[&stock](int n) { stock += n; },
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
@@ -1024,7 +1045,8 @@ TEST_CASE("BuildingSystem: setRecipe clears output buffer and active production"
int stock = 0;
std::mt19937 rng(0);
BuildingId nextBuildingId = 1;
BuildingSystem bs(cfg, belts,
FactoryState state_bs;
BuildingSystem bs(cfg, state_bs, belts,
[&nextBuildingId]() { return nextBuildingId++; },
[&stock](int n) { stock += n; },
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
@@ -1067,7 +1089,8 @@ TEST_CASE("BuildingSystem: reprocessing plant output buffer capacity equals max
int stock = 0;
std::mt19937 rng(0);
BuildingId nextBuildingId = 1;
BuildingSystem bs(cfg, belts,
FactoryState state_bs;
BuildingSystem bs(cfg, state_bs, belts,
[&nextBuildingId]() { return nextBuildingId++; },
[&stock](int n) { stock += n; },
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
@@ -1099,7 +1122,8 @@ TEST_CASE("BuildingSystem: reprocessing plant produces one cycle output then sta
// Seed chosen so first roll produces 2-item output (iron_ingot), filling buffer.
std::mt19937 rng(0);
BuildingId nextBuildingId = 1;
BuildingSystem bs(cfg, belts,
FactoryState state_bs;
BuildingSystem bs(cfg, state_bs, belts,
[&nextBuildingId]() { return nextBuildingId++; },
[&stock](int n) { stock += n; },
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
@@ -1157,7 +1181,8 @@ TEST_CASE("BuildingSystem: findRotateInPlaceTarget returns nullopt when tile is
int stock = 0;
std::mt19937 rng(0);
BuildingId nextBuildingId = 1;
BuildingSystem bs(cfg, belts,
FactoryState state_bs;
BuildingSystem bs(cfg, state_bs, belts,
[&nextBuildingId]() { return nextBuildingId++; },
[&stock](int n) { stock += n; },
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
@@ -1176,7 +1201,8 @@ TEST_CASE("BuildingSystem: findRotateInPlaceTarget returns the site id for a que
int stock = 0;
std::mt19937 rng(0);
BuildingId nextBuildingId = 1;
BuildingSystem bs(cfg, belts,
FactoryState state_bs;
BuildingSystem bs(cfg, state_bs, belts,
[&nextBuildingId]() { return nextBuildingId++; },
[&stock](int n) { stock += n; },
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
@@ -1199,7 +1225,8 @@ TEST_CASE("BuildingSystem: findRotateInPlaceTarget returns the building id for a
int stock = 0;
std::mt19937 rng(0);
BuildingId nextBuildingId = 1;
BuildingSystem bs(cfg, belts,
FactoryState state_bs;
BuildingSystem bs(cfg, state_bs, belts,
[&nextBuildingId]() { return nextBuildingId++; },
[&stock](int n) { stock += n; },
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
@@ -1226,7 +1253,8 @@ TEST_CASE("BuildingSystem: findRotateInPlaceTarget returns nullopt when building
int stock = 0;
std::mt19937 rng(0);
BuildingId nextBuildingId = 1;
BuildingSystem bs(cfg, belts,
FactoryState state_bs;
BuildingSystem bs(cfg, state_bs, belts,
[&nextBuildingId]() { return nextBuildingId++; },
[&stock](int n) { stock += n; },
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
@@ -1248,7 +1276,8 @@ TEST_CASE("BuildingSystem: findRotateInPlaceTarget never rotates a tunnel in pla
int stock = 0;
std::mt19937 rng(0);
BuildingId nextBuildingId = 1;
BuildingSystem bs(cfg, belts,
FactoryState state_bs;
BuildingSystem bs(cfg, state_bs, belts,
[&nextBuildingId]() { return nextBuildingId++; },
[&stock](int n) { stock += n; },
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
@@ -1274,7 +1303,8 @@ TEST_CASE("BuildingSystem: findRotateInPlaceTarget returns nullopt when footprin
int stock = 0;
std::mt19937 rng(0);
BuildingId nextBuildingId = 1;
BuildingSystem bs(cfg, belts,
FactoryState state_bs;
BuildingSystem bs(cfg, state_bs, belts,
[&nextBuildingId]() { return nextBuildingId++; },
[&stock](int n) { stock += n; },
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
@@ -1298,7 +1328,8 @@ TEST_CASE("BuildingSystem: findRotateInPlaceTarget works for a symmetric multi-t
int stock = 0;
std::mt19937 rng(0);
BuildingId nextBuildingId = 1;
BuildingSystem bs(cfg, belts,
FactoryState state_bs;
BuildingSystem bs(cfg, state_bs, belts,
[&nextBuildingId]() { return nextBuildingId++; },
[&stock](int n) { stock += n; },
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
@@ -1327,7 +1358,8 @@ TEST_CASE("BuildingSystem: rotateInPlace updates the rotation field of a constru
int stock = 0;
std::mt19937 rng(0);
BuildingId nextBuildingId = 1;
BuildingSystem bs(cfg, belts,
FactoryState state_bs;
BuildingSystem bs(cfg, state_bs, belts,
[&nextBuildingId]() { return nextBuildingId++; },
[&stock](int n) { stock += n; },
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
@@ -1350,7 +1382,8 @@ TEST_CASE("BuildingSystem: rotateInPlace preserves the construction progress of
int stock = 0;
std::mt19937 rng(0);
BuildingId nextBuildingId = 1;
BuildingSystem bs(cfg, belts,
FactoryState state_bs;
BuildingSystem bs(cfg, state_bs, belts,
[&nextBuildingId]() { return nextBuildingId++; },
[&stock](int n) { stock += n; },
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
@@ -1374,7 +1407,8 @@ TEST_CASE("BuildingSystem: rotateInPlace updates rotation and output port direct
int stock = 0;
std::mt19937 rng(0);
BuildingId nextBuildingId = 1;
BuildingSystem bs(cfg, belts,
FactoryState state_bs;
BuildingSystem bs(cfg, state_bs, belts,
[&nextBuildingId]() { return nextBuildingId++; },
[&stock](int n) { stock += n; },
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
@@ -1405,7 +1439,8 @@ TEST_CASE("BuildingSystem: rotateInPlace re-registers a belt tile with BeltSyste
int stock = 0;
std::mt19937 rng(0);
BuildingId nextBuildingId = 1;
BuildingSystem bs(cfg, belts,
FactoryState state_bs;
BuildingSystem bs(cfg, state_bs, belts,
[&nextBuildingId]() { return nextBuildingId++; },
[&stock](int n) { stock += n; },
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},

View File

@@ -5,6 +5,7 @@
#include "BeltSystem.h"
#include "Building.h"
#include "BuildingSystem.h"
#include "FactoryState.h"
#include "BuildingType.h"
#include "CombatSystem.h"
#include "ConfigLoader.h"
@@ -51,6 +52,7 @@ static entt::entity findWeaponChild(EntityAdmin& admin, entt::entity ship)
struct CombatFixture
{
GameConfig cfg;
FactoryState state;
std::mt19937 rng;
EntityAdmin admin;
BuildingId nextBuildingId;
@@ -65,7 +67,7 @@ struct CombatFixture
, nextBuildingId(1)
, belts(cfg.world.beltSpeed_tps)
, ships(cfg, admin)
, buildings(cfg, belts,
, buildings(cfg, state, belts,
[this]() { return nextBuildingId++; },
[](int){},
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},