Files
Ange-Marie MAURIN - A2M Systèmes ab219ad611 feat(core): simulation déterministe, enregistrement et rejeu
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).
2026-11-06 19:50:56 +01:00

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);
}