The member reference is gone. All 23 methods that read or write the factory now take FactoryState& (const for the two item walks and the checksum fold), so a BuildingSystem is no longer bound to one state and its signatures say which data each call touches. It holds only config, belts, rng and the callbacks — the same shape as AiSystem and CombatSystem. This completes what phase 2 set out to do; the ownership move landed earlier, but the systems kept reaching the data through a member until the queries were off them. Seeding the asteroid bound moved with the state, and that broke four tests: the fixtures build their own FactoryState, which defaulted the bound to 0 and refused every placement on the asteroid. Rather than fix the four call sites, makeFactoryState() now creates a run's state from the config, and Simulation, ArenaSimulation and the test fixtures all use it — there is one place that knows what a fresh factory looks like. Verified with a golden-checksum capture before and after — all four sample ticks identical — and by re-running the declaration/definition check over the header. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YHcUerKAZKWNvSKJxYKbnG
142 lines
5.0 KiB
C++
142 lines
5.0 KiB
C++
#include "catch.hpp"
|
|
#include "FactoryQueries.h"
|
|
|
|
#include <algorithm>
|
|
|
|
#include "Building.h"
|
|
#include "BuildingConfig.h"
|
|
#include "BuildingSystem.h"
|
|
#include "BuildingType.h"
|
|
#include "ConfigLoader.h"
|
|
#include "GameConfig.h"
|
|
#include "Rotation.h"
|
|
#include "ShipLayout.h"
|
|
#include "ShipsConfig.h"
|
|
#include "Simulation.h"
|
|
#include "SimulationTestAccess.h"
|
|
#include "TestConfig.h"
|
|
|
|
// readBuildingConfig underpins the copy-settings gesture (REQ-BLD-COPY-CONFIG):
|
|
// it extracts a building's recipe / schematic / layout / splitter filters so they
|
|
// can be stamped onto a same-type building. It reads operational buildings and
|
|
// construction sites alike.
|
|
|
|
namespace
|
|
{
|
|
const BuildingDef* findDef(const GameConfig& cfg, BuildingType type)
|
|
{
|
|
for (const BuildingDef& def : cfg.buildings.buildings)
|
|
{
|
|
if (def.type == type) { return &def; }
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
BuildingId placeOperational(Simulation& sim, const GameConfig& cfg,
|
|
BuildingType type, QPoint anchor)
|
|
{
|
|
const BuildingDef* def = findDef(cfg, type);
|
|
REQUIRE(def != nullptr);
|
|
return SimulationTestAccess::buildings(sim).placeImmediate(SimulationTestAccess::state(sim), type, def->surfaceMask, anchor, Rotation::East);
|
|
}
|
|
|
|
const ShipDef* findAvailableSchematic(const GameConfig& cfg)
|
|
{
|
|
for (const ShipDef& def : cfg.ships.ships)
|
|
{
|
|
// A ship starts unlocked iff no unlock group grants it (REQ-LOCK-EXPLICIT).
|
|
bool granted = false;
|
|
for (const UnlockGroupDef& group : cfg.unlocks.groups)
|
|
{
|
|
if (std::find(group.ships.begin(), group.ships.end(), def.id) != group.ships.end())
|
|
{
|
|
granted = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!granted) { return &def; }
|
|
}
|
|
return nullptr;
|
|
}
|
|
} // namespace
|
|
|
|
TEST_CASE("readBuildingConfig returns a miner's selected recipe", "[copyconfig]")
|
|
{
|
|
const GameConfig cfg = loadTestConfig();
|
|
Simulation sim(loadTestConfig(), 7);
|
|
|
|
const BuildingId id = placeOperational(sim, cfg, BuildingType::Miner, QPoint(0, 0));
|
|
SimulationTestAccess::buildings(sim).setRecipe(SimulationTestAccess::state(sim), id, "mine_iron_ore");
|
|
|
|
const std::optional<BuildingConfig> config = readBuildingConfig(sim, id);
|
|
REQUIRE(config.has_value());
|
|
CHECK(config->type == BuildingType::Miner);
|
|
REQUIRE(config->recipeId.has_value());
|
|
CHECK(*config->recipeId == "mine_iron_ore");
|
|
CHECK_FALSE(config->isSplitter);
|
|
CHECK_FALSE(config->shipLayout.has_value());
|
|
}
|
|
|
|
TEST_CASE("readBuildingConfig leaves recipe unset when nothing is selected",
|
|
"[copyconfig]")
|
|
{
|
|
const GameConfig cfg = loadTestConfig();
|
|
Simulation sim(loadTestConfig(), 7);
|
|
|
|
const BuildingId id = placeOperational(sim, cfg, BuildingType::Assembler, QPoint(0, 0));
|
|
|
|
const std::optional<BuildingConfig> config = readBuildingConfig(sim, id);
|
|
REQUIRE(config.has_value());
|
|
CHECK(config->type == BuildingType::Assembler);
|
|
CHECK_FALSE(config->recipeId.has_value()); // nothing to copy
|
|
CHECK_FALSE(config->isSplitter);
|
|
}
|
|
|
|
TEST_CASE("readBuildingConfig returns a shipyard's schematic and layout",
|
|
"[copyconfig]")
|
|
{
|
|
const GameConfig cfg = loadTestConfig();
|
|
Simulation sim(loadTestConfig(), 7);
|
|
|
|
const ShipDef* schematic = findAvailableSchematic(cfg);
|
|
REQUIRE(schematic != nullptr);
|
|
|
|
const BuildingId id = placeOperational(sim, cfg, BuildingType::Shipyard, QPoint(0, 0));
|
|
SimulationTestAccess::buildings(sim).setRecipe(SimulationTestAccess::state(sim), id, schematic->id);
|
|
SimulationTestAccess::buildings(sim).setShipLayout(SimulationTestAccess::state(sim), id, ShipLayoutConfig{});
|
|
|
|
const std::optional<BuildingConfig> config = readBuildingConfig(sim, id);
|
|
REQUIRE(config.has_value());
|
|
CHECK(config->type == BuildingType::Shipyard);
|
|
REQUIRE(config->recipeId.has_value());
|
|
CHECK(*config->recipeId == schematic->id);
|
|
CHECK(config->shipLayout.has_value());
|
|
}
|
|
|
|
TEST_CASE("readBuildingConfig reads a queued construction site", "[copyconfig]")
|
|
{
|
|
const GameConfig cfg = loadTestConfig();
|
|
Simulation sim(loadTestConfig(), 7);
|
|
|
|
// A placed miner enters the construction queue as a site (not yet operational).
|
|
const BuildingId id =
|
|
SimulationTestAccess::place(sim, BuildingType::Miner, QPoint(-2, 0), Rotation::East).value();
|
|
REQUIRE(id != kInvalidBuildingId);
|
|
REQUIRE(findBuilding(sim.getFactoryState(), id) == nullptr);
|
|
REQUIRE(findSite(sim.getFactoryState(), id) != nullptr);
|
|
|
|
SimulationTestAccess::buildings(sim).setRecipe(SimulationTestAccess::state(sim), id, "mine_iron_ore");
|
|
|
|
const std::optional<BuildingConfig> config = readBuildingConfig(sim, id);
|
|
REQUIRE(config.has_value());
|
|
CHECK(config->type == BuildingType::Miner);
|
|
REQUIRE(config->recipeId.has_value());
|
|
CHECK(*config->recipeId == "mine_iron_ore");
|
|
}
|
|
|
|
TEST_CASE("readBuildingConfig returns nullopt for an unknown id", "[copyconfig]")
|
|
{
|
|
Simulation sim(loadTestConfig(), 7);
|
|
CHECK_FALSE(readBuildingConfig(sim, kInvalidBuildingId).has_value());
|
|
}
|