// SPDX-License-Identifier: LGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
// Watches and samples resolve by path, including into their values
// ("bodies[1].pos.y" against the watch "bodies"), and set the same way:
// writing one member of a watched struct writes the whole struct back. Sets
// respect bounds and read-only watches, computed watches set through their
// setter (which may refuse), samples keep a frame-stamped history, unknown
// paths get a suggestion, and a Group's watches go with it.
import std;
import Crafter.Debug;
using namespace Crafter::Debug;
namespace {
std::int32_t failures = 0;
void Check(bool ok, std::string_view what) {
std::println("{} {}", ok ? "PASS" : "FAIL", what);
if (!ok) ++failures;
}
struct Vec3 {
float x = 0;
float y = 0;
float z = 0;
};
struct Body {
std::int32_t id = 0;
Vec3 pos;
};
float gravity = -9.81F;
const std::int32_t build = 7;
std::vector
bodies = { { 1, { 0, 1, 0 } }, { 2, { 3, 4, 5 } } };
std::int32_t lives = 3;
Detail::Registry& registry() {
return Detail::Registry::Instance();
}
}
int main() {
Watch("physics.gravity", gravity, { .min = -50, .max = 0, .help = "m/s^2" });
Watch("build", build);
Watch("bodies", bodies);
Watch("lives", [] { return lives; }, [](const std::int32_t& value) -> Result<> {
if (value < 0) return Error{ "Negative", "lives cannot be negative" };
lives = value;
return {};
});
Watch("score", [] { return 1234; });
Check(registry().Resolve("physics.gravity").value.AsNumber() == static_cast(-9.81F), "a variable watch reads the variable");
Check(registry().Resolve("bodies[1].pos.y").value.AsNumber() == 4, "a path reads into a watched value");
Check(registry().Resolve("bodies[1]").value.Find("id")->AsInteger() == 2, "an index reads an item");
Check(!registry().Set("physics.gravity", Value(-3.5)) && gravity == -3.5F, "a set writes the variable");
const auto bounded = registry().Set("physics.gravity", Value(10));
Check(bounded && bounded->find("outside") != std::string::npos && gravity == -3.5F, "a set outside the bounds is refused");
Check(registry().Set("build", Value(8)) == std::optional("'build' is read-only") && build == 7, "a const variable's watch is read-only");
Check(!registry().Set("bodies[0].pos.x", Value(9)) && bodies[0].pos.x == 9 && bodies[0].pos.y == 1, "a set into a member writes back the whole value");
const auto wrongType = registry().Set("physics.gravity", Value("down"));
Check(wrongType && wrongType->find("expected a number") != std::string::npos, "a set of the wrong type is refused");
Check(!registry().Set("lives", Value(5)) && lives == 5, "a computed watch sets through its setter");
const auto negative = registry().Set("lives", Value(-1));
Check(negative && negative->find("lives cannot be negative") != std::string::npos && lives == 5, "a setter can refuse");
Check(registry().Set("score", Value(1)) == std::optional("'score' is read-only"), "a getter-only watch is read-only");
const auto missing = registry().Resolve("physics.gravty");
Check(!missing.Ok() && missing.error.find("did you mean 'physics.gravity'") != std::string::npos, "an unknown path gets a suggestion");
const auto badMember = registry().Resolve("bodies[0].pos.w");
Check(!badMember.Ok() && badMember.error.find("has no member 'w'") != std::string::npos, "an unknown member is named");
const auto outOfRange = registry().Resolve("bodies[5]");
Check(!outOfRange.Ok() && outOfRange.error.find("2 items") != std::string::npos, "an index out of range gives the size");
for (std::uint64_t frame = 1; frame <= 3; ++frame) {
Detail::SetFrame(frame);
Sample("xpbd.residual", static_cast(frame) / 10);
}
Check(registry().Resolve("xpbd.residual").value.AsNumber() == 0.3, "a sample resolves to its latest value");
const auto history = registry().SampleHistory("xpbd.residual");
Check(history.size() == 3 && history.front().frame == 1 && history.back().frame == 3, "a sample keeps a frame-stamped history");
Check(registry().Watches("physics.*").size() == 1 && registry().Watches("*").size() == 5, "watches can be listed by glob");
Check(registry().Watches("physics.gravity").front()->help == "m/s^2" && registry().Watches("physics.gravity").front()->type.Find("maximum") != nullptr,
"a watch carries its help and bounds");
{
struct Fort {
std::int32_t hp = 100;
Group debug{ "fort1" };
Fort() { debug.Watch("core.hp", hp); }
};
Fort fort;
Check(registry().Resolve("fort1.core.hp").value.AsInteger() == 100, "a Group prefixes its watches");
}
Check(!registry().Resolve("fort1.core.hp").Ok(), "a Group's watches go with it");
return failures == 0 ? 0 : 1;
}