The AOT runtime is a fixed native C program that consumes PJGB cartridge data. It targets GBA hardware directly: bare-metal startup, hardware registers, no operating system, and no dynamic allocation during gameplay. All state lives in global structs in EWRAM and IWRAM. The runtime does not run Solid, Vue Vapor, QuickJS, or any part of the PocketJS framework — that boundary is intentional and keeps AOT independent of the PSP UI product line.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.
Responsibilities
- Initialize Mode 0 backgrounds, OBJ sprites, palettes, and VRAM tile transfers.
- Step the active map, actor state, flags, and script VM each frame.
- Render textbox windows using the baked glyph tile data from the cartridge.
- Decode player input into grid movement, collision checks, and NPC interaction.
- Run the suspendable script VM to completion each frame, or resume it when a suspension condition clears.
- Expose a fixed EWRAM debug block so the mGBA test harness can read game state without symbols.
Memory map
The runtime uses GBA hardware resources as follows:| Region | Usage |
|---|---|
Charblock 0 (BG VRAM 0x06000000) | 4bpp BG tiles: blank + tileset + font glyphs + textbox fill tile. Up to 512 tiles (v1 limit). |
| Screenblock 8 (BG VRAM + 0x10000) | BG0 map screenblock: u16[32×32] tile indices for the current map. |
| Screenblock 9 | BG1 screenblock for the textbox / choice menu overlay. |
OBJ VRAM (0x06010000) | 4bpp OBJ tiles for sprites. Up to 1024 tiles. Loaded once from TILES_OBJ. |
BG palette (0x05000000) | Bank 0 = tileset (up to 16 colors); bank 15 = textbox font palette. |
OBJ palette (0x05000200) | One bank of 16 colors per sprite (up to 16 banks). |
OAM (0x07000000) | Sprite hardware shadow, committed at VBlank via DMA. |
EWRAM (0x02000000) | Game struct (global state) + debug block at 0x02000000. |
| IWRAM | Stack and small hot data. |
Source modules
crt0.s — startup
ARM assembly startup: sets the stack pointer, zeroes .bss, copies .data from ROM to EWRAM, and calls main. The GBA requires an AGB-format entry point at address 0 in ROM.
gba.ld — linker script
Places the GBA ROM header at offset 0, the .text section in ROM (0x08000000), .data/.bss in EWRAM (0x02000000), and the stack in IWRAM.
cart.c — cartridge access
Reads the PJGB container blob linked into the ROM as pjgb_cart[]. The single exported function cart_chunk(kind, id, &size) scans the chunk directory and returns a pointer to the requested chunk’s payload.
video.c — display initialization
Sets DISPCNT for Mode 0 (tiled BG + OBJ), configures BG0 (map layer, charblock 0, screenblock 8) and BG1 (textbox overlay, charblock 0, screenblock 9), and transfers OBJ tiles and both palette banks from the cartridge into VRAM once at startup.
bg.c — background map rendering
Loads the current map’s tile data into charblock 0 (BG palette bank 0 tiles) and writes the u16[w×h] screen entries into screenblock 8. When the camera scrolls, bg_set_scroll writes the pixel offsets to REG_BG0HOFS and REG_BG0VOFS.
obj.c — sprite OAM management
Maintains a shadow OAM array. Each frame, obj_reset hides all shadow OAM entries, then obj_draw_scene places the player and any visible actors using obj_put. At VBlank, obj_commit DMA-transfers the shadow OAM to hardware OAM.
input.c — button polling
Reads REG_KEYINPUT each frame into g.keys and tracks the previous frame’s state in g.keys_prev. key_pressed returns true only for keys newly held this frame.
map.c — map loading and collision
map_enter switches the active map: reads the MAP chunk for the given map index, sets up the tile/collision/actor/warp pointers, loads BG tiles and the screenblock, and places the player at the given entrance. map_solid checks tile-level collision and actor solidity. map_actor_at returns the actor slot at a tile position for interaction.
player.c — movement and interaction
Each frame (when the script VM is not active), player_update reads directional input, moves the player one tile at a time with grid-aligned animation (pixel-smooth walking across the 8-pixel tile), checks collision via map_solid, and on A press calls vm_start for the actor at the adjacent tile’s on_talk script.
actor.c — NPC animation
actors_update advances the walk animation timers and frame indices for all actors on the current map, and handles movement patterns (wander, patrol).
camera.c — scroll clamping
camera_follow centers the camera on the player and clamps to the map bounds so the viewport never shows outside the map area.
script_vm.c — bytecode interpreter
The script VM is a small stack machine with a 16-entry s16 stack. It runs until it hits an END opcode or a suspension:
VM_SUSP_TEXT— waiting for the player to dismiss a text box (A press).VM_SUSP_CHOICE— waiting for the player to make a menu selection.VM_SUSP_WAIT— waiting for N frames to elapse.
vm_tick first checks whether the current suspension has cleared. If so, it resumes execution and runs opcodes until the next suspension or END.
OP constants (defined in pjgb_gen.h):
| Opcode | Operands | Effect |
|---|---|---|
END | — | Terminate; clear vm.active. |
NOP | — | No operation. |
TEXT | u16 textId | Show text box; suspend VM_SUSP_TEXT. |
SET_FLAG | u16 flagId | flags[flagId] = 1. |
CLEAR_FLAG | u16 flagId | flags[flagId] = 0. |
PUSH_FLAG | u16 flagId | Push flags[flagId] (0 or 1). |
PUSH_CONST | i16 value | Push a compile-time constant. |
POP | — | Discard top of stack. |
DUP | — | Duplicate top of stack. |
EQ / NE / NOT | — | Stack comparisons / negation. |
JUMP | i16 rel | Unconditional relative branch. |
JUMP_IF_FALSE | i16 rel | Pop; branch if zero. |
CHOICE | u8 n, u16 t0..t(n-1) | Show choice menu (up to 7 options); suspend VM_SUSP_CHOICE; push chosen index. |
LOCK_PLAYER | — | player.locked = 1. |
RELEASE_PLAYER | — | player.locked = 0. |
FACE_PLAYER | u8 slot | Turn actor slot to face the player. 0xFF = the actor that started the script. |
WARP | u8 map, u16 x, u16 y, u8 dir | Call map_enter; teleport player. |
SET_VAR | u16 varId, i16 val | vars[varId] = val. |
ADD_VAR | u16 varId, i16 delta | vars[varId] += delta. |
PUSH_VAR | u16 varId | Push vars[varId]. |
GIVE_ITEM | u16 itemId, u8 count | v1 stub; increments an inventory variable. |
BATTLE | u16 battleId | v1 stub; push 1 (win). |
WAIT | u16 frames | Suspend VM_SUSP_WAIT for N frames. |
PLAY_SFX | u16 sfxId | v1 stub; no-op. |
textbox.c — dialogue rendering
The textbox uses BG1 at higher priority than BG0. Text characters are rendered as glyph tiles from the font region of charblock 0 using the textbox palette (bank 15). The choice menu is rendered in the same BG1 area with an arrow cursor.
debug.c — EWRAM debug block
The debug block is written to the top of EWRAM at 0x02000000 every frame. Its layout is defined by DBG.* constants in pjgb_gen.h and mirrors the spec/pjgb.ts DBG object exactly. The mGBA test harness reads this block by absolute bus address — no symbols required.
The frame loop
main.c implements the game loop:
player_update is skipped so the player cannot move while a dialogue or choice is in progress.
Flags and variables
Flags and variables are stored ing.flags[16] (128 packed bits) and g.vars[16] (16 signed 16-bit integers) in the Game struct. They are persistent for the lifetime of a play session. The inline helpers flag_get and flag_set in runtime.h handle the bit packing:
Build
To build the ROM without the compiler (linking against an existinggen_cart.c):
arm-none-eabi-gcc with -mcpu=arm7tdmi -marm -ffreestanding -nostdlib -O2 and the gba.ld linker script, compiling all runtime C files and the startup assembly in one pass, then objcopy to strip ELF metadata.
Normally you do not need to invoke build.sh directly — bun run build in the aot/ directory runs the full pipeline including the compiler, which writes gen_cart.c and invokes arm-none-eabi-gcc via compiler/rom.ts.
The test harness (test/harness/mgba_runner.c)
The headless E2E test harness is a C program that links against libmgba. It loads a .gba ROM, executes a JSON scenario (sequences of advance, press, read, and screenshot steps), and prints a JSON result with named memory reads.
addr field is the absolute GBA bus address of a debug block field — for example, DEBUG_ADDR + DBG.PLAYER_Y = 0x02000006. The .debug.json emitted by the compiler contains the resolved addresses for every flag, map id, text id, and script id, so the test scenarios can be written symbolically and translated to addresses by aot/test/e2e.ts.
Build the harness once:
The runtime is structured for real GBA hardware but currently requires a Nintendo logo header and logo bitmap (design §26.6) before it will boot on an unmodified cartridge slot. It boots correctly in mGBA and passes all 19 E2E assertions in the headless harness.