number the buildings from the factory that holds them
The building-id counter was the last piece of factory data living on Simulation behind a callback: every construction site, every building, and every tile a station entity claims took its id from a std::function BuildingSystem held, which the arena and three test fixtures each had to supply. It moves into FactoryState as nextBuildingId, handed out by allocateBuildingId(state) in FactoryQueries beside the other operations over the state. BuildingSystem's callback is gone; so are Simulation::allocateBuildingId and ArenaSimulation::allocateBuildingId, whose remaining callers now allocate from the state directly. Checksum order is untouched: Simulation folds the counter where it always did. What is left on BuildingSystem is the config, the belts, the RNG, and two callbacks that reach genuinely outside the factory -- spawning a finished ship into the entity model, and testing an output group against the unlock state. Neither is factory data, so this is where the migration stops. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ne3mejABZoLWKLh8fgpM3x
This commit is contained in:
@@ -273,12 +273,12 @@ building could run and what its status light shows. A caller therefore depends o
|
||||
it reads rather than on whichever system happens to tick it — which is what let the UI, the
|
||||
balancing tool and the tests stop reaching through `BuildingSystem` for const answers.
|
||||
|
||||
What has not moved yet: the building-id counter and the global building block stock are
|
||||
factory data that still live on `Simulation`, reached through callbacks
|
||||
(`m_allocateBuildingId`, `m_addBuildingBlocks`) held by `BuildingSystem` and
|
||||
`DeconstructionSystem`. `m_spawnShip` and `m_isItemUnlocked` are genuine cross-domain
|
||||
reaches — into the entity model and the unlock state — and are not candidates for this
|
||||
struct.
|
||||
The struct also carries the two counters that used to live on `Simulation` and be reached
|
||||
through callbacks: `nextBuildingId` (handed out by `allocateBuildingId`) and
|
||||
`buildingBlocksStock`, which placement spends, deconstruction refunds, and HQ belt
|
||||
deliveries add to. What remains on `BuildingSystem` is `m_spawnShip` and
|
||||
`m_isItemUnlocked` — genuine cross-domain reaches into the entity model and the unlock
|
||||
state, not factory data, and so not candidates for this struct.
|
||||
|
||||
## Debris
|
||||
|
||||
|
||||
@@ -39,7 +39,6 @@ ArenaSimulation::ArenaSimulation(const GameConfig& gameConfig,
|
||||
, m_arenaConfig(std::move(arenaConfig))
|
||||
, m_rng(seed)
|
||||
, m_currentTick(0)
|
||||
, m_nextBuildingId(1)
|
||||
, m_beltSystem(1.0)
|
||||
, m_team1HqEntity(entt::null)
|
||||
, m_team2HqEntity(entt::null)
|
||||
@@ -51,7 +50,6 @@ ArenaSimulation::ArenaSimulation(const GameConfig& gameConfig,
|
||||
m_buildingSystem = std::make_unique<BuildingSystem>(
|
||||
m_gameConfig,
|
||||
m_beltSystem,
|
||||
[this]() { return allocateBuildingId(); },
|
||||
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
|
||||
[](const std::string&) -> bool { return true; },
|
||||
m_rng);
|
||||
@@ -133,10 +131,6 @@ void ArenaSimulation::computeTeamMaxEhp()
|
||||
|
||||
ArenaSimulation::~ArenaSimulation() = default;
|
||||
|
||||
BuildingId ArenaSimulation::allocateBuildingId()
|
||||
{
|
||||
return m_nextBuildingId++;
|
||||
}
|
||||
|
||||
void ArenaSimulation::placeStructures()
|
||||
{
|
||||
@@ -163,7 +157,8 @@ void ArenaSimulation::placeStructures()
|
||||
hp, hp, false);
|
||||
// Tag as an HQ so it is excluded from repair targeting (REQ-SHP-REPAIR).
|
||||
m_admin.addComponent<HqProxyComponent>(m_team1HqEntity);
|
||||
m_buildingSystem->registerTileOccupancy(m_factoryState, absCells, allocateBuildingId());
|
||||
m_buildingSystem->registerTileOccupancy(m_factoryState, absCells,
|
||||
allocateBuildingId(m_factoryState));
|
||||
}
|
||||
|
||||
// Team 2 HQ — ECS proxy entity, enemy faction (isEnemy=true). No weapon.
|
||||
@@ -184,7 +179,8 @@ void ArenaSimulation::placeStructures()
|
||||
hp, hp, true);
|
||||
// Tag as an HQ so it is excluded from repair targeting (REQ-SHP-REPAIR).
|
||||
m_admin.addComponent<HqProxyComponent>(m_team2HqEntity);
|
||||
m_buildingSystem->registerTileOccupancy(m_factoryState, absCells, allocateBuildingId());
|
||||
m_buildingSystem->registerTileOccupancy(m_factoryState, absCells,
|
||||
allocateBuildingId(m_factoryState));
|
||||
}
|
||||
|
||||
auto placeArenaStation = [&](const ArenaStationEntry& entry, bool isEnemy)
|
||||
@@ -238,7 +234,8 @@ void ArenaSimulation::placeStructures()
|
||||
m_admin.addComponent<ModuleOwnerComponent>(wChild,
|
||||
ModuleOwnerComponent{stationEntity});
|
||||
}
|
||||
m_buildingSystem->registerTileOccupancy(m_factoryState, absCells, allocateBuildingId());
|
||||
m_buildingSystem->registerTileOccupancy(m_factoryState, absCells,
|
||||
allocateBuildingId(m_factoryState));
|
||||
};
|
||||
|
||||
for (const ArenaStationEntry& entry : m_arenaConfig.teams[0].stations)
|
||||
|
||||
@@ -92,7 +92,6 @@ public:
|
||||
const EntityAdmin& getAdmin() const;
|
||||
|
||||
private:
|
||||
BuildingId allocateBuildingId();
|
||||
void placeStructures();
|
||||
void spawnShips();
|
||||
void computeTeamMaxEhp();
|
||||
@@ -105,7 +104,6 @@ private:
|
||||
std::mt19937 m_rng;
|
||||
|
||||
Tick m_currentTick;
|
||||
BuildingId m_nextBuildingId;
|
||||
|
||||
EntityAdmin m_admin;
|
||||
FactoryState m_factoryState;
|
||||
|
||||
@@ -27,14 +27,12 @@ bool inputLaneEntryFree(const std::vector<BeltItemSlot>& lane)
|
||||
|
||||
BuildingSystem::BuildingSystem(const GameConfig& config,
|
||||
BeltSystem& belts,
|
||||
std::function<BuildingId()> allocateBuildingId,
|
||||
std::function<void(const std::string&, QVector2D,
|
||||
const std::optional<ShipLayoutConfig>&)> spawnShip,
|
||||
std::function<bool(const std::string&)> isItemUnlocked,
|
||||
std::mt19937& rng)
|
||||
: m_config(config)
|
||||
, m_belts(belts)
|
||||
, m_allocateBuildingId(std::move(allocateBuildingId))
|
||||
, m_spawnShip(std::move(spawnShip))
|
||||
, m_isItemUnlocked(std::move(isItemUnlocked))
|
||||
, m_rng(rng)
|
||||
@@ -121,7 +119,7 @@ std::optional<BuildingId> BuildingSystem::place(FactoryState& state, BuildingTyp
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const BuildingId id = m_allocateBuildingId();
|
||||
const BuildingId id = allocateBuildingId(state);
|
||||
|
||||
// Record tile occupancy for body cells.
|
||||
for (const QPoint& cell : mask.bodyCells)
|
||||
@@ -839,7 +837,7 @@ BuildingId BuildingSystem::placeImmediate(FactoryState& state, BuildingType type
|
||||
const std::vector<std::string>& surfaceMask,
|
||||
QPoint anchor, Rotation rotation)
|
||||
{
|
||||
const BuildingId id = m_allocateBuildingId();
|
||||
const BuildingId id = allocateBuildingId(state);
|
||||
const ParsedSurfaceMask mask = parseSurfaceMask(surfaceMask, rotation);
|
||||
|
||||
Building building;
|
||||
|
||||
@@ -40,7 +40,6 @@ class BuildingSystem
|
||||
public:
|
||||
BuildingSystem(const GameConfig& config,
|
||||
BeltSystem& belts,
|
||||
std::function<BuildingId()> allocateBuildingId,
|
||||
std::function<void(const std::string&, QVector2D,
|
||||
const std::optional<ShipLayoutConfig>&)> spawnShip,
|
||||
std::function<bool(const std::string&)> isItemUnlocked,
|
||||
@@ -173,12 +172,12 @@ private:
|
||||
std::vector<Item> rollOutputGroup(const RecipeDef& recipe);
|
||||
|
||||
// No world data among these: the factory arrives per call (FactoryState.h). What is
|
||||
// left are the immutable config, the transport layer, the shared RNG, and four
|
||||
// callbacks into what this system cannot reach on its own -- the id counter and the
|
||||
// block stock, which live on Simulation, and the entity and unlock sides.
|
||||
// left are the immutable config, the transport layer, the shared RNG, and two
|
||||
// callbacks into what this system genuinely cannot reach -- the entity model a
|
||||
// finished ship is spawned into, and the unlock state an output group is tested
|
||||
// against. Neither is factory data, so neither belongs in the state.
|
||||
const GameConfig& m_config;
|
||||
BeltSystem& m_belts;
|
||||
std::function<BuildingId()> m_allocateBuildingId;
|
||||
std::function<void(const std::string&, QVector2D,
|
||||
const std::optional<ShipLayoutConfig>&)> m_spawnShip;
|
||||
std::function<bool(const std::string&)> m_isItemUnlocked;
|
||||
|
||||
@@ -235,6 +235,11 @@ TunnelTileMap collectTunnelTiles(const FactoryState& state)
|
||||
return tunnels;
|
||||
}
|
||||
|
||||
BuildingId allocateBuildingId(FactoryState& state)
|
||||
{
|
||||
return state.nextBuildingId++;
|
||||
}
|
||||
|
||||
void forEachEmergingItem(const FactoryState& state,
|
||||
const std::function<void(const ItemType&, QPointF)>& visit)
|
||||
{
|
||||
|
||||
@@ -85,6 +85,11 @@ std::vector<BuildingId> buildingsInBox(const FactoryState& state,
|
||||
// single-cell tile. Shared by the placement preview and the selection highlight.
|
||||
TunnelTileMap collectTunnelTiles(const FactoryState& state);
|
||||
|
||||
// Hands out the next building id and advances the counter (BuildingId.h). A mutation
|
||||
// rather than a query, like deliverScrapToSalvageBay above: it is one line over the state
|
||||
// and belongs to no system, every id-issuing path being a factory path.
|
||||
BuildingId allocateBuildingId(FactoryState& state);
|
||||
|
||||
// Visits every item currently emerging from a building output port on its virtual output
|
||||
// belt (REQ-MAT-OUTPUT-EMERGE), passing the item type and its world-space centre (in tile
|
||||
// units). Least-progressed first (drawn bottom) so callers can paint in visit order
|
||||
|
||||
@@ -58,6 +58,12 @@ struct FactoryState
|
||||
// Seeded from config by BuildingSystem's constructor.
|
||||
int asteroidWidth_tiles = 0;
|
||||
|
||||
// Next id to hand out for a building, a construction site, or a tile claimed by a
|
||||
// station entity. Strictly increasing and never reused, so an id names one thing for
|
||||
// the whole run -- which is what lets a command reference a building across a replay
|
||||
// (docs/replay_design.md). Allocated through allocateBuildingId (FactoryQueries.h).
|
||||
BuildingId nextBuildingId = 1;
|
||||
|
||||
// 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
|
||||
|
||||
@@ -41,7 +41,6 @@ Simulation::Simulation(GameConfig config, unsigned int seed)
|
||||
, m_seed(seed)
|
||||
, m_currentTick(0)
|
||||
, m_nextDepartureTick(secondsToTicks(m_config.world.departureIntervalSeconds))
|
||||
, m_nextBuildingId(1)
|
||||
, m_gameOver(false)
|
||||
, m_hqProxyEntity(entt::null)
|
||||
, m_playerStation1Entity(entt::null)
|
||||
@@ -83,7 +82,6 @@ void Simulation::reset(unsigned int seed)
|
||||
m_seed = seed;
|
||||
m_currentTick = 0;
|
||||
m_nextDepartureTick = secondsToTicks(m_config.world.departureIntervalSeconds);
|
||||
m_nextBuildingId = 1;
|
||||
m_expansionsPurchased = 0;
|
||||
m_gameOver = false;
|
||||
m_isWon = false;
|
||||
@@ -111,7 +109,6 @@ void Simulation::initializeSubsystems()
|
||||
m_buildingSystem = std::make_unique<BuildingSystem>(
|
||||
m_config,
|
||||
m_beltSystem,
|
||||
[this]() { return allocateBuildingId(); },
|
||||
[this](const std::string& id, QVector2D pos,
|
||||
const std::optional<ShipLayoutConfig>& layout) {
|
||||
if (!isSchematicUnlocked(id))
|
||||
@@ -356,7 +353,8 @@ void Simulation::placeInitialStructures()
|
||||
m_admin.addComponent<ModuleOwnerComponent>(wChild,
|
||||
ModuleOwnerComponent{m_playerStation1Entity});
|
||||
}
|
||||
m_buildingSystem->registerTileOccupancy(m_factoryState, absCells, allocateBuildingId());
|
||||
m_buildingSystem->registerTileOccupancy(m_factoryState, absCells,
|
||||
allocateBuildingId(m_factoryState));
|
||||
}
|
||||
{
|
||||
const QPoint anchor(psAnchorX, ps2Y);
|
||||
@@ -373,7 +371,8 @@ void Simulation::placeInitialStructures()
|
||||
m_admin.addComponent<ModuleOwnerComponent>(wChild,
|
||||
ModuleOwnerComponent{m_playerStation2Entity});
|
||||
}
|
||||
m_buildingSystem->registerTileOccupancy(m_factoryState, absCells, allocateBuildingId());
|
||||
m_buildingSystem->registerTileOccupancy(m_factoryState, absCells,
|
||||
allocateBuildingId(m_factoryState));
|
||||
}
|
||||
|
||||
// Rally point: center of the player defence stations' X column, world vertical midpoint.
|
||||
@@ -428,7 +427,8 @@ void Simulation::placeEnemyStationSet(int generation)
|
||||
m_admin.addComponent<ModuleOwnerComponent>(wChild,
|
||||
ModuleOwnerComponent{m_currentEnemyStationEntities[0]});
|
||||
}
|
||||
m_buildingSystem->registerTileOccupancy(m_factoryState, absCells, allocateBuildingId());
|
||||
m_buildingSystem->registerTileOccupancy(m_factoryState, absCells,
|
||||
allocateBuildingId(m_factoryState));
|
||||
}
|
||||
{
|
||||
const QPoint anchor(anchorX, y2);
|
||||
@@ -445,7 +445,8 @@ void Simulation::placeEnemyStationSet(int generation)
|
||||
m_admin.addComponent<ModuleOwnerComponent>(wChild,
|
||||
ModuleOwnerComponent{m_currentEnemyStationEntities[1]});
|
||||
}
|
||||
m_buildingSystem->registerTileOccupancy(m_factoryState, absCells, allocateBuildingId());
|
||||
m_buildingSystem->registerTileOccupancy(m_factoryState, absCells,
|
||||
allocateBuildingId(m_factoryState));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -653,7 +654,7 @@ unsigned long long Simulation::computeStateChecksum() const
|
||||
// Top-level scalars.
|
||||
hasher.append(m_currentTick);
|
||||
hasher.append(m_nextDepartureTick);
|
||||
hasher.append(m_nextBuildingId);
|
||||
hasher.append(m_factoryState.nextBuildingId);
|
||||
hasher.append(m_factoryState.buildingBlocksStock);
|
||||
hasher.append(m_gameOver);
|
||||
hasher.append(m_isWon);
|
||||
@@ -948,7 +949,3 @@ void Simulation::handleEvent(std::shared_ptr<const TracePrintRequestedEvent> eve
|
||||
PRINT_TRACES();
|
||||
}
|
||||
|
||||
BuildingId Simulation::allocateBuildingId()
|
||||
{
|
||||
return m_nextBuildingId++;
|
||||
}
|
||||
|
||||
@@ -169,7 +169,6 @@ private:
|
||||
|
||||
void handleEvent(std::shared_ptr<const TracePrintRequestedEvent> event) override;
|
||||
|
||||
BuildingId allocateBuildingId(); // Strictly increasing; never returns kInvalidBuildingId.
|
||||
|
||||
// (Re-)create every owned subsystem. Shared by the constructor and reset();
|
||||
// the construction order is load-bearing for determinism, so both paths must
|
||||
@@ -195,7 +194,6 @@ private:
|
||||
|
||||
Tick m_currentTick;
|
||||
Tick m_nextDepartureTick;
|
||||
BuildingId m_nextBuildingId;
|
||||
int m_expansionsPurchased = 0; // REQ-EXP-COST formula variable x
|
||||
bool m_gameOver = false;
|
||||
bool m_isWon = false;
|
||||
|
||||
@@ -59,7 +59,6 @@ struct Fixture
|
||||
GameConfig cfg;
|
||||
FactoryState state = makeFactoryState(cfg);
|
||||
BeltSystem belts;
|
||||
BuildingId nextBuildingId;
|
||||
std::mt19937 rng;
|
||||
EntityAdmin admin;
|
||||
BuildingSystem buildings;
|
||||
@@ -77,10 +76,8 @@ struct Fixture
|
||||
explicit Fixture()
|
||||
: cfg(loadTestConfig())
|
||||
, belts(cfg.world.beltSpeed_tps)
|
||||
, nextBuildingId(1)
|
||||
, rng(42)
|
||||
, buildings(cfg, belts,
|
||||
[this]() { return nextBuildingId++; },
|
||||
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
|
||||
[](const std::string&) -> bool { return true; },
|
||||
rng)
|
||||
|
||||
@@ -99,7 +99,6 @@ struct PlacementFixture
|
||||
FactoryState state = makeFactoryState(cfg);
|
||||
BeltSystem belts;
|
||||
std::mt19937 rng{0};
|
||||
BuildingId nextBuildingId = 1;
|
||||
BuildingSystem bs;
|
||||
|
||||
// Blocks credited back since the run began. The state is seeded with the configured
|
||||
@@ -118,7 +117,6 @@ struct PlacementFixture
|
||||
std::function<bool(const std::string&)> isItemUnlocked = nullptr)
|
||||
: belts(beltSpeed_tps.value_or(cfg.world.beltSpeed_tps))
|
||||
, bs(cfg, belts,
|
||||
[this]() { return nextBuildingId++; },
|
||||
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
|
||||
isItemUnlocked ? std::move(isItemUnlocked)
|
||||
: std::function<bool(const std::string&)>(
|
||||
|
||||
@@ -55,7 +55,6 @@ struct CombatFixture
|
||||
FactoryState state = makeFactoryState(cfg);
|
||||
std::mt19937 rng;
|
||||
EntityAdmin admin;
|
||||
BuildingId nextBuildingId;
|
||||
BeltSystem belts;
|
||||
ShipSystem ships;
|
||||
BuildingSystem buildings;
|
||||
@@ -64,11 +63,9 @@ struct CombatFixture
|
||||
explicit CombatFixture()
|
||||
: cfg(loadTestConfig())
|
||||
, rng(42)
|
||||
, nextBuildingId(1)
|
||||
, belts(cfg.world.beltSpeed_tps)
|
||||
, ships(cfg, admin)
|
||||
, buildings(cfg, belts,
|
||||
[this]() { return nextBuildingId++; },
|
||||
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
|
||||
[](const std::string&) -> bool { return true; },
|
||||
rng)
|
||||
|
||||
Reference in New Issue
Block a user