Initial public source release

Split reusable rendering and format support into vectorail-core and vectorail-gc.
This commit is contained in:
2026-08-02 17:05:27 +02:00
commit 831d96e562
109 changed files with 20558 additions and 0 deletions
+89
View File
@@ -0,0 +1,89 @@
#include "StageRuntime.hpp"
#include "Gameplay.hpp"
#include <cmath>
#include <cstddef>
#include <iostream>
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 <stage.orps>\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<float>(stage.header->durationMs) * 0.5f,
static_cast<float>(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<float>(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<int>(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;
}