Use std::optional instead of sentinel values for absent data

This commit is contained in:
2026-07-20 20:27:20 +02:00
parent 1cdafb7bcd
commit 9622fa4345
32 changed files with 228 additions and 209 deletions

View File

@@ -954,7 +954,7 @@ TEST_CASE("BehaviorSystem: full-cargo salvage ship moves toward SalvageBay", "[b
Fixture f;
const BuildingId bayId = f.buildings.place(BuildingType::SalvageBay,
QPoint(-4, 0), Rotation::East, 0);
QPoint(-4, 0), Rotation::East, 0).value();
Tick t = 0;
for (int i = 0; i < 500; ++i)
{
@@ -989,7 +989,7 @@ TEST_CASE("SalvagerSystem: full-cargo ship at its SalvageBay hands over cargo",
Fixture f;
const BuildingId bayId = f.buildings.place(BuildingType::SalvageBay,
QPoint(-4, 0), Rotation::East, 0);
QPoint(-4, 0), Rotation::East, 0).value();
Tick t = 0;
for (int i = 0; i < 500; ++i)
{

View File

@@ -527,9 +527,9 @@ TEST_CASE("Blueprint placement: buildings land at anchor + offset from cursor",
const QPoint offsetB( 1, 0);
const BuildingId idA = SimulationTestAccess::place(sim,
BuildingType::Belt, cursor + offsetA, Rotation::East);
BuildingType::Belt, cursor + offsetA, Rotation::East).value();
const BuildingId idB = SimulationTestAccess::place(sim,
BuildingType::Belt, cursor + offsetB, Rotation::East);
BuildingType::Belt, cursor + offsetB, Rotation::East).value();
REQUIRE(idA != kInvalidBuildingId);
REQUIRE(idB != kInvalidBuildingId);
@@ -583,11 +583,11 @@ TEST_CASE("Blueprint placement: insufficient blocks returns kInvalidBuildingId a
}
const int blocksBeforeAttempt = sim.getBuildingBlocksStock();
const BuildingId id = SimulationTestAccess::place(sim,
const std::optional<BuildingId> id = SimulationTestAccess::place(sim,
BuildingType::Miner, QPoint(col - 2, 0), Rotation::East);
// Placement must fail and leave the stock unchanged.
REQUIRE(id == kInvalidBuildingId);
REQUIRE_FALSE(id.has_value());
REQUIRE(sim.getBuildingBlocksStock() == blocksBeforeAttempt);
}
@@ -599,10 +599,10 @@ TEST_CASE("Simulation: tryPlaceBuilding rejects terrain-invalid placement and ch
// A miner is all-asteroid; placing it in space (x >= 0) violates the terrain
// rule, so it must be rejected without consuming building blocks.
const BuildingId id =
const std::optional<BuildingId> id =
SimulationTestAccess::place(sim,BuildingType::Miner, QPoint(0, 0), Rotation::East);
REQUIRE(id == kInvalidBuildingId);
REQUIRE_FALSE(id.has_value());
REQUIRE(sim.getBuildingBlocksStock() == startBlocks);
REQUIRE(sim.getBuildings().getAllSites().empty());
}
@@ -623,7 +623,7 @@ TEST_CASE("Simulation: tryPlaceBuilding accepts a valid asteroid spot, occupies
// Miner mask ["AA","A>"] East at (-3,0) → all-asteroid body at
// (-3,0),(-2,0),(-3,1); a valid spot.
const BuildingId id =
SimulationTestAccess::place(sim,BuildingType::Miner, QPoint(-3, 0), Rotation::East);
SimulationTestAccess::place(sim,BuildingType::Miner, QPoint(-3, 0), Rotation::East).value();
REQUIRE(id != kInvalidBuildingId);
REQUIRE(sim.getBuildingBlocksStock() == startBlocks - minerCost);
@@ -666,7 +666,7 @@ TEST_CASE("Blueprint placement: setRecipe on construction site stores recipe", "
Simulation sim(loadConfig());
// 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);
const BuildingId id = SimulationTestAccess::place(sim,BuildingType::Miner, QPoint(-2, 0), Rotation::East).value();
REQUIRE(id != kInvalidBuildingId);
SimulationTestAccess::buildings(sim).setRecipe(id, "mine_iron_ore");
@@ -681,7 +681,7 @@ TEST_CASE("Blueprint placement: recipe transfers to building after construction
{
Simulation sim(loadConfig());
const BuildingId id = SimulationTestAccess::place(sim,BuildingType::Miner, QPoint(-2, 0), Rotation::East);
const BuildingId id = SimulationTestAccess::place(sim,BuildingType::Miner, QPoint(-2, 0), Rotation::East).value();
REQUIRE(id != kInvalidBuildingId);
SimulationTestAccess::buildings(sim).setRecipe(id, "mine_copper_ore");
@@ -710,7 +710,7 @@ TEST_CASE("Blueprint creation: a construction site is captured", "[blueprint]")
// 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.
const BuildingId id =
SimulationTestAccess::place(sim, BuildingType::Belt, QPoint(-2, 0), Rotation::East);
SimulationTestAccess::place(sim, BuildingType::Belt, QPoint(-2, 0), Rotation::East).value();
REQUIRE(id != kInvalidBuildingId);
REQUIRE(sim.getBuildings().findSite(id) != nullptr);
REQUIRE(sim.getBuildings().findBuilding(id) == nullptr);
@@ -727,7 +727,7 @@ TEST_CASE("Blueprint creation: a construction site's recipe is captured", "[blue
Simulation sim(loadConfig());
const BuildingId id =
SimulationTestAccess::place(sim, BuildingType::Miner, QPoint(-2, 0), Rotation::East);
SimulationTestAccess::place(sim, BuildingType::Miner, QPoint(-2, 0), Rotation::East).value();
REQUIRE(id != kInvalidBuildingId);
SimulationTestAccess::buildings(sim).setRecipe(id, "mine_iron_ore");
@@ -744,7 +744,7 @@ TEST_CASE("Blueprint creation: mixed operational building and construction site
// Building A: place, configure, and tick to completion so it is operational.
const BuildingId idA =
SimulationTestAccess::place(sim, BuildingType::Miner, QPoint(-2, 0), Rotation::East);
SimulationTestAccess::place(sim, BuildingType::Miner, QPoint(-2, 0), Rotation::East).value();
REQUIRE(idA != kInvalidBuildingId);
SimulationTestAccess::buildings(sim).setRecipe(idA, "mine_iron_ore");
for (int i = 0; i <= static_cast<int>(secondsToTicks(10.0)); ++i) { sim.tick(); }
@@ -752,7 +752,7 @@ TEST_CASE("Blueprint creation: mixed operational building and construction site
// Building B: place and configure, but leave as a construction site.
const BuildingId idB =
SimulationTestAccess::place(sim, BuildingType::Miner, QPoint(-6, 0), Rotation::East);
SimulationTestAccess::place(sim, BuildingType::Miner, QPoint(-6, 0), Rotation::East).value();
REQUIRE(idB != kInvalidBuildingId);
SimulationTestAccess::buildings(sim).setRecipe(idB, "mine_copper_ore");
REQUIRE(sim.getBuildings().findSite(idB) != nullptr);
@@ -777,7 +777,7 @@ TEST_CASE("Blueprint creation: selectionHasPlaceableBuilding sees a construction
REQUIRE_FALSE(selectionHasPlaceableBuilding(sim, {}));
const BuildingId id =
SimulationTestAccess::place(sim, BuildingType::Miner, QPoint(-2, 0), Rotation::East);
SimulationTestAccess::place(sim, BuildingType::Miner, QPoint(-2, 0), Rotation::East).value();
REQUIRE(id != kInvalidBuildingId);
REQUIRE(sim.getBuildings().findSite(id) != nullptr);
REQUIRE(selectionHasPlaceableBuilding(sim, { id }));
@@ -847,7 +847,7 @@ TEST_CASE("Blueprint placement: setShipLayout on construction site stores layout
// 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.
// S-tile at (0,0) and (0,1) — x >= 0, valid space tiles.
const BuildingId id = SimulationTestAccess::place(sim,BuildingType::Shipyard, QPoint(-3, 0), Rotation::East);
const BuildingId id = SimulationTestAccess::place(sim,BuildingType::Shipyard, QPoint(-3, 0), Rotation::East).value();
REQUIRE(id != kInvalidBuildingId);
ShipLayoutConfig layout;
@@ -871,7 +871,7 @@ TEST_CASE("Blueprint placement: ship layout transfers to building after construc
{
Simulation sim(loadConfig());
const BuildingId id = SimulationTestAccess::place(sim,BuildingType::Shipyard, QPoint(-3, 0), Rotation::East);
const BuildingId id = SimulationTestAccess::place(sim,BuildingType::Shipyard, QPoint(-3, 0), Rotation::East).value();
REQUIRE(id != kInvalidBuildingId);
ShipLayoutConfig layout;

View File

@@ -112,7 +112,7 @@ TEST_CASE("readBuildingConfig reads a queued construction site", "[copyconfig]")
// A placed miner enters the construction queue as a site (not yet operational).
const BuildingId id =
SimulationTestAccess::place(sim, BuildingType::Miner, QPoint(-2, 0), Rotation::East);
SimulationTestAccess::place(sim, BuildingType::Miner, QPoint(-2, 0), Rotation::East).value();
REQUIRE(id != kInvalidBuildingId);
REQUIRE(sim.getBuildings().findBuilding(id) == nullptr);
REQUIRE(sim.getBuildings().findSite(id) != nullptr);

View File

@@ -119,7 +119,7 @@ TEST_CASE("BuildingSystem: place miner occupies expected body tiles", "[building
[](const std::string&) -> bool { return true; },
rng);
const BuildingId id = bs.place(BuildingType::Miner, QPoint(0, 0), Rotation::East, 0);
const BuildingId id = bs.place(BuildingType::Miner, QPoint(0, 0), Rotation::East, 0).value();
REQUIRE(id != kInvalidBuildingId);
// Miner mask ["AA","A>"] with East rotation → body at (0,0),(1,0),(0,1).
@@ -138,8 +138,8 @@ TEST_CASE("BuildingSystem: place rejects a building above the world (y < 0)", "[
// Miner mask ["AA","A>"] East → body at (0,0),(1,0),(0,1); at y=-1 the top
// row sits above the world.
const BuildingId id = f.bs.place(BuildingType::Miner, QPoint(0, -1), Rotation::East, 0);
REQUIRE(id == kInvalidBuildingId);
const std::optional<BuildingId> id = f.bs.place(BuildingType::Miner, QPoint(0, -1), Rotation::East, 0);
REQUIRE_FALSE(id.has_value());
REQUIRE(f.bs.getAllSites().empty());
REQUIRE_FALSE(f.bs.isTileOccupied(QPoint(0, 0)));
}
@@ -151,9 +151,9 @@ TEST_CASE("BuildingSystem: place rejects a building below the world (y >= height
// Anchored on the last in-bounds row, the miner's lower body row reaches
// y == heightTiles, which is outside the world.
const BuildingId id = f.bs.place(BuildingType::Miner,
const std::optional<BuildingId> id = f.bs.place(BuildingType::Miner,
QPoint(0, heightTiles - 1), Rotation::East, 0);
REQUIRE(id == kInvalidBuildingId);
REQUIRE_FALSE(id.has_value());
REQUIRE(f.bs.getAllSites().empty());
}
@@ -162,9 +162,9 @@ TEST_CASE("BuildingSystem: place rejects a building left of the asteroid edge",
PlacementFixture f;
const int leftEdgeX = -f.cfg.world.regions.asteroidWidth_tiles;
const BuildingId id = f.bs.place(BuildingType::Miner,
const std::optional<BuildingId> id = f.bs.place(BuildingType::Miner,
QPoint(leftEdgeX - 1, 0), Rotation::East, 0);
REQUIRE(id == kInvalidBuildingId);
REQUIRE_FALSE(id.has_value());
REQUIRE(f.bs.getAllSites().empty());
}
@@ -176,7 +176,7 @@ TEST_CASE("BuildingSystem: place accepts a building flush against the world's le
// Miner body min relative x is 0, so its leftmost cell sits exactly on the edge.
const BuildingId id = f.bs.place(BuildingType::Miner,
QPoint(leftEdgeX, 0), Rotation::East, 0);
QPoint(leftEdgeX, 0), Rotation::East, 0).value();
REQUIRE(id != kInvalidBuildingId);
REQUIRE(f.bs.isTileOccupied(QPoint(leftEdgeX, 0)));
}
@@ -187,7 +187,7 @@ TEST_CASE("BuildingSystem: place imposes no right-side bound (space extends righ
PlacementFixture f;
const BuildingId id = f.bs.place(BuildingType::Miner,
QPoint(1000, 0), Rotation::East, 0);
QPoint(1000, 0), Rotation::East, 0).value();
REQUIRE(id != kInvalidBuildingId);
}
@@ -256,7 +256,7 @@ TEST_CASE("BuildingSystem: placed building enters construction queue", "[buildin
[](const std::string&) -> bool { return true; },
rng);
const BuildingId id = bs.place(BuildingType::Miner, QPoint(0, 0), Rotation::East, 0);
const BuildingId id = bs.place(BuildingType::Miner, QPoint(0, 0), Rotation::East, 0).value();
REQUIRE(bs.getAllSites().size() == 1);
REQUIRE(bs.getAllBuildings().empty());
@@ -277,7 +277,7 @@ TEST_CASE("BuildingSystem: demolish frees tiles and returns refund", "[building]
[](const std::string&) -> bool { return true; },
rng);
const BuildingId id = bs.place(BuildingType::Miner, QPoint(0, 0), Rotation::East, 0);
const BuildingId id = bs.place(BuildingType::Miner, QPoint(0, 0), Rotation::East, 0).value();
// Miner construction_time_seconds = 10. completesAt = secondsToTicks(10) = 300.
// We need to process tick 300 itself, so run 301 ticks (ticks 0..300).
@@ -351,7 +351,7 @@ TEST_CASE("BuildingSystem: construction completes after configured duration", "[
[](const std::string&) -> bool { return true; },
rng);
const BuildingId id = bs.place(BuildingType::Miner, QPoint(0, 0), Rotation::East, 0);
const BuildingId id = bs.place(BuildingType::Miner, QPoint(0, 0), Rotation::East, 0).value();
// Miner construction_time_seconds = 10. completesAt = secondsToTicks(10) = 300.
// We need to process tick 300 itself, so run 301 ticks (ticks 0..300).
@@ -377,7 +377,7 @@ TEST_CASE("BuildingSystem: second building starts after first completes", "[buil
rng);
bs.place(BuildingType::Miner, QPoint(0, 0), Rotation::East, 0);
const BuildingId id2 = bs.place(BuildingType::Miner, QPoint(5, 5), Rotation::East, 0);
const BuildingId id2 = bs.place(BuildingType::Miner, QPoint(5, 5), Rotation::East, 0).value();
// Process through tick 300 to complete first miner's construction.
Tick tick = 0;
@@ -406,7 +406,7 @@ TEST_CASE("BuildingSystem: miner produces iron_ore after recipe duration", "[bui
[](const std::string&) -> bool { return true; },
rng);
const BuildingId id = bs.place(BuildingType::Miner, QPoint(0, 0), Rotation::East, 0);
const BuildingId id = bs.place(BuildingType::Miner, QPoint(0, 0), Rotation::East, 0).value();
bs.setRecipe(id, "mine_iron_ore");
Tick tick = 0;
@@ -439,7 +439,7 @@ TEST_CASE("BuildingSystem: miner output buffer stalls when full", "[building]")
[](const std::string&) -> bool { return true; },
rng);
const BuildingId id = bs.place(BuildingType::Miner, QPoint(0, 0), Rotation::East, 0);
const BuildingId id = bs.place(BuildingType::Miner, QPoint(0, 0), Rotation::East, 0).value();
bs.setRecipe(id, "mine_iron_ore");
Tick tick = 0;
@@ -479,8 +479,8 @@ TEST_CASE("BuildingSystem: productionBuildingCount excludes construction sites",
[](const std::string&) -> bool { return true; },
rng);
const BuildingId minerId = bs.place(BuildingType::Miner, QPoint(0, 0), Rotation::East, 0);
const BuildingId smelterId = bs.place(BuildingType::Smelter, QPoint(10, 0), Rotation::East, 0);
const BuildingId minerId = bs.place(BuildingType::Miner, QPoint(0, 0), Rotation::East, 0).value();
const BuildingId smelterId = bs.place(BuildingType::Smelter, QPoint(10, 0), Rotation::East, 0).value();
(void)smelterId;
Tick tick = 0;
@@ -519,7 +519,7 @@ TEST_CASE("BuildingSystem: activeProductionBuildingCount tracks production cycle
[](const std::string&) -> bool { return true; },
rng);
const BuildingId id = bs.place(BuildingType::Miner, QPoint(0, 0), Rotation::East, 0);
const BuildingId id = bs.place(BuildingType::Miner, QPoint(0, 0), Rotation::East, 0).value();
bs.setRecipe(id, "mine_iron_ore");
Tick tick = 0;
@@ -563,7 +563,7 @@ TEST_CASE("BuildingSystem: smelter input buffer fills from adjacent west-flowing
// Smelter mask ["AA ","AA>"] → body (0,0),(1,0),(0,1),(1,1).
// Output port (2,1) East. Input port example: (2,0) West.
const BuildingId sid = bs.place(BuildingType::Smelter, QPoint(0, 0), Rotation::East, 0);
const BuildingId sid = bs.place(BuildingType::Smelter, QPoint(0, 0), Rotation::East, 0).value();
// Smelters have no recipe selection (REQ-BLD-SMELTER); they auto-accept any
// ore/scrap that is an input to a smelter recipe.
@@ -603,7 +603,7 @@ TEST_CASE("BuildingSystem: accepted input travels inward before entering the buf
[](const std::string&) -> bool { return true; },
rng);
const BuildingId sid = bs.place(BuildingType::Smelter, QPoint(0, 0), Rotation::East, 0);
const BuildingId sid = bs.place(BuildingType::Smelter, QPoint(0, 0), Rotation::East, 0).value();
Tick tick = 0;
runTicks(bs, belts, static_cast<int>(secondsToTicks(15.0)) + 1, tick);
@@ -645,7 +645,7 @@ TEST_CASE("BuildingSystem: input reservation caps buffered plus in-transit at th
rng);
const BuildingId id = bs.place(BuildingType::ReprocessingPlant,
QPoint(0, 0), Rotation::East, 0);
QPoint(0, 0), Rotation::East, 0).value();
Tick tick = 0;
runTicks(bs, belts, static_cast<int>(secondsToTicks(25.0)) + 1, tick);
@@ -686,7 +686,7 @@ TEST_CASE("BuildingSystem: smelter auto-smelts ore without a recipe selection",
[](const std::string&) -> bool { return true; },
rng);
const BuildingId sid = bs.place(BuildingType::Smelter, QPoint(0, 0), Rotation::East, 0);
const BuildingId sid = bs.place(BuildingType::Smelter, QPoint(0, 0), Rotation::East, 0).value();
Tick tick = 0;
runTicks(bs, belts, static_cast<int>(secondsToTicks(15.0)) + 1, tick);
@@ -732,7 +732,7 @@ TEST_CASE("BuildingSystem: smelter runs a satisfiable recipe while an incomplete
[](const std::string&) -> bool { return true; },
rng);
const BuildingId sid = bs.place(BuildingType::Smelter, QPoint(0, 0), Rotation::East, 0);
const BuildingId sid = bs.place(BuildingType::Smelter, QPoint(0, 0), Rotation::East, 0).value();
Tick tick = 0;
runTicks(bs, belts, static_cast<int>(secondsToTicks(15.0)) + 1, tick);
@@ -786,7 +786,7 @@ TEST_CASE("BuildingSystem: miner output buffer drains onto adjacent belt", "[bui
[](const std::string&) -> bool { return true; },
rng);
const BuildingId id = bs.place(BuildingType::Miner, QPoint(0, 0), Rotation::East, 0);
const BuildingId id = bs.place(BuildingType::Miner, QPoint(0, 0), Rotation::East, 0).value();
bs.setRecipe(id, "mine_iron_ore");
// Belt at the miner's output port tile (1,1) flowing East.
@@ -826,12 +826,12 @@ TEST_CASE("BuildingSystem: output port couples directly into an adjacent input p
rng);
// Miner at (0,0): body (0,0),(1,0),(0,1); output port tile (1,1) flowing East.
const BuildingId minerId = bs.place(BuildingType::Miner, QPoint(0, 0), Rotation::East, 0);
const BuildingId minerId = bs.place(BuildingType::Miner, QPoint(0, 0), Rotation::East, 0).value();
bs.setRecipe(minerId, "mine_iron_ore");
// Smelter anchored at (1,1): body (1,1),(2,1),(1,2),(2,2). Its body cell (1,1) is
// the miner's output-port tile, and its west input edge there faces East, so the
// two ports meet — no belt placed anywhere.
const BuildingId smelterId = bs.place(BuildingType::Smelter, QPoint(1, 1), Rotation::East, 0);
const BuildingId smelterId = bs.place(BuildingType::Smelter, QPoint(1, 1), Rotation::East, 0).value();
Tick tick = 0;
// Smelter build (15s) + margin for coupling and a smelt cycle.
@@ -866,11 +866,11 @@ TEST_CASE("BuildingSystem: direct coupling to a non-consumer leaves the item stu
rng);
// Producing miner at (0,0), output port (1,1) East.
const BuildingId minerId = bs.place(BuildingType::Miner, QPoint(0, 0), Rotation::East, 0);
const BuildingId minerId = bs.place(BuildingType::Miner, QPoint(0, 0), Rotation::East, 0).value();
bs.setRecipe(minerId, "mine_iron_ore");
// A second, idle miner anchored at (1,1) occupies the output-port tile but takes
// no inputs, so it cannot accept the iron_ore.
const BuildingId sinkId = bs.place(BuildingType::Miner, QPoint(1, 1), Rotation::East, 0);
const BuildingId sinkId = bs.place(BuildingType::Miner, QPoint(1, 1), Rotation::East, 0).value();
Tick tick = 0;
// Both miners build sequentially (10s each), then the producer runs and jams.
@@ -904,7 +904,7 @@ TEST_CASE("BuildingSystem: setRecipe clears output buffer and active production"
[](const std::string&) -> bool { return true; },
rng);
const BuildingId id = bs.place(BuildingType::Miner, QPoint(0, 0), Rotation::East, 0);
const BuildingId id = bs.place(BuildingType::Miner, QPoint(0, 0), Rotation::East, 0).value();
bs.setRecipe(id, "mine_iron_ore");
Tick tick = 0;
@@ -948,7 +948,7 @@ TEST_CASE("BuildingSystem: reprocessing plant output buffer capacity equals max
rng);
const BuildingId id = bs.place(BuildingType::ReprocessingPlant,
QPoint(0, 0), Rotation::East, 0);
QPoint(0, 0), Rotation::East, 0).value();
// Reprocessing plants have no recipe selection (REQ-BLD-REPROCESSING); the
// single reprocessing recipe is applied automatically on completion.
@@ -980,7 +980,7 @@ TEST_CASE("BuildingSystem: reprocessing plant produces one cycle output then sta
rng);
const BuildingId id = bs.place(BuildingType::ReprocessingPlant,
QPoint(0, 0), Rotation::East, 0);
QPoint(0, 0), Rotation::East, 0).value();
// Reprocessing plants have no recipe selection (REQ-BLD-REPROCESSING); the
// single reprocessing recipe is applied automatically on completion.
@@ -1056,7 +1056,7 @@ TEST_CASE("BuildingSystem: findRotateInPlaceTarget returns the site id for a que
[](const std::string&) -> bool { return true; },
rng);
const BuildingId id = bs.place(BuildingType::Belt, QPoint(0, 0), Rotation::East, 0);
const BuildingId id = bs.place(BuildingType::Belt, QPoint(0, 0), Rotation::East, 0).value();
const std::optional<BuildingId> result =
bs.findRotateInPlaceTarget(BuildingType::Belt, QPoint(0, 0), Rotation::North);
@@ -1079,7 +1079,7 @@ TEST_CASE("BuildingSystem: findRotateInPlaceTarget returns the building id for a
[](const std::string&) -> bool { return true; },
rng);
const BuildingId id = bs.place(BuildingType::Belt, QPoint(0, 0), Rotation::East, 0);
const BuildingId id = bs.place(BuildingType::Belt, QPoint(0, 0), Rotation::East, 0).value();
Tick tick = 0;
runTicks(bs, belts, static_cast<int>(secondsToTicks(1.0)) + 1, tick);
@@ -1154,7 +1154,7 @@ TEST_CASE("BuildingSystem: findRotateInPlaceTarget works for a symmetric multi-t
// Smelter is a fully filled 2×2 footprint — rotating the ghost produces the
// same four body tiles, so findRotateInPlaceTarget must still return the id.
const BuildingId id = bs.place(BuildingType::Smelter, QPoint(0, 0), Rotation::East, 0);
const BuildingId id = bs.place(BuildingType::Smelter, QPoint(0, 0), Rotation::East, 0).value();
const std::optional<BuildingId> result =
bs.findRotateInPlaceTarget(BuildingType::Smelter, QPoint(0, 0), Rotation::North);
@@ -1181,7 +1181,7 @@ TEST_CASE("BuildingSystem: rotateInPlace updates the rotation field of a constru
[](const std::string&) -> bool { return true; },
rng);
const BuildingId id = bs.place(BuildingType::Belt, QPoint(0, 0), Rotation::East, 0);
const BuildingId id = bs.place(BuildingType::Belt, QPoint(0, 0), Rotation::East, 0).value();
REQUIRE(bs.findSite(id)->rotation == Rotation::East);
bs.rotateInPlace(id, Rotation::North);
@@ -1204,7 +1204,7 @@ TEST_CASE("BuildingSystem: rotateInPlace preserves the construction progress of
[](const std::string&) -> bool { return true; },
rng);
const BuildingId id = bs.place(BuildingType::Belt, QPoint(0, 0), Rotation::East, 0);
const BuildingId id = bs.place(BuildingType::Belt, QPoint(0, 0), Rotation::East, 0).value();
const Tick completesAt = bs.findSite(id)->completesAt;
REQUIRE(completesAt > 0);
@@ -1228,7 +1228,7 @@ TEST_CASE("BuildingSystem: rotateInPlace updates rotation and output port direct
[](const std::string&) -> bool { return true; },
rng);
const BuildingId id = bs.place(BuildingType::Belt, QPoint(0, 0), Rotation::East, 0);
const BuildingId id = bs.place(BuildingType::Belt, QPoint(0, 0), Rotation::East, 0).value();
Tick tick = 0;
runTicks(bs, belts, static_cast<int>(secondsToTicks(1.0)) + 1, tick);
@@ -1259,7 +1259,7 @@ TEST_CASE("BuildingSystem: rotateInPlace re-registers a belt tile with BeltSyste
[](const std::string&) -> bool { return true; },
rng);
const BuildingId id = bs.place(BuildingType::Belt, QPoint(0, 0), Rotation::East, 0);
const BuildingId id = bs.place(BuildingType::Belt, QPoint(0, 0), Rotation::East, 0).value();
Tick tick = 0;
runTicks(bs, belts, static_cast<int>(secondsToTicks(1.0)) + 1, tick);
@@ -1276,7 +1276,7 @@ TEST_CASE("BuildingSystem: splitter filters configured on a construction site ca
PlacementFixture f;
const QPoint tile(5, 5);
const BuildingId id = f.bs.place(BuildingType::Splitter, tile, Rotation::East, 0);
const BuildingId id = f.bs.place(BuildingType::Splitter, tile, Rotation::East, 0).value();
REQUIRE(id != kInvalidBuildingId);
REQUIRE(f.bs.findSite(id) != nullptr);

View File

@@ -52,7 +52,7 @@ TEST_CASE("apply(PlaceBuildingCommand) with recipe matches place-then-setRecipe"
viaCommand.apply(command);
const BuildingId id =
SimulationTestAccess::place(viaDirect, BuildingType::Miner, QPoint(-2, 0), Rotation::East);
SimulationTestAccess::place(viaDirect, BuildingType::Miner, QPoint(-2, 0), Rotation::East).value();
SimulationTestAccess::buildings(viaDirect).setRecipe(id, "mine_iron_ore");
REQUIRE(viaCommand.computeStateChecksum() == viaDirect.computeStateChecksum());
@@ -64,9 +64,9 @@ TEST_CASE("apply(DemolishCommand) matches direct demolish", "[command]")
Simulation viaDirect(loadConfig(), 99);
const BuildingId idA =
SimulationTestAccess::place(viaCommand, BuildingType::Miner, QPoint(-3, 0), Rotation::East);
SimulationTestAccess::place(viaCommand, BuildingType::Miner, QPoint(-3, 0), Rotation::East).value();
const BuildingId idB =
SimulationTestAccess::place(viaDirect, BuildingType::Miner, QPoint(-3, 0), Rotation::East);
SimulationTestAccess::place(viaDirect, BuildingType::Miner, QPoint(-3, 0), Rotation::East).value();
REQUIRE(idA == idB);
DemolishCommand command;

View File

@@ -224,7 +224,7 @@ TEST_CASE("ShipSystem: salvage_ship cargo capacity matches config", "[ship]")
// Cargo capacity is now a ship-level pool (REQ-MOD-CARGO-CAPACITY).
REQUIRE(admin.get<CargoComponent>(e).maxCapacity == 10);
REQUIRE(admin.get<CargoComponent>(e).current == 0);
REQUIRE(admin.get<DeliverScrapBehavior>(e).deliveryBay == kInvalidBuildingId);
REQUIRE_FALSE(admin.get<DeliverScrapBehavior>(e).deliveryBay.has_value());
REQUIRE_FALSE(admin.get<SalvageScrapBehavior>(e).scrapTarget.has_value());
REQUIRE(admin.get<SalvageScrapBehavior>(e).maxCollectionRange_tiles == Approx(50.0f));
}

View File

@@ -1,5 +1,7 @@
#pragma once
#include <optional>
#include <QPoint>
#include "BuildingId.h"
@@ -25,8 +27,8 @@ struct SimulationTestAccess
static BuildingSystem& buildings(Simulation& sim) { return sim.getBuildingsMutable(); }
static BeltSystem& belts(Simulation& sim) { return sim.getBeltsMutable(); }
static BuildingId place(Simulation& sim, BuildingType type, QPoint anchor,
Rotation rotation)
static std::optional<BuildingId> place(Simulation& sim, BuildingType type,
QPoint anchor, Rotation rotation)
{
return sim.tryPlaceBuilding(type, anchor, rotation);
}