diff --git a/docs/architecture.md b/docs/architecture.md index aef40a7..2bb4626 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -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 diff --git a/src/balancing/ArenaSimulation.cpp b/src/balancing/ArenaSimulation.cpp index aefa85a..e2c930d 100644 --- a/src/balancing/ArenaSimulation.cpp +++ b/src/balancing/ArenaSimulation.cpp @@ -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( m_gameConfig, m_beltSystem, - [this]() { return allocateBuildingId(); }, [](const std::string&, QVector2D, const std::optional&) {}, [](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(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(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(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) diff --git a/src/balancing/ArenaSimulation.h b/src/balancing/ArenaSimulation.h index b6ef73c..e752ff7 100644 --- a/src/balancing/ArenaSimulation.h +++ b/src/balancing/ArenaSimulation.h @@ -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; diff --git a/src/lib/sim/BuildingSystem.cpp b/src/lib/sim/BuildingSystem.cpp index 67b2bdb..0918c6f 100644 --- a/src/lib/sim/BuildingSystem.cpp +++ b/src/lib/sim/BuildingSystem.cpp @@ -27,14 +27,12 @@ bool inputLaneEntryFree(const std::vector& lane) BuildingSystem::BuildingSystem(const GameConfig& config, BeltSystem& belts, - std::function allocateBuildingId, std::function&)> spawnShip, std::function 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 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& surfaceMask, QPoint anchor, Rotation rotation) { - const BuildingId id = m_allocateBuildingId(); + const BuildingId id = allocateBuildingId(state); const ParsedSurfaceMask mask = parseSurfaceMask(surfaceMask, rotation); Building building; diff --git a/src/lib/sim/BuildingSystem.h b/src/lib/sim/BuildingSystem.h index d387ea0..1d7087d 100644 --- a/src/lib/sim/BuildingSystem.h +++ b/src/lib/sim/BuildingSystem.h @@ -40,7 +40,6 @@ class BuildingSystem public: BuildingSystem(const GameConfig& config, BeltSystem& belts, - std::function allocateBuildingId, std::function&)> spawnShip, std::function isItemUnlocked, @@ -173,12 +172,12 @@ private: std::vector 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 m_allocateBuildingId; std::function&)> m_spawnShip; std::function m_isItemUnlocked; diff --git a/src/lib/sim/FactoryQueries.cpp b/src/lib/sim/FactoryQueries.cpp index ed1e45a..8d0ab30 100644 --- a/src/lib/sim/FactoryQueries.cpp +++ b/src/lib/sim/FactoryQueries.cpp @@ -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& visit) { diff --git a/src/lib/sim/FactoryQueries.h b/src/lib/sim/FactoryQueries.h index 935cb09..75b712a 100644 --- a/src/lib/sim/FactoryQueries.h +++ b/src/lib/sim/FactoryQueries.h @@ -85,6 +85,11 @@ std::vector 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 diff --git a/src/lib/sim/FactoryState.h b/src/lib/sim/FactoryState.h index 6cac93b..f1a2066 100644 --- a/src/lib/sim/FactoryState.h +++ b/src/lib/sim/FactoryState.h @@ -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 diff --git a/src/lib/sim/Simulation.cpp b/src/lib/sim/Simulation.cpp index 4f7f87f..a347643 100644 --- a/src/lib/sim/Simulation.cpp +++ b/src/lib/sim/Simulation.cpp @@ -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( m_config, m_beltSystem, - [this]() { return allocateBuildingId(); }, [this](const std::string& id, QVector2D pos, const std::optional& layout) { if (!isSchematicUnlocked(id)) @@ -356,7 +353,8 @@ void Simulation::placeInitialStructures() m_admin.addComponent(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(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(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(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 eve PRINT_TRACES(); } -BuildingId Simulation::allocateBuildingId() -{ - return m_nextBuildingId++; -} diff --git a/src/lib/sim/Simulation.h b/src/lib/sim/Simulation.h index 81ea6ee..75f9e20 100644 --- a/src/lib/sim/Simulation.h +++ b/src/lib/sim/Simulation.h @@ -169,7 +169,6 @@ private: void handleEvent(std::shared_ptr 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; diff --git a/src/test/BehaviorSystemTest.cpp b/src/test/BehaviorSystemTest.cpp index 9291e03..9806023 100644 --- a/src/test/BehaviorSystemTest.cpp +++ b/src/test/BehaviorSystemTest.cpp @@ -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&) {}, [](const std::string&) -> bool { return true; }, rng) diff --git a/src/test/BuildingTest.cpp b/src/test/BuildingTest.cpp index 60820f6..f68c20a 100644 --- a/src/test/BuildingTest.cpp +++ b/src/test/BuildingTest.cpp @@ -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 isItemUnlocked = nullptr) : belts(beltSpeed_tps.value_or(cfg.world.beltSpeed_tps)) , bs(cfg, belts, - [this]() { return nextBuildingId++; }, [](const std::string&, QVector2D, const std::optional&) {}, isItemUnlocked ? std::move(isItemUnlocked) : std::function( diff --git a/src/test/CombatSystemTest.cpp b/src/test/CombatSystemTest.cpp index 2d4c293..27d9e81 100644 --- a/src/test/CombatSystemTest.cpp +++ b/src/test/CombatSystemTest.cpp @@ -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&) {}, [](const std::string&) -> bool { return true; }, rng)