Files
openroller/docs/psp_port.md
tsuki 831d96e562 Initial public source release
Split reusable rendering and format support into vectorail-core and vectorail-gc.
2026-08-02 17:05:27 +02:00

9.3 KiB

OpenRoller PSP port

The first hardware target is a PSP-1004 (PSP-1000/Fat). The runtime must fit the original model's memory limits and must not depend on the extra application memory available on later PSP models.

Display contract

The arcade game uses a 720x1280 portrait canvas. OpenRoller keeps those logical coordinates on PSP and rotates the final scene into the physical 480x272 panel:

  • scale: 3/8;
  • resulting image: 270x480;
  • physical border: one pixel on each long side;
  • SELECT: swap clockwise/counter-clockwise tate orientation;
  • START: pause/resume once a stage is loaded.

This transform must remain above both the gameplay renderer and the song-select renderer. Content code should not contain PSP screen coordinates.

Gameplay geometry is submitted to the native PSP Geometry Engine as GU_TRANSFORM_3D vertices. The 720x1280 portrait projection is preserved in tate mode by swapping its horizontal/vertical projection extents and rolling the camera basis by 90 degrees. The GU performs perspective division, near/far clipping, and depth testing; CPU projection is not part of the runtime renderer except for the original screen-relative directional-note arrow and lower-right helper overlays.

Memory rules for PSP-1000

  • Use RGB565 double framebuffers and a 16-bit depth buffer. Together they use 0xcc000 bytes of VRAM, leaving about 1.2 MiB of VRAM for resident textures.
  • Never load a whole song into RAM. Audio is decoded from a bounded streaming ring and the decoded sample count is the gameplay clock.
  • Keep only the current jacket resident; replace it when selection moves.
  • Convert DDS/MTX textures on the host to RGB565, RGBA4444, or indexed T8 data.
  • Parse arcade formats on the host. PSP consumes little-endian, aligned binary arrays and never instantiates the reverse-engineering object graph.
  • Generate/copy only the visible rail window and visible notes each frame.
  • Background meshes require culling and a fixed resident-memory budget.

Build

The local machine does not need a permanent PSPDEV installation. The build script uses the official PSPDEV Docker image:

tools/build_psp.sh

The result is psp/EBOOT.PBP. Copy the runtime directory to:

ms0:/PSP/GAME/OpenRoller/

When catalog.orpc is present next to EBOOT.PBP, the runtime opens the portrait song-select screen and lazily loads the selected jacket, chart and audio stream. It renders the real track, camera, notes, draw window, four-corner color background, and the stage-authored .tumo objects. Object translation, scale, rotation, color and visibility channels are evaluated on PSP; one-level parent composition, wireframe selection, the fixed 250 ms visibility fade and sorted alpha rendering follow the recovered game471.exe path.

Particle repeat/lifetime, spawn shape, direction, velocity and opacity are now evaluated against the authored timeline and current BPM. The sprite resource lookup is not ported yet, so particle instances use a small geometric marker. Visualizer type, color and timeline are also carried by .orps; the current GE renderer maps those types to rings, rays, bars and scanlines, but still uses a deterministic pulse rather than the original audio analyser/FFT path. TUMO geometry is position/color only at this stage; its material textures remain the next background fidelity layer.

Without a catalog, the legacy stage.orps/audio.mp3 pair still works. Without either format, the executable falls back to the synthetic smoke scene.

The host preparation script applies both per-difficulty volumes from stage_param.dat, pre-mixes the authored BGM and SHOT WAVs with a latency-compensated limiter, and emits one CBR 128 kbit/s, 44.1 kHz stereo MP3 per chart. The PSP runtime decodes that stream incrementally through one handle of the native MP3 module and submits it through one SRC channel. This is deliberately single-stream: simultaneous MP3 handles stutter on PSP-1000 while the 3D stage is active. Reactive SHOT muting is therefore temporarily disabled in the PSP build. The number of samples accepted by audio output is the gameplay clock; rendering framerate does not advance song time. SE_ARRANGE, TAP_SE1 and TAP_SE2 are host-converted to small 44.1 kHz PCM effects mixed by the same thread.

The host-side chart normalizer is built with the regular desktop CMake build:

cmake -S . -B build
cmake --build build --target openroller-psp-pack
build/openroller-psp-pack GC/data/stage/ac_example_easy.dat stage.orps

