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.

spec/spec.ts is the single source of truth for every constant that crosses a language boundary in PocketJS. It defines the op codes, node types, property IDs, style-table binary format, font atlas format, pak container constants, the PSP button bitmask, and the target screen dimensions. Every host — QuickJS on PSP hardware, the browser WASM module, and headless Bun — speaks this exact wire protocol. The Rust core (core/src/spec.rs) is fully code-generated from spec/spec.ts and must never be edited by hand.
All numeric values on this page are extracted directly from spec/spec.ts. That file is the canonical source. If anything on this page disagrees with the file, the file wins.

Code generation and the drift guard

Running bun spec/gen-rust.ts from the repository root deterministically regenerates core/src/spec.rs from the TypeScript constants. The CI contract test (test/contract.ts) regenerates the file in memory on every run and byte-compares the result against the committed core/src/spec.rs — so TypeScript and Rust can never drift. If you change any value in spec/spec.ts, you must run codegen and commit the updated Rust file:
// spec/spec.ts — change a value, then:
bun spec/gen-rust.ts   // regenerates core/src/spec.rs
git add core/src/spec.rs
The generated file opens with a comment making its provenance clear:
//! GENERATED — do not edit; run `bun spec/gen-rust.ts` (from PocketJS/).
//!
//! Source of truth: PocketJS/spec/spec.ts — every constant here mirrors it.
//! test/contract.ts regenerates this file in-memory and byte-compares;
//! if that fails, run `bun spec/gen-rust.ts` and commit the result.
The append-only rule. Numeric codes are never renumbered and never reused. Removing a code from the wire protocol would break any host that has already shipped. To deprecate a code, stop emitting it; the slot remains reserved forever. 0 is reserved as invalid/nop across every table.

Screen dimensions

The logical (and physical PSP) screen is always 480 × 272 pixels. Every host renders at this exact resolution.
export const SCREEN_W = 480;
export const SCREEN_H = 272;

Node types

The type argument passed to createNode must be one of these values:
ValueNameDescription
0viewFlex container / box. The workhorse element.
1textText run, rendered from a baked font atlas.
2imageTexture quad; can also carry an animated sprite.
export const NODE_TYPE = {
  view:  0,
  text:  1,
  image: 2,
} as const;

OP codes

Every ui.* method has a numeric code that is its wasm/FFI ABI identity. The TypeScript HostOps interface uses method names; these codes appear on the wire and in core/src/spec.rs.
CodeNameSignature
1createNode(type: i32) → id
2destroyNode(id) → void
3insertBefore(parent, child, anchorOr0) → void
4removeChild(parent, child) → void
5setStyle(id, styleId) → void
6setProp(id, propId: i32, value: f64) → void
7setText(id, str) → void
8replaceText(id, str) → void
9uploadTexture(buf, w, h, psm) → handle
10setImage(id, texHandle) → void
11animate(id, propId, to: f64, durMs, easing, delayMs) → animId
12cancelAnim(animId) → void
13setFocus(idOr0) → void
14loadStyles(buf) → void
15loadFontAtlas(buf) → void
16measureText(str, fontSlot) → width: f32
17setSprite(id, atlas, frames, cols, step) → void
export const OP = {
  createNode:    1,
  destroyNode:   2,
  insertBefore:  3,
  removeChild:   4,
  setStyle:      5,
  setProp:       6,
  setText:       7,
  replaceText:   8,
  uploadTexture: 9,
  setImage:      10,
  animate:       11,
  cancelAnim:    12,
  setFocus:      13,
  loadStyles:    14,
  loadFontAtlas: 15,
  measureText:   16,
  setSprite:     17,
} as const;

PROP ids

