Files
Ange-Marie MAURIN - A2M Systèmes a185b3414a feat(core): protocole Studio - Core 0.2, session en direct, bail et sécurité
CoreServices / CoreRuntime / LiveSession (conception reprise d'un subagent interrompu, implémentation complétée) : session à 60 Hz avec entrée maintenue, export de l'enregistrement et des empreintes par tick, import par rejeu (sim::rebuild_session) puis poursuite dans le même enregistrement ; bail réversible (--linked, core/attach, core/detach, délai de grâce, code de sortie 3) ; core/shutdown ; jeton en temps constant et contrôle de l'Origin (origines ws:// des clients natifs acceptées, spec mise à jour). Base64 strict corrigé (rejetait des entrées valides). 56 cas de test, ASan vert sur les tests serveur. build.ps1 : -Target accepte a,b.
2026-11-06 23:00:53 +01:00

145 lines
4.6 KiB
C++

// 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 <doctest/doctest.h>
#include <chrono>
#include <thread>
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, &copy}) {
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);
}