diff --git a/docs/architecture.md b/docs/architecture.md index 5a0db73..aef40a7 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -242,6 +242,44 @@ struct Building { - Belts and splitters are separate types owned by the belt subsystem, not general `Building` instances. - No ECS for buildings. A miner is never also an assembler; there is no composition benefit to decomposing buildings into components. +### Factory State and Queries + +The buildings, the construction and deconstruction queues, and the tile-ownership grid +live in one struct — `FactoryState` — owned by `Simulation` (and by `ArenaSimulation` in +the balancing tool), never by a system. It is data with no behaviour of its own, the +buildings-side counterpart to `EntityAdmin`. + +```cpp +struct FactoryState { + std::vector buildings; + std::deque constructionQueue; + std::deque deconstructionQueue; + BuildingGrid grid; // who owns which tile + int asteroidWidth_tiles = 0; // left placement bound +}; +``` + +Every system that touches the factory — `BuildingSystem`, `ConstructionSystem`, +`DeconstructionSystem` — takes it as an argument and holds none of it, the same shape the +`lib/ecs/system` classes have, where the world arrives per tick. This is why +`ConstructionSystem` can complete a building itself instead of handing the finished site +back to `BuildingSystem`: with the state in the argument there is no owner to route +through. + +**Reading the factory needs no system.** The queries are free functions over the state: +`FactoryQueries.h` for what is where (`findBuilding`, `getInputPorts`, `collectBeltTiles`), +`PlacementRules.h` for whether a placement is legal, `ProductionRules.h` for what a +building could run and what its status light shows. A caller therefore depends on the data +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. + ## Debris Debris — the salvageable object dropped by destroyed ships and defence stations — is the diff --git a/src/lib/sim/BuildingSystem.h b/src/lib/sim/BuildingSystem.h index 7743827..adc32ac 100644 --- a/src/lib/sim/BuildingSystem.h +++ b/src/lib/sim/BuildingSystem.h @@ -57,12 +57,6 @@ public: std::optional place(FactoryState& state, BuildingType type, QPoint anchor, Rotation rotation, Tick currentTick); - // Returns true if the placement satisfies REQ-BLD-PLACE-VALID terrain and - // world-bounds rules: every ship-dock (S) cell sits in space (x >= 0), every - // other body (A) cell sits on the asteroid (x < 0 and x >= the left edge), - // and every cell has 0 <= y < world.height_tiles. There is no right-side - // bound — space extends rightward. Tile occupancy is NOT checked here. - // Sets the current buildable asteroid width in tiles. Grows the left // placement bound as the player unlocks asteroid expansions (REQ-EXP-UNLOCK). // Defaults to world.regions.asteroid_width_tiles at construction. @@ -83,8 +77,6 @@ public: // progress and credits no refund. No-op if the id is not queued. void cancelDeconstruction(FactoryState& state, BuildingId id); - // True if the building is currently in the deconstruction queue. - // Set the recipe (or schematic id for shipyard) on a building or queued // construction site. Clears both buffers on an operational building. void setRecipe(FactoryState& state, BuildingId id, const std::string& recipeId); @@ -105,9 +97,9 @@ public: const std::vector& filterB); // -- Tick hooks (called from Simulation::tick in the documented order) --- - // Advances the deconstruction queue (REQ-BLD-DECON-QUEUE): one building at a - // time, in parallel with tickConstruction. Removes the front building and - // credits its refund when its timer elapses. + // Advances every building's virtual input belts, delivers what arrives into the + // input buffers (into the global block stock for the HQ), and takes what the + // adjacent real belts offer (REQ-MAT-INPUT-INTAKE, REQ-HQ-BELT-INPUT). void tickBeltPull(FactoryState& state); void tickProduction(FactoryState& state, Tick currentTick); void tickShipyardProduction(FactoryState& state, Tick currentTick); @@ -117,18 +109,10 @@ public: void tickOutputBelts(FactoryState& state); // -- Queries ------------------------------------------------------------- - - - // REQ-UI-DEBUG-OVERLAY "Max Factory Production": count of completed - // (operational) Miner/Smelter/Assembler/ReprocessingPlant/Shipyard buildings. - - // REQ-UI-DEBUG-OVERLAY "Current Factory Production": subset of the above - // that currently has an active production cycle. - - // Production state for the UI status light (REQ-UI-STATUS-LIGHT). Returns - // nullopt for building types that show no light (belts, splitters, tunnels, - // HQ, defence stations). The Salvage Bay is a two-state special case: - // Producing while its output buffer holds scrap, Starved when empty. + // Reading the factory needs no system: those queries are free functions over the + // state (FactoryQueries.h), the placement rules (PlacementRules.h) and the + // production rules (ProductionRules.h). Only the two visitors below are still + // here, and only because their callers reach them through this system. // 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 @@ -148,19 +132,10 @@ public: // currently on the tile are discarded by BeltSystem::removeTile). void rotateInPlace(FactoryState& state, BuildingId id, Rotation newRotation); - - // Input-capable adjacent tiles for a building or construction site - // (REQ-BLD-BELT-DRAG, REQ-MAT-INPUT-PORTS): each returned Port.tile is the - // outside adjacent tile and Port.direction is the belt facing that points into - // the target. Output-port edges are excluded. Empty for an unknown id. - // Register / unregister tile occupancy for ECS station entities. void registerTileOccupancy(FactoryState& state, const std::vector& cells, BuildingId ownerPlaceholder); void unregisterTileOccupancy(FactoryState& state, const std::vector& cells); - // Place one "scrap" item into a SalvageBay's output buffer. - // Returns false if bay not found, wrong type, or output buffer is full. - // Bypass the construction queue and create a fully-operational Building // immediately. Used for pre-placed structures (HQ, defence stations). // surfaceMask comes from the relevant config struct. @@ -181,13 +156,6 @@ public: void appendChecksum(const FactoryState& state, Hasher& hasher) const; private: - // Starts the front deconstruction-queue entry's timer if not yet started - // (mirrors how tickConstruction starts a queued construction site). - - // Registers a belt/splitter/tunnel building's tile with the belt subsystem - // (on construction completion, or when un-queuing a deconstruction). No-op for - // non-belt-subsystem types. Splitter filters are (re)applied after placement. - // Selects a recipe for an auto-recipe building that has none, from a material being // offered to it at one of its input ports (REQ-BLD-AUTO-RECIPE). No-op for every // other building, for one that already holds a recipe, and for a material none of @@ -213,28 +181,19 @@ private: const Port& outputPort, const Item& item); - // Candidate recipes an idle building would try this tick: an auto-recipe - // building (Smelter, Reprocessing Plant) offers every recipe of its type with - // inputs; other buildings offer only their selected recipe. Shared by - // tickProduction and the status classifier (REQ-MAT-CYCLE, REQ-UI-STATUS-LIGHT). - // True if every input of `recipe` is present in `b`'s input buffers in the - // required per-cycle amount (REQ-MAT-CYCLE input check). - // Combined base + module materials a shipyard needs per ship (REQ-BLD-SHIPYARD). - // True if the building currently has all inputs/materials to start a cycle - // (ignoring output-buffer space); drives the Starved/Blocked distinction of - // the status light (REQ-UI-STATUS-LIGHT). - // What one cycle of this recipe produces: the items of its one output group // (REQ-MAT-OUTPUT-GROUP). Where the recipe has several, one is picked by weight from // those currently eligible (REQ-LOCK-OUTPUT-POOL) and the result is empty if none is; // where it has one, that group is returned with no draw and no eligibility test. 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. const GameConfig& m_config; - - BeltSystem& m_belts; - std::function m_allocateBuildingId; + std::function m_allocateBuildingId; std::function m_addBuildingBlocks; std::function&)> m_spawnShip; diff --git a/src/lib/sim/FactoryState.h b/src/lib/sim/FactoryState.h index 74a0493..f71d78e 100644 --- a/src/lib/sim/FactoryState.h +++ b/src/lib/sim/FactoryState.h @@ -33,10 +33,15 @@ struct DeconstructionEntry // 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. +// systems that operate on it. Every system that touches the factory — +// BuildingSystem, ConstructionSystem, DeconstructionSystem — takes it as an argument +// and holds none of it, so each is stateless over the world it works on. Reading it +// needs no system at all: the queries are free functions over this struct +// (FactoryQueries.h, PlacementRules.h, ProductionRules.h). +// +// What is not here yet is the data those systems still reach back into Simulation +// for through callbacks: the building-id counter and the global building block stock, +// both of which are factory data living outside the factory's state. struct FactoryState { std::vector buildings;