Property ids are stable u8 values grouped by category with numeric gaps left for future growth. 0 is reserved. Ids are append-only: never renumber a shipped id. Layout group (1–63) — fed to taffy; changes trigger a relayout.
IDNameTypeDescription
1widthf32 pxElement width. SIZE_FULL (-1) means 100% of parent.
2heightf32 pxElement height. SIZE_FULL (-1) means 100% of parent.
3minWf32 pxMinimum width.
4minHf32 pxMinimum height.
5maxWf32 pxMaximum width.
6maxHf32 pxMaximum height.
8paddingTf32 pxTop padding.
9paddingRf32 pxRight padding.
10paddingBf32 pxBottom padding.
11paddingLf32 pxLeft padding.
12marginTf32 pxTop margin.
13marginRf32 pxRight margin.
14marginBf32 pxBottom margin.
15marginLf32 pxLeft margin.
16gapf32 pxGap between flex children (both axes).
17flexDirenum FlexDir0 = Row, 1 = Col.
18justifyenum Justifyjustify-content.
19alignenum Alignalign-items.
20growf32Flex grow factor.
21shrinkf32Flex shrink factor.
22basisf32 pxFlex basis.
23flexWrapint0 = nowrap, 1 = wrap.
24posTypeenum PosType0 = Relative, 1 = Absolute.
25insetTf32 pxAbsolute positioning top offset.
26insetRf32 pxAbsolute positioning right offset.
27insetBf32 pxAbsolute positioning bottom offset.
28insetLf32 pxAbsolute positioning left offset.
29displayenum Display0 = Flex, 1 = None (removes from layout and paint).
30overflowenum Overflow0 = Visible, 1 = Hidden (scissor in draw).
31zIndexi32Paint order among siblings. Paint-only despite its layout-group ID.
Visual group (64–95) — paint-only; no relayout.
IDNameTypeDescription
64bgColorcolor u32 ABGRBackground fill color.
65gradFromcolor u32 ABGRGradient start color (used when gradDir is set).
66gradTocolor u32 ABGRGradient end color.
67gradDirenum GradDir0 ToTop, 1 ToBottom, 2 ToLeft, 3 ToRight.
68radiusf32 pxCorner radius.
69opacityf32 0–1Multiplies subtree alpha per-vertex.
70borderColorcolor u32 ABGRBorder color.
71borderWidthf32 pxBorder width, drawn inset. Does not affect layout.
72shadowi32Baked shadow index: 0 = none, 1 = shadow, 2 = md, 3 = lg.
Text group (96–127) — text layout props invalidate text measurement; paint-only for color.
IDNameTypeDescription
96textColorcolor u32 ABGRText color.
97fontSloti32Baked font atlas slot index. Changes trigger text re-measure.
98textAlignenum TextAlign0 Left, 1 Center, 2 Right.
99lineHeightf32 pxOverrides the atlas default line advance.
100trackingf32 pxExtra advance per glyph (letter-spacing).
Transform group (128–159) — paint-only; never invalidate layout.
IDNameTypeDescription
128translateXf32 pxHorizontal translation (about the node’s position).
129translateYf32 pxVertical translation.
130scalef32Uniform scale about the node center (1 = identity).
131rotatef32 degreesRotation about the node center.
132scaleXf32Horizontal scale about the node center.
133scaleYf32Vertical scale about the node center.

Animatable props

Props listed in the ANIMATABLE array can be tweened by animate(), driven by springs, and included in style-table transition masks. Their position in the array is their anim bit index — the bit used in the transition mask field of a style record (see Style table format). The 32 animatable props in order (bit 0 first):
export const ANIMATABLE: readonly PropName[] = [
  "width",       // bit 0
  "height",      // bit 1
  "paddingT",    // bit 2  ... paddingR, paddingB, paddingL
  "paddingR",    // bit 3
  "paddingB",    // bit 4
  "paddingL",    // bit 5
  "marginT",     // bit 6  ... marginR, marginB, marginL
  "marginR",     // bit 7
  "marginB",     // bit 8
  "marginL",     // bit 9
  "gap",         // bit 10
  "basis",       // bit 11
  "insetT",      // bit 12  ... insetR, insetB, insetL
  "insetR",      // bit 13
  "insetB",      // bit 14
  "insetL",      // bit 15
  "bgColor",     // bit 16  (colors lerp per ABGR channel)
  "gradFrom",    // bit 17
  "gradTo",      // bit 18
  "radius",      // bit 19
  "opacity",     // bit 20
  "borderColor", // bit 21
  "borderWidth", // bit 22
  "textColor",   // bit 23
  "lineHeight",  // bit 24
  "tracking",    // bit 25
  "translateX",  // bit 26
  "translateY",  // bit 27
  "scale",       // bit 28
  "rotate",      // bit 29
  "scaleX",      // bit 30
  "scaleY",      // bit 31
];
TRANSITION_MASK_ALL = 0xffffffff means “every animatable prop” (transition-all).

