share one loadTestConfig() helper across the tests
This commit is contained in:
@@ -11,11 +11,7 @@
|
||||
#include "Simulation.h"
|
||||
#include "SimulationTestAccess.h"
|
||||
#include "StationBodyComponent.h"
|
||||
|
||||
static GameConfig loadConfig()
|
||||
{
|
||||
return ConfigLoader::loadFromDirectory(CONFIG_DIR);
|
||||
}
|
||||
#include "TestConfig.h"
|
||||
|
||||
static void killEnemyStations(Simulation& sim)
|
||||
{
|
||||
@@ -51,7 +47,7 @@ static int findArtifactChoiceIndex(const Simulation& sim)
|
||||
TEST_CASE("ArtifactWinCondition: artifact_chance_formula and artifact_win_count are loaded",
|
||||
"[artifact_win]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
CHECK(cfg.world.artifacts.artifactWinCount == 3);
|
||||
// 0.05 * x at x=2 should be 0.1
|
||||
CHECK(cfg.world.artifacts.artifactChanceFormula.evaluate(2.0) == Approx(0.1));
|
||||
@@ -64,7 +60,7 @@ TEST_CASE("ArtifactWinCondition: artifact_chance_formula and artifact_win_count
|
||||
TEST_CASE("ArtifactWinCondition: artifact count is 0 and isWon is false at game start",
|
||||
"[artifact_win]")
|
||||
{
|
||||
const Simulation sim(loadConfig());
|
||||
const Simulation sim(loadTestConfig());
|
||||
CHECK(sim.getArtifactCount() == 0);
|
||||
CHECK_FALSE(sim.isWon());
|
||||
}
|
||||
@@ -76,7 +72,7 @@ TEST_CASE("ArtifactWinCondition: artifact count is 0 and isWon is false at game
|
||||
TEST_CASE("ArtifactWinCondition: artifact option appears when chance formula returns 1",
|
||||
"[artifact_win]")
|
||||
{
|
||||
GameConfig cfg = loadConfig();
|
||||
GameConfig cfg = loadTestConfig();
|
||||
cfg.world.artifacts.artifactChanceFormula = Formula::compile("1");
|
||||
Simulation sim(std::move(cfg));
|
||||
|
||||
@@ -95,7 +91,7 @@ TEST_CASE("ArtifactWinCondition: artifact option appears when chance formula ret
|
||||
TEST_CASE("ArtifactWinCondition: at most 2 schematic options accompany the artifact",
|
||||
"[artifact_win]")
|
||||
{
|
||||
GameConfig cfg = loadConfig();
|
||||
GameConfig cfg = loadTestConfig();
|
||||
cfg.world.artifacts.artifactChanceFormula = Formula::compile("1");
|
||||
Simulation sim(std::move(cfg));
|
||||
|
||||
@@ -112,7 +108,7 @@ TEST_CASE("ArtifactWinCondition: at most 2 schematic options accompany the artif
|
||||
TEST_CASE("ArtifactWinCondition: no artifact option when chance formula returns 0",
|
||||
"[artifact_win]")
|
||||
{
|
||||
GameConfig cfg = loadConfig();
|
||||
GameConfig cfg = loadTestConfig();
|
||||
cfg.world.artifacts.artifactChanceFormula = Formula::compile("0");
|
||||
Simulation sim(std::move(cfg));
|
||||
|
||||
@@ -133,7 +129,7 @@ TEST_CASE("ArtifactWinCondition: no artifact option when chance formula returns
|
||||
TEST_CASE("ArtifactWinCondition: selecting artifact increments artifact count",
|
||||
"[artifact_win]")
|
||||
{
|
||||
GameConfig cfg = loadConfig();
|
||||
GameConfig cfg = loadTestConfig();
|
||||
cfg.world.artifacts.artifactChanceFormula = Formula::compile("1");
|
||||
Simulation sim(std::move(cfg));
|
||||
|
||||
@@ -151,7 +147,7 @@ TEST_CASE("ArtifactWinCondition: selecting artifact increments artifact count",
|
||||
TEST_CASE("ArtifactWinCondition: selecting a non-artifact option does not increment artifact count",
|
||||
"[artifact_win]")
|
||||
{
|
||||
GameConfig cfg = loadConfig();
|
||||
GameConfig cfg = loadTestConfig();
|
||||
cfg.world.artifacts.artifactChanceFormula = Formula::compile("1");
|
||||
Simulation sim(std::move(cfg));
|
||||
|
||||
@@ -176,7 +172,7 @@ TEST_CASE("ArtifactWinCondition: selecting a non-artifact option does not increm
|
||||
TEST_CASE("ArtifactWinCondition: isWon becomes true when artifact count reaches win count",
|
||||
"[artifact_win]")
|
||||
{
|
||||
GameConfig cfg = loadConfig();
|
||||
GameConfig cfg = loadTestConfig();
|
||||
cfg.world.artifacts.artifactChanceFormula = Formula::compile("1");
|
||||
cfg.world.artifacts.artifactWinCount = 1;
|
||||
Simulation sim(std::move(cfg));
|
||||
@@ -196,7 +192,7 @@ TEST_CASE("ArtifactWinCondition: isWon becomes true when artifact count reaches
|
||||
TEST_CASE("ArtifactWinCondition: isWon stays false when artifact count is below win count",
|
||||
"[artifact_win]")
|
||||
{
|
||||
GameConfig cfg = loadConfig();
|
||||
GameConfig cfg = loadTestConfig();
|
||||
cfg.world.artifacts.artifactChanceFormula = Formula::compile("1");
|
||||
cfg.world.artifacts.artifactWinCount = 2;
|
||||
Simulation sim(std::move(cfg));
|
||||
@@ -212,7 +208,7 @@ TEST_CASE("ArtifactWinCondition: isWon stays false when artifact count is below
|
||||
TEST_CASE("ArtifactWinCondition: isWon becomes true after collecting required number of artifacts",
|
||||
"[artifact_win]")
|
||||
{
|
||||
GameConfig cfg = loadConfig();
|
||||
GameConfig cfg = loadTestConfig();
|
||||
cfg.world.artifacts.artifactChanceFormula = Formula::compile("1");
|
||||
cfg.world.artifacts.artifactWinCount = 2;
|
||||
Simulation sim(std::move(cfg));
|
||||
@@ -237,7 +233,7 @@ TEST_CASE("ArtifactWinCondition: isWon becomes true after collecting required nu
|
||||
TEST_CASE("ArtifactWinCondition: reset clears artifact count and win state",
|
||||
"[artifact_win]")
|
||||
{
|
||||
GameConfig cfg = loadConfig();
|
||||
GameConfig cfg = loadTestConfig();
|
||||
cfg.world.artifacts.artifactChanceFormula = Formula::compile("1");
|
||||
cfg.world.artifacts.artifactWinCount = 1;
|
||||
Simulation sim(std::move(cfg));
|
||||
|
||||
@@ -45,16 +45,12 @@
|
||||
#include "ShipLayout.h"
|
||||
#include "ShipSystem.h"
|
||||
#include "Tick.h"
|
||||
#include "TestConfig.h"
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Fixture
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static GameConfig loadConfig()
|
||||
{
|
||||
return ConfigLoader::loadFromDirectory(CONFIG_DIR);
|
||||
}
|
||||
|
||||
struct Fixture
|
||||
{
|
||||
GameConfig cfg;
|
||||
@@ -75,7 +71,7 @@ struct Fixture
|
||||
std::vector<BeamFiredEvent> beamEvents;
|
||||
|
||||
explicit Fixture()
|
||||
: cfg(loadConfig())
|
||||
: cfg(loadTestConfig())
|
||||
, belts(cfg.world.beltSpeed_tps)
|
||||
, nextBuildingId(1)
|
||||
, stock(0)
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "SimulationTestAccess.h"
|
||||
#include "SurfaceMask.h"
|
||||
#include "Tick.h"
|
||||
#include "TestConfig.h"
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers that mirror the production implementations under test.
|
||||
@@ -147,11 +148,6 @@ static void applyRotationCCW(Blueprint& bp, const GameConfig& cfg)
|
||||
}
|
||||
}
|
||||
|
||||
static GameConfig loadConfig()
|
||||
{
|
||||
return ConfigLoader::loadFromDirectory(CONFIG_DIR);
|
||||
}
|
||||
|
||||
// Mirrors BlueprintPanel::createBlueprintFromSelection's player-placeable filter:
|
||||
// building types absent from buildings.toml (HQ, stations) or with playerPlaceable=false
|
||||
// are silently excluded before the bounding-box center and offsets are computed.
|
||||
@@ -312,7 +308,7 @@ TEST_CASE("Blueprint: non-axis-aligned offset rotates correctly", "[blueprint]")
|
||||
|
||||
TEST_CASE("Blueprint: CW constellation rotation updates offset and building rotation", "[blueprint]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
// Building one tile to the right, facing East.
|
||||
Blueprint bp;
|
||||
bp.name = "test";
|
||||
@@ -332,7 +328,7 @@ TEST_CASE("Blueprint: CW constellation rotation updates offset and building rota
|
||||
|
||||
TEST_CASE("Blueprint: CCW constellation rotation updates offset and building rotation", "[blueprint]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
Blueprint bp;
|
||||
bp.name = "test";
|
||||
BlueprintBuilding bb;
|
||||
@@ -351,7 +347,7 @@ TEST_CASE("Blueprint: CCW constellation rotation updates offset and building rot
|
||||
|
||||
TEST_CASE("Blueprint: four CW rotations restore offset and building rotation", "[blueprint]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
Blueprint bp;
|
||||
bp.name = "test";
|
||||
BlueprintBuilding bb;
|
||||
@@ -371,7 +367,7 @@ TEST_CASE("Blueprint: four CW rotations restore offset and building rotation", "
|
||||
|
||||
TEST_CASE("Blueprint: multi-building constellation rotates symmetrically CW", "[blueprint]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
// Two buildings left and right of center; after CW they should be above and below.
|
||||
Blueprint bp;
|
||||
bp.name = "test";
|
||||
@@ -398,7 +394,7 @@ TEST_CASE("Blueprint: multi-building constellation rotates symmetrically CW", "[
|
||||
|
||||
TEST_CASE("Blueprint: CW rotation keeps belt adjacent to miner output port", "[blueprint]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
|
||||
// East miner: anchor (0,0), body cells (0,0),(1,0),(0,1).
|
||||
// Output port indicator '>' at (1,1) → port tile (1,1), direction East.
|
||||
@@ -436,7 +432,7 @@ TEST_CASE("Blueprint: CW rotation keeps belt adjacent to miner output port", "[b
|
||||
|
||||
TEST_CASE("Blueprint: CCW rotation keeps belt adjacent to miner output port", "[blueprint]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
|
||||
Blueprint bp;
|
||||
bp.name = "test";
|
||||
@@ -473,7 +469,7 @@ TEST_CASE("Blueprint: CCW rotation keeps belt adjacent to miner output port", "[
|
||||
TEST_CASE("Blueprint creation: non-player-placeable building alone yields empty blueprint",
|
||||
"[blueprint]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
|
||||
// Hq has no entry in buildings.toml, so it is treated as non-player-placeable.
|
||||
const BuildingSpec hq{ QPoint(-5, 0), {QPoint(-5, 0)}, BuildingType::Hq, Rotation::East };
|
||||
@@ -485,7 +481,7 @@ TEST_CASE("Blueprint creation: non-player-placeable building alone yields empty
|
||||
TEST_CASE("Blueprint creation: mixed selection keeps only player-placeable buildings",
|
||||
"[blueprint]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
|
||||
const BuildingSpec belt{ QPoint(-5, 0), {QPoint(-5, 0)}, BuildingType::Belt, Rotation::East };
|
||||
const BuildingSpec hq { QPoint(-3, 0), {QPoint(-3, 0)}, BuildingType::Hq, Rotation::East };
|
||||
@@ -498,7 +494,7 @@ TEST_CASE("Blueprint creation: mixed selection keeps only player-placeable build
|
||||
TEST_CASE("Blueprint creation: bounding box ignores non-player-placeable buildings",
|
||||
"[blueprint]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
|
||||
// Belt at (-5, 0). HQ at (-3, 0) — excluded from the blueprint.
|
||||
// If HQ were included: bboxX = [-5, -3], center.x = -4, belt offset = -1.
|
||||
@@ -520,7 +516,7 @@ TEST_CASE("Blueprint placement: buildings land at anchor + offset from cursor",
|
||||
// Simulate placing a two-belt blueprint with offsets (-1, 0) and (+1, 0)
|
||||
// at cursor tile (-5, 0). Expected anchors: (-6, 0) and (-4, 0).
|
||||
// (Belt surface_mask ["A>"] — body at relative (0,0), port at (1,0).)
|
||||
Simulation sim(loadConfig());
|
||||
Simulation sim(loadTestConfig());
|
||||
|
||||
const QPoint cursor(-5, 0);
|
||||
const QPoint offsetA(-1, 0);
|
||||
@@ -540,7 +536,7 @@ TEST_CASE("Blueprint placement: buildings land at anchor + offset from cursor",
|
||||
|
||||
TEST_CASE("Blueprint placement: cost is deducted for each building in sequence", "[blueprint]")
|
||||
{
|
||||
Simulation sim(loadConfig());
|
||||
Simulation sim(loadTestConfig());
|
||||
|
||||
// Find belt cost from config (belt cost = 2 in test config).
|
||||
int beltCost = 0;
|
||||
@@ -563,7 +559,7 @@ TEST_CASE("Blueprint placement: cost is deducted for each building in sequence",
|
||||
TEST_CASE("Blueprint placement: insufficient blocks returns kInvalidBuildingId and deducts nothing",
|
||||
"[blueprint]")
|
||||
{
|
||||
Simulation sim(loadConfig());
|
||||
Simulation sim(loadTestConfig());
|
||||
|
||||
// Find miner cost (15 in test config) — expensive enough to exhaust a small stock.
|
||||
int minerCost = 0;
|
||||
@@ -594,7 +590,7 @@ TEST_CASE("Blueprint placement: insufficient blocks returns kInvalidBuildingId a
|
||||
TEST_CASE("Simulation: tryPlaceBuilding rejects terrain-invalid placement and charges nothing",
|
||||
"[blueprint]")
|
||||
{
|
||||
Simulation sim(loadConfig());
|
||||
Simulation sim(loadTestConfig());
|
||||
const int startBlocks = sim.getBuildingBlocksStock();
|
||||
|
||||
// A miner is all-asteroid; placing it in space (x >= 0) violates the terrain
|
||||
@@ -610,7 +606,7 @@ TEST_CASE("Simulation: tryPlaceBuilding rejects terrain-invalid placement and ch
|
||||
TEST_CASE("Simulation: tryPlaceBuilding accepts a valid asteroid spot, occupies tiles, charges cost",
|
||||
"[blueprint]")
|
||||
{
|
||||
Simulation sim(loadConfig());
|
||||
Simulation sim(loadTestConfig());
|
||||
|
||||
int minerCost = 0;
|
||||
for (const BuildingDef& def : sim.getConfig().buildings.buildings)
|
||||
@@ -663,7 +659,7 @@ TEST_CASE("Blueprint: building with no recipe has empty recipeId", "[blueprint]"
|
||||
|
||||
TEST_CASE("Blueprint placement: setRecipe on construction site stores recipe", "[blueprint]")
|
||||
{
|
||||
Simulation sim(loadConfig());
|
||||
Simulation sim(loadTestConfig());
|
||||
|
||||
// Miner body cells: (0,0),(1,0),(0,1) — all at x < 0, valid for asteroid.
|
||||
const BuildingId id = SimulationTestAccess::place(sim,BuildingType::Miner, QPoint(-2, 0), Rotation::East).value();
|
||||
@@ -679,7 +675,7 @@ TEST_CASE("Blueprint placement: setRecipe on construction site stores recipe", "
|
||||
TEST_CASE("Blueprint placement: recipe transfers to building after construction completes",
|
||||
"[blueprint]")
|
||||
{
|
||||
Simulation sim(loadConfig());
|
||||
Simulation sim(loadTestConfig());
|
||||
|
||||
const BuildingId id = SimulationTestAccess::place(sim,BuildingType::Miner, QPoint(-2, 0), Rotation::East).value();
|
||||
REQUIRE(id != kInvalidBuildingId);
|
||||
@@ -705,7 +701,7 @@ TEST_CASE("Blueprint placement: recipe transfers to building after construction
|
||||
|
||||
TEST_CASE("Blueprint creation: a construction site is captured", "[blueprint]")
|
||||
{
|
||||
Simulation sim(loadConfig());
|
||||
Simulation sim(loadTestConfig());
|
||||
|
||||
// Freshly placed → a ConstructionSite (not ticked to completion). A 1x1 belt keeps
|
||||
// the body-cell bounding-box centered on the anchor, so a single site → zero offset.
|
||||
@@ -724,7 +720,7 @@ TEST_CASE("Blueprint creation: a construction site is captured", "[blueprint]")
|
||||
|
||||
TEST_CASE("Blueprint creation: a construction site's recipe is captured", "[blueprint]")
|
||||
{
|
||||
Simulation sim(loadConfig());
|
||||
Simulation sim(loadTestConfig());
|
||||
|
||||
const BuildingId id =
|
||||
SimulationTestAccess::place(sim, BuildingType::Miner, QPoint(-2, 0), Rotation::East).value();
|
||||
@@ -740,7 +736,7 @@ TEST_CASE("Blueprint creation: a construction site's recipe is captured", "[blue
|
||||
TEST_CASE("Blueprint creation: mixed operational building and construction site are both captured",
|
||||
"[blueprint]")
|
||||
{
|
||||
Simulation sim(loadConfig());
|
||||
Simulation sim(loadTestConfig());
|
||||
|
||||
// Building A: place, configure, and tick to completion so it is operational.
|
||||
const BuildingId idA =
|
||||
@@ -772,7 +768,7 @@ TEST_CASE("Blueprint creation: mixed operational building and construction site
|
||||
|
||||
TEST_CASE("Blueprint creation: selectionHasPlaceableBuilding sees a construction site", "[blueprint]")
|
||||
{
|
||||
Simulation sim(loadConfig());
|
||||
Simulation sim(loadTestConfig());
|
||||
|
||||
REQUIRE_FALSE(selectionHasPlaceableBuilding(sim, {}));
|
||||
|
||||
@@ -787,7 +783,7 @@ TEST_CASE("Blueprint placement: interceptor schematic is unlocked at game start"
|
||||
{
|
||||
// "interceptor" has unlock_at_station_level = -1 in the test config.
|
||||
// This confirms the guard in placeBlueprintAtTile passes for start-unlocked schematics.
|
||||
Simulation sim(loadConfig());
|
||||
Simulation sim(loadTestConfig());
|
||||
REQUIRE(sim.isSchematicUnlocked("interceptor"));
|
||||
}
|
||||
|
||||
@@ -796,7 +792,7 @@ TEST_CASE("Blueprint placement: repair_ship schematic is locked at game start",
|
||||
// "repair_ship" has unlock_at_station_level = 0 in the test config.
|
||||
// This confirms the guard in placeBlueprintAtTile blocks locked schematics,
|
||||
// leaving the shipyard's schematic unset.
|
||||
Simulation sim(loadConfig());
|
||||
Simulation sim(loadTestConfig());
|
||||
REQUIRE_FALSE(sim.isSchematicUnlocked("repair_ship"));
|
||||
}
|
||||
|
||||
@@ -842,7 +838,7 @@ TEST_CASE("Blueprint: building without layout has nullopt shipLayout", "[bluepri
|
||||
|
||||
TEST_CASE("Blueprint placement: setShipLayout on construction site stores layout", "[blueprint]")
|
||||
{
|
||||
Simulation sim(loadConfig());
|
||||
Simulation sim(loadTestConfig());
|
||||
|
||||
// Shipyard surface_mask ["AAAS>","AAAS "] with Rotation::East:
|
||||
// A-tiles at (-3,0),(-2,0),(-1,0),(-3,1),(-2,1),(-1,1) — all x < 0, valid asteroid tiles.
|
||||
@@ -869,7 +865,7 @@ TEST_CASE("Blueprint placement: setShipLayout on construction site stores layout
|
||||
TEST_CASE("Blueprint placement: ship layout transfers to building after construction completes",
|
||||
"[blueprint]")
|
||||
{
|
||||
Simulation sim(loadConfig());
|
||||
Simulation sim(loadTestConfig());
|
||||
|
||||
const BuildingId id = SimulationTestAccess::place(sim,BuildingType::Shipyard, QPoint(-3, 0), Rotation::East).value();
|
||||
REQUIRE(id != kInvalidBuildingId);
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#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
|
||||
@@ -21,11 +22,6 @@
|
||||
|
||||
namespace
|
||||
{
|
||||
GameConfig loadConfig()
|
||||
{
|
||||
return ConfigLoader::loadFromDirectory(CONFIG_DIR);
|
||||
}
|
||||
|
||||
const BuildingDef* findDef(const GameConfig& cfg, BuildingType type)
|
||||
{
|
||||
for (const BuildingDef& def : cfg.buildings.buildings)
|
||||
@@ -66,8 +62,8 @@ const ShipDef* findAvailableSchematic(const GameConfig& cfg)
|
||||
|
||||
TEST_CASE("readBuildingConfig returns a miner's selected recipe", "[copyconfig]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
Simulation sim(loadConfig(), 7);
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
Simulation sim(loadTestConfig(), 7);
|
||||
|
||||
const BuildingId id = placeOperational(sim, cfg, BuildingType::Miner, QPoint(0, 0));
|
||||
SimulationTestAccess::buildings(sim).setRecipe(id, "mine_iron_ore");
|
||||
@@ -84,8 +80,8 @@ TEST_CASE("readBuildingConfig returns a miner's selected recipe", "[copyconfig]"
|
||||
TEST_CASE("readBuildingConfig leaves recipe unset when nothing is selected",
|
||||
"[copyconfig]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
Simulation sim(loadConfig(), 7);
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
Simulation sim(loadTestConfig(), 7);
|
||||
|
||||
const BuildingId id = placeOperational(sim, cfg, BuildingType::Assembler, QPoint(0, 0));
|
||||
|
||||
@@ -99,8 +95,8 @@ TEST_CASE("readBuildingConfig leaves recipe unset when nothing is selected",
|
||||
TEST_CASE("readBuildingConfig returns a shipyard's schematic and layout",
|
||||
"[copyconfig]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
Simulation sim(loadConfig(), 7);
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
Simulation sim(loadTestConfig(), 7);
|
||||
|
||||
const ShipDef* schematic = findAvailableSchematic(cfg);
|
||||
REQUIRE(schematic != nullptr);
|
||||
@@ -119,8 +115,8 @@ TEST_CASE("readBuildingConfig returns a shipyard's schematic and layout",
|
||||
|
||||
TEST_CASE("readBuildingConfig reads a queued construction site", "[copyconfig]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
Simulation sim(loadConfig(), 7);
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
Simulation sim(loadTestConfig(), 7);
|
||||
|
||||
// A placed miner enters the construction queue as a site (not yet operational).
|
||||
const BuildingId id =
|
||||
@@ -140,6 +136,6 @@ TEST_CASE("readBuildingConfig reads a queued construction site", "[copyconfig]")
|
||||
|
||||
TEST_CASE("readBuildingConfig returns nullopt for an unknown id", "[copyconfig]")
|
||||
{
|
||||
Simulation sim(loadConfig(), 7);
|
||||
Simulation sim(loadTestConfig(), 7);
|
||||
CHECK_FALSE(readBuildingConfig(sim, kInvalidBuildingId).has_value());
|
||||
}
|
||||
|
||||
@@ -19,16 +19,12 @@
|
||||
#include "Port.h"
|
||||
#include "Rotation.h"
|
||||
#include "Tick.h"
|
||||
#include "TestConfig.h"
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Fixture helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static GameConfig loadConfig()
|
||||
{
|
||||
return ConfigLoader::loadFromDirectory(CONFIG_DIR);
|
||||
}
|
||||
|
||||
static Item makeItem(const std::string& id)
|
||||
{
|
||||
Item item;
|
||||
@@ -86,7 +82,7 @@ static std::vector<Item> outputSideItems(const Building& b)
|
||||
// Owns a BuildingSystem and its dependencies for placement-bounds tests.
|
||||
struct PlacementFixture
|
||||
{
|
||||
GameConfig cfg = loadConfig();
|
||||
GameConfig cfg = loadTestConfig();
|
||||
BeltSystem belts{cfg.world.beltSpeed_tps};
|
||||
int stock = 0;
|
||||
std::mt19937 rng{0};
|
||||
@@ -110,7 +106,7 @@ struct PlacementFixture
|
||||
|
||||
TEST_CASE("BuildingSystem: place miner occupies expected body tiles", "[building]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
BeltSystem belts(cfg.world.beltSpeed_tps);
|
||||
int stock = 0;
|
||||
std::mt19937 rng(0);
|
||||
@@ -218,7 +214,7 @@ TEST_CASE("BuildingSystem: isPlacementValid enforces terrain and world bounds",
|
||||
TEST_CASE("BuildingSystem: placing a belt registers it with BeltSystem after construction",
|
||||
"[building]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
BeltSystem belts(cfg.world.beltSpeed_tps);
|
||||
int stock = 0;
|
||||
std::mt19937 rng(0);
|
||||
@@ -247,7 +243,7 @@ TEST_CASE("BuildingSystem: placing a belt registers it with BeltSystem after con
|
||||
|
||||
TEST_CASE("BuildingSystem: placed building enters construction queue", "[building]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
BeltSystem belts(cfg.world.beltSpeed_tps);
|
||||
int stock = 0;
|
||||
std::mt19937 rng(0);
|
||||
@@ -289,7 +285,7 @@ TEST_CASE("BuildingSystem: deconstructing a construction site removes it instant
|
||||
TEST_CASE("BuildingSystem: first queued building starts construction immediately",
|
||||
"[building]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
BeltSystem belts(cfg.world.beltSpeed_tps);
|
||||
int stock = 0;
|
||||
std::mt19937 rng(0);
|
||||
@@ -307,7 +303,7 @@ TEST_CASE("BuildingSystem: first queued building starts construction immediately
|
||||
|
||||
TEST_CASE("BuildingSystem: second queued building waits (completesAt == 0)", "[building]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
BeltSystem belts(cfg.world.beltSpeed_tps);
|
||||
int stock = 0;
|
||||
std::mt19937 rng(0);
|
||||
@@ -329,7 +325,7 @@ TEST_CASE("BuildingSystem: second queued building waits (completesAt == 0)", "[b
|
||||
|
||||
TEST_CASE("BuildingSystem: construction completes after configured duration", "[building]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
BeltSystem belts(cfg.world.beltSpeed_tps);
|
||||
int stock = 0;
|
||||
std::mt19937 rng(0);
|
||||
@@ -495,7 +491,7 @@ TEST_CASE("BuildingSystem: splitter filters survive a queue/un-queue round-trip"
|
||||
|
||||
TEST_CASE("BuildingSystem: second building starts after first completes", "[building]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
BeltSystem belts(cfg.world.beltSpeed_tps);
|
||||
int stock = 0;
|
||||
std::mt19937 rng(0);
|
||||
@@ -525,7 +521,7 @@ TEST_CASE("BuildingSystem: second building starts after first completes", "[buil
|
||||
|
||||
TEST_CASE("BuildingSystem: miner produces iron_ore after recipe duration", "[building]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
BeltSystem belts(cfg.world.beltSpeed_tps);
|
||||
int stock = 0;
|
||||
std::mt19937 rng(0);
|
||||
@@ -558,7 +554,7 @@ TEST_CASE("BuildingSystem: miner produces iron_ore after recipe duration", "[bui
|
||||
|
||||
TEST_CASE("BuildingSystem: miner output buffer stalls when full", "[building]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
BeltSystem belts(cfg.world.beltSpeed_tps);
|
||||
int stock = 0;
|
||||
std::mt19937 rng(0);
|
||||
@@ -598,7 +594,7 @@ TEST_CASE("BuildingSystem: miner output buffer stalls when full", "[building]")
|
||||
|
||||
TEST_CASE("BuildingSystem: productionBuildingCount excludes construction sites", "[building]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
BeltSystem belts(cfg.world.beltSpeed_tps);
|
||||
int stock = 0;
|
||||
std::mt19937 rng(0);
|
||||
@@ -638,7 +634,7 @@ TEST_CASE("BuildingSystem: productionBuildingCount excludes construction sites",
|
||||
TEST_CASE("BuildingSystem: activeProductionBuildingCount tracks production cycle state",
|
||||
"[building]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
BeltSystem belts(cfg.world.beltSpeed_tps);
|
||||
int stock = 0;
|
||||
std::mt19937 rng(0);
|
||||
@@ -679,7 +675,7 @@ TEST_CASE("BuildingSystem: activeProductionBuildingCount tracks production cycle
|
||||
TEST_CASE("BuildingSystem: smelter input buffer fills from adjacent west-flowing belt",
|
||||
"[building]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
// Fast belt so items are immediately available for peek/take.
|
||||
BeltSystem belts(static_cast<double>(kTickRateHz));
|
||||
int stock = 0;
|
||||
@@ -722,7 +718,7 @@ TEST_CASE("BuildingSystem: smelter input buffer fills from adjacent west-flowing
|
||||
TEST_CASE("BuildingSystem: accepted input travels inward before entering the buffer",
|
||||
"[building]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
BeltSystem belts(static_cast<double>(kTickRateHz)); // fast belt: 1 tile/tick
|
||||
int stock = 0;
|
||||
std::mt19937 rng(0);
|
||||
@@ -763,7 +759,7 @@ TEST_CASE("BuildingSystem: accepted input travels inward before entering the buf
|
||||
TEST_CASE("BuildingSystem: input reservation caps buffered plus in-transit at the cap",
|
||||
"[building]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
BeltSystem belts(static_cast<double>(kTickRateHz)); // fast belt
|
||||
int stock = 0;
|
||||
std::mt19937 rng(0);
|
||||
@@ -805,7 +801,7 @@ TEST_CASE("BuildingSystem: input reservation caps buffered plus in-transit at th
|
||||
TEST_CASE("BuildingSystem: smelter auto-smelts ore without a recipe selection",
|
||||
"[building]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
BeltSystem belts(static_cast<double>(kTickRateHz));
|
||||
int stock = 0;
|
||||
std::mt19937 rng(0);
|
||||
@@ -851,7 +847,7 @@ TEST_CASE("BuildingSystem: smelter auto-smelts ore without a recipe selection",
|
||||
TEST_CASE("BuildingSystem: smelter runs a satisfiable recipe while an incomplete batch waits",
|
||||
"[building]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
BeltSystem belts(static_cast<double>(kTickRateHz));
|
||||
int stock = 0;
|
||||
std::mt19937 rng(0);
|
||||
@@ -905,7 +901,7 @@ TEST_CASE("BuildingSystem: smelter runs a satisfiable recipe while an incomplete
|
||||
|
||||
TEST_CASE("BuildingSystem: miner output buffer drains onto adjacent belt", "[building]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
BeltSystem belts(static_cast<double>(kTickRateHz));
|
||||
int stock = 0;
|
||||
std::mt19937 rng(0);
|
||||
@@ -944,7 +940,7 @@ TEST_CASE("BuildingSystem: miner output buffer drains onto adjacent belt", "[bui
|
||||
TEST_CASE("BuildingSystem: output port couples directly into an adjacent input port",
|
||||
"[building]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
BeltSystem belts(cfg.world.beltSpeed_tps);
|
||||
int stock = 0;
|
||||
std::mt19937 rng(0);
|
||||
@@ -984,7 +980,7 @@ TEST_CASE("BuildingSystem: output port couples directly into an adjacent input p
|
||||
TEST_CASE("BuildingSystem: direct coupling to a non-consumer leaves the item stuck",
|
||||
"[building]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
BeltSystem belts(cfg.world.beltSpeed_tps);
|
||||
int stock = 0;
|
||||
std::mt19937 rng(0);
|
||||
@@ -1023,7 +1019,7 @@ TEST_CASE("BuildingSystem: direct coupling to a non-consumer leaves the item stu
|
||||
TEST_CASE("BuildingSystem: setRecipe clears output buffer and active production",
|
||||
"[building]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
BeltSystem belts(static_cast<double>(kTickRateHz));
|
||||
int stock = 0;
|
||||
std::mt19937 rng(0);
|
||||
@@ -1066,7 +1062,7 @@ TEST_CASE("BuildingSystem: setRecipe clears output buffer and active production"
|
||||
TEST_CASE("BuildingSystem: reprocessing plant output buffer capacity equals max output per roll",
|
||||
"[building]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
BeltSystem belts(cfg.world.beltSpeed_tps);
|
||||
int stock = 0;
|
||||
std::mt19937 rng(0);
|
||||
@@ -1097,7 +1093,7 @@ TEST_CASE("BuildingSystem: reprocessing plant output buffer capacity equals max
|
||||
TEST_CASE("BuildingSystem: reprocessing plant produces one cycle output then stalls",
|
||||
"[building]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
BeltSystem belts(static_cast<double>(kTickRateHz));
|
||||
int stock = 0;
|
||||
// Seed chosen so first roll produces 2-item output (iron_ingot), filling buffer.
|
||||
@@ -1156,7 +1152,7 @@ TEST_CASE("BuildingSystem: reprocessing plant produces one cycle output then sta
|
||||
TEST_CASE("BuildingSystem: findRotateInPlaceTarget returns nullopt when tile is empty",
|
||||
"[building][rotate-in-place]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
BeltSystem belts(cfg.world.beltSpeed_tps);
|
||||
int stock = 0;
|
||||
std::mt19937 rng(0);
|
||||
@@ -1175,7 +1171,7 @@ TEST_CASE("BuildingSystem: findRotateInPlaceTarget returns nullopt when tile is
|
||||
TEST_CASE("BuildingSystem: findRotateInPlaceTarget returns the site id for a queued belt (same type, different rotation)",
|
||||
"[building][rotate-in-place]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
BeltSystem belts(cfg.world.beltSpeed_tps);
|
||||
int stock = 0;
|
||||
std::mt19937 rng(0);
|
||||
@@ -1198,7 +1194,7 @@ TEST_CASE("BuildingSystem: findRotateInPlaceTarget returns the site id for a que
|
||||
TEST_CASE("BuildingSystem: findRotateInPlaceTarget returns the building id for a completed operational belt",
|
||||
"[building][rotate-in-place]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
BeltSystem belts(cfg.world.beltSpeed_tps);
|
||||
int stock = 0;
|
||||
std::mt19937 rng(0);
|
||||
@@ -1225,7 +1221,7 @@ TEST_CASE("BuildingSystem: findRotateInPlaceTarget returns the building id for a
|
||||
TEST_CASE("BuildingSystem: findRotateInPlaceTarget returns nullopt when building type differs",
|
||||
"[building][rotate-in-place]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
BeltSystem belts(cfg.world.beltSpeed_tps);
|
||||
int stock = 0;
|
||||
std::mt19937 rng(0);
|
||||
@@ -1247,7 +1243,7 @@ TEST_CASE("BuildingSystem: findRotateInPlaceTarget returns nullopt when building
|
||||
TEST_CASE("BuildingSystem: findRotateInPlaceTarget never rotates a tunnel in place",
|
||||
"[building][rotate-in-place]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
BeltSystem belts(cfg.world.beltSpeed_tps);
|
||||
int stock = 0;
|
||||
std::mt19937 rng(0);
|
||||
@@ -1273,7 +1269,7 @@ TEST_CASE("BuildingSystem: findRotateInPlaceTarget never rotates a tunnel in pla
|
||||
TEST_CASE("BuildingSystem: findRotateInPlaceTarget returns nullopt when footprints only partially overlap",
|
||||
"[building][rotate-in-place]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
BeltSystem belts(cfg.world.beltSpeed_tps);
|
||||
int stock = 0;
|
||||
std::mt19937 rng(0);
|
||||
@@ -1297,7 +1293,7 @@ TEST_CASE("BuildingSystem: findRotateInPlaceTarget returns nullopt when footprin
|
||||
TEST_CASE("BuildingSystem: findRotateInPlaceTarget works for a symmetric multi-tile building with rotated ghost",
|
||||
"[building][rotate-in-place]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
BeltSystem belts(cfg.world.beltSpeed_tps);
|
||||
int stock = 0;
|
||||
std::mt19937 rng(0);
|
||||
@@ -1326,7 +1322,7 @@ TEST_CASE("BuildingSystem: findRotateInPlaceTarget works for a symmetric multi-t
|
||||
TEST_CASE("BuildingSystem: rotateInPlace updates the rotation field of a construction site",
|
||||
"[building][rotate-in-place]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
BeltSystem belts(cfg.world.beltSpeed_tps);
|
||||
int stock = 0;
|
||||
std::mt19937 rng(0);
|
||||
@@ -1349,7 +1345,7 @@ TEST_CASE("BuildingSystem: rotateInPlace updates the rotation field of a constru
|
||||
TEST_CASE("BuildingSystem: rotateInPlace preserves the construction progress of a queued site",
|
||||
"[building][rotate-in-place]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
BeltSystem belts(cfg.world.beltSpeed_tps);
|
||||
int stock = 0;
|
||||
std::mt19937 rng(0);
|
||||
@@ -1373,7 +1369,7 @@ TEST_CASE("BuildingSystem: rotateInPlace preserves the construction progress of
|
||||
TEST_CASE("BuildingSystem: rotateInPlace updates rotation and output port direction on an operational building",
|
||||
"[building][rotate-in-place]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
BeltSystem belts(cfg.world.beltSpeed_tps);
|
||||
int stock = 0;
|
||||
std::mt19937 rng(0);
|
||||
@@ -1404,7 +1400,7 @@ TEST_CASE("BuildingSystem: rotateInPlace updates rotation and output port direct
|
||||
TEST_CASE("BuildingSystem: rotateInPlace re-registers a belt tile with BeltSystem so it still accepts items",
|
||||
"[building][rotate-in-place]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
BeltSystem belts(cfg.world.beltSpeed_tps);
|
||||
int stock = 0;
|
||||
std::mt19937 rng(0);
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
add_files(
|
||||
TEST_FILES
|
||||
|
||||
TestConfig.h
|
||||
|
||||
test.cpp
|
||||
FormulaTest.cpp
|
||||
ConfigLoaderTest.cpp
|
||||
|
||||
@@ -22,11 +22,7 @@
|
||||
#include "StationBodyComponent.h"
|
||||
#include "Tick.h"
|
||||
#include "WeaponComponent.h"
|
||||
|
||||
static GameConfig loadConfig()
|
||||
{
|
||||
return ConfigLoader::loadFromDirectory(CONFIG_DIR);
|
||||
}
|
||||
#include "TestConfig.h"
|
||||
|
||||
static const ShipDef* findCombatShip(const GameConfig& cfg)
|
||||
{
|
||||
@@ -64,7 +60,7 @@ struct CombatFixture
|
||||
CombatSystem combat;
|
||||
|
||||
explicit CombatFixture()
|
||||
: cfg(loadConfig())
|
||||
: cfg(loadTestConfig())
|
||||
, rng(42)
|
||||
, nextBuildingId(1)
|
||||
, belts(cfg.world.beltSpeed_tps)
|
||||
@@ -178,7 +174,7 @@ TEST_CASE("CombatSystem: no fire when target is out of range", "[combat]")
|
||||
|
||||
TEST_CASE("CombatSystem: player station fires at enemy ship in range", "[combat]")
|
||||
{
|
||||
Simulation sim(loadConfig(), 42);
|
||||
Simulation sim(loadTestConfig(), 42);
|
||||
|
||||
// Find the player station entity via ECS.
|
||||
entt::entity stationEntity = entt::null;
|
||||
@@ -217,7 +213,7 @@ TEST_CASE("CombatSystem: player station fires at enemy ship in range", "[combat]
|
||||
|
||||
TEST_CASE("CombatSystem: enemy station fires at player ship in range", "[combat]")
|
||||
{
|
||||
Simulation sim(loadConfig(), 42);
|
||||
Simulation sim(loadTestConfig(), 42);
|
||||
|
||||
entt::entity stationEntity = entt::null;
|
||||
QVector2D stationCenter;
|
||||
@@ -255,7 +251,7 @@ TEST_CASE("CombatSystem: enemy station fires at player ship in range", "[combat]
|
||||
|
||||
TEST_CASE("CombatSystem: player ship fires at enemy station in range", "[combat]")
|
||||
{
|
||||
Simulation sim(loadConfig(), 42);
|
||||
Simulation sim(loadTestConfig(), 42);
|
||||
|
||||
entt::entity stationEntity = entt::null;
|
||||
QVector2D stationCenter;
|
||||
@@ -388,7 +384,7 @@ TEST_CASE("CombatSystem: damage still applied if shooter already dead", "[combat
|
||||
|
||||
TEST_CASE("CombatSystem: dead ship is removed after tick step 9", "[combat]")
|
||||
{
|
||||
Simulation sim(loadConfig(), 42);
|
||||
Simulation sim(loadTestConfig(), 42);
|
||||
|
||||
const ShipDef* combatDef = findCombatShip(sim.getConfig());
|
||||
REQUIRE(combatDef != nullptr);
|
||||
@@ -405,7 +401,7 @@ TEST_CASE("CombatSystem: dead ship is removed after tick step 9", "[combat]")
|
||||
|
||||
TEST_CASE("CombatSystem: scrap is spawned on ship death", "[combat]")
|
||||
{
|
||||
Simulation sim(loadConfig(), 42);
|
||||
Simulation sim(loadTestConfig(), 42);
|
||||
|
||||
// Scrap dropped on death is derived from the ship's as-built threat cost
|
||||
// (REQ-RES-DEBRIS-DROP): round(threat * scrap_per_threat). The interceptor's
|
||||
@@ -424,7 +420,7 @@ TEST_CASE("CombatSystem: scrap is spawned on ship death", "[combat]")
|
||||
|
||||
TEST_CASE("CombatSystem: HQ death sets game over", "[combat]")
|
||||
{
|
||||
Simulation sim(loadConfig(), 42);
|
||||
Simulation sim(loadTestConfig(), 42);
|
||||
|
||||
// Damage the HQ proxy entity (has HqProxy + Health).
|
||||
sim.getAdmin().forEach<HqProxyComponent, HealthComponent>(
|
||||
|
||||
@@ -11,14 +11,7 @@
|
||||
#include "Simulation.h"
|
||||
#include "SimulationTestAccess.h"
|
||||
#include "Tick.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
GameConfig loadConfig()
|
||||
{
|
||||
return ConfigLoader::loadFromDirectory(CONFIG_DIR);
|
||||
}
|
||||
} // namespace
|
||||
#include "TestConfig.h"
|
||||
|
||||
// The command chokepoint (Simulation::apply) must produce exactly the same state
|
||||
// as driving the underlying mutators directly — that equivalence is what lets a
|
||||
@@ -26,8 +19,8 @@ GameConfig loadConfig()
|
||||
|
||||
TEST_CASE("apply(PlaceBuildingCommand) matches direct placement", "[command]")
|
||||
{
|
||||
Simulation viaCommand(loadConfig(), 99);
|
||||
Simulation viaDirect(loadConfig(), 99);
|
||||
Simulation viaCommand(loadTestConfig(), 99);
|
||||
Simulation viaDirect(loadTestConfig(), 99);
|
||||
|
||||
PlaceBuildingCommand command;
|
||||
command.type = BuildingType::Miner;
|
||||
@@ -42,8 +35,8 @@ TEST_CASE("apply(PlaceBuildingCommand) matches direct placement", "[command]")
|
||||
|
||||
TEST_CASE("apply(PlaceBuildingCommand) with recipe matches place-then-setRecipe", "[command]")
|
||||
{
|
||||
Simulation viaCommand(loadConfig(), 99);
|
||||
Simulation viaDirect(loadConfig(), 99);
|
||||
Simulation viaCommand(loadTestConfig(), 99);
|
||||
Simulation viaDirect(loadTestConfig(), 99);
|
||||
|
||||
PlaceBuildingCommand command;
|
||||
command.type = BuildingType::Miner;
|
||||
@@ -61,8 +54,8 @@ TEST_CASE("apply(PlaceBuildingCommand) with recipe matches place-then-setRecipe"
|
||||
|
||||
TEST_CASE("apply(DeconstructCommand) matches direct deconstruct", "[command]")
|
||||
{
|
||||
Simulation viaCommand(loadConfig(), 99);
|
||||
Simulation viaDirect(loadConfig(), 99);
|
||||
Simulation viaCommand(loadTestConfig(), 99);
|
||||
Simulation viaDirect(loadTestConfig(), 99);
|
||||
|
||||
const BuildingId idA =
|
||||
SimulationTestAccess::place(viaCommand, BuildingType::Miner, QPoint(-3, 0), Rotation::East).value();
|
||||
@@ -81,8 +74,8 @@ TEST_CASE("apply(DeconstructCommand) matches direct deconstruct", "[command]")
|
||||
|
||||
TEST_CASE("apply(CancelDeconstructionCommand) matches direct cancelDeconstruction", "[command]")
|
||||
{
|
||||
Simulation viaCommand(loadConfig(), 99);
|
||||
Simulation viaDirect(loadConfig(), 99);
|
||||
Simulation viaCommand(loadTestConfig(), 99);
|
||||
Simulation viaDirect(loadTestConfig(), 99);
|
||||
|
||||
const BuildingId idA =
|
||||
SimulationTestAccess::place(viaCommand, BuildingType::Miner, QPoint(-3, 0), Rotation::East).value();
|
||||
@@ -110,8 +103,8 @@ TEST_CASE("apply(CancelDeconstructionCommand) matches direct cancelDeconstructio
|
||||
|
||||
TEST_CASE("CommandManager drains queued commands in FIFO order through apply", "[command]")
|
||||
{
|
||||
Simulation viaManager(loadConfig(), 99);
|
||||
Simulation viaDirect(loadConfig(), 99);
|
||||
Simulation viaManager(loadTestConfig(), 99);
|
||||
Simulation viaDirect(loadTestConfig(), 99);
|
||||
|
||||
CommandManager manager(viaManager);
|
||||
|
||||
|
||||
@@ -11,14 +11,10 @@
|
||||
#include "SimulationTestAccess.h"
|
||||
#include "StateChecksum.h"
|
||||
#include "Tick.h"
|
||||
#include "TestConfig.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
GameConfig loadConfig()
|
||||
{
|
||||
return ConfigLoader::loadFromDirectory(CONFIG_DIR);
|
||||
}
|
||||
|
||||
constexpr int kScriptTicks = 2000;
|
||||
|
||||
// Runs a fixed scripted session and returns the full-state checksum after every
|
||||
@@ -26,7 +22,7 @@ constexpr int kScriptTicks = 2000;
|
||||
// otherwise lets waves/combat run so the RNG stream and ECS state are exercised.
|
||||
std::vector<std::uint64_t> runScriptedSession(unsigned int seed)
|
||||
{
|
||||
Simulation sim(loadConfig(), seed);
|
||||
Simulation sim(loadTestConfig(), seed);
|
||||
|
||||
// Tick 0: a miner feeding a short belt line on the asteroid.
|
||||
SimulationTestAccess::place(sim, BuildingType::Miner, QPoint(-3, 0), Rotation::East);
|
||||
@@ -121,8 +117,8 @@ TEST_CASE("fingerprintRng: equal states match, advanced states differ", "[determ
|
||||
|
||||
TEST_CASE("Simulation::rngFingerprint is stable for equal seeds", "[determinism]")
|
||||
{
|
||||
const Simulation a(loadConfig(), 777);
|
||||
const Simulation b(loadConfig(), 777);
|
||||
const Simulation a(loadTestConfig(), 777);
|
||||
const Simulation b(loadTestConfig(), 777);
|
||||
|
||||
REQUIRE(a.getRngFingerprint() == b.getRngFingerprint());
|
||||
}
|
||||
|
||||
@@ -2,11 +2,7 @@
|
||||
|
||||
#include "ConfigLoader.h"
|
||||
#include "ModulesConfig.h"
|
||||
|
||||
static GameConfig loadConfig()
|
||||
{
|
||||
return ConfigLoader::loadFromDirectory(CONFIG_DIR);
|
||||
}
|
||||
#include "TestConfig.h"
|
||||
|
||||
static const ModuleDef* findModule(const GameConfig& cfg, const std::string& id)
|
||||
{
|
||||
@@ -28,7 +24,7 @@ static const ModuleStatModifier* findModifier(const ModuleDef& def, const std::s
|
||||
|
||||
TEST_CASE("ConfigLoader: loadModules parses modules.toml", "[config][modules]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
REQUIRE(cfg.modules.modules.size() >= 2);
|
||||
|
||||
const ModuleDef& armor = cfg.modules.modules[0];
|
||||
@@ -49,7 +45,7 @@ TEST_CASE("ConfigLoader: loadModules parses modules.toml", "[config][modules]")
|
||||
|
||||
TEST_CASE("ConfigLoader: loadModules parses additive modifiers", "[config][modules]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
REQUIRE(cfg.modules.modules.size() >= 2);
|
||||
|
||||
const ModuleDef& sensor = cfg.modules.modules[1];
|
||||
@@ -62,7 +58,7 @@ TEST_CASE("ConfigLoader: loadModules parses additive modifiers", "[config][modul
|
||||
|
||||
TEST_CASE("ConfigLoader: multiplicative modifier with unit suffix is parsed (weapon_primer)", "[config][modules]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
const ModuleDef* primer = findModule(cfg, "weapon_primer");
|
||||
REQUIRE(primer != nullptr);
|
||||
REQUIRE(primer->statModifiers.size() == 1);
|
||||
@@ -74,7 +70,7 @@ TEST_CASE("ConfigLoader: multiplicative modifier with unit suffix is parsed (wea
|
||||
|
||||
TEST_CASE("ConfigLoader: weapon_stabilizer parses two multiplicative weapon modifiers", "[config][modules]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
const ModuleDef* stab = findModule(cfg, "weapon_stabilizer");
|
||||
REQUIRE(stab != nullptr);
|
||||
REQUIRE(stab->statModifiers.size() == 2);
|
||||
@@ -92,7 +88,7 @@ TEST_CASE("ConfigLoader: weapon_stabilizer parses two multiplicative weapon modi
|
||||
|
||||
TEST_CASE("ConfigLoader: afterburner parses multiplicative speed and additive main_acceleration", "[config][modules]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
const ModuleDef* ab = findModule(cfg, "afterburner");
|
||||
REQUIRE(ab != nullptr);
|
||||
REQUIRE(ab->statModifiers.size() == 2);
|
||||
@@ -110,7 +106,7 @@ TEST_CASE("ConfigLoader: afterburner parses multiplicative speed and additive ma
|
||||
|
||||
TEST_CASE("ConfigLoader: maneuvering_thrusters parses multiplicative speed and additive maneuvering_acceleration", "[config][modules]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
const ModuleDef* mt = findModule(cfg, "maneuvering_thrusters");
|
||||
REQUIRE(mt != nullptr);
|
||||
REQUIRE(mt->statModifiers.size() == 2);
|
||||
@@ -128,7 +124,7 @@ TEST_CASE("ConfigLoader: maneuvering_thrusters parses multiplicative speed and a
|
||||
|
||||
TEST_CASE("ConfigLoader: loadShips parses layout field", "[config][ships]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
REQUIRE(!cfg.ships.ships.empty());
|
||||
|
||||
const ShipDef& ship = cfg.ships.ships[0];
|
||||
|
||||
@@ -12,11 +12,7 @@
|
||||
#include "Simulation.h"
|
||||
#include "SimulationTestAccess.h"
|
||||
#include "StationBodyComponent.h"
|
||||
|
||||
static GameConfig loadConfig()
|
||||
{
|
||||
return ConfigLoader::loadFromDirectory(CONFIG_DIR);
|
||||
}
|
||||
#include "TestConfig.h"
|
||||
|
||||
// Zeros the HP of both enemy defence stations and advances one tick so that
|
||||
// tickDeathsAndLoot fires, triggering the push and schematic choices.
|
||||
@@ -62,7 +58,7 @@ static bool awaitRecipeUnlock(Simulation& sim, const std::string& recipeId,
|
||||
|
||||
TEST_CASE("RecipeSchematic: unlocked_at_start = true parsed correctly", "[recipe_schematic]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
const auto it = std::find_if(cfg.recipes.recipes.begin(), cfg.recipes.recipes.end(),
|
||||
[](const RecipeDef& r) { return r.id == "premium_circuit"; });
|
||||
REQUIRE(it != cfg.recipes.recipes.end());
|
||||
@@ -71,7 +67,7 @@ TEST_CASE("RecipeSchematic: unlocked_at_start = true parsed correctly", "[recipe
|
||||
|
||||
TEST_CASE("RecipeSchematic: a gated recipe is granted by an unlock group", "[recipe_schematic]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
const bool granted = std::any_of(cfg.unlocks.groups.begin(), cfg.unlocks.groups.end(),
|
||||
[](const UnlockGroupDef& g)
|
||||
{
|
||||
@@ -82,7 +78,7 @@ TEST_CASE("RecipeSchematic: a gated recipe is granted by an unlock group", "[rec
|
||||
|
||||
TEST_CASE("RecipeSchematic: an untagged assembler recipe has unlocked_at_start = false", "[recipe_schematic]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
const auto it = std::find_if(cfg.recipes.recipes.begin(), cfg.recipes.recipes.end(),
|
||||
[](const RecipeDef& r) { return r.id == "circuit_board"; });
|
||||
REQUIRE(it != cfg.recipes.recipes.end());
|
||||
@@ -96,21 +92,21 @@ TEST_CASE("RecipeSchematic: an untagged assembler recipe has unlocked_at_start =
|
||||
TEST_CASE("RecipeSchematic: recipe with unlock_at_station_level = -1 is unlocked at game start",
|
||||
"[recipe_schematic]")
|
||||
{
|
||||
const Simulation sim(loadConfig());
|
||||
const Simulation sim(loadTestConfig());
|
||||
REQUIRE(sim.isRecipeUnlocked("premium_circuit"));
|
||||
}
|
||||
|
||||
TEST_CASE("RecipeSchematic: recipe with unlock_at_station_level = 0 is locked at game start",
|
||||
"[recipe_schematic]")
|
||||
{
|
||||
const Simulation sim(loadConfig());
|
||||
const Simulation sim(loadTestConfig());
|
||||
REQUIRE_FALSE(sim.isRecipeUnlocked("quick_circuit"));
|
||||
}
|
||||
|
||||
TEST_CASE("RecipeSchematic: recipe with unlock_at_station_level = 1 is locked at game start",
|
||||
"[recipe_schematic]")
|
||||
{
|
||||
const Simulation sim(loadConfig());
|
||||
const Simulation sim(loadTestConfig());
|
||||
REQUIRE_FALSE(sim.isRecipeUnlocked("advanced_circuit"));
|
||||
}
|
||||
|
||||
@@ -123,7 +119,7 @@ TEST_CASE("RecipeSchematic: -1 recipe seeds its output item into the implicit un
|
||||
{
|
||||
// premium_circuit is not needed by any ship or module schematic, so it can
|
||||
// only reach the implicit set via the -1 recipe seed in Phase 1.
|
||||
const Simulation sim(loadConfig());
|
||||
const Simulation sim(loadTestConfig());
|
||||
REQUIRE(sim.isItemUnlocked("premium_circuit"));
|
||||
}
|
||||
|
||||
@@ -132,7 +128,7 @@ TEST_CASE("RecipeSchematic: -1 recipe's inputs are in the implicit unlock set",
|
||||
{
|
||||
// premium_circuit takes circuit_board as input; that item was already
|
||||
// implicitly unlocked by ship schematics, so it must remain unlocked.
|
||||
const Simulation sim(loadConfig());
|
||||
const Simulation sim(loadTestConfig());
|
||||
REQUIRE(sim.isItemUnlocked("circuit_board"));
|
||||
}
|
||||
|
||||
@@ -142,7 +138,7 @@ TEST_CASE("RecipeSchematic: locked recipe's unique input is not in the implicit
|
||||
// exotic_alloy has unlock_at_station_level = 0 (locked at start) and takes
|
||||
// exotic_ore as input. exotic_ore is only reachable through this locked
|
||||
// recipe, so it must not appear in the implicit set.
|
||||
const Simulation sim(loadConfig());
|
||||
const Simulation sim(loadTestConfig());
|
||||
REQUIRE_FALSE(sim.isItemUnlocked("exotic_ore"));
|
||||
}
|
||||
|
||||
@@ -152,7 +148,7 @@ TEST_CASE("RecipeSchematic: locked recipe's output item is not in the implicit u
|
||||
// exotic_alloy is produced only by the locked recipe of the same name and
|
||||
// is not needed by any schematic, so neither the item nor the recipe should
|
||||
// be unlocked at game start.
|
||||
const Simulation sim(loadConfig());
|
||||
const Simulation sim(loadTestConfig());
|
||||
REQUIRE_FALSE(sim.isItemUnlocked("exotic_alloy"));
|
||||
REQUIRE_FALSE(sim.isRecipeUnlocked("exotic_alloy"));
|
||||
}
|
||||
@@ -162,7 +158,7 @@ TEST_CASE("RecipeSchematic: normal implicit unlock is unaffected for untagged as
|
||||
{
|
||||
// circuit_board carries no unlock_at_station_level and is needed by ships
|
||||
// that start unlocked, so it must still be implicitly unlocked.
|
||||
const Simulation sim(loadConfig());
|
||||
const Simulation sim(loadTestConfig());
|
||||
REQUIRE(sim.isRecipeUnlocked("circuit_board"));
|
||||
}
|
||||
|
||||
@@ -176,7 +172,7 @@ TEST_CASE("RecipeSchematic: eligible recipe schematic is eventually awarded on s
|
||||
// quick_circuit has unlock_at_station_level = 0 and produces circuit_board
|
||||
// (already implicitly unlocked), so it is eligible from the first station
|
||||
// destruction. With up to 150 trials it must be awarded at least once.
|
||||
Simulation sim(loadConfig());
|
||||
Simulation sim(loadTestConfig());
|
||||
REQUIRE(awaitRecipeUnlock(sim, "quick_circuit"));
|
||||
}
|
||||
|
||||
@@ -185,7 +181,7 @@ TEST_CASE("RecipeSchematic: an implicitly-gated recipe with no unlock group is n
|
||||
{
|
||||
// exotic_alloy is in no unlock group and its output/inputs are unreachable
|
||||
// via the item graph, so it can never be dropped or implicitly unlocked.
|
||||
Simulation sim(loadConfig());
|
||||
Simulation sim(loadTestConfig());
|
||||
for (int i = 0; i < 50; ++i)
|
||||
{
|
||||
killEnemyStationsAndApply(sim);
|
||||
@@ -198,7 +194,7 @@ TEST_CASE("RecipeSchematic: recipe with level > destroyed station level is not a
|
||||
{
|
||||
// advanced_circuit has unlock_at_station_level = 1. Destroying a single
|
||||
// level-0 station set must not award it regardless of the RNG outcome.
|
||||
Simulation sim(loadConfig());
|
||||
Simulation sim(loadTestConfig());
|
||||
killEnemyStationsAndApply(sim);
|
||||
REQUIRE_FALSE(sim.isRecipeUnlocked("advanced_circuit"));
|
||||
}
|
||||
@@ -208,14 +204,14 @@ TEST_CASE("RecipeSchematic: recipe with higher level is awarded once eligible st
|
||||
{
|
||||
// After enough destructions to pass station level 1, advanced_circuit must
|
||||
// eventually be awarded.
|
||||
Simulation sim(loadConfig());
|
||||
Simulation sim(loadTestConfig());
|
||||
REQUIRE(awaitRecipeUnlock(sim, "advanced_circuit", 300));
|
||||
}
|
||||
|
||||
TEST_CASE("RecipeSchematic: awarded recipe schematic stays unlocked and is not awarded again",
|
||||
"[recipe_schematic]")
|
||||
{
|
||||
Simulation sim(loadConfig());
|
||||
Simulation sim(loadTestConfig());
|
||||
awaitRecipeUnlock(sim, "quick_circuit");
|
||||
REQUIRE(sim.isRecipeUnlocked("quick_circuit"));
|
||||
|
||||
@@ -231,7 +227,7 @@ TEST_CASE("RecipeSchematic: awarded recipe schematic stays unlocked and is not a
|
||||
TEST_CASE("RecipeSchematic: recipe schematic can appear in pending choices",
|
||||
"[recipe_schematic]")
|
||||
{
|
||||
Simulation sim(loadConfig());
|
||||
Simulation sim(loadTestConfig());
|
||||
|
||||
bool foundRecipeChoice = false;
|
||||
for (int i = 0; i < 150 && !foundRecipeChoice; ++i)
|
||||
@@ -260,7 +256,7 @@ TEST_CASE("RecipeSchematic: recipe schematic can appear in pending choices",
|
||||
TEST_CASE("RecipeSchematic: reset re-locks a previously awarded recipe schematic",
|
||||
"[recipe_schematic]")
|
||||
{
|
||||
Simulation sim(loadConfig());
|
||||
Simulation sim(loadTestConfig());
|
||||
awaitRecipeUnlock(sim, "quick_circuit");
|
||||
REQUIRE(sim.isRecipeUnlocked("quick_circuit"));
|
||||
|
||||
@@ -272,7 +268,7 @@ TEST_CASE("RecipeSchematic: reset re-locks a previously awarded recipe schematic
|
||||
TEST_CASE("RecipeSchematic: reset keeps -1 recipes unlocked and their seed items accessible",
|
||||
"[recipe_schematic]")
|
||||
{
|
||||
Simulation sim(loadConfig());
|
||||
Simulation sim(loadTestConfig());
|
||||
sim.reset();
|
||||
|
||||
REQUIRE(sim.isRecipeUnlocked("premium_circuit"));
|
||||
@@ -286,7 +282,7 @@ TEST_CASE("RecipeSchematic: reset keeps -1 recipes unlocked and their seed items
|
||||
TEST_CASE("RecipeSchematic: newlyUnlockedRecipeIds is sorted, deduplicated, and empty for level-ups",
|
||||
"[recipe_schematic]")
|
||||
{
|
||||
Simulation sim(loadConfig());
|
||||
Simulation sim(loadTestConfig());
|
||||
|
||||
for (int i = 0; i < 100; ++i)
|
||||
{
|
||||
@@ -310,8 +306,8 @@ TEST_CASE("RecipeSchematic: newlyUnlockedRecipeIds is sorted, deduplicated, and
|
||||
TEST_CASE("RecipeSchematic: newlyUnlockedRecipeIds matches recipes that actually become unlocked",
|
||||
"[recipe_schematic]")
|
||||
{
|
||||
Simulation sim(loadConfig());
|
||||
const GameConfig cfg = loadConfig();
|
||||
Simulation sim(loadTestConfig());
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
|
||||
auto unlockedTrackedRecipeIds = [&]()
|
||||
{
|
||||
@@ -365,7 +361,7 @@ TEST_CASE("SchematicDrop: an owned ship schematic is never offered again",
|
||||
{
|
||||
// repair_ship has unlock_at_station_level = 0 in the test config, so it is
|
||||
// locked at start and becomes eligible once a station set is destroyed.
|
||||
Simulation sim(loadConfig());
|
||||
Simulation sim(loadTestConfig());
|
||||
REQUIRE_FALSE(sim.isSchematicUnlocked("repair_ship"));
|
||||
|
||||
bool wasUnlocked = false;
|
||||
|
||||
@@ -16,14 +16,10 @@
|
||||
#include "ReplayRecorder.h"
|
||||
#include "Rotation.h"
|
||||
#include "Simulation.h"
|
||||
#include "TestConfig.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
GameConfig loadConfig()
|
||||
{
|
||||
return ConfigLoader::loadFromDirectory(CONFIG_DIR);
|
||||
}
|
||||
|
||||
std::string tempOutputDir()
|
||||
{
|
||||
return (QDir::tempPath() + "/dota_factory_replay_playback_test").toStdString();
|
||||
@@ -148,7 +144,7 @@ TEST_CASE("a recorded run replays to byte-identical state with no desync", "[rep
|
||||
std::string replayPath;
|
||||
std::uint64_t recordedFinalChecksum = 0;
|
||||
{
|
||||
Simulation rec(loadConfig(), seed);
|
||||
Simulation rec(loadTestConfig(), seed);
|
||||
CommandManager manager(rec);
|
||||
std::unique_ptr<ReplayRecorder> recorder =
|
||||
std::make_unique<ReplayRecorder>(CONFIG_DIR, tempOutputDir());
|
||||
@@ -179,7 +175,7 @@ TEST_CASE("a recorded run replays to byte-identical state with no desync", "[rep
|
||||
REQUIRE_FALSE(parsed->entries.empty());
|
||||
|
||||
// --- Replay it. ---
|
||||
Simulation play(loadConfig(), parsed->header.seed);
|
||||
Simulation play(loadTestConfig(), parsed->header.seed);
|
||||
ReplayPlayer player(play, parsed->entries);
|
||||
player.start();
|
||||
while (!player.isFinished())
|
||||
@@ -201,7 +197,7 @@ TEST_CASE("ReplayPlayer reports a desync when a checksum is corrupted", "[replay
|
||||
|
||||
std::string replayPath;
|
||||
{
|
||||
Simulation rec(loadConfig(), seed);
|
||||
Simulation rec(loadTestConfig(), seed);
|
||||
CommandManager manager(rec);
|
||||
std::unique_ptr<ReplayRecorder> recorder =
|
||||
std::make_unique<ReplayRecorder>(CONFIG_DIR, tempOutputDir());
|
||||
@@ -228,7 +224,7 @@ TEST_CASE("ReplayPlayer reports a desync when a checksum is corrupted", "[replay
|
||||
}
|
||||
REQUIRE(corrupted);
|
||||
|
||||
Simulation play(loadConfig(), parsed->header.seed);
|
||||
Simulation play(loadTestConfig(), parsed->header.seed);
|
||||
ReplayPlayer player(play, parsed->entries);
|
||||
player.start();
|
||||
while (!player.isFinished())
|
||||
@@ -250,7 +246,7 @@ TEST_CASE("a long recorded run (through waves and combat) replays with no desync
|
||||
std::string replayPath;
|
||||
std::uint64_t recordedFinalChecksum = 0;
|
||||
{
|
||||
Simulation rec(loadConfig(), seed);
|
||||
Simulation rec(loadTestConfig(), seed);
|
||||
CommandManager manager(rec);
|
||||
std::unique_ptr<ReplayRecorder> recorder =
|
||||
std::make_unique<ReplayRecorder>(CONFIG_DIR, tempOutputDir());
|
||||
@@ -275,7 +271,7 @@ TEST_CASE("a long recorded run (through waves and combat) replays with no desync
|
||||
const std::optional<ParsedReplay> parsed = readReplayFile(replayPath);
|
||||
REQUIRE(parsed.has_value());
|
||||
|
||||
Simulation play(loadConfig(), parsed->header.seed);
|
||||
Simulation play(loadTestConfig(), parsed->header.seed);
|
||||
ReplayPlayer player(play, parsed->entries);
|
||||
player.start();
|
||||
while (!player.isFinished())
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "GameConfig.h"
|
||||
#include "ReplayRecorder.h"
|
||||
#include "Simulation.h"
|
||||
#include "TestConfig.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
@@ -144,7 +145,7 @@ TEST_CASE("ReplayRecorder writes a well-formed file", "[replay]")
|
||||
|
||||
TEST_CASE("CommandManager records commands and an initial checksum on drain", "[replay]")
|
||||
{
|
||||
Simulation sim(ConfigLoader::loadFromDirectory(CONFIG_DIR), 7u);
|
||||
Simulation sim(loadTestConfig(), 7u);
|
||||
CommandManager manager(sim);
|
||||
|
||||
std::unique_ptr<ReplayRecorder> recorder =
|
||||
@@ -187,7 +188,7 @@ TEST_CASE("ReplayRecorder startNewRun rolls to a new file", "[replay]")
|
||||
|
||||
TEST_CASE("CommandManager rolls the replay file on a Reset command", "[replay]")
|
||||
{
|
||||
Simulation sim(ConfigLoader::loadFromDirectory(CONFIG_DIR), 1u);
|
||||
Simulation sim(loadTestConfig(), 1u);
|
||||
CommandManager manager(sim);
|
||||
|
||||
std::unique_ptr<ReplayRecorder> recorder =
|
||||
@@ -197,7 +198,7 @@ TEST_CASE("CommandManager rolls the replay file on a Reset command", "[replay]")
|
||||
const std::string firstPath = recorderPtr->getCurrentFilePath();
|
||||
|
||||
std::shared_ptr<ResetCommand> reset = std::make_shared<ResetCommand>();
|
||||
reset->config = std::make_shared<GameConfig>(ConfigLoader::loadFromDirectory(CONFIG_DIR));
|
||||
reset->config = std::make_shared<GameConfig>(loadTestConfig());
|
||||
reset->seed = 999u;
|
||||
manager.enqueue(reset);
|
||||
manager.drain();
|
||||
|
||||
@@ -22,11 +22,7 @@
|
||||
#include "SimulationTestAccess.h"
|
||||
#include "Tick.h"
|
||||
#include "WeaponComponent.h"
|
||||
|
||||
static GameConfig loadConfig()
|
||||
{
|
||||
return ConfigLoader::loadFromDirectory(CONFIG_DIR);
|
||||
}
|
||||
#include "TestConfig.h"
|
||||
|
||||
static const ShipDef* findSchematic(const GameConfig& cfg, const std::string& id)
|
||||
{
|
||||
@@ -108,7 +104,7 @@ static void fillMaterials(Simulation& sim, BuildingId yardId,
|
||||
|
||||
TEST_CASE("Ship spawn: no modules leaves base stats unchanged", "[modules]")
|
||||
{
|
||||
Simulation sim(loadConfig(), 42);
|
||||
Simulation sim(loadTestConfig(), 42);
|
||||
const ShipDef* def = findSchematic(sim.getConfig(), "interceptor");
|
||||
REQUIRE(def != nullptr);
|
||||
|
||||
@@ -123,7 +119,7 @@ TEST_CASE("Ship spawn: no modules leaves base stats unchanged", "[modules]")
|
||||
|
||||
TEST_CASE("Ship spawn: multiplicative HP module applies correctly", "[modules]")
|
||||
{
|
||||
Simulation sim(loadConfig(), 42);
|
||||
Simulation sim(loadTestConfig(), 42);
|
||||
const ShipDef* def = findSchematic(sim.getConfig(), "interceptor");
|
||||
REQUIRE(def != nullptr);
|
||||
|
||||
@@ -148,7 +144,7 @@ TEST_CASE("Ship spawn: multiplicative HP module applies correctly", "[modules]")
|
||||
|
||||
TEST_CASE("Ship spawn: additive sensor module applies correctly", "[modules]")
|
||||
{
|
||||
Simulation sim(loadConfig(), 42);
|
||||
Simulation sim(loadTestConfig(), 42);
|
||||
const ShipDef* def = findSchematic(sim.getConfig(), "interceptor");
|
||||
REQUIRE(def != nullptr);
|
||||
|
||||
@@ -173,7 +169,7 @@ TEST_CASE("Ship spawn: additive sensor module applies correctly", "[modules]")
|
||||
|
||||
TEST_CASE("Ship spawn: multiple modules stack correctly", "[modules]")
|
||||
{
|
||||
Simulation sim(loadConfig(), 42);
|
||||
Simulation sim(loadTestConfig(), 42);
|
||||
const ShipDef* def = findSchematic(sim.getConfig(), "interceptor");
|
||||
REQUIRE(def != nullptr);
|
||||
|
||||
@@ -206,7 +202,7 @@ TEST_CASE("Ship spawn: multiple modules stack correctly", "[modules]")
|
||||
TEST_CASE("Shipyard: setShipLayout reinitializes buffers with module materials",
|
||||
"[modules][shipyard]")
|
||||
{
|
||||
Simulation sim(loadConfig(), 42);
|
||||
Simulation sim(loadTestConfig(), 42);
|
||||
const BuildingDef* yardDef = findShipyardDef(sim.getConfig());
|
||||
REQUIRE(yardDef != nullptr);
|
||||
|
||||
@@ -233,7 +229,7 @@ TEST_CASE("Shipyard: setShipLayout reinitializes buffers with module materials",
|
||||
TEST_CASE("Shipyard: setShipLayout cancels in-progress production",
|
||||
"[modules][shipyard]")
|
||||
{
|
||||
Simulation sim(loadConfig(), 42);
|
||||
Simulation sim(loadTestConfig(), 42);
|
||||
const ShipDef* def = findSchematic(sim.getConfig(), "interceptor");
|
||||
REQUIRE(def != nullptr);
|
||||
const BuildingDef* yardDef = findShipyardDef(sim.getConfig());
|
||||
@@ -269,7 +265,7 @@ TEST_CASE("Shipyard: setShipLayout cancels in-progress production",
|
||||
TEST_CASE("Shipyard: builds a bare hull when no layout is configured",
|
||||
"[modules][shipyard]")
|
||||
{
|
||||
Simulation sim(loadConfig(), 42);
|
||||
Simulation sim(loadTestConfig(), 42);
|
||||
const ShipDef* def = findSchematic(sim.getConfig(), "interceptor");
|
||||
REQUIRE(def != nullptr);
|
||||
// The schematic carries a weapon in its (wave-only) default loadout. This
|
||||
@@ -312,7 +308,7 @@ TEST_CASE("Shipyard: builds a bare hull when no layout is configured",
|
||||
|
||||
TEST_CASE("Shipyard: setRecipe clears ship layout", "[modules][shipyard]")
|
||||
{
|
||||
Simulation sim(loadConfig(), 42);
|
||||
Simulation sim(loadTestConfig(), 42);
|
||||
const BuildingDef* yardDef = findShipyardDef(sim.getConfig());
|
||||
REQUIRE(yardDef != nullptr);
|
||||
|
||||
@@ -341,7 +337,7 @@ TEST_CASE("Shipyard: setRecipe clears ship layout", "[modules][shipyard]")
|
||||
TEST_CASE("Shipyard: setRecipe with unchanged recipe keeps ship layout",
|
||||
"[modules][shipyard]")
|
||||
{
|
||||
Simulation sim(loadConfig(), 42);
|
||||
Simulation sim(loadTestConfig(), 42);
|
||||
const BuildingDef* yardDef = findShipyardDef(sim.getConfig());
|
||||
REQUIRE(yardDef != nullptr);
|
||||
|
||||
@@ -376,7 +372,7 @@ TEST_CASE("Shipyard: setRecipe with unchanged recipe keeps ship layout",
|
||||
|
||||
TEST_CASE("Ship spawn: weapon_primer multiplies attack rate in simulation", "[modules]")
|
||||
{
|
||||
Simulation sim(loadConfig(), 42);
|
||||
Simulation sim(loadTestConfig(), 42);
|
||||
const ShipDef* def = findSchematic(sim.getConfig(), "interceptor");
|
||||
REQUIRE(def != nullptr);
|
||||
|
||||
@@ -401,7 +397,7 @@ TEST_CASE("Ship spawn: weapon_primer multiplies attack rate in simulation", "[mo
|
||||
|
||||
TEST_CASE("Ship spawn: weapon_stabilizer multiplies attack range in simulation", "[modules]")
|
||||
{
|
||||
Simulation sim(loadConfig(), 42);
|
||||
Simulation sim(loadTestConfig(), 42);
|
||||
const ShipDef* def = findSchematic(sim.getConfig(), "interceptor");
|
||||
REQUIRE(def != nullptr);
|
||||
|
||||
@@ -428,7 +424,7 @@ TEST_CASE("Ship spawn: weapon_stabilizer multiplies attack range in simulation",
|
||||
|
||||
TEST_CASE("Ship spawn: afterburner additive main_acceleration is converted m/s² to tiles/tick", "[modules]")
|
||||
{
|
||||
Simulation sim(loadConfig(), 42);
|
||||
Simulation sim(loadTestConfig(), 42);
|
||||
const ShipDef* def = findSchematic(sim.getConfig(), "interceptor");
|
||||
REQUIRE(def != nullptr);
|
||||
|
||||
@@ -453,7 +449,7 @@ TEST_CASE("Ship spawn: afterburner additive main_acceleration is converted m/s²
|
||||
|
||||
TEST_CASE("Ship spawn: maneuvering_thrusters additive maneuvering_acceleration is converted m/s² to tiles/tick", "[modules]")
|
||||
{
|
||||
Simulation sim(loadConfig(), 42);
|
||||
Simulation sim(loadTestConfig(), 42);
|
||||
const ShipDef* def = findSchematic(sim.getConfig(), "interceptor");
|
||||
REQUIRE(def != nullptr);
|
||||
|
||||
@@ -482,7 +478,7 @@ TEST_CASE("Ship spawn: maneuvering_thrusters additive maneuvering_acceleration i
|
||||
|
||||
TEST_CASE("calculateShipStats: weapon_primer multiplies attack rate in stats view", "[modules]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
const ShipDef* def = findSchematic(cfg, "interceptor");
|
||||
REQUIRE(def != nullptr);
|
||||
|
||||
@@ -506,7 +502,7 @@ TEST_CASE("calculateShipStats: weapon_primer multiplies attack rate in stats vie
|
||||
|
||||
TEST_CASE("calculateShipStats: weapon_stabilizer multiplies attack range in stats view", "[modules]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
const ShipDef* def = findSchematic(cfg, "interceptor");
|
||||
REQUIRE(def != nullptr);
|
||||
|
||||
@@ -532,7 +528,7 @@ TEST_CASE("calculateShipStats: weapon_stabilizer multiplies attack range in stat
|
||||
|
||||
TEST_CASE("calculateShipStats: afterburner additive main_acceleration is converted m/s² to tiles/s²", "[modules]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
const ShipDef* def = findSchematic(cfg, "interceptor");
|
||||
REQUIRE(def != nullptr);
|
||||
|
||||
@@ -556,7 +552,7 @@ TEST_CASE("calculateShipStats: afterburner additive main_acceleration is convert
|
||||
|
||||
TEST_CASE("calculateShipStats: maneuvering_thrusters additive maneuvering_acceleration is converted m/s² to tiles/s²", "[modules]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
const ShipDef* def = findSchematic(cfg, "interceptor");
|
||||
REQUIRE(def != nullptr);
|
||||
|
||||
|
||||
@@ -29,11 +29,7 @@
|
||||
#include "ShipSystem.h"
|
||||
#include "Tick.h"
|
||||
#include "WeaponComponent.h"
|
||||
|
||||
static GameConfig loadConfig()
|
||||
{
|
||||
return ConfigLoader::loadFromDirectory(CONFIG_DIR);
|
||||
}
|
||||
#include "TestConfig.h"
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers
|
||||
@@ -91,7 +87,7 @@ TEST_CASE("ShipSystem: interceptor spawn has weapon child and attack behavior, n
|
||||
"[ship]")
|
||||
{
|
||||
EntityAdmin admin;
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
ShipSystem ss(cfg, admin);
|
||||
|
||||
const entt::entity e = ss.spawn("interceptor", QVector2D(0.0f, 0.0f));
|
||||
@@ -115,7 +111,7 @@ TEST_CASE("ShipSystem: interceptor spawn has weapon child and attack behavior, n
|
||||
TEST_CASE("ShipSystem: enemy combat ship has no rally or retreat behavior", "[ship]")
|
||||
{
|
||||
EntityAdmin admin;
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
ShipSystem ss(cfg, admin);
|
||||
|
||||
const entt::entity e = ss.spawn("interceptor", QVector2D(0.0f, 0.0f), /*isEnemy=*/true);
|
||||
@@ -129,7 +125,7 @@ TEST_CASE("ShipSystem: enemy combat ship has no rally or retreat behavior", "[sh
|
||||
TEST_CASE("ShipSystem: setRetreatEnabled(false) suppresses player retreat behavior", "[ship]")
|
||||
{
|
||||
EntityAdmin admin;
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
ShipSystem ss(cfg, admin);
|
||||
ss.setRetreatEnabled(false);
|
||||
|
||||
@@ -144,7 +140,7 @@ TEST_CASE("ShipSystem: setRetreatEnabled(false) suppresses player retreat behavi
|
||||
TEST_CASE("ShipSystem: interceptor level 1 stats match config formulas", "[ship]")
|
||||
{
|
||||
EntityAdmin admin;
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
ShipSystem ss(cfg, admin);
|
||||
|
||||
const entt::entity e = ss.spawn("interceptor", QVector2D(0.0f, 0.0f));
|
||||
@@ -166,7 +162,7 @@ TEST_CASE("ShipSystem: interceptor level 1 stats match config formulas", "[ship]
|
||||
TEST_CASE("ShipSystem: interceptor hp matches config value", "[ship]")
|
||||
{
|
||||
EntityAdmin admin;
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
ShipSystem ss(cfg, admin);
|
||||
|
||||
const entt::entity e = ss.spawn("interceptor", QVector2D(0.0f, 0.0f));
|
||||
@@ -178,7 +174,7 @@ TEST_CASE("ShipSystem: interceptor hp matches config value", "[ship]")
|
||||
TEST_CASE("ShipSystem: interceptor maxSpeed_tpt matches config value / tileSize / kTickRateHz", "[ship]")
|
||||
{
|
||||
EntityAdmin admin;
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
ShipSystem ss(cfg, admin);
|
||||
|
||||
const entt::entity e = ss.spawn("interceptor", QVector2D(0.0f, 0.0f));
|
||||
@@ -196,7 +192,7 @@ TEST_CASE("ShipSystem: salvage_ship spawn with salvage module has cargo child an
|
||||
"[ship]")
|
||||
{
|
||||
EntityAdmin admin;
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
ShipSystem ss(cfg, admin);
|
||||
|
||||
const ShipLayoutConfig layout = makeSingleModuleLayout("salvager");
|
||||
@@ -212,7 +208,7 @@ TEST_CASE("ShipSystem: salvage_ship spawn with salvage module has cargo child an
|
||||
TEST_CASE("ShipSystem: salvage_ship cargo capacity matches config", "[ship]")
|
||||
{
|
||||
EntityAdmin admin;
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
ShipSystem ss(cfg, admin);
|
||||
|
||||
const ShipLayoutConfig layout = makeSingleModuleLayout("salvager");
|
||||
@@ -237,7 +233,7 @@ TEST_CASE("ShipSystem: repair_ship spawn with repair module has repair child and
|
||||
"[ship]")
|
||||
{
|
||||
EntityAdmin admin;
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
ShipSystem ss(cfg, admin);
|
||||
|
||||
const ShipLayoutConfig layout = makeSingleModuleLayout("repair_tool");
|
||||
@@ -252,7 +248,7 @@ TEST_CASE("ShipSystem: repair_ship spawn with repair module has repair child and
|
||||
TEST_CASE("ShipSystem: repair_ship level 1 repair stats match config formulas", "[ship]")
|
||||
{
|
||||
EntityAdmin admin;
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
ShipSystem ss(cfg, admin);
|
||||
|
||||
const ShipLayoutConfig layout = makeSingleModuleLayout("repair_tool");
|
||||
@@ -277,7 +273,7 @@ TEST_CASE("ShipSystem: repair_ship level 1 repair stats match config formulas",
|
||||
TEST_CASE("ShipSystem: spawned ships are valid entities", "[ship]")
|
||||
{
|
||||
EntityAdmin admin;
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
ShipSystem ss(cfg, admin);
|
||||
|
||||
const entt::entity e1 = ss.spawn("interceptor", QVector2D(0.0f, 0.0f));
|
||||
@@ -292,7 +288,7 @@ TEST_CASE("ShipSystem: spawned ships are valid entities", "[ship]")
|
||||
TEST_CASE("ShipSystem: despawn removes the ship and its weapon children", "[ship]")
|
||||
{
|
||||
EntityAdmin admin;
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
ShipSystem ss(cfg, admin);
|
||||
|
||||
const entt::entity e = ss.spawn("interceptor", QVector2D(0.0f, 0.0f));
|
||||
|
||||
@@ -16,11 +16,7 @@
|
||||
#include "Simulation.h"
|
||||
#include "SimulationTestAccess.h"
|
||||
#include "Tick.h"
|
||||
|
||||
static GameConfig loadConfig()
|
||||
{
|
||||
return ConfigLoader::loadFromDirectory(CONFIG_DIR);
|
||||
}
|
||||
#include "TestConfig.h"
|
||||
|
||||
// A ship starts unlocked iff no unlock group grants it (REQ-LOCK-EXPLICIT).
|
||||
static bool startsUnlocked(const GameConfig& cfg, const std::string& shipId)
|
||||
@@ -98,7 +94,7 @@ static void fillMaterials(Simulation& sim, BuildingId yardId, const ShipDef& def
|
||||
TEST_CASE("Shipyard: spawns a player ship after production cycle completes",
|
||||
"[shipyard]")
|
||||
{
|
||||
Simulation sim(loadConfig(), 42);
|
||||
Simulation sim(loadTestConfig(), 42);
|
||||
|
||||
const ShipDef* def = findAvailableSchematic(sim.getConfig());
|
||||
REQUIRE(def != nullptr);
|
||||
@@ -143,7 +139,7 @@ TEST_CASE("Shipyard: spawns a player ship after production cycle completes",
|
||||
|
||||
TEST_CASE("Shipyard: does not spawn without a schematic set", "[shipyard]")
|
||||
{
|
||||
Simulation sim(loadConfig(), 42);
|
||||
Simulation sim(loadTestConfig(), 42);
|
||||
|
||||
const BuildingDef* yardDef = findShipyardDef(sim.getConfig());
|
||||
REQUIRE(yardDef != nullptr);
|
||||
@@ -159,7 +155,7 @@ TEST_CASE("Shipyard: does not spawn without a schematic set", "[shipyard]")
|
||||
|
||||
TEST_CASE("Shipyard: does not spawn with insufficient materials", "[shipyard]")
|
||||
{
|
||||
Simulation sim(loadConfig(), 42);
|
||||
Simulation sim(loadTestConfig(), 42);
|
||||
|
||||
const ShipDef* def = findAvailableSchematic(sim.getConfig());
|
||||
REQUIRE(def != nullptr);
|
||||
@@ -183,7 +179,7 @@ TEST_CASE("Shipyard: does not spawn with insufficient materials", "[shipyard]")
|
||||
|
||||
TEST_CASE("Shipyard: spawns a second ship after materials replenished", "[shipyard]")
|
||||
{
|
||||
Simulation sim(loadConfig(), 42);
|
||||
Simulation sim(loadTestConfig(), 42);
|
||||
|
||||
const ShipDef* def = findAvailableSchematic(sim.getConfig());
|
||||
REQUIRE(def != nullptr);
|
||||
|
||||
@@ -5,11 +5,7 @@
|
||||
#include "Simulation.h"
|
||||
#include "Tick.h"
|
||||
#include "TickDriver.h"
|
||||
|
||||
static GameConfig loadConfig()
|
||||
{
|
||||
return ConfigLoader::loadFromDirectory(CONFIG_DIR);
|
||||
}
|
||||
#include "TestConfig.h"
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Simulation
|
||||
@@ -17,14 +13,14 @@ static GameConfig loadConfig()
|
||||
|
||||
TEST_CASE("Simulation::currentTick starts at 0", "[simulation]")
|
||||
{
|
||||
const Simulation sim(loadConfig());
|
||||
const Simulation sim(loadTestConfig());
|
||||
|
||||
REQUIRE(sim.getCurrentTick() == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("Simulation::tick increments currentTick by 1", "[simulation]")
|
||||
{
|
||||
Simulation sim(loadConfig());
|
||||
Simulation sim(loadTestConfig());
|
||||
|
||||
sim.tick();
|
||||
|
||||
@@ -33,7 +29,7 @@ TEST_CASE("Simulation::tick increments currentTick by 1", "[simulation]")
|
||||
|
||||
TEST_CASE("Simulation::tick 10 times yields currentTick == 10", "[simulation]")
|
||||
{
|
||||
Simulation sim(loadConfig());
|
||||
Simulation sim(loadTestConfig());
|
||||
|
||||
for (int i = 0; i < 10; ++i)
|
||||
{
|
||||
@@ -45,14 +41,14 @@ TEST_CASE("Simulation::tick 10 times yields currentTick == 10", "[simulation]")
|
||||
|
||||
TEST_CASE("Simulation::drainBeamFiredEvents returns empty initially", "[simulation]")
|
||||
{
|
||||
Simulation sim(loadConfig());
|
||||
Simulation sim(loadTestConfig());
|
||||
|
||||
REQUIRE(sim.drainBeamFiredEvents().empty());
|
||||
}
|
||||
|
||||
TEST_CASE("Simulation::drainBeamFiredEvents clears queue on drain", "[simulation]")
|
||||
{
|
||||
Simulation sim(loadConfig());
|
||||
Simulation sim(loadTestConfig());
|
||||
|
||||
// First drain: empty.
|
||||
sim.drainBeamFiredEvents();
|
||||
@@ -63,7 +59,7 @@ TEST_CASE("Simulation::drainBeamFiredEvents clears queue on drain", "[simulation
|
||||
|
||||
TEST_CASE("Simulation::hasSchematicChoicesPending returns false initially", "[simulation]")
|
||||
{
|
||||
Simulation sim(loadConfig());
|
||||
Simulation sim(loadTestConfig());
|
||||
|
||||
REQUIRE_FALSE(sim.hasSchematicChoicesPending());
|
||||
}
|
||||
|
||||
15
src/test/TestConfig.h
Normal file
15
src/test/TestConfig.h
Normal file
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include "ConfigLoader.h"
|
||||
#include "GameConfig.h"
|
||||
|
||||
// Loads the test fixture config set (CONFIG_DIR points at bin/test/data/config
|
||||
// for the test target). Shared by every test that needs a full GameConfig, so
|
||||
// the one-liner is not repeated per translation unit.
|
||||
//
|
||||
// Like SimulationTestAccess.h this header lives under src/test and is
|
||||
// deliberately off the lib/ui/app include path.
|
||||
inline GameConfig loadTestConfig()
|
||||
{
|
||||
return ConfigLoader::loadFromDirectory(CONFIG_DIR);
|
||||
}
|
||||
@@ -2,15 +2,11 @@
|
||||
|
||||
#include "ConfigLoader.h"
|
||||
#include "ThreatCostCalculator.h"
|
||||
|
||||
static GameConfig loadConfig()
|
||||
{
|
||||
return ConfigLoader::loadFromDirectory(CONFIG_DIR);
|
||||
}
|
||||
#include "TestConfig.h"
|
||||
|
||||
TEST_CASE("ThreatCostCalculator: miner item threat equals duration", "[threat]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
const ThreatCostTable& table = cfg.threatCosts;
|
||||
|
||||
CHECK(table.itemThreat.at("iron_ore") == Approx(1.0));
|
||||
@@ -19,7 +15,7 @@ TEST_CASE("ThreatCostCalculator: miner item threat equals duration", "[threat]")
|
||||
|
||||
TEST_CASE("ThreatCostCalculator: smelter item threat includes input costs", "[threat]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
const ThreatCostTable& table = cfg.threatCosts;
|
||||
|
||||
// iron_ingot: duration 2.0 + iron_ore(1.0) * 2 = 4.0
|
||||
@@ -30,7 +26,7 @@ TEST_CASE("ThreatCostCalculator: smelter item threat includes input costs", "[th
|
||||
|
||||
TEST_CASE("ThreatCostCalculator: assembler takes max across recipes", "[threat]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
const ThreatCostTable& table = cfg.threatCosts;
|
||||
|
||||
// circuit_board has three non-reprocessing recipes:
|
||||
@@ -43,7 +39,7 @@ TEST_CASE("ThreatCostCalculator: assembler takes max across recipes", "[threat]"
|
||||
|
||||
TEST_CASE("ThreatCostCalculator: scrap threat is 1 / scrap_per_threat", "[threat]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
const ThreatCostTable& table = cfg.threatCosts;
|
||||
|
||||
// REQ-THREAT-SCRAP: scrap threat is the constant 1 / world.scrap_per_threat.
|
||||
@@ -54,7 +50,7 @@ TEST_CASE("ThreatCostCalculator: scrap threat is 1 / scrap_per_threat", "[threat
|
||||
|
||||
TEST_CASE("ThreatCostCalculator: reprocessing-only item threat", "[threat]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
const ThreatCostTable& table = cfg.threatCosts;
|
||||
|
||||
// advanced_alloy: reprocessing recipe with scrap*5, duration 3.0, probability 0.1
|
||||
@@ -64,7 +60,7 @@ TEST_CASE("ThreatCostCalculator: reprocessing-only item threat", "[threat]")
|
||||
|
||||
TEST_CASE("ThreatCostCalculator: ship threat with default modules", "[threat]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
const ThreatCostTable& table = cfg.threatCosts;
|
||||
|
||||
// interceptor: 10 + iron_ingot(4)*3 + circuit_board(28)*1 + laser_cannon(5 + 4*1) = 59.0
|
||||
@@ -80,7 +76,7 @@ TEST_CASE("ThreatCostCalculator: ship threat with default modules", "[threat]")
|
||||
|
||||
TEST_CASE("ThreatCostCalculator: ship threat with custom modules", "[threat]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
const ThreatCostTable& table = cfg.threatCosts;
|
||||
|
||||
// interceptor base: 10 + iron_ingot(4)*3 + circuit_board(28)*1 = 50.0
|
||||
@@ -101,7 +97,7 @@ TEST_CASE("ThreatCostCalculator: ship threat with custom modules", "[threat]")
|
||||
|
||||
TEST_CASE("ThreatCostCalculator: unknown ship returns zero", "[threat]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
const ThreatCostTable& table = cfg.threatCosts;
|
||||
|
||||
double threat = calculateShipThreatCost(table, cfg, "nonexistent_ship", {});
|
||||
@@ -113,7 +109,7 @@ TEST_CASE("ThreatCostCalculator: unknown ship returns zero", "[threat]")
|
||||
// be excluded. iron_ingot threat must not be inflated by the scrap path.
|
||||
TEST_CASE("ThreatCostCalculator: scrap-consuming recipe excluded when scrap-free recipe exists", "[threat]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
const ThreatCostTable& table = cfg.threatCosts;
|
||||
|
||||
// scrap_iron recipe: duration=1.0, 1 scrap (threat=1.0) -> 1 iron_ingot.
|
||||
@@ -132,7 +128,7 @@ TEST_CASE("ThreatCostCalculator: scrap-consuming recipe excluded when scrap-free
|
||||
// Per-unit threat = (3.0 + iron_ore(1.0)*1) / 2 = 4.0 / 2 = 2.0.
|
||||
TEST_CASE("ThreatCostCalculator: per-unit division by output amount", "[threat]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
const ThreatCostTable& table = cfg.threatCosts;
|
||||
|
||||
CHECK(table.itemThreat.at("dual_wire") == Approx(2.0));
|
||||
@@ -146,7 +142,7 @@ TEST_CASE("ThreatCostCalculator: per-unit division by output amount", "[threat]"
|
||||
// downstream_product = 2.0 + 80.0*1 = 82.0.
|
||||
TEST_CASE("ThreatCostCalculator: downstream-of-reprocessing item resolves via fixpoint", "[threat]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
const ThreatCostTable& table = cfg.threatCosts;
|
||||
|
||||
CHECK(table.itemThreat.at("advanced_alloy") == Approx(80.0));
|
||||
@@ -161,7 +157,7 @@ TEST_CASE("ThreatCostCalculator: downstream-of-reprocessing item resolves via fi
|
||||
// expected threat = max(2.0, 29.0) = 29.0, not 2.0.
|
||||
TEST_CASE("ThreatCostCalculator: staggered recipes committed only when all computable", "[threat]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
const ThreatCostTable& table = cfg.threatCosts;
|
||||
|
||||
CHECK(table.itemThreat.at("staggered_item") == Approx(29.0));
|
||||
|
||||
@@ -19,15 +19,11 @@
|
||||
#include "SimulationTestAccess.h"
|
||||
#include "StationBodyComponent.h"
|
||||
#include "UnlocksConfig.h"
|
||||
#include "TestConfig.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
GameConfig loadConfig()
|
||||
{
|
||||
return ConfigLoader::loadFromDirectory(CONFIG_DIR);
|
||||
}
|
||||
|
||||
void killEnemyStations(Simulation& sim)
|
||||
{
|
||||
sim.getAdmin().forEach<StationBodyComponent, FactionComponent, HealthComponent>(
|
||||
@@ -42,7 +38,7 @@ void killEnemyStations(Simulation& sim)
|
||||
// bay together, gated to the given station level.
|
||||
GameConfig configWithSalvageGroup(int stationLevel)
|
||||
{
|
||||
GameConfig cfg = loadConfig();
|
||||
GameConfig cfg = loadTestConfig();
|
||||
cfg.unlocks.groups.clear();
|
||||
cfg.unlocks.groups.push_back(
|
||||
UnlockGroupDef{"salvage_operations", stationLevel, {}, {}, {"salvager"}, {"salvage_bay"}, {}});
|
||||
|
||||
@@ -22,15 +22,11 @@
|
||||
#include "SimulationTestAccess.h"
|
||||
#include "StationBodyComponent.h"
|
||||
#include "UnlocksConfig.h"
|
||||
#include "TestConfig.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
GameConfig loadConfig()
|
||||
{
|
||||
return ConfigLoader::loadFromDirectory(CONFIG_DIR);
|
||||
}
|
||||
|
||||
UnlockGroupDef& findGroup(GameConfig& cfg, const std::string& id)
|
||||
{
|
||||
for (UnlockGroupDef& group : cfg.unlocks.groups)
|
||||
@@ -85,7 +81,7 @@ TEST_CASE("UnlockPrereq: a group gated behind a locked ship is withheld from the
|
||||
// quick_circuit (recipe group, level 0) would normally be eligible at the
|
||||
// first station destruction. Gate it behind the repair_ship group (level 0,
|
||||
// locked at game start).
|
||||
GameConfig cfg = loadConfig();
|
||||
GameConfig cfg = loadTestConfig();
|
||||
findGroup(cfg, "quick_circuit").requiredGroupIds = {"repair_ship"};
|
||||
|
||||
Simulation sim(std::move(cfg), 123);
|
||||
@@ -103,7 +99,7 @@ TEST_CASE("UnlockPrereq: a group gated behind a locked ship is withheld from the
|
||||
TEST_CASE("UnlockPrereq: the gated group becomes eligible once its prerequisite is awarded",
|
||||
"[unlock_prereq]")
|
||||
{
|
||||
GameConfig cfg = loadConfig();
|
||||
GameConfig cfg = loadTestConfig();
|
||||
findGroup(cfg, "quick_circuit").requiredGroupIds = {"repair_ship"};
|
||||
|
||||
Simulation sim(std::move(cfg), 123);
|
||||
@@ -126,7 +122,7 @@ TEST_CASE("UnlockPrereq: a module group gated behind another module is withheld
|
||||
{
|
||||
// Make laser_cannon and armor_plate (both start-unlocked in the test config)
|
||||
// lockable via new unlock groups, with armor_plate gated behind laser_cannon.
|
||||
GameConfig cfg = loadConfig();
|
||||
GameConfig cfg = loadTestConfig();
|
||||
cfg.unlocks.groups.push_back(
|
||||
UnlockGroupDef{"laser_cannon", 0, {}, {}, {"laser_cannon"}, {}, {}});
|
||||
cfg.unlocks.groups.push_back(
|
||||
|
||||
@@ -25,11 +25,7 @@
|
||||
#include "Tick.h"
|
||||
#include "ThreatCostCalculator.h"
|
||||
#include "WaveSystem.h"
|
||||
|
||||
static GameConfig loadConfig()
|
||||
{
|
||||
return ConfigLoader::loadFromDirectory(CONFIG_DIR);
|
||||
}
|
||||
#include "TestConfig.h"
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Threat accumulation
|
||||
@@ -37,7 +33,7 @@ static GameConfig loadConfig()
|
||||
|
||||
TEST_CASE("WaveSystem: threat accumulates at boss wave counter rate", "[wave]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
std::mt19937 rng(42);
|
||||
WaveSystem ws(cfg, rng);
|
||||
|
||||
@@ -55,7 +51,7 @@ TEST_CASE("WaveSystem: threat accumulates at boss wave counter rate", "[wave]")
|
||||
TEST_CASE("WaveSystem: threatAccumulationRate matches the rate formula outside quiet windows",
|
||||
"[wave]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
std::mt19937 rng(42);
|
||||
WaveSystem ws(cfg, rng);
|
||||
|
||||
@@ -65,7 +61,7 @@ TEST_CASE("WaveSystem: threatAccumulationRate matches the rate formula outside q
|
||||
|
||||
TEST_CASE("WaveSystem: threatAccumulationRate is 0 during a quiet window", "[wave]")
|
||||
{
|
||||
GameConfig cfg = loadConfig();
|
||||
GameConfig cfg = loadTestConfig();
|
||||
// Start with the boss countdown already at the pre-boss quiet threshold.
|
||||
cfg.world.waves.bossCountdownSeconds = cfg.world.waves.bossQuietBeforeSeconds;
|
||||
std::mt19937 rng(42);
|
||||
@@ -83,7 +79,7 @@ TEST_CASE("WaveSystem: threatAccumulationRate is 0 during a quiet window", "[wav
|
||||
|
||||
TEST_CASE("WaveSystem: generation starts at 0 and increments on station destruction", "[wave]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
std::mt19937 rng(42);
|
||||
WaveSystem ws(cfg, rng);
|
||||
|
||||
@@ -100,7 +96,7 @@ TEST_CASE("WaveSystem: generation starts at 0 and increments on station destruct
|
||||
|
||||
TEST_CASE("WaveSystem: Simulation pre-places HQ + 2 player + 2 enemy stations", "[wave]")
|
||||
{
|
||||
const Simulation sim(loadConfig(), 42);
|
||||
const Simulation sim(loadTestConfig(), 42);
|
||||
|
||||
// HQ is still a Building (for belt integration).
|
||||
int hqCount = 0;
|
||||
@@ -126,7 +122,7 @@ TEST_CASE("WaveSystem: Simulation pre-places HQ + 2 player + 2 enemy stations",
|
||||
|
||||
TEST_CASE("WaveSystem: HQ has correct initial HP from config", "[wave]")
|
||||
{
|
||||
const Simulation sim(loadConfig(), 42);
|
||||
const Simulation sim(loadTestConfig(), 42);
|
||||
|
||||
const float expectedHp =
|
||||
static_cast<float>(sim.getConfig().stations.hq.hpFormula.evaluate(0.0));
|
||||
@@ -145,7 +141,7 @@ TEST_CASE("WaveSystem: HQ has correct initial HP from config", "[wave]")
|
||||
|
||||
TEST_CASE("WaveSystem: HQ anchor is at asteroid right edge", "[wave]")
|
||||
{
|
||||
const Simulation sim(loadConfig(), 42);
|
||||
const Simulation sim(loadTestConfig(), 42);
|
||||
|
||||
for (const Building& b : sim.getBuildings().getAllBuildings())
|
||||
{
|
||||
@@ -162,7 +158,7 @@ TEST_CASE("WaveSystem: HQ anchor is at asteroid right edge", "[wave]")
|
||||
|
||||
TEST_CASE("WaveSystem: player stations have weapon set", "[wave]")
|
||||
{
|
||||
Simulation sim(loadConfig(), 42);
|
||||
Simulation sim(loadTestConfig(), 42);
|
||||
|
||||
int armedPlayerStations = 0;
|
||||
sim.getAdmin().forEach<WeaponComponent, ModuleOwnerComponent>(
|
||||
@@ -183,7 +179,7 @@ TEST_CASE("WaveSystem: player stations have weapon set", "[wave]")
|
||||
|
||||
TEST_CASE("WaveSystem: enemy stations have weapon set", "[wave]")
|
||||
{
|
||||
Simulation sim(loadConfig(), 42);
|
||||
Simulation sim(loadTestConfig(), 42);
|
||||
|
||||
int armedEnemyStations = 0;
|
||||
sim.getAdmin().forEach<WeaponComponent, ModuleOwnerComponent>(
|
||||
@@ -208,7 +204,7 @@ TEST_CASE("WaveSystem: enemy stations have weapon set", "[wave]")
|
||||
|
||||
TEST_CASE("WaveSystem: enemy ships spawn after the initial gap elapses", "[wave]")
|
||||
{
|
||||
Simulation sim(loadConfig(), 42);
|
||||
Simulation sim(loadTestConfig(), 42);
|
||||
|
||||
// The maximum gap is gapMaxSeconds = 45s → 1350 ticks.
|
||||
// Run 1500 ticks to guarantee at least one wave has triggered.
|
||||
@@ -234,7 +230,7 @@ TEST_CASE("WaveSystem: enemy ships spawn after the initial gap elapses", "[wave]
|
||||
|
||||
TEST_CASE("WaveSystem: all ships have positive dynamic threat cost", "[wave]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
const GameConfig cfg = loadTestConfig();
|
||||
|
||||
for (const ShipDef& def : cfg.ships.ships)
|
||||
{
|
||||
@@ -250,7 +246,7 @@ TEST_CASE("WaveSystem: all ships have positive dynamic threat cost", "[wave]")
|
||||
|
||||
TEST_CASE("WaveSystem: destroying both enemy stations triggers a push", "[wave]")
|
||||
{
|
||||
Simulation sim(loadConfig(), 42);
|
||||
Simulation sim(loadTestConfig(), 42);
|
||||
|
||||
// Damage both enemy stations to 0.
|
||||
sim.getAdmin().forEach<StationBodyComponent, FactionComponent, HealthComponent>(
|
||||
@@ -273,7 +269,7 @@ TEST_CASE("WaveSystem: destroying both enemy stations triggers a push", "[wave]"
|
||||
|
||||
TEST_CASE("WaveSystem: push generates pending schematic choices", "[wave]")
|
||||
{
|
||||
Simulation sim(loadConfig(), 42);
|
||||
Simulation sim(loadTestConfig(), 42);
|
||||
|
||||
sim.getAdmin().forEach<StationBodyComponent, FactionComponent, HealthComponent>(
|
||||
[](entt::entity /*e*/, const StationBodyComponent& /*sb*/, const FactionComponent& f, HealthComponent& h)
|
||||
@@ -291,7 +287,7 @@ TEST_CASE("WaveSystem: push generates pending schematic choices", "[wave]")
|
||||
|
||||
TEST_CASE("WaveSystem: push schematic choices have valid ids", "[wave]")
|
||||
{
|
||||
Simulation sim(loadConfig(), 42);
|
||||
Simulation sim(loadTestConfig(), 42);
|
||||
|
||||
sim.getAdmin().forEach<StationBodyComponent, FactionComponent, HealthComponent>(
|
||||
[](entt::entity /*e*/, const StationBodyComponent& /*sb*/, const FactionComponent& f, HealthComponent& h)
|
||||
@@ -339,7 +335,7 @@ TEST_CASE("WaveSystem: push schematic choices have valid ids", "[wave]")
|
||||
|
||||
TEST_CASE("WaveSystem: schematic choices have no duplicates", "[wave]")
|
||||
{
|
||||
Simulation sim(loadConfig(), 42);
|
||||
Simulation sim(loadTestConfig(), 42);
|
||||
|
||||
sim.getAdmin().forEach<StationBodyComponent, FactionComponent, HealthComponent>(
|
||||
[](entt::entity /*e*/, const StationBodyComponent& /*sb*/, const FactionComponent& f, HealthComponent& h)
|
||||
@@ -359,7 +355,7 @@ TEST_CASE("WaveSystem: schematic choices have no duplicates", "[wave]")
|
||||
|
||||
TEST_CASE("WaveSystem: applySchematicChoice clears pending and applies", "[wave]")
|
||||
{
|
||||
Simulation sim(loadConfig(), 42);
|
||||
Simulation sim(loadTestConfig(), 42);
|
||||
|
||||
sim.getAdmin().forEach<StationBodyComponent, FactionComponent, HealthComponent>(
|
||||
[](entt::entity /*e*/, const StationBodyComponent& /*sb*/, const FactionComponent& f, HealthComponent& h)
|
||||
@@ -375,7 +371,7 @@ TEST_CASE("WaveSystem: applySchematicChoice clears pending and applies", "[wave]
|
||||
|
||||
TEST_CASE("WaveSystem: push places new enemy stations further right", "[wave]")
|
||||
{
|
||||
Simulation sim(loadConfig(), 42);
|
||||
Simulation sim(loadTestConfig(), 42);
|
||||
|
||||
// Record the X position of the initial enemy stations.
|
||||
int initialX = std::numeric_limits<int>::min();
|
||||
|
||||
Reference in New Issue
Block a user