Files
EVEngine/test/definition_runtime_skill_vehicle.cpp

148 lines
6.2 KiB
C++

#include "zeroerr/assert.h"
#include "zeroerr/unittest.h"
#include "common/ECS.h"
#include "common/definitions/RuntimeSnapshot.h"
#include "definitions/Definitions.h"
#include "rpg/RPGActor.h"
#include "rpg/Skill.h"
#include "rpg/SkillConditionCodec.h"
#include "rpg/SkillDefinitionRuntime.h"
#include "vehicle/VehicleDefinitionRuntime.h"
#include <cstdint>
#include <string_view>
namespace {
eve::PersistentId testId() {
const auto parsed = eve::PersistentId::parse("22222222-2222-7222-8222-222222222222");
return *parsed;
}
eve::SnapshotHashProvider testHash() {
return [](std::string_view input) -> eve::Result<eve::ContentId> {
std::uint64_t first = 14695981039346656037ull;
std::uint64_t second = 1099511628211ull;
for (const unsigned char byte : input) {
first ^= byte;
first *= 1099511628211ull;
second ^= static_cast<std::uint64_t>(byte + 0x9du);
second *= 14029467366897019727ull;
}
eve::ContentId::Bytes bytes{};
for (std::size_t index = 0; index < sizeof(first); ++index) {
bytes[index] = static_cast<std::uint8_t>((first >> (index * 8u)) & 0xffu);
bytes[8 + index] = static_cast<std::uint8_t>((second >> (index * 8u)) & 0xffu);
}
return eve::Result<eve::ContentId>::success(eve::ContentId(bytes));
};
}
} // namespace
TEST_CASE("definitionRuntime.skillAdapterUsesCanonicalRegistryAndAtomicSnapshot") {
eve::rpg::SkillRegistry::clear();
eve::rpg::SkillDefinition definition;
definition.id = "fireball";
definition.cooldown = 8.f;
definition.castTime = 1.25f;
definition.targetType = "single";
definition.tags = {"fire"};
definition.castCondition =
eve::decision::Condition::compare("skill.cooldown", eve::decision::CompareOperator::Equal, eve::Value(0));
eve::rpg::SkillRegistry::registerSkill(definition);
auto runtimeResult = eve::rpg::SkillDefinitionRuntime::create(testId(), "fireball");
REQUIRE(runtimeResult.ok());
auto runtime = std::move(runtimeResult).takeValue();
const auto originalGeneration = runtime.identity().definitionGeneration;
auto typed = runtime.definition();
REQUIRE(typed.ok());
CHECK_EQ(typed.value().targetType, std::string("single"));
CHECK_EQ(static_cast<int>(typed.value().castCondition.kind()),
static_cast<int>(eve::decision::ConditionKind::Compare));
runtime.state().cooldownRemaining = 4.f;
auto snapshot = runtime.snapshot(eve::Revision(3), eve::SimulationTick(12), testHash());
REQUIRE(snapshot.ok());
runtime.state().cooldownRemaining = 0.f;
auto restored = runtime.restore(snapshot.value(), testHash());
REQUIRE(restored.ok());
CHECK_EQ(runtime.state().cooldownRemaining, 4.f);
eve::rpg::SkillDefinition replacement = definition;
replacement.cooldown = 3.f;
eve::rpg::SkillRegistry::registerSkill(replacement);
auto rejectedRestore = runtime.restore(snapshot.value(), testHash());
CHECK(!rejectedRestore.ok());
CHECK_EQ(static_cast<int>(rejectedRestore.error()->code()), static_cast<int>(eve::DiagnosticCode::StaleHandle));
CHECK_EQ(runtime.state().cooldownRemaining, 4.f);
runtime.setActive(true);
auto rejectedReload = runtime.reload(eve::definition::ReloadPolicy::RejectWhileActive);
CHECK(!rejectedReload.ok());
CHECK_EQ(runtime.identity().definitionGeneration, originalGeneration);
runtime.setActive(false);
auto reloaded = runtime.reload(eve::definition::ReloadPolicy::RebuildInstance);
REQUIRE(reloaded.ok());
CHECK_EQ(runtime.state().cooldownRemaining, 3.f);
ecs::Table world;
ecs::ScopedTable guard(world);
auto* actor = eve::rpg::RPGActor::createActor();
REQUIRE(actor != nullptr);
auto projected = runtime.applyTo(actor);
REQUIRE(projected.ok());
CHECK(actor->knowsSkill("fireball"));
CHECK_EQ(actor->getSkillCooldown("fireball"), 3.f);
actor->release();
}
TEST_CASE("definitionRuntime.vehicleAdapterProjectsEntityAndRejectsStaleSnapshot") {
eve::definitions::DefinitionRegistry registry;
auto inserted = registry.insert(
"vehicle", "scout", 1,
R"({"category":"vehicle","mobility":"kinematic","maxSpeed":80,"radius":6,"maxHealth":100,"tags":["recon"]})");
REQUIRE(inserted.ok());
auto reference = eve::DefinitionRef::parse("vehicle:scout");
REQUIRE(reference.ok());
auto runtimeResult =
eve::vehicle::VehicleDefinitionRuntime::create(registry, std::move(reference).takeValue(), testId());
REQUIRE(runtimeResult.ok());
auto runtime = std::move(runtimeResult).takeValue();
runtime.state().x = -12.f;
runtime.state().y = 4.f;
runtime.state().health = 75.f;
runtime.state().faction = "blue";
ecs::Table world;
ecs::ScopedTable guard(world);
auto* entity = eve::vehicle::VehicleEntity::createVehicle();
REQUIRE(entity != nullptr);
auto projected = runtime.applyTo(entity);
REQUIRE(projected.ok());
CHECK_EQ(entity->motion()->x, -12.f);
CHECK_EQ(entity->health()->hp, 75.f);
CHECK_EQ(entity->identity()->faction, std::string("blue"));
CHECK_EQ(entity->definitionBinding()->identity.definitionGeneration, runtime.identity().definitionGeneration);
CHECK_EQ(entity->definition()->def->category, std::string("vehicle"));
auto snapshot = runtime.snapshot(eve::Revision(4), eve::SimulationTick(20), testHash());
REQUIRE(snapshot.ok());
runtime.state().x = 99.f;
auto restored = runtime.restore(snapshot.value(), testHash());
REQUIRE(restored.ok());
CHECK_EQ(runtime.state().x, -12.f);
auto replaced =
registry.replace("vehicle", "scout", 2,
R"({"category":"vehicle","mobility":"kinematic","maxSpeed":90,"radius":6,"maxHealth":120})");
REQUIRE(replaced.ok());
auto stale = runtime.restore(snapshot.value(), testHash());
CHECK(!stale.ok());
CHECK_EQ(static_cast<int>(stale.error()->code()), static_cast<int>(eve::DiagnosticCode::StaleHandle));
CHECK_EQ(runtime.state().x, -12.f);
entity->release();
}