// SPDX-License-Identifier: MPL-2.0 #include "sequa/core/server/live_session.hpp" #include "sequa/core/sim/input.hpp" #include "sequa/core/sim/replay.hpp" #include #include #include using namespace sequa::core; using server::LiveSession; namespace { LiveSession::Options manual(std::uint64_t seed = 7) { LiveSession::Options options; options.seed = seed; options.real_time = false; return options; } // Une petite partie : courir, sauter, s'arrêter, repartir. void play(LiveSession& session) { (void)session.set_input(0, 32767, 0); session.advance(50); (void)session.set_input(sim::button::jump, 32767, 0); session.advance(3); (void)session.set_input(0, 0, 0); session.advance(40); (void)session.set_input(0, -20000, 0); session.advance(30); } } // namespace TEST_CASE("Session : export puis import dans une autre instance -> meme tick, meme empreinte") { LiveSession original(manual()); play(original); const auto exported = original.export_session(); CHECK(exported.tick == 123); CHECK(exported.hashes.size() == 123); LiveSession copy(manual(999)); // autre graine : l'import doit tout remplacer const auto imported = copy.import_session(exported.recording, false); REQUIRE(imported.has_value()); CHECK(imported->state.tick == exported.tick); CHECK(imported->state.state_hash == exported.state_hash); CHECK(imported->state.seed == 7); CHECK_FALSE(imported->state.running); CHECK(imported->hashes == exported.hashes); } TEST_CASE("Session : apres import, la suite s'enregistre dans le meme enregistrement") { LiveSession original(manual()); play(original); LiveSession copy(manual()); REQUIRE(copy.import_session(original.export_session().recording, true).has_value()); // Les deux sessions continuent avec les mêmes entrées : elles restent identiques. for (LiveSession* session : {&original, ©}) { session->advance(20); (void)session->set_input(sim::button::jump, 0, 0); session->advance(30); } const auto a = original.export_session(); const auto b = copy.export_session(); CHECK(a.tick == 173); CHECK(b.tick == a.tick); CHECK(b.state_hash == a.state_hash); CHECK(b.recording == a.recording); // export complet : depuis le tick 0 CHECK(b.hashes == a.hashes); } TEST_CASE("Session : l'entree reste maintenue jusqu'a la suivante") { LiveSession held(manual()); (void)held.set_input(0, 32767, 0); held.advance(30); LiveSession repeated(manual()); for (int i = 0; i < 30; ++i) { (void)repeated.set_input(0, 32767, 0); repeated.advance(1); } CHECK(held.status().state_hash == repeated.status().state_hash); CHECK(held.status().input_count == 30); // une entrée non neutre enregistrée par tick } TEST_CASE("Session : la derniere entree avant un tick gagne et le tick annonce est le bon") { LiveSession session(manual()); session.advance(5); CHECK(session.set_input(0, 100, 0) == 5); CHECK(session.set_input(0, -100, 0) == 5); session.advance(1); const auto recording = sim::deserialize_recording(session.export_session().recording); REQUIRE(recording.has_value()); REQUIRE(recording->inputs.size() == 1); CHECK(recording->inputs.front().tick == 5); CHECK(recording->inputs.front().move_x == -100); } TEST_CASE("Session : import d'un enregistrement illisible -> session conservee") { LiveSession session(manual()); session.advance(10); const auto before = session.status(); const std::byte garbage[] = {std::byte{1}, std::byte{2}, std::byte{3}}; CHECK_FALSE(session.import_session(garbage, true).has_value()); CHECK(session.status().tick == before.tick); CHECK(session.status().state_hash == before.state_hash); } TEST_CASE("Session : restart, pause et resume") { LiveSession session(manual()); session.advance(10); const auto restarted = session.restart(42); CHECK(restarted.tick == 0); CHECK(restarted.seed == 42); CHECK(restarted.running); CHECK_FALSE(session.pause().running); CHECK(session.resume().running); } TEST_CASE("Session : en temps reel, le fil avance a 60 Hz et la pause l'arrete") { LiveSession::Options options; options.real_time = true; LiveSession session(options); std::this_thread::sleep_for(std::chrono::milliseconds(250)); const auto running = session.pause(); CHECK(running.tick >= 5); // ~15 ticks attendus, marge pour une machine chargée CHECK(running.tick <= 30); std::this_thread::sleep_for(std::chrono::milliseconds(100)); CHECK(session.status().tick == running.tick); }