All three screenshots are captures from a real PSP-2000 over PSPLINK.
A Game Boy creature-RPG, presented as a voxelized 3D diorama on PSP-class hardware. The gameplay is a TypeScript port of the gen1recomp Lua engine running in an embedded QuickJS guest; the presentation is a Rust reimplementation of the DramaticShape Voxel Mod diorama renderer. Both upstreams are MIT-licensed; both serve here as executable specifications, not vendored code.
Pocket Voxel is a specialized runtime of
PocketJS — the same
⟨ core, surface, guest ⟩ composition as
OpenStrike, with the ownership
split inverted: the game state lives in the guest (world, battle, script
VM, menus, saves — every formula cites the Lua it ports), and the Rust core
owns only the retained scene — cooked voxel chunks, entity billboards, camera
rungs, the battle stage, a GB UI tile layer, and the chip synth that renders
the ROM's own sound programs to PCM. Steady-state boundary traffic is a few
ops per tick against a measured QuickJS budget of ~8k ops per frame.
This repository is ROM-fed, exactly like upstream gen1recomp: the only
game-content input is a canonical US Gen-1 ROM you already own. The importer
verifies its SHA-1 before decoding one byte, everything decoded lands under
git-ignored dist/, and no ROM-derived byte is ever committed — no
cooked pak, no extracted art, no decoded text; the rendering goldens are
frame hashes, never pixels. The screenshots above are hardware captures of
the running device, the same standard as the EBOOT's XMB art.
cook time (Bun, your machine) run time (PSP)
├─ import/ ROM → gen/ (SHA-1 gated) ├─ QuickJS guest: the gameplay port,
├─ cook/ voxelizer: classify tiles, │ one frame(buttons) per tick
│ carve trees, place 42 building ├─ voxel surface: ~10-40 ops/tick
│ templates, bake ground+facades, │ drive the retained scene
│ pack chunks → voxelmon.vxpak ├─ pocketvoxel-core: culling, camera
└─ tapes/ intent tapes → .vtrace │ rungs, draw list, chip synth
(the acceptance path) └─ pocketvoxel-gu: the sceGu backend
- One pak, many machines. Fidelity is a runtime ladder, not a build flag: the same 31 MB pak serves the PSP rung (30 fps present lock, 60 Hz logic), a Vita-class rung, and the desktop identity rung — which replays the pre-ladder picture pixel-for-pixel and is pinned by committed frame hashes no dial edit may move.
- No camera-relative representation change inside the visible field. The PSP rung pays its frame budget with uniform dials only (coarse-carved trees, ground baked to per-chunk pages, stratified detail density) — a distance boundary that moves with the player plays as flicker, and this repo's rule is that it never ships.
- Deterministic to the byte. Two cooks are byte-identical; gameplay is a fixed 60 Hz step with tape-recorded intent; the software rasterizer and the GE resolve the same draw list within a measured pixel tolerance, enforced by a PPSSPP-headless e2e at every story checkpoint.
Needs Bun, a Rust toolchain, and for device builds the
cargo-psp toolchain (resolved
and pinned automatically by tools/voxel.ts).
git clone --recursive https://github.com/pocket-stack/pocket-voxel
cd pocket-voxel && bun install
export VOXELMON_ROM=/path/to/your/rom.gb # SHA-1 verified before any decode
export VOXELMON_G1R=~/code/gen1recomp # reference checkouts: the manifest
export VOXELMON_VOXELMOD=~/code/DramaticShapeVoxelMod # and the tile profiles
bun tools/voxel.ts import # ROM → dist/voxelmon/gen/
bun tools/voxel.ts cook # gen/ → dist/voxelmon/voxelmon.vxpak
bun tools/voxel.ts check # replay the tapes, assert both rungs' hashes
bun tools/voxel.ts psp --release # the EBOOTPut EBOOT.PBP and voxelmon.vxpak in one folder under ms0:/PSP/GAME/, or
develop over PSPLINK with the pak
served from host0:.
bun test # 208 tests; ROM-gated suites skip with a reason
bun tests/e2e/voxel-ppsspp.ts # GE-vs-sim parity at 11 story marksThe full design record is docs/VOXEL.md: the content
boundary, the guest/core split, the VXPK format, the quality ladder and its
identity anchor, the fetch-bound GE findings, and the determinism ceremony
that governs when a committed hash may ever be re-based. The engine arrives
as a pinned git submodule (vendor/pocketjs), the OpenStrike pattern: the
PSP host library, the audio module and the toolchain pins all come from one
engine commit — a mainline commit, moved forward deliberately.
MIT. The ROM, and everything derived from it, stays yours and stays local.


