replay: compile-enforce the command chokepoint
Privatize the Simulation player-action mutators (tryPlaceBuilding, demolish, applySchematicChoice) and the mutable subsystem accessors (now buildingsMutable/ beltsMutable; only the const buildings()/belts() stay public). Production's only handle to a subsystem is through Simulation, and every production buildings()/ belts() call is a const query, so this fully locks gameplay out of any command-bypassing mutation path -- such code now fails to compile instead of silently desyncing replays. Tests reach the private mutators through SimulationTestAccess, a friend struct under src/test (off the lib/ui/app include path), so they keep calling the real mutators and keep their return values -- no id-by-position recovery needed. The BuildingSystem subsystem mutators stay public (BuildingTest unit-tests a bare subsystem; unreachable from production anyway). Upgrades the Phase 1 "convention only" decision to structural enforcement. All targets build; 354 cases / 3418 assertions pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DUsFgd2Ga6pmLz8giS8WUn
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
#include "ShipStatsCalculator.h"
|
||||
#include "ShipSystem.h"
|
||||
#include "Simulation.h"
|
||||
#include "SimulationTestAccess.h"
|
||||
#include "Tick.h"
|
||||
#include "WeaponComponent.h"
|
||||
|
||||
@@ -62,7 +63,7 @@ static const BuildingDef* findShipyardDef(const GameConfig& cfg)
|
||||
|
||||
static BuildingId placeShipyard(Simulation& sim, const BuildingDef& yardDef)
|
||||
{
|
||||
return sim.buildings().placeImmediate(
|
||||
return SimulationTestAccess::buildings(sim).placeImmediate(
|
||||
BuildingType::Shipyard,
|
||||
yardDef.surfaceMask,
|
||||
QPoint(0, 0),
|
||||
@@ -73,7 +74,7 @@ static void fillMaterials(Simulation& sim, BuildingId yardId,
|
||||
const ShipDef& def,
|
||||
const ShipLayoutConfig& layout)
|
||||
{
|
||||
sim.buildings().forEachBuilding([&](Building& b) {
|
||||
SimulationTestAccess::buildings(sim).forEachBuilding([&](Building& b) {
|
||||
if (b.id != yardId)
|
||||
{
|
||||
return;
|
||||
@@ -216,7 +217,7 @@ TEST_CASE("Shipyard: setShipLayout reinitializes buffers with module materials",
|
||||
REQUIRE(yardDef != nullptr);
|
||||
|
||||
const BuildingId yardId = placeShipyard(sim, *yardDef);
|
||||
sim.buildings().setRecipe(yardId, "interceptor");
|
||||
SimulationTestAccess::buildings(sim).setRecipe(yardId,"interceptor");
|
||||
|
||||
ShipLayoutConfig layout;
|
||||
PlacedModule pm;
|
||||
@@ -225,7 +226,7 @@ TEST_CASE("Shipyard: setShipLayout reinitializes buffers with module materials",
|
||||
pm.rotation = Rotation::East;
|
||||
layout.placedModules.push_back(pm);
|
||||
|
||||
sim.buildings().setShipLayout(yardId, layout);
|
||||
SimulationTestAccess::buildings(sim).setShipLayout(yardId, layout);
|
||||
|
||||
const Building* b = sim.buildings().findBuilding(yardId);
|
||||
REQUIRE(b != nullptr);
|
||||
@@ -245,7 +246,7 @@ TEST_CASE("Shipyard: setShipLayout cancels in-progress production",
|
||||
REQUIRE(yardDef != nullptr);
|
||||
|
||||
const BuildingId yardId = placeShipyard(sim, *yardDef);
|
||||
sim.buildings().setRecipe(yardId, "interceptor");
|
||||
SimulationTestAccess::buildings(sim).setRecipe(yardId,"interceptor");
|
||||
|
||||
// Fill materials and tick to start production.
|
||||
ShipLayoutConfig emptyLayout;
|
||||
@@ -264,7 +265,7 @@ TEST_CASE("Shipyard: setShipLayout cancels in-progress production",
|
||||
pm.rotation = Rotation::East;
|
||||
layout.placedModules.push_back(pm);
|
||||
|
||||
sim.buildings().setShipLayout(yardId, layout);
|
||||
SimulationTestAccess::buildings(sim).setShipLayout(yardId, layout);
|
||||
|
||||
const Building* b2 = sim.buildings().findBuilding(yardId);
|
||||
REQUIRE(b2 != nullptr);
|
||||
@@ -278,7 +279,7 @@ TEST_CASE("Shipyard: setRecipe clears ship layout", "[modules][shipyard]")
|
||||
REQUIRE(yardDef != nullptr);
|
||||
|
||||
const BuildingId yardId = placeShipyard(sim, *yardDef);
|
||||
sim.buildings().setRecipe(yardId, "interceptor");
|
||||
SimulationTestAccess::buildings(sim).setRecipe(yardId,"interceptor");
|
||||
|
||||
ShipLayoutConfig layout;
|
||||
PlacedModule pm;
|
||||
@@ -286,13 +287,13 @@ TEST_CASE("Shipyard: setRecipe clears ship layout", "[modules][shipyard]")
|
||||
pm.position = QPoint(0, 0);
|
||||
pm.rotation = Rotation::East;
|
||||
layout.placedModules.push_back(pm);
|
||||
sim.buildings().setShipLayout(yardId, layout);
|
||||
SimulationTestAccess::buildings(sim).setShipLayout(yardId, layout);
|
||||
|
||||
const Building* b1 = sim.buildings().findBuilding(yardId);
|
||||
REQUIRE(b1 != nullptr);
|
||||
REQUIRE(b1->shipLayout.has_value());
|
||||
|
||||
sim.buildings().setRecipe(yardId, "destroyer");
|
||||
SimulationTestAccess::buildings(sim).setRecipe(yardId,"destroyer");
|
||||
|
||||
const Building* b2 = sim.buildings().findBuilding(yardId);
|
||||
REQUIRE(b2 != nullptr);
|
||||
|
||||
Reference in New Issue
Block a user