#include "game_server/NetworkProtocol.h" #include "LocationState.h" #include #include #include namespace { void require(bool condition, const char* message) { if (condition) return; std::cerr << "NetworkProtocolTests: " << message << '\n'; std::exit(EXIT_FAILURE); } } // namespace int main() { FRG::LocationNetworkState locationState; nlohmann::json savedLocationState; locationState.save(savedLocationState); const nlohmann::json original{ {"schemaVersion", 2}, {"campaignId", "battle_zone_multiplayer"}, {"players", {{"player", {{"hp", 200.0}}}}}, {"locations", {{"uni_exterior", {{"state", savedLocationState}}}}} }; FRG::InitialStateMessage initial; initial.state = original; initial.assignedPlayerId = "player"; initial.snapshotTick = 42; initial.compatibilityFingerprint = "lockstep-v2:battle_zone_multiplayer"; const std::string message = FRG::makeInitialStateMessage(initial); FRG::InitialStateMessage parsed; std::string error; require( FRG::parseInitialStateMessage(message, parsed, error), "valid initial-state envelope was rejected"); require(parsed.state == original, "state payload did not round trip"); require(parsed.snapshotTick == 42, "snapshot tick did not round trip"); require( parsed.assignedPlayerId == "player", "assigned player did not round trip"); require( !parsed.state["locations"]["uni_exterior"]["state"].contains("cameraAzimuth") && !parsed.state["locations"]["uni_exterior"]["state"].contains("cameraInclination"), "local camera state leaked into the network payload"); FRG::DestinationInputMessage input; input.clientSequence = 7; input.target = Eigen::Vector3f(1.234f, 0.f, -5.678f); FRG::DestinationInputMessage parsedInput; require( FRG::parseDestinationInputMessage( FRG::makeDestinationInputMessage(input), parsedInput, error), "destination input did not round trip"); require( parsedInput.clientSequence == 7 && parsedInput.target.isApprox(Eigen::Vector3f(1.23f, 0.f, -5.68f)), "destination input was not normalized"); FRG::TickMessage tick; tick.tickId = 43; tick.commands.push_back(FRG::ScheduledDestinationCommand{ 1, "player", parsedInput.target}); tick.commands.push_back(FRG::ScheduledCharacterEnabledCommand{ 2, "uni_exterior", "player2", true}); tick.stateHash = "0123456789abcdef"; FRG::TickMessage parsedTick; require( FRG::parseTickMessage( FRG::makeTickMessage(tick), parsedTick, error), "tick message did not round trip"); require( parsedTick.tickId == 43 && parsedTick.commands.size() == 2 && std::holds_alternative( parsedTick.commands[1]) && std::get( parsedTick.commands[1]).enabled, "tick contents did not round trip"); require( !FRG::parseInitialStateMessage( R"({"protocolVersion":1,"type":"initial_state","payload":{}})", parsed, error), "unknown protocol version was accepted"); require( !FRG::parseDestinationInputMessage( R"({"protocolVersion":2,"type":"command","payload":{}})", parsedInput, error), "unknown message type was accepted"); require( !FRG::parseInitialStateMessage("not json", parsed, error), "malformed JSON was accepted"); require( !FRG::parseDestinationInputMessage( R"({"protocolVersion":2,"type":"destination_input","payload":{"clientSequence":1,"target":[1e999,0,0]}})", parsedInput, error), "non-finite destination was accepted"); std::string errorCode; std::string errorDetail; require( FRG::parseErrorMessage( FRG::makeErrorMessage("server_full", "No slot"), errorCode, errorDetail) && errorCode == "server_full" && errorDetail == "No slot", "error message did not round trip"); nlohmann::json hashState = original; hashState["savedAt"] = "first"; const std::string firstHash = FRG::canonicalStateHash(hashState); hashState["savedAt"] = "second"; require( firstHash == FRG::canonicalStateHash(hashState), "persistence timestamp changed deterministic hash"); FRG::LockstepTimeline timeline(2); const std::uint64_t firstSequence = timeline.schedule("player", Eigen::Vector3f(1.f, 0.f, 1.f)); const std::uint64_t secondSequence = timeline.schedule("player_2", Eigen::Vector3f(2.f, 0.f, 2.f)); const FRG::TickMessage emptyTick = timeline.advance(); require( emptyTick.tickId == 1 && emptyTick.commands.empty(), "input-delay tick was not empty"); const FRG::TickMessage commandTick = timeline.advance(); require( commandTick.tickId == 2 && commandTick.commands.size() == 2 && std::get( commandTick.commands[0]).commandSequence == firstSequence && std::get( commandTick.commands[1]).commandSequence == secondSequence, "scheduled commands were not finalized in stable order"); timeline.scheduleCharacterEnabled("uni_exterior", "player2", true); const FRG::TickMessage presenceTick = timeline.advance(); require( presenceTick.commands.size() == 1 && std::holds_alternative( presenceTick.commands[0]), "character enabled command was not scheduled for the next tick"); return EXIT_SUCCESS; }