#include "StageRuntime.hpp" #include "Gameplay.hpp" #include #include #include namespace { bool finite(openroller::psp::Vec3 value) { return std::isfinite(value.x) && std::isfinite(value.y) && std::isfinite(value.z); } } // namespace int main(int argc, char** argv) { if (argc != 2) { std::cerr << "usage: openroller-psp-runtime-probe \n"; return 2; } openroller::psp::StageView stage{}; char error[128]{}; if (!openroller::psp::loadStagePackage(argv[1], &stage, error, sizeof(error))) { std::cerr << argv[1] << ": " << error << '\n'; return 1; } bool valid = true; const float samples[] = { 0.0f, static_cast(stage.header->durationMs) * 0.5f, static_cast(stage.header->durationMs), }; for (float timeMs : samples) { const auto position = openroller::psp::trackPositionAt(stage, timeMs); const auto camera = openroller::psp::evaluateCamera(stage, timeMs); valid = valid && finite(position) && finite(camera.eye) && finite(camera.target) && finite(camera.up) && std::isfinite(camera.projectionBlend); std::cout << "t=" << timeMs << " track=(" << position.x << ',' << position.y << ',' << position.z << ')' << " eye=(" << camera.eye.x << ',' << camera.eye.y << ',' << camera.eye.z << ')' << " target=(" << camera.target.x << ',' << camera.target.y << ',' << camera.target.z << ')' << " projection=" << camera.projectionBlend << '\n'; } std::cout << "bytes=" << stage.storageSize << " track=" << stage.header->track.count << " notes=" << stage.header->notes.count << " cameras=" << stage.header->cameras.count << " bg_models=" << stage.header->backgroundModels.count << " bg_objects=" << stage.header->backgroundObjects.count << " bg_vertices=" << stage.header->backgroundVertices.count << " duration_ms=" << stage.header->durationMs << '\n'; openroller::psp::GameplayState gameplay{}; if (!openroller::psp::initializeGameplay(stage, &gameplay)) { std::cerr << "could not initialize PSP gameplay state\n"; valid = false; } else { std::uint32_t tapIndex = 0; while (tapIndex < stage.header->notes.count && stage.notes[tapIndex].effectiveType != 1 && stage.notes[tapIndex].effectiveType != 2) { ++tapIndex; } if (tapIndex == stage.header->notes.count) { std::cerr << "stage has no single-tap note for gameplay probe\n"; valid = false; tapIndex = 0; } const float firstNoteMs = static_cast(stage.notes[tapIndex].timeMs); const auto judgment = openroller::psp::pressGameplay( stage, &gameplay, firstNoteMs, 1u); valid = valid && judgment == openroller::psp::Judgment::Great && gameplay.combo == 1; std::cout << "tap_at=" << firstNoteMs << " judgment=" << static_cast(judgment) << " combo=" << gameplay.combo << '\n'; openroller::psp::seekGameplay(stage, &gameplay, 0.0f); openroller::psp::updateGameplay( stage, &gameplay, firstNoteMs + stage.notes[tapIndex].lateTimingMs + 1.0f); valid = valid && gameplay.missCount > 0; std::cout << "misses_after_window=" << gameplay.missCount << '\n'; openroller::psp::destroyGameplay(&gameplay); } openroller::psp::unloadStagePackage(&stage); return valid ? 0 : 1; }