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.

PJGB is the packed cartridge data model consumed by the GBA runtime. It is not a general application bundle — it is a purpose-built layout for tile RPG scenes and their scripts. The format is defined in aot/spec/pjgb.ts, which is the single source of truth for both the TypeScript compiler (which encodes) and the C runtime (which decodes via the generated runtime/pjgb_gen.h header).
The PJGB format is an internal compiler-to-runtime contract. For typical authoring workflows the DSL and compiler handle all encoding — you never need to read or write PJGB binary data directly. This reference is for contributors, tooling authors, or anyone debugging the binary output.

Container layout

A PJGB blob has three regions:
[16-byte header]
[chunk directory: chunk_count × 16-byte entries]
[chunk payloads, each 4-byte aligned]
All integers are little-endian throughout (GBA ARM7TDMI is LE). All offsets are from the start of the blob.

Header (16 bytes, offset 0)

OffsetTypeFieldValue
0u8[4]magic'P' 'J' 'G' 'B' (bytes 0x50 0x4A 0x47 0x42)
4u16version1 (current)
6u16chunk_countNumber of chunks in the directory.
8u32chunk_table_offsetByte offset of the chunk directory from blob start.
12u32total_sizeTotal blob size in bytes.

Chunk directory entry (16 bytes each)

OffsetTypeFieldDescription
0u32kindChunk type (CHUNK.* constant).
4u32idPer-kind index (e.g. map index for MAP chunks).
8u32offsetByte offset of the chunk payload from blob start.
12u32sizePayload size in bytes.
The C runtime reads the chunk directory in cart_chunk(kind, id, &size) by linear scan.

Chunk types

CHUNK.GAME (kind 1, id 0) — Game header

A 44-byte header describing the game metadata and starting position.
OffsetTypeFieldDescription
0u8[24]titleASCII game title, null-padded to 24 bytes.
24u8start_mapMap index of the starting map.
25u8start_dirStarting facing direction (DIR.*: 0=down, 1=up, 2=left, 3=right).
26u16start_xStarting tile X coordinate.
28u16start_yStarting tile Y coordinate.
30u8map_countNumber of maps.
31u8sprite_countNumber of sprites.
32u16flag_countNumber of declared flags.
34u16text_countNumber of text strings.
36u16script_countNumber of scripts.
38u16font_baseBG tile index of the first glyph (ASCII 0x20 = space). Glyph for codepoint c: font_base + c - 0x20.
40u16box_tileBG tile index of the opaque textbox fill tile.
42u16reservedZero.
How the compiler encodes it: lower.ts calls gameHeader(), which writes the title (ASCII, null-padded to 24 bytes), start position, and counts from GameModel and Ctx. How the runtime reads it: main.c calls cart_chunk(CHUNK_GAME, 0, 0) and casts the result to const GameHeader *. map_enter uses start_map/start_x/start_y/start_dir. textbox.c uses font_base and box_tile.

CHUNK.PAL_BG (kind 2, id 0) — Background palette

u16[] of 256 BGR555 color entries (16 banks of 16 colors). All integers are 15-bit: bits 0–4 = red, 5–9 = green, 10–14 = blue, bit 15 unused.
BankUsage
0 (indices 0–15)Tileset palette. Index 0 is the transparent/backdrop color.
1–14Unused in v1.
15 (indices 240–255)Textbox font palette. Indices 1–5 = subpixel ink coverage shades; index 6 = opaque textbox background.
How the compiler encodes it: bake.ts fills ctx.bgPalette[0..15] from tileset.palette and ctx.bgPalette[240..245] with the hardcoded textbox ink shades. lower.ts emits the full 256-entry u16 array.

CHUNK.PAL_OBJ (kind 3, id 0) — Sprite palette

u16[] of 256 BGR555 color entries (16 banks of 16 colors). In v1 each sprite occupies one OBJ palette bank starting at bank 0.

CHUNK.TILES_BG (kind 4, id 0) — Background tile data

