separate what buildings are from what flows through them
BuildingSystem was four unrelated jobs in one class: building lifecycle, building configuration, the per-tick material flow, and (until last commit) the checksum. The flow was the odd one out -- it is what the RNG, the ship spawner and the unlock test were held for, none of which placement, rotation or demolition has any business reaching. Tick steps 3 to 5 move to a new ProductionSystem: tickBeltPull, tickProduction, tickShipyardProduction, tickOutputBelts, their five private helpers and rollOutputGroup. The cut is clean in both directions -- nothing in the block called a topology or configuration method, and nothing outside it called the helpers -- so the bodies move verbatim; a scripted comparison against the old file confirms all nine differ only by class qualifier, the m_belts -> belts rename, and the two added parameters. (Seven em dashes in comments became `--`; the new file is ASCII, as the guidelines require.) Belts arrive per tick rather than being held, matching ConstructionSystem, and only the two methods that touch them take the parameter. BuildingSystem is left holding the config and the belts, and its constructor takes exactly those two. The arena constructs no ProductionSystem at all: it stages ships directly and never runs a factory. Determinism rests on the RNG stream: step 4's weighted output-group pick is the factory's only draw, so the four calls must keep their order and position in Simulation::tick. They do, and the tick-order section now says why, since no test can catch a reordering here. 913 lines of BuildingSystem.cpp become 444 there and 483 in ProductionSystem.cpp. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ne3mejABZoLWKLh8fgpM3x
This commit is contained in:
@@ -109,9 +109,11 @@ Within a single simulation tick, subsystems run in this fixed order. The order i
|
||||
|
||||
1. **Wave scheduler** — advance wave timer; on trigger, compute wave composition per REQ-WAV-TRIGGER and schedule spawn times across REQ-WAV-SPAWN-DURATION; spawn any enemy ships whose scheduled time has arrived this tick.
|
||||
2. **Threat accumulation** — add `max(0, threat_rate_formula(t))` × tick_dt to threat level (REQ-WAV-THREAT-RATE).
|
||||
3. **Belt → building pull** — buildings drain eligible items from adjacent belt tiles into per-material input buffers (REQ-MAT-INPUT-PORTS).
|
||||
4. **Building production** — advance production timers; start new cycles when inputs and output-buffer space permit (REQ-MAT-CYCLE); on completion, deposit output.
|
||||
5. **Building → belt push** — buildings push items from output buffer onto the belt tile at their output port (REQ-MAT-OUTPUT-PORT).
|
||||
3. **Belt → building pull** — buildings drain eligible items from adjacent belt tiles into per-material input buffers (REQ-MAT-INPUT-PORTS). `ProductionSystem::tickBeltPull`.
|
||||
4. **Building production** — advance production timers; start new cycles when inputs and output-buffer space permit (REQ-MAT-CYCLE); on completion, deposit output. `ProductionSystem::tickProduction`, then `tickShipyardProduction` for the shipyard's ship (REQ-BLD-SHIPYARD).
|
||||
5. **Building → belt push** — buildings push items from output buffer onto the belt tile at their output port (REQ-MAT-OUTPUT-PORT). `ProductionSystem::tickOutputBelts`.
|
||||
|
||||
Steps 3–5 are one system and must stay adjacent and in this order: an item arriving in step 3 is consumable in step 4, and an item produced in step 4 starts travelling in step 5. Step 4 is also the only place the factory draws from the RNG (an output group's weighted pick, REQ-LOCK-OUTPUT-POOL), so moving these calls relative to any other draw invalidates recorded replays.
|
||||
6. **Belt tick** — advance items along belt tiles; apply splitter routing (REQ-BLD-SPLITTER).
|
||||
7. **Ship behavior systems** — clear `MovementIntent` on each ship, then the `AiSystem` runs three batched phases: every behavior **evaluator** scores its behavior and sets its target data; a **selection** pass records the highest-scoring behavior per ship in `SelectedBehaviorComponent`; each behavior **executor** runs for the winner, writing `MovementIntent` and preferred module targets. The module systems then perform world mutation: `SalvagerSystem` (scrap collection/delivery) and `RepairSystem` (healing). See Movement Arbitration.
|
||||
8. **Combat resolution** — ships and defence stations validate/acquire targets, fire, apply damage; queue deaths. Each fire appends a `BeamFiredEvent` to the sim's beam-fired-event queue (REQ-SHP-FIRING-BEAM). The repair and salvage module systems (tick step 7d) append their own `BeamFiredEvent`s to the same queue when they start a cycle.
|
||||
@@ -241,6 +243,7 @@ struct Building {
|
||||
- The uniform "input buffer → production timer → output buffer" pattern across miner, smelter, assembler, reprocessing plant, and shipyard is driven by the recipe config, not by a class hierarchy.
|
||||
- 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.
|
||||
- **What buildings are is separate from what flows through them.** `BuildingSystem` places, demolishes, rotates and configures; `ProductionSystem` runs intake, production cycles and output (tick steps 3–5). The split follows what each needs: only the flow side draws from the RNG, tests unlock state, and spawns a ship into the entity model, so only it holds those. `BuildingSystem` holds the config and the belts and nothing else.
|
||||
|
||||
### Factory State and Queries
|
||||
|
||||
@@ -259,8 +262,8 @@ struct FactoryState {
|
||||
};
|
||||
```
|
||||
|
||||
Every system that touches the factory — `BuildingSystem`, `ConstructionSystem`,
|
||||
`DeconstructionSystem` — takes it as an argument and holds none of it, the same shape the
|
||||
Every system that touches the factory — `BuildingSystem`, `ProductionSystem`,
|
||||
`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
|
||||
|
||||
Reference in New Issue
Block a user