fix issue where construction sites could be placed outside of game world and add tests
This commit is contained in:
@@ -64,6 +64,27 @@ static void runTicks(BuildingSystem& bs, BeltSystem& belts, int n, Tick& tick)
|
||||
}
|
||||
}
|
||||
|
||||
// Owns a BuildingSystem and its dependencies for placement-bounds tests.
|
||||
struct PlacementFixture
|
||||
{
|
||||
GameConfig cfg = loadConfig();
|
||||
BeltSystem belts{cfg.world.beltSpeed_tps};
|
||||
int stock = 0;
|
||||
std::mt19937 rng{0};
|
||||
BuildingId nextBuildingId = 1;
|
||||
BuildingSystem bs;
|
||||
|
||||
PlacementFixture()
|
||||
: bs(cfg, belts,
|
||||
[this]() { return nextBuildingId++; },
|
||||
[this](int n) { stock += n; },
|
||||
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
|
||||
[](const std::string&) -> bool { return true; },
|
||||
rng)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Placement
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -93,6 +114,88 @@ TEST_CASE("BuildingSystem: place miner occupies expected body tiles", "[building
|
||||
REQUIRE_FALSE(bs.isTileOccupied(QPoint(1, 1)));
|
||||
}
|
||||
|
||||
// -- World-bounds rejection (REQ-BLD-PLACE-VALID) ---------------------------
|
||||
|
||||
TEST_CASE("BuildingSystem: place rejects a building above the world (y < 0)", "[building]")
|
||||
{
|
||||
PlacementFixture f;
|
||||
|
||||
// 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);
|
||||
REQUIRE(f.bs.allSites().empty());
|
||||
REQUIRE_FALSE(f.bs.isTileOccupied(QPoint(0, 0)));
|
||||
}
|
||||
|
||||
TEST_CASE("BuildingSystem: place rejects a building below the world (y >= height)", "[building]")
|
||||
{
|
||||
PlacementFixture f;
|
||||
const int heightTiles = f.cfg.world.heightTiles;
|
||||
|
||||
// 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,
|
||||
QPoint(0, heightTiles - 1), Rotation::East, 0);
|
||||
REQUIRE(id == kInvalidBuildingId);
|
||||
REQUIRE(f.bs.allSites().empty());
|
||||
}
|
||||
|
||||
TEST_CASE("BuildingSystem: place rejects a building left of the asteroid edge", "[building]")
|
||||
{
|
||||
PlacementFixture f;
|
||||
const int leftEdgeX = -f.cfg.world.regions.asteroidWidth_tiles;
|
||||
|
||||
const BuildingId id = f.bs.place(BuildingType::Miner,
|
||||
QPoint(leftEdgeX - 1, 0), Rotation::East, 0);
|
||||
REQUIRE(id == kInvalidBuildingId);
|
||||
REQUIRE(f.bs.allSites().empty());
|
||||
}
|
||||
|
||||
TEST_CASE("BuildingSystem: place accepts a building flush against the world's left edge",
|
||||
"[building]")
|
||||
{
|
||||
PlacementFixture f;
|
||||
const int leftEdgeX = -f.cfg.world.regions.asteroidWidth_tiles;
|
||||
|
||||
// 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);
|
||||
REQUIRE(id != kInvalidBuildingId);
|
||||
REQUIRE(f.bs.isTileOccupied(QPoint(leftEdgeX, 0)));
|
||||
}
|
||||
|
||||
TEST_CASE("BuildingSystem: place imposes no right-side bound (space extends rightward)",
|
||||
"[building]")
|
||||
{
|
||||
PlacementFixture f;
|
||||
|
||||
const BuildingId id = f.bs.place(BuildingType::Miner,
|
||||
QPoint(1000, 0), Rotation::East, 0);
|
||||
REQUIRE(id != kInvalidBuildingId);
|
||||
}
|
||||
|
||||
TEST_CASE("BuildingSystem: isPlacementValid enforces terrain and world bounds", "[building]")
|
||||
{
|
||||
PlacementFixture f;
|
||||
const int leftEdgeX = -f.cfg.world.regions.asteroidWidth_tiles;
|
||||
|
||||
// Miner is all-asteroid (A): valid only fully on the asteroid (x < 0).
|
||||
REQUIRE(f.bs.isPlacementValid(BuildingType::Miner, QPoint(-3, 0), Rotation::East));
|
||||
REQUIRE_FALSE(f.bs.isPlacementValid(BuildingType::Miner, QPoint(0, 0), Rotation::East)); // A cells in space
|
||||
REQUIRE_FALSE(f.bs.isPlacementValid(BuildingType::Miner, QPoint(0, -1), Rotation::East)); // above world
|
||||
REQUIRE(f.bs.isPlacementValid(BuildingType::Miner, QPoint(leftEdgeX, 0), Rotation::East));
|
||||
REQUIRE_FALSE(f.bs.isPlacementValid(BuildingType::Miner,
|
||||
QPoint(leftEdgeX - 1, 0), Rotation::East)); // past left edge
|
||||
|
||||
// Shipyard mask ["AAAS>","AAAS "] straddles the boundary: A cells on the
|
||||
// asteroid, the S (dock) cell in space. At anchor (-3,0) the A cells land at
|
||||
// x=-3..-1 and the dock at x=0.
|
||||
REQUIRE(f.bs.isPlacementValid(BuildingType::Shipyard, QPoint(-3, 0), Rotation::East));
|
||||
REQUIRE_FALSE(f.bs.isPlacementValid(BuildingType::Shipyard, QPoint(0, 0), Rotation::East)); // A cells in space
|
||||
REQUIRE_FALSE(f.bs.isPlacementValid(BuildingType::Shipyard, QPoint(-4, 0), Rotation::East)); // dock on asteroid
|
||||
}
|
||||
|
||||
TEST_CASE("BuildingSystem: placing a belt registers it with BeltSystem after construction",
|
||||
"[building]")
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user