forEachEmergingItem and forEachIncomingItem took a const FactoryState& and touched no member of BuildingSystem: they read the state and the free geometry helpers and nothing else. They are queries wearing a system's uniform, and their one caller -- the renderer -- reached through getBuildings() to call them, passing the state back in as an argument. They move to FactoryQueries beside the rest of the read surface, and the renderer calls them directly. With that, BuildingSystem answers no queries at all: its const accessor on Simulation and on ArenaSimulation had no other user, so both are gone. Nothing outside the command path can now reach the system. No behaviour changes; the bodies move verbatim apart from two arrows in comments that were non-ASCII. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ne3mejABZoLWKLh8fgpM3x
239 lines
9.9 KiB
C++
239 lines
9.9 KiB
C++
#pragma once
|
|
|
|
#include <memory>
|
|
#include <optional>
|
|
#include <random>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include <QPoint>
|
|
|
|
#include "BeltSystem.h"
|
|
#include "FactoryState.h"
|
|
#include "EntityAdmin.h"
|
|
#include "entt/entity/entity.hpp"
|
|
#include "SchematicChoiceOption.h"
|
|
#include "BuildingType.h"
|
|
#include "BuildingId.h"
|
|
#include "EventHandler.h"
|
|
#include "BeamFiredEvent.h"
|
|
#include "GameConfig.h"
|
|
#include "Rotation.h"
|
|
#include "Tick.h"
|
|
#include "TracePrintRequestedEvent.h"
|
|
#include "UnlockState.h"
|
|
|
|
class AiSystem;
|
|
class BuildingSystem;
|
|
class ConstructionSystem;
|
|
class DeconstructionSystem;
|
|
struct Command;
|
|
class Hasher;
|
|
class CombatSystem;
|
|
class DynamicBodySystem;
|
|
class MovementIntentSystem;
|
|
class RepairSystem;
|
|
class SalvagerSystem;
|
|
class ShipSystem;
|
|
class DebrisSystem;
|
|
class WaveSystem;
|
|
|
|
class Simulation: public CombinedEventHandler<TracePrintRequestedEvent>
|
|
{
|
|
public:
|
|
explicit Simulation(GameConfig config, unsigned int seed = 0);
|
|
~Simulation();
|
|
|
|
const GameConfig& getConfig() const;
|
|
|
|
// Reinitializes all simulation state as if constructed fresh.
|
|
void reset(unsigned int seed = 0);
|
|
|
|
// Reloads config then reinitializes all simulation state.
|
|
void reset(GameConfig newConfig, unsigned int seed = 0);
|
|
|
|
// Advances the simulation by one tick. Tick order per architecture.md §Tick Order.
|
|
void tick();
|
|
|
|
// The single command chokepoint: applies one player command by dispatching
|
|
// to the underlying mutators. Every sim mutation during play must flow
|
|
// through here so it can be recorded and replayed (see docs/replay_design.md
|
|
// and CommandManager). Reached via CommandManager::drain.
|
|
void apply(const Command& command);
|
|
|
|
// Returns all fire events accumulated since the last drain, clearing the
|
|
// internal queue. Call once per rendered frame (REQ-SHP-FIRING-BEAM).
|
|
std::vector<BeamFiredEvent> drainBeamFiredEvents();
|
|
|
|
// Returns the pending schematic choices (empty if no drop is pending).
|
|
const std::vector<SchematicChoiceOption>& getPendingSchematicChoices() const;
|
|
|
|
// Returns true if there are pending schematic choices waiting for player input.
|
|
bool hasSchematicChoicesPending() const;
|
|
|
|
Tick getCurrentTick() const;
|
|
// The seed this run was (re)initialized with; written to the replay header.
|
|
unsigned int getSeed() const;
|
|
int getBuildingBlocksStock() const;
|
|
// Current asteroid width in tiles = base width + purchased expansions
|
|
// (REQ-EXP-UNLOCK, REQ-GW-ASTEROID-EXPAND).
|
|
int getCurrentAsteroidWidth_tiles() const;
|
|
// Building block cost of the next expansion, floored to an integer
|
|
// (REQ-EXP-COST); x = number of expansions already purchased.
|
|
int getCurrentExpansionCost() const;
|
|
bool isGameOver() const;
|
|
bool isWon() const;
|
|
int getArtifactCount() const;
|
|
double getThreatLevel() const;
|
|
double getThreatAccumulationRate() const;
|
|
double getMaxFactoryProductionThreatRate() const;
|
|
double getCurrentFactoryProductionThreatRate() const;
|
|
int getBossWaveCounter() const;
|
|
Tick getBossCountdownTicks() const;
|
|
Tick getNormalGapRemainingTicks() const;
|
|
|
|
// Ship schematic state query.
|
|
bool isSchematicUnlocked(const std::string& shipId) const;
|
|
|
|
// Module schematic state query.
|
|
bool isModuleSchematicUnlocked(const std::string& moduleId) const;
|
|
|
|
// Implicit recipe/item unlock queries (REQ-LOCK-IMPLICIT).
|
|
bool isRecipeUnlocked(const std::string& recipeId) const;
|
|
bool isItemUnlocked(const std::string& itemId) const;
|
|
|
|
// Building unlock query (REQ-LOCK-BUILDING). True if the building type is not
|
|
// gated by any unlock group, or its granting group has been awarded.
|
|
bool isBuildingUnlocked(BuildingType type) const;
|
|
|
|
// -- Determinism (see docs/replay_design.md) -----------------------------
|
|
// 64-bit fingerprint of the RNG stream state. Cheap; written to the replay
|
|
// file periodically + after each command for desync detection.
|
|
unsigned long long getRngFingerprint() const;
|
|
|
|
// 64-bit fingerprint of the full simulation state (RNG, scalars, buildings,
|
|
// belts, and ECS component state). Used by the double-run determinism test;
|
|
// a superset of getRngFingerprint().
|
|
unsigned long long computeStateChecksum() const;
|
|
|
|
// Const subsystem accessors (queries only). The mutable counterparts are
|
|
// private and reachable only through Simulation::apply (the command
|
|
// chokepoint) or, in tests, SimulationTestAccess — so production code cannot
|
|
// mutate the factory outside the recorded command path (docs/replay_design.md).
|
|
// BuildingSystem has no const accessor: it answers no queries, so nothing outside
|
|
// the command path has a reason to reach it (FactoryQueries.h).
|
|
|
|
// The factory's world data, for the free queries in FactoryQueries.h.
|
|
const FactoryState& getFactoryState() const;
|
|
const BeltSystem& getBelts() const;
|
|
ShipSystem& getShips();
|
|
const ShipSystem& getShips() const;
|
|
DebrisSystem& getDebrisSystem();
|
|
const DebrisSystem& getDebrisSystem() const;
|
|
EntityAdmin& getAdmin();
|
|
const EntityAdmin& getAdmin() const;
|
|
|
|
private:
|
|
// Grants tests access to the private player-action mutators below without
|
|
// opening them to production code (see src/test/SimulationTestAccess.h).
|
|
friend struct SimulationTestAccess;
|
|
|
|
// -- Player-action mutators (command chokepoint only) --------------------
|
|
// Reached during play exclusively via apply(); never called by UI/app code.
|
|
|
|
// Checks affordability, deducts building blocks, and places the building.
|
|
// Returns the new entity id, or nullopt if blocks are insufficient.
|
|
std::optional<BuildingId> tryPlaceBuilding(BuildingType type, QPoint anchor, Rotation rotation);
|
|
|
|
// Marks the building with the given id for demolition: a construction site is
|
|
// removed instantly (full refund), a built building is queued for timed
|
|
// deconstruction (REQ-BLD-DECON-QUEUE), refunded on completion.
|
|
void deconstruct(BuildingId id);
|
|
|
|
// Takes a queued building back out of the deconstruction queue (REQ-BLD-DECON-QUEUE).
|
|
void cancelDeconstruction(BuildingId id);
|
|
|
|
// Applies the player's chosen schematic from the pending choices.
|
|
// choiceIndex must be in [0, pendingChoices.size()).
|
|
// Clears the pending choices after application.
|
|
void applySchematicChoice(int choiceIndex);
|
|
|
|
// Unlocks one asteroid expansion if affordable (REQ-EXP-UNLOCK): checks the
|
|
// current cost against the stock, deducts it, increments the expansion
|
|
// counter, and widens the buildable asteroid. No-op if blocks are short.
|
|
void tryExpandAsteroid();
|
|
|
|
// Mutable subsystem accessors; same chokepoint rule as the mutators above.
|
|
BuildingSystem& getBuildingsMutable();
|
|
BeltSystem& getBeltsMutable();
|
|
|
|
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
|
|
// go through here. Only called before the first tick of a run.
|
|
void initializeSubsystems();
|
|
|
|
// Populate HQ, player defence stations, and the first enemy station set.
|
|
void placeInitialStructures();
|
|
|
|
// Place two enemy defence stations for the given generation level.
|
|
// Stores their IDs in m_currentEnemyStationIds.
|
|
void placeEnemyStationSet(int generation);
|
|
|
|
// Tick step 9: remove dead ships and buildings, drop debris, handle push.
|
|
void tickDeathsAndLoot();
|
|
|
|
// Generate up to 3 schematic choices (REQ-DEF-SCHEMATIC-DROP) for the player.
|
|
void generateSchematicChoices(int destroyedStationLevel);
|
|
|
|
GameConfig m_config;
|
|
std::mt19937 m_rng;
|
|
unsigned int m_seed;
|
|
|
|
Tick m_currentTick;
|
|
Tick m_nextDepartureTick;
|
|
BuildingId m_nextBuildingId;
|
|
int m_buildingBlocksStock;
|
|
int m_expansionsPurchased = 0; // REQ-EXP-COST formula variable x
|
|
bool m_gameOver = false;
|
|
bool m_isWon = false;
|
|
int m_artifactCount = 0;
|
|
|
|
// Pre-placed structure IDs.
|
|
std::optional<BuildingId> m_hqBuildingId; // Building id (for belt integration)
|
|
entt::entity m_hqProxyEntity; // ECS entity (HP, targeting)
|
|
entt::entity m_playerStation1Entity;
|
|
entt::entity m_playerStation2Entity;
|
|
entt::entity m_currentEnemyStationEntities[2];
|
|
|
|
// Schematic/unlock bookkeeping (REQ-DEF-SCHEMATIC-DROP, REQ-LOCK-EXPLICIT,
|
|
// REQ-LOCK-IMPLICIT, REQ-LOCK-BUILDING, REQ-LOCK-PREREQ). Constructed before
|
|
// initializeSubsystems() runs since BuildingSystem's spawn-gating lambda
|
|
// calls into it (see initializeSubsystems()).
|
|
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<ConstructionSystem> m_constructionSystem;
|
|
std::unique_ptr<DeconstructionSystem> m_deconstructionSystem;
|
|
std::unique_ptr<ShipSystem> m_shipSystem;
|
|
std::unique_ptr<AiSystem> m_aiSystem;
|
|
std::unique_ptr<MovementIntentSystem> m_movementIntentSystem;
|
|
std::unique_ptr<DynamicBodySystem> m_dynamicBodySystem;
|
|
std::unique_ptr<DebrisSystem> m_debrisSystem;
|
|
std::unique_ptr<SalvagerSystem> m_salvagerSystem;
|
|
std::unique_ptr<RepairSystem> m_repairSystem;
|
|
std::unique_ptr<WaveSystem> m_waveSystem;
|
|
std::unique_ptr<CombatSystem> m_combatSystem;
|
|
|
|
std::vector<BeamFiredEvent> m_beamFiredEvents;
|
|
std::vector<SchematicChoiceOption> m_pendingSchematicChoices;
|
|
};
|