Files
shadow-over-bishkek001/server/tests/NetworkProtocolTests.cpp

146 lines
5.1 KiB
C++

#include "game_server/NetworkProtocol.h"
#include "LocationState.h"
#include <cstdlib>
#include <iostream>
#include <string>
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<FRG::ScheduledCharacterEnabledCommand>(
parsedTick.commands[1]) &&
std::get<FRG::ScheduledCharacterEnabledCommand>(
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<FRG::ScheduledDestinationCommand>(
commandTick.commands[0]).commandSequence == firstSequence &&
std::get<FRG::ScheduledDestinationCommand>(
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<FRG::ScheduledCharacterEnabledCommand>(
presenceTick.commands[0]),
"character enabled command was not scheduled for the next tick");
return EXIT_SUCCESS;
}