feat(simulation): add NavalDomain::serializeStateJson for WASM state export

Returns {"t":...,"vessels":[{"id":...,"x":...,"psi":...,"u":...,"r":...},...]}
2 tests verify JSON structure and time accumulation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
vinihcrosaandClaude Sonnet 4.6 committed 2026-06-27 23:51:56 -03:00
1 parent 6196f0ca0e
commit 9a16d8d0cf
3 files changed
+89

No files matched your search

@@ -87,6 +87,14 @@ public:
/** Return Euclidean distance between two registered bodies. Asserts both ids exist. */
double distanceBetween(int idA, int idB) const;
/**
* Serialize all body states to a JSON string.
*
* Format: {"t":<sim_time>,"vessels":[{"id":<n>,"x":...,"y":...,"z":...,"phi":...,"theta":...,"psi":...,"u":...,"v":...,"r":...},...]}
* Used by the WASM binding to transfer state to JavaScript.
*/
std::string serializeStateJson() const;
NavalDomain(const NavalDomain&) = delete;
NavalDomain& operator=(const NavalDomain&) = delete;
NavalDomain(NavalDomain&&) = delete;
+33
View File
@@ -4,6 +4,7 @@
#include <cassert>
#include <cmath>
#include <sstream>
#include <stdexcept>
namespace ymir {
@@ -188,4 +189,36 @@ naval::NavalContext NavalDomain::buildContext(int id, const BodyEntry& entry, do
env_->waterDepth(), env_->tide()};
}
std::string NavalDomain::serializeStateJson() const
{
std::ostringstream os;
os << "{\"t\":" << sim_.time() << ",\"vessels\":[";
bool first = true;
for (const auto& [id, entry] : entries_)
{
const BodyState s = sim_.state(id);
const Vector6& q = s.q();
const Vector6& qd = s.qdot();
if (!first) os << ',';
first = false;
os << "{\"id\":" << id
<< ",\"x\":" << q[0]
<< ",\"y\":" << q[1]
<< ",\"z\":" << q[2]
<< ",\"phi\":" << q[3]
<< ",\"theta\":" << q[4]
<< ",\"psi\":" << q[5]
<< ",\"u\":" << qd[0]
<< ",\"v\":" << qd[1]
<< ",\"r\":" << qd[5]
<< '}';
}
os << "]}";
return os.str();
}
} // namespace ymir
+48
View File
@@ -282,3 +282,51 @@ TEST_CASE("NavalDomain regression: matches NavalSimulation within 1e-8 over 1000
}
}
}
// ---------------------------------------------------------------------------
// serializeStateJson
// ---------------------------------------------------------------------------
TEST_CASE("NavalDomain: serializeStateJson produces valid JSON with expected keys", "[naval_domain]")
{
ymir::Environment env;
ymir::CouplingRegistry coupling;
ymir::NavalDomain domain;
ymir::Vector6 q0{}, qd0{};
q0[0] = 5.0; q0[5] = 0.1;
domain.addBody(0, makeBody(0, 1e6, q0, qd0));
domain.onAddedToWorld(env, coupling);
domain.initialize();
domain.step(0.1);
const std::string json = domain.serializeStateJson();
REQUIRE(json.find("\"t\":") != std::string::npos);
REQUIRE(json.find("\"vessels\":") != std::string::npos);
REQUIRE(json.find("\"id\":0") != std::string::npos);
REQUIRE(json.find("\"x\":") != std::string::npos);
REQUIRE(json.find("\"psi\":") != std::string::npos);
REQUIRE(json.find("\"u\":") != std::string::npos);
REQUIRE(json.find("\"r\":") != std::string::npos);
REQUIRE(json.front() == '{');
REQUIRE(json.back() == '}');
}
TEST_CASE("NavalDomain: serializeStateJson t advances with steps", "[naval_domain]")
{
ymir::Environment env;
ymir::CouplingRegistry coupling;
ymir::NavalDomain domain;
domain.addBody(0, makeBody(0));
domain.onAddedToWorld(env, coupling);
domain.initialize();
domain.step(0.5);
domain.step(0.5);
const std::string json = domain.serializeStateJson();
const std::size_t tpos = json.find("\"t\":") + 4;
const double t = std::stod(json.substr(tpos));
REQUIRE(t == Approx(1.0).margin(1e-9));
}