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.
| Offset | Type | Field | Value |
|---|
| 0 | u8[4] | magic | 'P' 'J' 'G' 'B' (bytes 0x50 0x4A 0x47 0x42) |
| 4 | u16 | version | 1 (current) |
| 6 | u16 | chunk_count | Number of chunks in the directory. |
| 8 | u32 | chunk_table_offset | Byte offset of the chunk directory from blob start. |
| 12 | u32 | total_size | Total blob size in bytes. |
Chunk directory entry (16 bytes each)
| Offset | Type | Field | Description |
|---|
| 0 | u32 | kind | Chunk type (CHUNK.* constant). |
| 4 | u32 | id | Per-kind index (e.g. map index for MAP chunks). |
| 8 | u32 | offset | Byte offset of the chunk payload from blob start. |
| 12 | u32 | size | Payload size in bytes. |
The C runtime reads the chunk directory in cart_chunk(kind, id, &size) by linear scan.
Chunk types
A 44-byte header describing the game metadata and starting position.
| Offset | Type | Field | Description |
|---|
| 0 | u8[24] | title | ASCII game title, null-padded to 24 bytes. |
| 24 | u8 | start_map | Map index of the starting map. |
| 25 | u8 | start_dir | Starting facing direction (DIR.*: 0=down, 1=up, 2=left, 3=right). |
| 26 | u16 | start_x | Starting tile X coordinate. |
| 28 | u16 | start_y | Starting tile Y coordinate. |
| 30 | u8 | map_count | Number of maps. |
| 31 | u8 | sprite_count | Number of sprites. |
| 32 | u16 | flag_count | Number of declared flags. |
| 34 | u16 | text_count | Number of text strings. |
| 36 | u16 | script_count | Number of scripts. |
| 38 | u16 | font_base | BG tile index of the first glyph (ASCII 0x20 = space). Glyph for codepoint c: font_base + c - 0x20. |
| 40 | u16 | box_tile | BG tile index of the opaque textbox fill tile. |
| 42 | u16 | reserved | Zero. |
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.
| Bank | Usage |
|---|
| 0 (indices 0–15) | Tileset palette. Index 0 is the transparent/backdrop color. |
| 1–14 | Unused 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.
| Offset | Type | Field | Description |
|---|
| 0 | u16 | tile_base | First OBJ tile index in TILES_OBJ. |
| 2 | u8 | w_px | Sprite width in pixels (v1: always 16). |
| 3 | u8 | h_px | Sprite height in pixels (v1: always 16). |
| 4 | u8 | palbank | OBJ palette bank index. |
| 5 | u8 | frames | Walk frames per direction. |
| 6 | u16 | reserved | Zero. |
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):
| Offset | Type | Field | Description |
|---|
| 0 | u16 | width | Map width in tiles. |
| 2 | u16 | height | Map height in tiles. |
| 4 | u16 | num_actors | Number of actor instances. |
| 6 | u16 | num_warps | Number of warp records. |
| 8 | u8 | bg_palbank | BG palette bank for map tiles (v1: always 0). |
| 9 | u8 | on_load_script | Script id to run on map load (0xFF = none, reserved). |
| 10 | u16 | reserved | Zero. |
| 12 | u32 | tiles_off | Offset of the tile index table from chunk start. |
| 16 | u32 | collision_off | Offset of the collision table from chunk start. |
| 20 | u32 | actors_off | Offset of the actor instance table from chunk start. |
| 24 | u32 | warps_off | Offset 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):
| Offset | Type | Field | Description |
|---|
| 0 | u16 | x | Tile X position. |
| 2 | u16 | y | Tile Y position. |
| 4 | u8 | sprite_id | Sprite index (0xFF = invisible, used for signs). |
| 5 | u8 | facing | Initial facing direction (DIR.*). |
| 6 | u8 | movement | Movement pattern (MOVE.*: 0=static, 1=wander, 2=patrolH, 3=patrolV). |
| 7 | u8 | flags | Actor flags (ACTOR_FLAG.SOLID = 1). |
| 8 | u16 | on_talk | Script id to run on A-press interaction (0xFFFF = none). |
| 10 | u16 | reserved | Zero. |
Warp table (Warp[num_warps], 12 bytes each):
| Offset | Type | Field | Description |
|---|
| 0 | u16 | x | Source tile X that triggers the warp. |
| 2 | u16 | y | Source tile Y. |
| 4 | u8 | dest_map | Destination map index. |
| 5 | u8 | dest_dir | Destination facing direction. |
| 6 | u16 | dest_x | Destination tile X. |
| 8 | u16 | dest_y | Destination tile Y. |
| 10 | u16 | reserved | Zero. |
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):
| Offset | Type | Field |
|---|
| 0 | u16 | count |
| 2 | u16 | reserved |
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.c — vm_start(script_id, actor_slot) reads table[script_id] and sets vm.code = code_base + offset.
Section summary table
| Chunk | Kind | Purpose | Emitter | Reader |
|---|
GAME | 1 | Game metadata and start position | lower.ts:gameHeader | main.c, map.c |
PAL_BG | 2 | 256 BGR555 BG palette entries | lower.ts, via bake.ts | video.c |
PAL_OBJ | 3 | 256 BGR555 OBJ palette entries | lower.ts, via bake.ts | video.c |
TILES_BG | 4 | 4bpp BG tiles: blank + tileset + font + box | lower.ts, via bake.ts + font.ts | bg.c |
TILES_OBJ | 5 | 4bpp OBJ tiles for sprites | lower.ts, via bake.ts | video.c |
SPRITE_TABLE | 10 | SpriteProto[] metadata | lower.ts:spriteTable | obj.c |
TEXT_BANK | 7 | Indexed null-terminated ASCII strings | lower.ts:textBank | textbox.c |
SCRIPT_CODE | 8 | Raw VM bytecode for all scripts | lower.ts:scriptChunks | script_vm.c |
SCRIPT_TABLE | 9 | u32[] offsets into SCRIPT_CODE | lower.ts:scriptChunks | script_vm.c |
MAP (×N) | 6 | Per-map tiles, collision, actors, warps | lower.ts:mapChunk | map.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.
| Offset | Type | Field | Description |
|---|
0x00 | u32 | magic | 'P' 'J' 'D' 'B' = 0x50 0x4A 0x44 0x42. |
0x04 | u16 | player_x | Player tile X. |
0x06 | u16 | player_y | Player tile Y. |
0x08 | u8 | player_dir | Player facing. |
0x09 | u8 | cur_map | Current map index. |
0x0A | u8 | text_active | 1 if a text box is visible. |
0x0B | u8 | script_active | 1 if a script is running. |
0x0C | u32 | frame | Frame counter since boot. |
0x10 | u16 | cur_text | Currently displayed text id (0xFFFF = none). |
0x12 | u8 | choice_cursor | Highlighted choice row index while a menu is up. |
0x13 | u8 | booted | 1 once the main loop has started. |
0x14 | u8[16] | flags | 128 packed flag bits (flag n → byte n>>3, bit n&7). |
0x24 | i16[16] | vars | 16 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.