Concatenated 4bpp BG tiles, 32 bytes each (8×8 pixels at 4 bits per pixel, low nibble = left pixel, row-major). The tile order is:
[0]       blank tile (all zeros)
[1..N]    tileset tiles in defineTileset order
[N+1..M]  font glyphs: one tile per ASCII codepoint 0x20..0x7E (95 glyphs)
[M+1]     textbox fill tile (all pixels = palette index 6)
font_base in the game header equals N+1. box_tile equals M+1. The runtime references tiles by their index into this block. How the compiler encodes it: bake.ts builds ctx.bgTiles[] using tile4() (packs number[] pixel grid → 32-byte Uint8Array). Font glyphs are rasterized by font.ts from Inter Bold outlines. lower.ts concatenates all tiles with catTiles(). How the runtime reads it: video.c / bg.c transfer this chunk’s payload to charblock 0 in BG VRAM (0x06000000).

CHUNK.TILES_OBJ (kind 5, id 0) — Sprite tile data

Concatenated 4bpp OBJ tiles, 32 bytes each. For a 16×16 sprite with F walk frames and 4 directions, the tile layout is:
for each direction d in [down, up, left, right]:
  for each frame f in 0..F-1:
    tiles: TL, TR, BL, BR (4 tiles for the 16×16 sprite)
SpriteProto.tile_base is the index of the first tile for that sprite. Frame f of direction d starts at tile_base + (d * frames + f) * 4. How the runtime reads it: video.c transfers this chunk to OBJ VRAM (0x06010000). obj.c uses tile_base + direction*frames*4 to select the correct OAM tile index.

CHUNK.SPRITE_TABLE (kind 10, id 0) — Sprite prototypes

Array of 8-byte SpriteProto records, one per sprite, indexed by sprite id.
OffsetTypeFieldDescription
0u16tile_baseFirst OBJ tile index in TILES_OBJ.
2u8w_pxSprite width in pixels (v1: always 16).
3u8h_pxSprite height in pixels (v1: always 16).
4u8palbankOBJ palette bank index.
5u8framesWalk frames per direction.
6u16reservedZero.

CHUNK.MAP (kind 6, id = map index) — Map data

One MAP chunk per map. The payload is a self-describing variable-length block. Header (28 bytes):
OffsetTypeFieldDescription
0u16widthMap width in tiles.
2u16heightMap height in tiles.
4u16num_actorsNumber of actor instances.
6u16num_warpsNumber of warp records.
8u8bg_palbankBG palette bank for map tiles (v1: always 0).
9u8on_load_scriptScript id to run on map load (0xFF = none, reserved).
10u16reservedZero.
12u32tiles_offOffset of the tile index table from chunk start.
16u32collision_offOffset of the collision table from chunk start.
20u32actors_offOffset of the actor instance table from chunk start.
24u32warps_offOffset of the warp table from chunk start.
Tile index table (u16[width × height]): BG screen entries (tile id = charblock-0 tile index). Row-major, origin top-left. Collision table (u8[width × height]): 0 = walkable, 1 = solid. Padded to 4-byte alignment. Actor instance table (ActorInstance[num_actors], 12 bytes each):
OffsetTypeFieldDescription
0u16xTile X position.
2u16yTile Y position.
4u8sprite_idSprite index (0xFF = invisible, used for signs).
5u8facingInitial facing direction (DIR.*).
6u8movementMovement pattern (MOVE.*: 0=static, 1=wander, 2=patrolH, 3=patrolV).
7u8flagsActor flags (ACTOR_FLAG.SOLID = 1).
8u16on_talkScript id to run on A-press interaction (0xFFFF = none).
10u16reservedZero.
Warp table (Warp[num_warps], 12 bytes each):
OffsetTypeFieldDescription
0u16xSource tile X that triggers the warp.
2u16ySource tile Y.
4u8dest_mapDestination map index.
5u8dest_dirDestination facing direction.
6u16dest_xDestination tile X.
8u16dest_yDestination tile Y.
10u16reservedZero.
The compiler resolves all "mapName:entranceId" warp targets to concrete indices and coordinates at build time. There are no string references in the warp table.

CHUNK.TEXT_BANK (kind 7, id 0) — Dialogue strings

