Virgule fixe Q16.16, générateur PCG32 à graine explicite, horloge à pas fixe exacte, simulation d'un micro-mode de démonstration (marqué SIMULÉ), empreinte FNV-1a de l'état, enregistrement binaire versionné des entrées, rejeu avec d'autres réglages de ressenti et détection du premier tick divergent. Empreinte de référence multiplateforme figée. 22 tests (Debug, Release, ASan).
85 lines
2.3 KiB
C++
85 lines
2.3 KiB
C++
// SPDX-License-Identifier: MPL-2.0
|
|
#include "sequa/core/sim/simulation.hpp"
|
|
|
|
#include "scripted_session.hpp"
|
|
|
|
#include <doctest/doctest.h>
|
|
|
|
using namespace sequa::core::sim;
|
|
|
|
namespace {
|
|
|
|
SimConfig config_with_seed(std::uint64_t seed)
|
|
{
|
|
SimConfig config;
|
|
config.seed = seed;
|
|
return config;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST_CASE("Simulation : deux instances identiques restent identiques bit a bit")
|
|
{
|
|
Simulation a(config_with_seed(42));
|
|
Simulation b(config_with_seed(42));
|
|
CHECK(a.state_hash() == b.state_hash());
|
|
for (Tick tick = 0; tick < 3600; ++tick) {
|
|
const InputFrame input = testing::scripted_input(tick);
|
|
a.step(input);
|
|
b.step(input);
|
|
REQUIRE(a.state_hash() == b.state_hash());
|
|
}
|
|
CHECK(a.serialize_state() == b.serialize_state());
|
|
}
|
|
|
|
TEST_CASE("Simulation : la graine change le placement des pieces")
|
|
{
|
|
const Simulation a(config_with_seed(1));
|
|
const Simulation b(config_with_seed(2));
|
|
CHECK(a.state_hash() != b.state_hash());
|
|
}
|
|
|
|
TEST_CASE("Simulation : une entree hors sequence est neutralisee")
|
|
{
|
|
Simulation a(config_with_seed(5));
|
|
Simulation b(config_with_seed(5));
|
|
InputFrame wrong_tick = testing::scripted_input(0);
|
|
wrong_tick.tick = 99;
|
|
a.step(wrong_tick);
|
|
b.step(neutral_input(0));
|
|
CHECK(a.state_hash() == b.state_hash());
|
|
}
|
|
|
|
TEST_CASE("Simulation : le joueur tombe, atterrit et saute")
|
|
{
|
|
Simulation sim(config_with_seed(3));
|
|
CHECK(sim.world().player.on_ground);
|
|
|
|
InputFrame jump = neutral_input(0);
|
|
jump.buttons = button::jump;
|
|
sim.step(jump);
|
|
CHECK_FALSE(sim.world().player.on_ground);
|
|
CHECK(sim.world().player.y > Fixed{});
|
|
|
|
// Bouton maintenu : un seul saut (front montant), puis retour au sol.
|
|
for (Tick tick = 1; tick < 120; ++tick) {
|
|
InputFrame held = neutral_input(tick);
|
|
held.buttons = button::jump;
|
|
sim.step(held);
|
|
}
|
|
CHECK(sim.world().player.on_ground);
|
|
CHECK(sim.world().player.y == Fixed{});
|
|
}
|
|
|
|
TEST_CASE("Simulation : le joueur ne sort pas de l'arene")
|
|
{
|
|
Simulation sim(config_with_seed(4));
|
|
for (Tick tick = 0; tick < 1200; ++tick) {
|
|
InputFrame right = neutral_input(tick);
|
|
right.move_x = 32767;
|
|
sim.step(right);
|
|
}
|
|
CHECK(sim.world().player.x == sim.config().arena.half_width);
|
|
CHECK(abs(sim.world().player.vx) <= sim.config().feel.max_speed);
|
|
}
|