boss waves
This commit is contained in:
@@ -75,12 +75,12 @@ TEST_CASE("ConfigLoader loads the committed bin/config/ configs end-to-end", "[c
|
||||
REQUIRE(cfg.world.regions.playerBufferWidth == 10);
|
||||
REQUIRE(cfg.world.regions.enemyBufferWidth == 15);
|
||||
REQUIRE(cfg.world.expansion.columnsPerExpansion == 10);
|
||||
REQUIRE(cfg.world.push.scalingFactor == Approx(1.2));
|
||||
REQUIRE(cfg.world.push.bossAdvanceSeconds == Approx(60.0));
|
||||
|
||||
// Spot-check that a config-derived formula computes as expected.
|
||||
// threat_rate_formula = "1*x - 30": zero at x=30, 30 at x=60.
|
||||
REQUIRE(cfg.world.waves.threatRateFormula.evaluate(30.0) == Approx(0.0));
|
||||
REQUIRE(cfg.world.waves.threatRateFormula.evaluate(60.0) == Approx(30.0));
|
||||
// threat_rate_formula = "x": evaluates to the input value.
|
||||
REQUIRE(cfg.world.waves.threatRateFormula.evaluate(1.0) == Approx(1.0));
|
||||
REQUIRE(cfg.world.waves.threatRateFormula.evaluate(5.0) == Approx(5.0));
|
||||
|
||||
// buildings.toml
|
||||
REQUIRE(cfg.buildings.buildings.size() >= 8);
|
||||
@@ -222,11 +222,11 @@ cost_building_blocks = 200
|
||||
|
||||
[push]
|
||||
push_expand_columns = 20
|
||||
scaling_factor = 1.2
|
||||
boss_advance_seconds = 60
|
||||
|
||||
[waves]
|
||||
threat_rate_formula = "1 +"
|
||||
ship_level_formula = "1 + x / 120"
|
||||
ship_level_formula = "1 + x / 10"
|
||||
gap_min_seconds = 15
|
||||
gap_max_seconds = 45
|
||||
spawn_duration_seconds = 10
|
||||
|
||||
@@ -29,73 +29,33 @@ static GameConfig loadConfig()
|
||||
// Threat accumulation
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST_CASE("WaveSystem: threat stays 0 for first 30 game-seconds", "[wave]")
|
||||
TEST_CASE("WaveSystem: threat accumulates at boss wave counter rate", "[wave]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
std::mt19937 rng(42);
|
||||
WaveSystem ws(cfg, rng);
|
||||
|
||||
// threat_rate_formula = "1*x - 30", which is <= 0 for x <= 30.
|
||||
const int ticks30s = static_cast<int>(secondsToTicks(30.0));
|
||||
for (int i = 0; i < ticks30s; ++i)
|
||||
// threat_rate_formula = "x", boss wave counter starts at 1 → rate = 1 threat/s.
|
||||
// After 1 second: threat ≈ 1.0.
|
||||
const int ticks1s = static_cast<int>(secondsToTicks(1.0));
|
||||
for (int i = 0; i < ticks1s; ++i)
|
||||
{
|
||||
ws.tickThreatAccumulation(static_cast<Tick>(i));
|
||||
ws.tickThreatAccumulation();
|
||||
}
|
||||
|
||||
REQUIRE(ws.threatLevel() == Approx(0.0));
|
||||
REQUIRE(ws.threatLevel() == Approx(1.0));
|
||||
}
|
||||
|
||||
TEST_CASE("WaveSystem: threat accumulates after 30 game-seconds", "[wave]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
std::mt19937 rng(42);
|
||||
WaveSystem ws(cfg, rng);
|
||||
|
||||
// Run 31 seconds worth of ticks.
|
||||
const int ticks31s = static_cast<int>(secondsToTicks(31.0));
|
||||
for (int i = 0; i < ticks31s; ++i)
|
||||
{
|
||||
ws.tickThreatAccumulation(static_cast<Tick>(i));
|
||||
}
|
||||
|
||||
REQUIRE(ws.threatLevel() > 0.0);
|
||||
}
|
||||
|
||||
TEST_CASE("WaveSystem: applyPush increases threat accumulation rate", "[wave]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
std::mt19937 rng(42);
|
||||
WaveSystem ws(cfg, rng);
|
||||
|
||||
// Accumulate for 1 tick past the 30s mark to get a baseline rate.
|
||||
const Tick baseTick = secondsToTicks(31.0);
|
||||
ws.tickThreatAccumulation(baseTick);
|
||||
const double levelBefore = ws.threatLevel();
|
||||
|
||||
// Apply push: multiplier should increase.
|
||||
ws.applyPush();
|
||||
|
||||
WaveSystem ws2(cfg, rng);
|
||||
ws2.tickThreatAccumulation(baseTick);
|
||||
|
||||
// After the push the same tick adds more threat.
|
||||
ws.tickThreatAccumulation(baseTick + 1);
|
||||
ws2.tickThreatAccumulation(baseTick + 1);
|
||||
|
||||
// ws has the push multiplier applied; ws2 does not.
|
||||
REQUIRE(ws.threatLevel() > ws2.threatLevel());
|
||||
}
|
||||
|
||||
TEST_CASE("WaveSystem: generation starts at 0 and increments on push", "[wave]")
|
||||
TEST_CASE("WaveSystem: generation starts at 0 and increments on station destruction", "[wave]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
std::mt19937 rng(42);
|
||||
WaveSystem ws(cfg, rng);
|
||||
|
||||
REQUIRE(ws.generation() == 0);
|
||||
ws.applyPush();
|
||||
ws.onEnemyStationsDestroyed();
|
||||
REQUIRE(ws.generation() == 1);
|
||||
ws.applyPush();
|
||||
ws.onEnemyStationsDestroyed();
|
||||
REQUIRE(ws.generation() == 2);
|
||||
}
|
||||
|
||||
@@ -217,18 +177,23 @@ TEST_CASE("WaveSystem: enemy ships spawn after the initial gap elapses", "[wave]
|
||||
|
||||
// The maximum gap is gapMaxSeconds = 45s → 1350 ticks.
|
||||
// Run 1500 ticks to guarantee at least one wave has triggered.
|
||||
// Check each tick: enemy ships may be killed quickly by player stations,
|
||||
// so we must detect them while they are alive, not only after the loop.
|
||||
const int limit = static_cast<int>(secondsToTicks(50.0));
|
||||
bool foundEnemyShip = false;
|
||||
for (int i = 0; i < limit; ++i)
|
||||
{
|
||||
sim.tick();
|
||||
}
|
||||
|
||||
bool foundEnemyShip = false;
|
||||
sim.admin().forEach<ShipIdentityComponent, FactionComponent>(
|
||||
[&](entt::entity /*e*/, const ShipIdentityComponent& /*si*/, const FactionComponent& f)
|
||||
if (!foundEnemyShip)
|
||||
{
|
||||
if (f.isEnemy) { foundEnemyShip = true; }
|
||||
});
|
||||
sim.admin().forEach<ShipIdentityComponent, FactionComponent>(
|
||||
[&](entt::entity /*e*/, const ShipIdentityComponent& /*si*/,
|
||||
const FactionComponent& f)
|
||||
{
|
||||
if (f.isEnemy) { foundEnemyShip = true; }
|
||||
});
|
||||
}
|
||||
}
|
||||
REQUIRE(foundEnemyShip);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user