#include "Gameplay.hpp" #include #include #include #include #include #include #include namespace { using namespace faset::runtime; void check(bool value, const char* message) { if (!value) throw std::runtime_error(message); } nlohmann::json read(const std::filesystem::path& path) { nlohmann::json value; std::ifstream input(path); input >> value; return value; } } // namespace int main() { try { const std::filesystem::path project = FASET_EXAMPLE_PROJECT; const auto manifest = read(project / "project.faset.json"); const auto scene = read(project / manifest.at("start_scene").get()); check(manifest.at("dimension") == FASET_EXAMPLE_DIMENSION, "project dimension matches test"); std::set ids; for (const auto& entity : scene.at("entities")) { check(ids.insert(entity.at("id").get()).second, "unique entity IDs"); for (const auto& component : entity.at("components")) check(ids.insert(component.at("id").get()).second, "unique component IDs"); } const auto schemas = faset::gameplay::schema(); check(schemas.size() == (FASET_EXAMPLE_DIMENSION == 3 ? 2u : 1u), "3D module also exports independent Beacon component"); Runtime world; faset::gameplay::registerGameplay(world); world.load(scene); auto player = world.find("player"); const auto start = world.transform(player); for (int i = 0; i < 15; ++i) world.singleStep(); check(world.grounded(player), "player starts supported by actual physics"); check(world.transform(world.find("win_marker")).position[1] < -40, "victory hidden until completion"); auto walk = [&](Vec3 target, bool allowJump, float tolerance = .15f) { for (int tick = 0; tick < 650; ++tick) { const auto p = world.transform(player).position; const float dx = target[0] - p[0], dz = FASET_EXAMPLE_DIMENSION == 3 ? target[2] - p[2] : 0; if (std::hypot(dx, dz) < tolerance) { for (int i = 0; i < 20; ++i) world.singleStep(); return; } InputState input; input.horizontal = std::clamp(dx * 4.0f, -1.0f, 1.0f); input.vertical = -std::clamp(dz * 4.0f, -1.0f, 1.0f); input.jumpPressed = allowJump && world.grounded(player); world.singleStep(input); } const auto p = world.transform(player).position; throw std::runtime_error("Cannot reach waypoint " + std::to_string(target[0]) + "," + std::to_string(target[2]) + " from " + std::to_string(p[0]) + "," + std::to_string(p[1]) + "," + std::to_string(p[2])); }; // Drive the published level through input. No teleports are used to collect // its tokens or bypass the gate; this exercises level reachability as well // as the same C++ module linked into the actual Player. const auto first = world.transform(world.find("token_1")).position; walk(first, false); check(world.transform(world.find("token_1")).position[1] > 1.5f, "first token collected through movement"); const auto middle = world.transform(world.find("token_2")).position; walk(middle, true); check(world.transform(world.find("token_2")).position[1] > 2, "platform token collected with grounded jumps"); const auto third = world.transform(world.find("token_3")).position; walk(third, false); check(world.transform(world.find("gate")).position[1] < -20, "three pickups open physical gate"); const auto goal = world.transform(world.find("goal")).position; if (FASET_EXAMPLE_DIMENSION == 3) walk({4, 0, goal[2]}, false); walk(goal, false, .95f); check(world.transform(world.find("win_marker")).position[1] > 2, "exit completes objective and reveals victory marker"); world.singleStep({0, 0, false, true}); check(world.transform(world.find("gate")).position[1] > 0, "E resets gate"); check(world.transform(world.find("win_marker")).position[1] < -40, "E clears victory"); check(std::abs(world.transform(player).position[0] - start.position[0]) < .01f, "E resets player spawn"); check(world.transform(world.find("token_1")).position[1] < 1, "E restores pickups"); const auto previousPlayer = player; world.load(scene); check(!world.valid(previousPlayer) && world.valid(world.find("player")), "restart clears captured state and stale handles"); check(world.diagnostics().empty(), "playthrough must not hide gameplay exceptions"); std::cout << "Playable " << FASET_EXAMPLE_DIMENSION << "D project reached its objective and reset\n"; return 0; } catch (const std::exception& error) { std::cerr << error.what() << '\n'; return 1; } }