Files
Ymir/core/tests/persistence/TestScenarioReader.cpp
vinihcrosaandClaude Sonnet 4.6 6295d540a4 feat: migrate to monorepo with WASM core, Fastify API, and React frontend
- Move C++ sources to core/ subdirectory (T05)
- Add pnpm workspaces + Turborepo pipeline (T06)
- Add Emscripten CMake target and build-wasm.sh (T07)
- Implement Embind bindings via YmirBindings.cpp (T08)
- Add packages/types with TypeBox schemas for VesselDTO, ScenarioDTO, SimulationStateDTO (T09)
- Create apps/api: Fastify v4 + Drizzle ORM + SQLite with vessels/scenarios CRUD (T10-T13)
- Create apps/web: Vite + React 18 + COOP/COEP headers (T14)
- Add simulation.worker.ts for WASM loop in Web Worker (T15)
- Add Zustand simulationStore (T16)
- Add TelemetryPanel component (T17)
- Verified: pnpm build 3/3 tasks, 21ms cached; ctest 211/211 pass (T18-T19)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 00:08:28 -03:00

136 lines
4.3 KiB
C++

#include <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers_floating_point.hpp>
#include <ymir/persistence/json/ScenarioReader.h>
#include <filesystem>
#include <stdexcept>
using Catch::Matchers::WithinAbs;
using namespace ymir;
using namespace ymir::persistence;
#ifndef YMIR_EXAMPLES_DIR
#define YMIR_EXAMPLES_DIR "."
#endif
static std::filesystem::path scenariosDir()
{
return std::filesystem::path(YMIR_EXAMPLES_DIR) / "scenarios";
}
static std::filesystem::path bodyTypesDir()
{
return std::filesystem::path(YMIR_EXAMPLES_DIR) / "body_types";
}
// ---------------------------------------------------------------------------
TEST_CASE("ScenarioReader: parse inline body types", "[scenario][json]")
{
Scenario s = ScenarioReader::parse(scenariosDir() / "two_bodies_at_rest.json");
REQUIRE(s.bodyTypes.count("simple_box") == 1);
REQUIRE(s.bodies.size() == 2);
const BodyDefinition& def = s.bodyTypes.at("simple_box");
for (int i = 0; i < 6; ++i)
REQUIRE_THAT(def.massMatrix[i][i], WithinAbs(1000.0, 1e-10));
}
TEST_CASE("ScenarioReader: parse body instance fields", "[scenario][json]")
{
Scenario s = ScenarioReader::parse(scenariosDir() / "body_with_initial_velocity.json");
REQUIRE(s.bodies.size() == 1);
const BodyInstanceConfig& inst = s.bodies[0];
REQUIRE(inst.id == 1);
REQUIRE(inst.bodyType == "simple_box");
REQUIRE_THAT(inst.position[0], WithinAbs(10.0, 1e-10));
REQUIRE_THAT(inst.position[1], WithinAbs(5.0, 1e-10));
REQUIRE_THAT(inst.position[5], WithinAbs(0.5236, 1e-6));
REQUIRE_THAT(inst.velocity[0], WithinAbs(2.0, 1e-10));
for (int i = 0; i < 6; ++i)
REQUIRE_THAT(inst.acceleration[i], WithinAbs(0.0, 1e-10));
}
TEST_CASE("ScenarioReader: build simulation from scenario — bodies at rest", "[scenario][json][integration]")
{
Simulation sim = ScenarioReader::load(scenariosDir() / "two_bodies_at_rest.json");
REQUIRE(sim.bodyCount() == 2);
REQUIRE(sim.hasBody(1));
REQUIRE(sim.hasBody(2));
for (int id : {1, 2})
{
BodyState s = sim.state(id);
for (int i = 0; i < 6; ++i)
{
REQUIRE_THAT(s.q()[i], WithinAbs(0.0, 1e-10));
REQUIRE_THAT(s.qdot()[i], WithinAbs(0.0, 1e-10));
}
}
}
TEST_CASE("ScenarioReader: build simulation from scenario — initial position and velocity", "[scenario][json][integration]")
{
Simulation sim = ScenarioReader::load(scenariosDir() / "body_with_initial_velocity.json");
REQUIRE(sim.bodyCount() == 1);
REQUIRE(sim.hasBody(1));
BodyState s = sim.state(1);
REQUIRE_THAT(s.q()[0], WithinAbs(10.0, 1e-10));
REQUIRE_THAT(s.q()[1], WithinAbs(5.0, 1e-10));
REQUIRE_THAT(s.q()[5], WithinAbs(0.5236, 1e-6));
REQUIRE_THAT(s.qdot()[0], WithinAbs(2.0, 1e-10));
}
TEST_CASE("ScenarioReader: body type from external file", "[scenario][json]")
{
// two_bodies_at_rest.json has inline bodyTypes, but we can also load from files
// Use body_with_initial_velocity.json with external dir override by creating
// a minimal scenario without inline bodyTypes
// (covered by the bodyTypesDir path in ScenarioReader::parse)
// Direct test: load simple_box from bodyTypesDir
std::filesystem::path defPath = bodyTypesDir() / "simple_box.json";
REQUIRE(std::filesystem::exists(defPath));
// Build a minimal scenario struct manually to test buildSimulation
BodyDefinition def;
def.name = "simple_box";
for (int i = 0; i < 6; ++i) def.massMatrix[i][i] = 1000.0;
BodyInstanceConfig inst;
inst.id = 42;
inst.bodyType = "simple_box";
Scenario scenario;
scenario.bodyTypes["simple_box"] = def;
scenario.bodies.push_back(inst);
Simulation sim = ScenarioReader::buildSimulation(scenario);
REQUIRE(sim.hasBody(42));
}
TEST_CASE("ScenarioReader: bodies at rest stay at rest after stepping", "[scenario][json][integration]")
{
Simulation sim = ScenarioReader::load(scenariosDir() / "two_bodies_at_rest.json");
for (int i = 0; i < 50; ++i)
sim.step(0.1);
for (int id : {1, 2})
{
BodyState s = sim.state(id);
for (int i = 0; i < 6; ++i)
{
REQUIRE_THAT(s.q()[i], WithinAbs(0.0, 1e-8));
REQUIRE_THAT(s.qdot()[i], WithinAbs(0.0, 1e-8));
}
}
}