ENUMS

All enum values are ordinals on the wire. They are append-only.

Easing

Used as the easing argument to animate(). Spring and SpringBouncy ignore durMs — physics determines the duration. OutBack overshoots approximately 10%. All easings tick at a fixed dt = 1/60 s.
OrdinalNameNotes
0LinearConstant rate.
1EaseInStarts slow, ends fast.
2EaseOutStarts fast, ends slow.
3EaseInOutSlow start and end.
4OutBackOvershoots ~10% before settling.
5SpringPhysics spring; durMs ignored.
6SpringBouncyBouncier spring; durMs ignored.

PosType

OrdinalNameDescription
0RelativeDefault; participates in flex flow.
1AbsolutePositioned via insetT/R/B/L relative to nearest ancestor.

Overflow

OrdinalNameDescription
0VisibleDefault; children render outside the box bounds.
1HiddenEmits a scissor rect in the DrawList; children clip to the box.

Align (align-items)

OrdinalName
0Start
1Center
2End
3Stretch

Justify (justify-content)

OrdinalName
0Start
1Center
2End
3Between
4Around

FontSlot

Font slots correspond to baked atlas files at specific pixel sizes. The compiler derives which slots are needed from the Tailwind text-size utilities found in the app source (text-xs through text-4xl, both regular and bold). Slot indices are assigned by the build and encoded in each atlas header.
Tailwind utilitypx size
text-xs12 px
text-sm14 px
text-base16 px
text-lg18 px
text-xl20 px
text-2xl24 px
text-4xl36 px
The system supports up to MAX_FONT_SLOTS = 16 atlas slots.

TextAlign

OrdinalName
0Left
1Center
2Right

PSM (texture pixel format)

Values must equal the corresponding rust-psp TexturePixelFormat values (sceGuTexMode argument).
ValueNameDescription
2PSM_4444RGBA 4444, 16-bit. Used for baked corner and shadow alpha sprites.
3PSM_8888RGBA 8888, 32-bit. Used for uploaded images.

BTN bitmask

The PSP button bitmask is identical on every host. Web and Bun hosts remap keyboard keys to these bits. Pass a BTN value as the mask argument to onButtonPress(), or combine bits with | to watch multiple buttons at once.
ConstantValue (hex)PSP Button
SELECT0x0001SELECT
START0x0008START
UP0x0010D-pad Up
RIGHT0x0020D-pad Right
DOWN0x0040D-pad Down
LEFT0x0080D-pad Left
LTRIGGER0x0100L trigger
RTRIGGER0x0200R trigger
TRIANGLE0x1000Triangle
CIRCLE0x2000Circle
CROSS0x4000Cross
SQUARE0x8000Square
export const BTN = {
  SELECT:   0x0001,
  START:    0x0008,
  UP:       0x0010,
  RIGHT:    0x0020,
  DOWN:     0x0040,
  LEFT:     0x0080,
  LTRIGGER: 0x0100,
  RTRIGGER: 0x0200,
  TRIANGLE: 0x1000,
  CIRCLE:   0x2000,
  CROSS:    0x4000,
  SQUARE:   0x8000,
} as const;
By convention, CIRCLE is the primary action button (fires onPress on a focused Focusable), and LTRIGGER/RTRIGGER are used for paging by Gallery.

PAK container constants

PocketJS packs styles, font atlases, and images into a .pak file that reuses the dreamcart container format byte-for-byte, so existing tooling can open them.

Header and entry layout

The header is 32 bytes; entries are 24 bytes each. All multibyte fields are little-endian.
Header (32 bytes):
  off  0  u32  magic          = 0x4b504344  ('DCPK' in LE)
  off  4  u16  version        = 1
  off  6  u16  flags
  off  8  u32  entryCount
  off 12  u32  dirOffset
  off 16  u32  namesOffset
  off 20  u32  blobsOffset    (16-byte aligned)
  off 24  u32  fileSize
  off 28  u32  reserved

