#include "catch.hpp" #include "ConfigLoader.h" #include "GameConfig.h" #include "Simulation.h" #include "Tick.h" #include "TickDriver.h" static GameConfig loadConfig() { return ConfigLoader::loadFromDirectory(DOTA_FACTORY_CONFIG_DIR); } // --------------------------------------------------------------------------- // Simulation // --------------------------------------------------------------------------- TEST_CASE("Simulation::currentTick starts at 0", "[simulation]") { const Simulation sim(loadConfig()); REQUIRE(sim.currentTick() == 0); } TEST_CASE("Simulation::tick increments currentTick by 1", "[simulation]") { Simulation sim(loadConfig()); sim.tick(); REQUIRE(sim.currentTick() == 1); } TEST_CASE("Simulation::tick 10 times yields currentTick == 10", "[simulation]") { Simulation sim(loadConfig()); for (int i = 0; i < 10; ++i) { sim.tick(); } REQUIRE(sim.currentTick() == 10); } TEST_CASE("Simulation::drainFireEvents returns empty initially", "[simulation]") { Simulation sim(loadConfig()); REQUIRE(sim.drainFireEvents().empty()); } TEST_CASE("Simulation::drainFireEvents clears queue on drain", "[simulation]") { Simulation sim(loadConfig()); // First drain: empty. sim.drainFireEvents(); // Second drain must also be empty (not a double-return). REQUIRE(sim.drainFireEvents().empty()); } TEST_CASE("Simulation::drainSchematicDropEvents returns empty initially", "[simulation]") { Simulation sim(loadConfig()); REQUIRE(sim.drainSchematicDropEvents().empty()); } // --------------------------------------------------------------------------- // TickDriver // --------------------------------------------------------------------------- TEST_CASE("TickDriver: 0x speed never produces ticks", "[simulation]") { TickDriver driver; REQUIRE(driver.advance(100.0, 0.0) == 0); REQUIRE(driver.advance(1000.0, 0.0) == 0); } TEST_CASE("TickDriver: exactly one tick duration at 1x produces 1 tick", "[simulation]") { TickDriver driver; REQUIRE(driver.advance(kTickDurationMs, 1.0) == 1); } TEST_CASE("TickDriver: two tick durations at 1x produces 2 ticks", "[simulation]") { TickDriver driver; REQUIRE(driver.advance(2.0 * kTickDurationMs, 1.0) == 2); } TEST_CASE("TickDriver: 4x speed with quarter tick duration produces 1 tick", "[simulation]") { TickDriver driver; REQUIRE(driver.advance(kTickDurationMs / 4.0, 4.0) == 1); } TEST_CASE("TickDriver: 0.5x speed with double tick duration produces 1 tick", "[simulation]") { TickDriver driver; REQUIRE(driver.advance(kTickDurationMs / 0.5, 0.5) == 1); } TEST_CASE("TickDriver: accumulator carries partial progress across frames", "[simulation]") { TickDriver driver; // 60% of a tick — not enough to fire yet. REQUIRE(driver.advance(kTickDurationMs * 0.6, 1.0) == 0); // Another 60% — cumulative 120%, so exactly 1 tick fires. REQUIRE(driver.advance(kTickDurationMs * 0.6, 1.0) == 1); } TEST_CASE("TickDriver::reset clears the accumulator", "[simulation]") { TickDriver driver; // Advance to just below one tick. driver.advance(kTickDurationMs * 0.9, 1.0); driver.reset(); // Nothing in the accumulator: zero elapsed time should not fire. REQUIRE(driver.advance(0.0, 1.0) == 0); }