All text strings (dialogue lines, sign text, choice option labels) in a single indexed table. Header (4 bytes):
OffsetTypeField
0u16count
2u16reserved
Offset table: u32[count] byte offsets from the start of the chunk to each null-terminated ASCII string. String data: null-terminated ASCII. The newline character (0x0A) within a string triggers a line break in the textbox renderer. All characters must be in the range 0x20–0x7E (printable ASCII). How the compiler encodes it: lower.ts calls textBank(), which writes the count, offset table (absolute from chunk start), and string bytes. Texts are interned via ctx.internText(s) — repeated strings share one entry. How the runtime reads it: cart.c locates the chunk; textbox.c calls text_get(id) which reads the offset table and returns a pointer to the string in ROM.

CHUNK.SCRIPT_CODE (kind 8, id 0) — Bytecode

All script bytecode concatenated, without padding between scripts. The SCRIPT_TABLE chunk provides the byte offset of each script within this region.

CHUNK.SCRIPT_TABLE (kind 9, id 0) — Script offset table

u32[script_count] byte offsets into SCRIPT_CODE, indexed by script id. The runtime indexes this table to find the entry point for any script. How the runtime reads it: script_vm.cvm_start(script_id, actor_slot) reads table[script_id] and sets vm.code = code_base + offset.

Section summary table

ChunkKindPurposeEmitterReader
GAME1Game metadata and start positionlower.ts:gameHeadermain.c, map.c
PAL_BG2256 BGR555 BG palette entrieslower.ts, via bake.tsvideo.c
PAL_OBJ3256 BGR555 OBJ palette entrieslower.ts, via bake.tsvideo.c
TILES_BG44bpp BG tiles: blank + tileset + font + boxlower.ts, via bake.ts + font.tsbg.c
TILES_OBJ54bpp OBJ tiles for spriteslower.ts, via bake.tsvideo.c
SPRITE_TABLE10SpriteProto[] metadatalower.ts:spriteTableobj.c
TEXT_BANK7Indexed null-terminated ASCII stringslower.ts:textBanktextbox.c
SCRIPT_CODE8Raw VM bytecode for all scriptslower.ts:scriptChunksscript_vm.c
SCRIPT_TABLE9u32[] offsets into SCRIPT_CODElower.ts:scriptChunksscript_vm.c
MAP (×N)6Per-map tiles, collision, actors, warpslower.ts:mapChunkmap.c

Debug block (EWRAM, not in the PJGB blob)

The runtime writes a separate debug block to the top of EWRAM at 0x02000000 each frame. This is not part of the cartridge blob — it is a live game state mirror for the test harness.
OffsetTypeFieldDescription
0x00u32magic'P' 'J' 'D' 'B' = 0x50 0x4A 0x44 0x42.
0x04u16player_xPlayer tile X.
0x06u16player_yPlayer tile Y.
0x08u8player_dirPlayer facing.
0x09u8cur_mapCurrent map index.
0x0Au8text_active1 if a text box is visible.
0x0Bu8script_active1 if a script is running.
0x0Cu32frameFrame counter since boot.
0x10u16cur_textCurrently displayed text id (0xFFFF = none).
0x12u8choice_cursorHighlighted choice row index while a menu is up.
0x13u8booted1 once the main loop has started.
0x14u8[16]flags128 packed flag bits (flag n → byte n>>3, bit n&7).
0x24i16[16]vars16 general-purpose integer variables.
The .debug.json emitted by the compiler maps symbolic names (flag names, map names, text strings) to their EWRAM addresses so the test harness can assert game state using the debug block fields.

The spec as a binary contract

aot/spec/pjgb.ts and aot/runtime/pjgb_gen.h (generated by aot/spec/gen-c.ts) are kept in sync automatically. Every #define constant in the C header, including chunk kind values, struct sizes, opcode values, budget limits, and debug block offsets, is derived from the TypeScript spec. If you modify the spec, run bun aot/spec/gen-c.ts to regenerate the header before rebuilding the runtime.

Build docs developers (and LLMs) love