Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/pocket-stack/pocketjs/llms.txt

Use this file to discover all available pages before exploring further.

PocketJS AOT turns TypeScript and JSX source files into real GBA .gba ROMs — no JavaScript engine ships on the cartridge. This guide walks through the prerequisites, the demo entry point, the compile command, and a minimal example of the authoring DSL so you can see the full pipeline end-to-end.

Prerequisites

1
Install Bun
2
The compiler, asset pipeline, and test suite all run under Bun. Bun is the only Node-compatible prerequisite needed to author and compile a game.
3
curl -fsSL https://bun.sh/install | bash
4
Install the ARM cross-compiler
5
Linking the C runtime to produce a .gba ROM requires arm-none-eabi-gcc and arm-none-eabi-objcopy. On macOS with Homebrew:
6
brew install --cask gcc-arm-embedded
7
On Debian/Ubuntu:
8
sudo apt-get install gcc-arm-none-eabi binutils-arm-none-eabi
9
If you only want the compiler output (the .pjgb cartridge blob and the .ir.json/.debug.json metadata files) and do not need a runnable ROM, pass --no-rom to the build command and skip this step.
10
(Optional) Install mGBA
11
mGBA plays .gba ROMs produced by the AOT compiler. The headless E2E test suite also requires libmgba when running bun run test. A standard mGBA desktop install is sufficient for playing the ROM interactively.
12
Generate the spec header
13
The compiler encodes the PJGB format from aot/spec/pjgb.ts. The C runtime decodes it via a generated header. Regenerate runtime/pjgb_gen.h whenever the spec changes:
14
bun aot/spec/gen-c.ts
15
This step is a one-time setup. The output is committed to the repository so fresh checkouts do not require it immediately.

The demo entry point

The demo game lives at aot/demo/game.tsx. It is a Pokémon-style overworld vertical slice with two maps (Littleroot Town and Route 101), NPCs with full dialogue trees, a choice menu, a battle→flag→item reward sequence, and a warp between maps. Reading it gives a complete picture of the authoring DSL. Here is the essential shape of a minimal game file:
/** @jsxImportSource @pocketjs/aot */
import {
  ascii,
  defineGame,
  defineMap,
  defineTileset,
  PlayerSpawn,
  Sign,
  tile,
} from "@pocketjs/aot";

const rows8 = (v: string) => Array.from({ length: 8 }, () => v);

const town = defineTileset("town", {
  palette: [[0, 0, 0]],
  tiles: {
    grass: { px: rows8("11111111") },
    wall:  { solid: true, px: rows8("22222222") },
  },
});

function TownEntities() {
  return (
    <>
      <PlayerSpawn id="spawn" at={[1, 1]} facing="down" />
      <Sign text="POCKET TOWN" at={[1, 0]} />
    </>
  );
}

const Town = defineMap("town")
  .tileset(town)
  .layer(
    ascii`
      ####
      #..#
      ####
    `.legend({
      "#": tile("wall"),
      ".": tile("grass"),
    }),
  )
  .entities(<TownEntities />)
  .done();

export default defineGame({
  title: "POCKET TOWN",
  start: "town:spawn",
  maps: [Town],
});
The /** @jsxImportSource @pocketjs/aot */ pragma at the top of every .tsx file points JSX to the AOT DSL runtime. The JSX factory builds static scene node trees during the Bun build — not at GBA runtime.

Build the demo

Run the full build from the repository root. The bun run build script first refreshes the image generation assets (the GBA 4bpp sprite sheets), then invokes the compiler:
cd aot
bun run build
This is equivalent to running the two steps separately:
bun demo/imagegen/build-assets.ts
bun compiler/cli.ts build demo/game.tsx --out dist/pocket-town.gba

What the compiler produces

The dist/ directory receives four output files:
FileDescription
pocket-town.gbaThe linked GBA ROM — load this in mGBA or on hardware.
pocket-town.pjgbThe raw PJGB cartridge blob (without the ARM runtime).
pocket-town.ir.jsonHuman-readable IR snapshot: maps, actors, warps, scripts, flags, texts.
pocket-town.debug.jsonDebug symbol map for the mGBA E2E test harness: flag addresses, map indices, text ids.
The compiler prints a build summary to stdout:
PocketJS-AOT build: POCKET TOWN
  maps: 2
  scripts: 4   texts: 11   flags: 2
  BG tiles: 23   OBJ tiles: 32   sprites: 1
  cartridge: 4096 bytes
  ROM: dist/pocket-town.gba (262144 bytes)
  done in 312ms

Compile flags

FlagDescription
--out <file.gba>Output path for the .gba ROM. Defaults to aot/dist/pocket-town.gba. Companion files (.pjgb, .ir.json, .debug.json) use the same base name.
--no-romSkip the ARM cross-compiler step. Emits only .pjgb, .ir.json, and .debug.json. Useful if arm-none-eabi-gcc is not installed.

Play the ROM

Open the compiled ROM in mGBA:
mgba dist/pocket-town.gba
Or use the convenience script from the aot/ directory:
bash play.sh

Run the E2E test suite

The headless test suite runs the ROM in libmgBA, feeds it scripted input sequences, and asserts game state via the EWRAM debug block. Build the test harness once (requires libmgba), then run:
bash aot/test/harness/build.sh   # once
cd aot
bun run test
The suite covers 19 assertions across boot, grid movement, collision, NPC interaction, the choice menu, the battle→flag→item reward, and map warping.

What comes next

With the demo compiling and running, the natural next steps are:
  • Authoring — learn the full DSL surface: tile maps, entity prefabs, dialogue, choices, flags, warps, and residual scripts.
  • Compiler — understand each pipeline stage and what the compiler validates.
  • Cartridge Format — the PJGB binary layout and how the runtime reads each section.

Build docs developers (and LLMs) love