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
+26
View File
@@ -0,0 +1,26 @@
#pragma once
#include <cstdint>
namespace openroller::psp {
enum class AudioEffect : std::uint8_t {
Adlib = 0,
Tap1 = 1,
Tap2 = 2,
};
bool startAudioPlayer(
const char* bgmPath,
const char* shotPath,
const char* effectDirectory);
void stopAudioPlayer();
bool audioPlayerRunning();
bool audioPlayerFinished();
std::uint32_t audioPlayerTimeMs();
void setAudioPlayerPaused(bool paused);
void seekAudioPlayer(std::uint32_t timeMs);
void setAudioPlayerShotMuted(bool muted);
void playAudioPlayerEffect(AudioEffect effect);
} // namespace openroller::psp
+63
View File
@@ -0,0 +1,63 @@
#pragma once
#include "StageRuntime.hpp"
#include <cstdint>
namespace openroller::psp {
enum class Judgment : std::uint8_t {
// Values 0..3 match the original game's recovered enum.
Miss = 0,
Good = 1,
Cool = 2,
Great = 3,
Pending = 0xff,
};
struct GameplayState {
struct NoteRuntime {
std::uint8_t judgment = static_cast<std::uint8_t>(Judgment::Pending);
std::uint8_t holding = 0;
std::uint8_t shotMuteApplied = 0;
std::uint8_t reserved = 0;
float inputStartTimeMs = -1.0f;
float lastInputTimeMs = -1.0f;
std::uint32_t inputMask = 0;
std::uint32_t lastInputBit = 0;
};
NoteRuntime* notes = nullptr;
std::uint32_t noteCount = 0;
std::uint32_t nextPending = 0;
std::uint32_t combo = 0;
std::uint32_t maximumCombo = 0;
std::uint32_t greatCount = 0;
std::uint32_t coolCount = 0;
std::uint32_t goodCount = 0;
std::uint32_t missCount = 0;
float previousClockMs = 0.0f;
float lastJudgmentClockMs = -10000.0f;
std::int32_t lastJudgedNote = -1;
Judgment lastJudgment = Judgment::Pending;
bool shotMuted = false;
};
bool initializeGameplay(const StageView& stage, GameplayState* gameplay);
void destroyGameplay(GameplayState* gameplay);
void seekGameplay(const StageView& stage, GameplayState* gameplay, float clockMs);
void updateGameplay(const StageView& stage, GameplayState* gameplay, float clockMs);
Judgment tapGameplay(const StageView& stage, GameplayState* gameplay, float clockMs);
Judgment pressGameplay(
const StageView& stage,
GameplayState* gameplay,
float clockMs,
std::uint32_t inputBit);
Judgment releaseGameplay(
const StageView& stage,
GameplayState* gameplay,
float clockMs,
std::uint32_t inputBit);
Judgment noteJudgment(const GameplayState& gameplay, std::uint32_t noteIndex);
} // namespace openroller::psp
+35
View File
@@ -0,0 +1,35 @@
#pragma once
#include "openroller/psp/SongCatalog.hpp"
#include <cstddef>
#include <cstdint>
namespace openroller::psp {
struct SongMenu {
void* catalogStorage = nullptr;
std::size_t catalogSize = 0;
const SongCatalogHeader* header = nullptr;
const SongCatalogRecord* songs = nullptr;
int selection = 0;
int difficulty = 0;
bool difficultyMode = false;
void* jacketStorage = nullptr;
std::size_t jacketSize = 0;
const std::uint16_t* jacketPixels = nullptr;
std::uint16_t jacketWidth = 0;
std::uint16_t jacketHeight = 0;
char rootPath[256]{};
};
bool loadSongMenu(const char* catalogPath, SongMenu* menu, char* error, std::size_t errorCapacity);
void unloadSongMenu(SongMenu* menu);
bool moveSongSelection(SongMenu* menu, int delta);
bool moveDifficultySelection(SongMenu* menu, int delta);
const SongCatalogRecord* selectedSong(const SongMenu& menu);
bool selectedSongPath(const SongMenu& menu, char* output, std::size_t capacity);
bool selectedAudioPath(const SongMenu& menu, char* output, std::size_t capacity);
bool selectedShotAudioPath(const SongMenu& menu, char* output, std::size_t capacity);
} // namespace openroller::psp
+60
View File
@@ -0,0 +1,60 @@
#pragma once
#include "openroller/psp/StagePackage.hpp"
#include <cstddef>
#include <cstdint>
namespace openroller::psp {
struct Vec3 {
float x = 0.0f;
float y = 0.0f;
float z = 0.0f;
};
struct CameraState {
Vec3 eye;
Vec3 target;
Vec3 up{0.0f, 1.0f, 0.0f};
float projectionBlend = 0.0f;
};
struct StageView {
void* storage = nullptr;
std::size_t storageSize = 0;
const StagePackageHeader* header = nullptr;
const PackageTrackPoint* track = nullptr;
const PackageNote* notes = nullptr;
const PackageCameraPoint* cameras = nullptr;
const PackageDrawDistancePoint* drawDistances = nullptr;
const PackageBackgroundColorPoint* backgroundColors = nullptr;
const PackageBackgroundModel* backgroundModels = nullptr;
const PackageBackgroundVertex* backgroundVertices = nullptr;
const PackageBackgroundObject* backgroundObjects = nullptr;
const PackageVisibilityKey* visibilityKeys = nullptr;
const PackageTransformKey* transformKeys = nullptr;
const PackageObjectColorKey* objectColorKeys = nullptr;
const PackageParticlePoint* particles = nullptr;
const PackageVisualizerPoint* visualizer = nullptr;
const PackageBpmPoint* bpmChanges = nullptr;
};
struct BackgroundColors {
std::uint32_t topRight = 0;
std::uint32_t topLeft = 0;
std::uint32_t bottomRight = 0;
std::uint32_t bottomLeft = 0;
};
bool loadStagePackage(const char* path, StageView* stage, char* error, std::size_t errorCapacity);
void unloadStagePackage(StageView* stage);
Vec3 trackPositionAt(const StageView& stage, float timeMs);
Vec3 trackTangentAt(const StageView& stage, float timeMs);
CameraState evaluateCamera(const StageView& stage, float timeMs);
BackgroundColors evaluateBackground(const StageView& stage, float timeMs);
float evaluateDrawAhead(const StageView& stage, float timeMs);
float evaluateBeatDurationMs(const StageView& stage, float timeMs);
} // namespace openroller::psp
+43
View File
@@ -0,0 +1,43 @@
#pragma once
namespace openroller::psp {
constexpr int kScreenWidth = 480;
constexpr int kScreenHeight = 272;
constexpr int kLogicalWidth = 720;
constexpr int kLogicalHeight = 1280;
constexpr float kLogicalScale = 3.0f / 8.0f;
constexpr int kScaledWidth = 270;
constexpr int kBorder = (kScreenHeight - kScaledWidth) / 2;
enum class TateSide {
Clockwise,
CounterClockwise,
};
struct Point {
float x;
float y;
};
inline Point toScreen(float logicalX, float logicalY, TateSide side) {
if (side == TateSide::Clockwise) {
return {
static_cast<float>(kScreenWidth) - logicalY * kLogicalScale,
static_cast<float>(kBorder) + logicalX * kLogicalScale,
};
}
return {
logicalY * kLogicalScale,
static_cast<float>(kBorder + kScaledWidth) - logicalX * kLogicalScale,
};
}
inline Point screenDirectionToLogical(float screenX, float screenY, TateSide side) {
if (side == TateSide::Clockwise) {
return {screenY, -screenX};
}
return {-screenY, screenX};
}
} // namespace openroller::psp