Reshapes every UI-driven sim mutation to flow through one path so it can be recorded and replayed later, with behaviour unchanged. - Command model (lib): Command base + derived types (PlaceBuilding, Demolish, RotateInPlace, SetRecipe, SetShipLayout, Set[Site]SplitterFilters, ClearBeltTiles, ApplySchematicChoice, Reset), each with a playerId for the future-multiplayer shape. PlaceBuilding is atomic (carries optional recipe/layout/filters) because deferred commands never return the new BuildingId to the caller. - CommandManager (lib): FIFO queue holding a Simulation&; enqueue + drain. - Simulation::apply(const Command&): the single chokepoint, dispatching by kind to the existing mutators. Mutators stay public (enforced by convention, not compile-time, so the test suite keeps driving the sim directly). - Timing: GameWorldView owns the CommandManager and drains it once per frame in onFrame, before the tick batch (runs at 0x too, so build-while-paused is preserved). A drained Reset triggers the view reset. - UI fan-in: GameWorldView enqueues its own input directly; MainWindow and SelectedBuildingPanel emit CommandRequestedEvent, which GameWorldView subscribes to and enqueues. No UI site mutates the sim directly anymore. - CommandTest: asserts apply(...) yields byte-identical state to the direct mutator path, and that CommandManager drains FIFO through apply. Full suite green (338 cases / 3353 assertions); determinism double-run still passes. Design doc updated with the atomic-PlaceBuilding and convention- enforcement decisions. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DUsFgd2Ga6pmLz8giS8WUn
221 lines
8.5 KiB
C++
221 lines
8.5 KiB
C++
#pragma once
|
|
|
|
#include <map>
|
|
#include <memory>
|
|
#include <random>
|
|
#include <set>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include <QPoint>
|
|
|
|
#include "BeltSystem.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"
|
|
|
|
class AiSystem;
|
|
class BuildingSystem;
|
|
struct Command;
|
|
class Hasher;
|
|
class CombatSystem;
|
|
class DynamicBodySystem;
|
|
class MovementIntentSystem;
|
|
class RepairSystem;
|
|
class SalvagerSystem;
|
|
class ShipSystem;
|
|
class ScrapSystem;
|
|
class WaveSystem;
|
|
|
|
class Simulation: public CombinedEventHandler<TracePrintRequestedEvent>
|
|
{
|
|
public:
|
|
explicit Simulation(GameConfig config, unsigned int seed = 0);
|
|
~Simulation();
|
|
|
|
const GameConfig& config() 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;
|
|
|
|
// 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);
|
|
|
|
Tick currentTick() const;
|
|
int buildingBlocksStock() const;
|
|
bool isGameOver() const;
|
|
double threatLevel() const;
|
|
double threatAccumulationRate() const;
|
|
double maxFactoryProductionThreatRate() const;
|
|
double currentFactoryProductionThreatRate() const;
|
|
int bossWaveCounter() const;
|
|
Tick bossCountdownTicks() const;
|
|
Tick normalGapRemainingTicks() const;
|
|
|
|
// Ship schematic state queries.
|
|
int schematicLevel(const std::string& shipId) const;
|
|
bool isSchematicUnlocked(const std::string& shipId) const;
|
|
|
|
// Module schematic state queries.
|
|
int moduleSchematicLevel(const std::string& moduleId) const;
|
|
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;
|
|
|
|
// -- 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 rngFingerprint() 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 rngFingerprint().
|
|
unsigned long long computeStateChecksum() const;
|
|
|
|
// Checks affordability, deducts building blocks, and places the building.
|
|
// Returns the new entity id, or kInvalidBuildingId if blocks are insufficient.
|
|
BuildingId tryPlaceBuilding(BuildingType type, QPoint anchor, Rotation rotation);
|
|
|
|
// Demolishes the building with the given id and refunds building blocks.
|
|
void demolish(BuildingId id);
|
|
|
|
BuildingSystem& buildings();
|
|
const BuildingSystem& buildings() const;
|
|
BeltSystem& belts();
|
|
const BeltSystem& belts() const;
|
|
ShipSystem& ships();
|
|
const ShipSystem& ships() const;
|
|
ScrapSystem& scraps();
|
|
const ScrapSystem& scraps() const;
|
|
EntityAdmin& admin();
|
|
const EntityAdmin& admin() const;
|
|
|
|
private:
|
|
void handleEvent(std::shared_ptr<const TracePrintRequestedEvent> event) override;
|
|
|
|
BuildingId allocateBuildingId(); // Strictly increasing; never returns kInvalidBuildingId.
|
|
|
|
// 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 scrap, 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;
|
|
|
|
Tick m_currentTick;
|
|
Tick m_nextDepartureTick;
|
|
BuildingId m_nextBuildingId;
|
|
int m_buildingBlocksStock;
|
|
bool m_gameOver = false;
|
|
|
|
// Pre-placed structure IDs.
|
|
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 state (REQ-DEF-SCHEMATIC-DROP).
|
|
struct SchematicState
|
|
{
|
|
bool unlocked;
|
|
int level;
|
|
};
|
|
std::map<std::string, SchematicState> m_schematicLevels;
|
|
std::map<std::string, SchematicState> m_moduleSchematicLevels;
|
|
|
|
// Determinism helpers — fold sub-state into the hasher in deterministic order.
|
|
static void appendSchematicMap(Hasher& hasher,
|
|
const std::map<std::string, SchematicState>& levels);
|
|
static void appendStringSet(Hasher& hasher, const std::set<std::string>& ids);
|
|
|
|
// Explicitly unlocked assembler recipe schematics (REQ-LOCK-EXPLICIT).
|
|
std::set<std::string> m_unlockedRecipeSchematicIds;
|
|
|
|
// Implicit unlock sets derived from schematic state (REQ-LOCK-IMPLICIT).
|
|
std::set<std::string> m_unlockedRecipeIds;
|
|
std::set<std::string> m_unlockedItemIds;
|
|
|
|
// Recomputes m_unlockedRecipeIds and m_unlockedItemIds from current schematic state.
|
|
void recomputeUnlocked();
|
|
|
|
// Result of the REQ-LOCK-IMPLICIT traversal.
|
|
struct UnlockedSets
|
|
{
|
|
std::set<std::string> itemIds;
|
|
std::set<std::string> recipeIds;
|
|
};
|
|
|
|
// Pure REQ-LOCK-IMPLICIT traversal given hypothetical explicit-unlock sets.
|
|
UnlockedSets computeUnlockedSets(const std::set<std::string>& unlockedShipSchematicIds,
|
|
const std::set<std::string>& unlockedModuleSchematicIds,
|
|
const std::set<std::string>& unlockedRecipeSchematicIds) const;
|
|
|
|
// Current explicit-unlock id sets, derived from m_schematicLevels / m_moduleSchematicLevels.
|
|
std::set<std::string> getUnlockedShipSchematicIds() const;
|
|
std::set<std::string> getUnlockedModuleSchematicIds() const;
|
|
|
|
// Display names (deduplicated, alphabetical) of output items of recipes in
|
|
// hypothetical.recipeIds that are not yet in m_unlockedRecipeIds.
|
|
std::vector<std::string> computeNewlyUnlockedItemNames(const UnlockedSets& hypothetical) const;
|
|
|
|
EntityAdmin m_admin;
|
|
BeltSystem m_beltSystem;
|
|
std::unique_ptr<BuildingSystem> m_buildingSystem;
|
|
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<ScrapSystem> m_scrapSystem;
|
|
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;
|
|
};
|