The version-4 .orps file is a little-endian, 16-byte-aligned snapshot containing the track, notes, camera, draw distance, background colors, pre-triangulated TUMO vertices, stage-object records, flattened animation keys, particles, visualizer keys and BPM changes. Each note also carries the effective runtime type, appearance/end times, directional vector, per-note early/late/miss/mute windows, colour and effect selector produced by the recovered BuildTimingDataSub path. It deliberately retains unknown note fields so discoveries made later in the reverse do not invalidate imported charts. For ac_10pt8tion_easy.dat the full package is about 493 KiB; the 36 source models contribute about 356 KiB of position-only vertices and are submitted to GU directly from the loaded package.

For a ready-to-copy Memory Stick directory using the default test chart:

tools/prepare_psp_demo.sh

This creates dist/PSP/GAME/OpenRoller/. Copy that OpenRoller directory to ms0:/PSP/GAME/. Runtime controls are:

For the 30-song library build used by the PSP-1004 port:

tools/prepare_psp_library.sh

This produces 97 difficulty packages for 10pt8ion, Oshama Scramble!, Bonetrousle, Shadow, Planet connection, Departure, Journey, SPACE ARCADIAN, Altale, Analysis Division, Thrash Beat, Agent Angels, FLOWER, ADRENA, and 7 days a week, plus Satisfiction and fourteen VOCALOID tracks: MikuMiku, The Disappearance of Hatsune Miku, World's End Dancehall, Rolling Girl, Uraomote Lovers, Unknown Mother-Goose, Redial, Tell Your World, Umiyuri, Karakuri Pierrot, Dappo Rock, ECHO, Vampire, and PaIII.SENSATION. Audio is CBR 128 kbit/s MP3 and jackets are 128x128 RGBA4444. Only the selected song's resources are resident.

The song selector has a continuously scrolling public-test notice. Its build identifier is compiled as tsuki@kagebaito-yyyymmdd-hhmmss by tools/build_psp.sh; each release build therefore exposes its exact timestamp on screen.

Song-select controls are:

  • D-pad up/down: previous/next song;
  • L / R: move five songs;
  • CIRCLE: enter difficulty selection, then start the selected chart;
  • D-pad in difficulty selection: choose an available mode;
  • CROSS: back (or exit from the song list);
  • SELECT: switch tate side.

The physical D-pad is rotated into the portrait coordinate system. In the default clockwise orientation physical left/up/right/down becomes logical down/left/up/right; counter-clockwise tate uses the inverse mapping.

Gameplay controls are:

  • D-pad or CROSS / CIRCLE / TRIANGLE / SQUARE: tap;
  • SELECT: switch tate side;
  • START: open the pause menu;
  • L / R: seek backward/forward five seconds;
  • START + SELECT: exit (the PSP HOME menu also remains available).

The pause menu contains CONTINUE, RESTART, and BACK TO MENU; CIRCLE confirms and CROSS cancels. At chart end the current song is unloaded and the runtime returns to song select automatically. The Linux runtime performs the same transition after the authored track duration and reopens its selector.

Every newly pressed D-pad or face-button bit is retained as an independent input, so simultaneous buttons can judge CRITICAL and start DUAL HOLD. Releases terminate HOLD/DUAL HOLD, while alternating/repeated presses keep SCRATCH and BEAT active. The recovered game471.exe judge routine uses 25% / 50% / 100% of the full early or late input-window width for GREAT / COOL / GOOD, with GreatMinTime = 32 ms as the lower GREAT bound. Windows come from the chart timing lists or the per-difficulty system.cfg override, including the recovered FLICK extension. Long-note ranks use the original held-percentage thresholds and short-tail correction. SLIDE HOLD and MERRY GO ROUND rendering data is packaged, but their final input state machines remain unresolved in the desktop reverse as well.

Notes fade in/out on authored beat-relative times and are submitted after the rail without depth testing. HOLD/SCRATCH/BEAT/SLIDE/DUAL bodies and matching end markers are generated as PSP GE geometry; directional arrows are fixed in screen orientation using the camera at the note timestamp, and the control helper stays in the lower-right HUD slot. The original note/effect atlases are not yet converted for PSP, so these marks remain geometric rather than using the arcade textures.

Runtime package

OpenRoller/
  EBOOT.PBP
  catalog.orpc
  songs/
    <song-id>/
      easy.orps
      normal.orps
      hard.orps
      extra.orps       # when present
      easy_bgm.mp3
      normal_bgm.mp3
      ...
      jacket.orpj
  sounds/
    adlib.pcm
    tap1.pcm
    tap2.pcm

catalog.orpc is a fixed-record index generated from the original stage_param.dat; it binds display title, artist, BPM, duration, genre, difficulty ratings and availability to a stable song key. The menu uses that same key to resolve the chart package, per-difficulty mix and jacket paths.