Entry (24 bytes):
  off  0  u32  keyHash        (FNV-1a 32-bit of the key string)
  off  4  u32  blobOff        (from start of file)
  off  8  u32  byteLen
  off 12  u32  nameOff        (from namesOffset)
  off 16  u16  nameLen
  off 18  u8   dtype          (advisory element type)
  off 19  u8   reserved
  off 20  u32  reserved
Entries are sorted by keyHash; blobs are 16-byte aligned.

Constants

ConstantValueDescription
PAK_MAGIC0x4b504344Magic bytes 'DCPK' (little-endian).
PAK_VERSION1Format version.
PAK_HEADER_SIZE32Byte size of the header.
PAK_ENTRY_SIZE24Byte size of one directory entry.
PAK_ALIGN16Blob alignment in bytes.
PAK_FNV1A_OFFSET_BASIS0x811c9dc5FNV-1a 32-bit offset basis.
PAK_FNV1A_PRIME0x01000193FNV-1a 32-bit prime.

Key strings

The runtime uses these well-known key conventions to locate assets in the pak. Entry lookup hashes the key with FNV-1a 32-bit (h ^= byte; h *= prime).
Key patternDescription
'ui:styles'The compiled styles.bin blob.
'ui:font.<slot>'One baked font atlas. <slot> is the numeric slot index encoded in the blob header.
'ui:img.<name>'An image texture. <name> is the image filename without its ui:img. prefix.
'ui:sprite.<name>'An animated sprite atlas. <name> is the sprite atlas name.
On PSP, native/src/pak.rs reads these keys and feeds Ui::load_styles, Ui::load_font_atlas, and Ui::upload_texture directly from the embedded bytes before JavaScript evaluation starts. The texture and sprite handles are then exposed to JS as ui.__textures and ui.__sprites. On web and Bun test hosts, pak.ts reads the same pak and delivers assets through the HostOps methods.

Style table binary format (styles.bin)

The style table is compiled by compiler/tailwind.ts and parsed by core/src/style.rs. It is little-endian and has no inter-field padding.
Header (8 bytes):
  off 0  u32  magic      = 0x54534344  ('DCST' in LE)
  off 4  u16  version    = 1
  off 6  u16  styleCount

Then styleCount records, back-to-back. styleId = record index (0-based).

Record:
  off 0  u8   flags
           bit 0  STYLE_VARIANT_BASE    base variant present
           bit 1  STYLE_VARIANT_FOCUS   focus: variant present
           bit 2  STYLE_VARIANT_ACTIVE  active: variant present
           bit 3  STYLE_HAS_TRANSITION  transition block present

  [if STYLE_HAS_TRANSITION] transition block (12 bytes):
    +0  u32  mask     anim-bit mask (ANIMATABLE order; 0xffffffff = all)
    +4  u16  durMs
    +6  u16  delayMs
    +8  u8   easing   (ENUMS.Easing ordinal)
    +9  u8[3] reserved (0)

  For each present variant in order (base, focus, active):
    +0  u8   propCount
    then propCount × 6-byte prop records:
      +0  u8   propId    (PROP value)
      +1  u8   reserved  (0)
      +2  u32  value     (f32 bits | ABGR color | int, per PROP_VALUE_KIND)
export const STYLE_MAGIC        = 0x54534344; // 'DCST' LE
export const STYLE_VERSION      = 1;
export const STYLE_HEADER_SIZE  = 8;
export const STYLE_TRANSITION_SIZE = 12;
export const STYLE_PROP_RECORD_SIZE = 6;
STYLE_ID_NONE = -1 passed to setStyle clears a node back to default style.

Fixed timestep

The core animation engine always ticks at a fixed dt = 1/60 s, regardless of host. Frame content is a pure function of frame index, which is what makes byte-exact golden screenshots possible across PSP hardware, PPSSPP, WASM, and headless Bun.
export const FIXED_DT = 1 / 60; // seconds

Build docs developers (and LLMs) love