Skip to content

CLI — tools/voxel.ts ​

One command drives the whole pipeline. Every subcommand is safe to re-run; anything missing an input prints a reason and exits rather than failing into a half-decoded state.

sh
bun tools/voxel.ts <command> [args]

Environment ​

VariableMeaningDefault
VOXELMON_ROMthe canonical US Red ROM (SHA-1 gated)none committed
VOXELMON_G1Rgen1recomp checkout — manifest, parity reference, RED++ pack~/code/gen1recomp
VOXELMON_VOXELMODDramaticShapeVoxelMod checkout — tile profiles, arenas~/code/DramaticShapeVoxelMod
VITASDKVitaSDK root (Vita builds)~/vitasdk

Commands ​

Pipeline ​

CommandWhat it does
importdecode the ROM into dist/voxelmon/gen/ (SHA-1 gated; 17 datasets + gfx.bin + programs.bin)
paritydeep-compare gen/*.json against the reference extractor's output, field for field
cookvoxelize + pack dist/voxelmon/voxelmon.vxpak; extra args pass to the cooker
simrun the story tape headless → dist/voxelmon/trace/story.vtrace

Verdicts ​

Each of these prepares first — import if gen/ is missing, always re-cook, re-run both tapes (story, battle) at the pinned seed:

CommandWhat it does
checkrasterize both traces at both rungs and assert against the committed hashes: <tape>.hashes (shipped psp rung) and <tape>-max.hashes (the identity)
recordrewrite the shipped goldens only, then re-prove — never rewrite — the -max identity anchor
shotswrite PNG frames to dist/voxelmon/shots-<tape>-<tier>/; --tier <name> picks one rung, default both
wavrender any song/sfx/cry through the real core synth to dist/voxelmon/audio/*.wav, printing peak and RMS — so "it renders" and "it is audible" stay two different claims

Device builds ​

Both prepare the pak + traces and bundle the guest (voxelmon/game/psp-main.ts → one IIFE, no module system) before invoking cargo:

CommandWhat it does
pspcargo psp under the pinned toolchain, then re-pack the PBP with MEMSIZE=1 (full PSP-2000 memory — the pak + QuickJS heap cannot share the 24 MB partition) and copy the pak next to the EBOOT. Extra args go to cargo: --release, --features capture
runpsp, then launch the EBOOT in PPSSPP (open -a PPSSPPSDL)
vitacargo vita build vpk on the nightly pinned by the vendored Vita host; packages the pak inside the VPK. --tier <psp|vita|desktop> names the rung the shell requests (default vita); other args go to cargo

package.json aliases ​

sh
bun run setup    # git submodule update --init
bun run import   # = tools/voxel.ts import
bun run cook     # = tools/voxel.ts cook
bun run check    # = tools/voxel.ts check
bun run shots    # = tools/voxel.ts shots
bun run psp      # = tools/voxel.ts psp --release
bun run vita     # = tools/voxel.ts vita --release
bun run e2e      # = bun tests/e2e/voxel-ppsspp.ts
bun test         # the whole suite
bun run tsc      # typecheck, no emit

Notes worth knowing ​

  • Every verdict re-cooks. A stale pak is the one failure that looks like an engine bug; the re-cook is cheap insurance and cooks are byte-identical anyway.
  • The tape seed is pinned (STORY_SEED = 17): both tapes' routes are plotted against it. Changing it invalidates every committed hash.
  • record cannot damage the identity. It refuses to rewrite -max.hashes by construction; a red -max after record means a dial broke the identity rung — see the golden ceremony.
  • The cook flags (VOXEL_KEEP_HIDDEN=1, VOXEL_TREE_BOXES=1) are documented in The Asset Pipeline.

MIT Licensed. The ROM, and everything derived from it, stays yours and stays local.