# Frame format

The complete local voxel, layer, animation and binary contract for blueprint authors.

## File envelope

```json
{
  "format": "eggox-blueprint",
  "version": 1,
  "display_name": "Small block",
  "item_type": "solid",
  "size": { "x": 4, "y": 4, "z": 4 },
  "palette": ["#000000", "#d98c45"],
  "rotations": 4,
  "frames": [{ "duration_ms": 100, "voxels": [[1, 1, 0, 1], [1, 1, 1, 1]] }]
}
```

Pulled files also carry `id`, `revision`, and `server`. Preserve those when generating content. `display_name` is nonblank, at most 80 characters. `item_type` is one of `solid`, `seat`, `bed`, `water`, `wearable_head`, `wearable_body`, `wearable_legs`, `wearable_hand`, `character`.

Each side is an integer from 1 to 128 voxels. One tile is 32 voxels, so the canvas fits at most four tiles per axis. +x projects down-right, +y down-left, +z up. Unrotated, the camera sees the top (+z), the +y side on the left (the brighter side) and the +x side on the right; the -x and -y sides face away. Put a thing's face on +y or +x. Placing it at 90, 180 or 270 degrees turns it. Origin is the minimum corner. The x coordinate varies fastest: `offset = x + y*size.x + z*size.x*size.y`. The pixel bake follows the brick lattice described by the editor; author voxel geometry, not hand-drawn sprites.

A palette has 1–256 `#RRGGBB` strings. Index 0 always means empty, regardless of its RGB value. Every other voxel index must exist in the palette. `rotations` is 1 or 4; the baker derives the views from the same grid, not separate per-rotation frames.

## Complete frames and layers

An item has 1–500 full frames. Every frame holds exactly 100 ms, with no per-item speed. `duration_ms` may be omitted in local files and defaults to 100. The world cycle is 16 ticks, 1600 ms. Repeated sequence entries hold a pose; shorter movement sequences repeat and restart at the world cycle boundary.

Each frame supplies exactly one of:

- `voxels`: sparse `[x, y, z, paletteIndex]` rows. Coordinates are integer, zero-based and strictly inside `size`. Each coordinate occurs at most once. Unlisted cells are empty. Index 0 explicitly clears a cell but can simply be omitted.
- `voxels_b64`: canonical base64 of exactly `size.x*size.y*size.z` palette-index bytes, in x-fast order. This keeps dense models compact in local files. The CLI uses it on pull for grids with more than 4096 filled cells.
- `layers`: 1–12 objects with `name` (up to 24 characters), `visible` (boolean), and exactly one of `voxels` (sparse) or `voxels_b64` (dense). Optional `activeLayer` selects a zero-based layer, default 0. Layers are ordered bottom to top. The last visible nonempty cell wins. Empty cells do not erase lower layers; hidden layers remain editable but do not render. The CLI computes the composite.

Frames are snapshots, **never deltas**. All share one size and palette. To generate another pose, clone a frame locally and change it; replacing a frame removes cells absent from the replacement. Example layered frame:

```json
{
  "layers": [
    { "name": "body", "visible": true, "voxels": [[1, 1, 0, 1]] },
    { "name": "detail", "visible": true, "voxels": [[1, 1, 1, 1]] }
  ],
  "activeLayer": 1
}
```

`eggox blueprint frame blueprint.json 0 frame.json` replaces frame 0 locally. An index equal to the current frame count appends. Update animation references, ground levels, zone tracks and wearable host alignment when adding or removing poses. Then `check` and `push` the complete document.

Uploads are bounded to 6,000,000 compressed source bytes and 64 MiB of decoded grids, counting composites and all layer grids. Each optional metadata map is at most 200,000 JSON bytes. The request itself may be exactly as large as those rules allow, and no larger. These budgets can be reached before the 500-frame limit on large canvases.

## Behavior and frame references

Optional `behavior` is an object with `clips`, `states`, `interactions`, `use`, `who`, `areas`, `frameGround`, `footprint`, `surfacePose`. The first three default to empty arrays and hold at most 12 entries each. Names have 1–24 characters. A clip:

```json
{ "name": "wave", "frames": [2, 3], "order": [0, 1, 0], "loop": false, "command": "wave", "repeat": 1 }
```

- Object clips: `frames` is a play sequence of 1–64 existing frame indices; repeats are allowed. No `order`.
- Character clips: `frames` owns unique indices, unshared across clips. `walk`/`idle` own at most 8 frames each; other clips at most 32. Optional `order` is a nonempty sequence of positions into that clip's own frames, at most 16 entries for walk/idle and 64 otherwise. Without `order`, owned frames play in turn. Supply walk and idle for normal locomotion.
- Optional `loop` defaults true. `whileMoving` defaults false and allows the clip to continue through movement. `repeat` is 1–20 when supplied. `region` is a 1–24 character authored part/layer name for partial animation; retain the corresponding named layers.
- Optional `command` matches `[a-z0-9_-]{1,24}` and is unique across clips. Walk/idle cannot have commands. Reserved words are `stand`, `stop`, `reveal`, `walk`, `idle`.
- `frameGround` lists per-frame ground cuts, each an integer from 0 through `min(64, size.z)`, no longer than the frame list. Missing values behave as 0. Voxels below the cut are below the standing surface.

States are described under Object states below.

Optional object `footprint` is `{ "sizeX": 64, "sizeY": 32, "cells": [16, -1] }`. Sizes equal the canvas x/y; cells are x-fast tile order, exactly `ceil(x/32)*ceil(y/32)` entries. -1 blocks, 0 passes, positive values are standing heights at most `size.z`. Characters do not have footprints. Optional `surfacePose` on an object is `{ "name": "sit", "facing": 0 }`, facing 0–3.

Optional `collision_height` is 0–512 voxel units or null; the footprint offers per-tile control. Omitted/null metadata means the document has none, so retain metadata from pulls unless deliberately changing it.

### Object states

An object is always in one state, the same for everyone and kept by the server (it survives restarts). The first state is where every placed copy starts. A state plays its own frames:

```json
{ "name": "opening", "frames": [3, 4], "then": { "kind": "goto", "state": "open" } }
```

- `frames`: 1–64 existing frame indices, played on the 100 ms tick.
- `then`, what follows when they have played: `{ "kind": "loop" }` (the default), `{ "kind": "hold" }` (stay on the last frame) or `{ "kind": "goto", "state": "open" }`. A state that does not loop is busy until it has played, and uses are ignored until then.
- `on`, what using the object does while it shows this state: a map from where it was used to `{ "kind": "next" }` (the next state in the list, round) or `{ "kind": "goto", "state": "…" }`.
- `walk`, optional: this state's own footprint `cells` (same count as `footprint.cells`). Use it for a gate that opens or a stair that rises. Nothing can be placed on an object whose states walk differently. A chain of `goto`s that goes round forever must walk the same all the way round.

Top-level `use` says how the object is used, one way only:
- `"click"`: a double-click by anyone within one tile. Keys in `on` are `"all"`, plus `"layer:<name>"` or `"group:<name>"` for each entry in `areas`. With `"who": "owner"` only the object's owner can use it, from anywhere in the room, the way they turn it (a lamp only you switch).
- `"step"`: walking onto it. Keys in `on` are `"step_on"` (the first person arrives) and `"step_off"` (the last one leaves).

`areas` lists up to 12 click areas, `{ "kind": "layer", "name": "lid" }` or `{ "kind": "group", "name": "door" }`. Each is the voxels of that layer, or of that group's layers. A double-click checks the most specific match first (layer, then group, then `"all"`).

Nothing is left to chance: there is no random state or roll.

```json
{
  "use": "click",
  "areas": [{ "kind": "layer", "name": "lid" }],
  "states": [
    { "name": "shut", "frames": [0], "on": { "layer:lid": { "kind": "goto", "state": "opening" } } },
    { "name": "opening", "frames": [1, 2], "then": { "kind": "goto", "state": "open" } },
    { "name": "open", "frames": [3], "then": { "kind": "hold" }, "on": { "all": { "kind": "goto", "state": "shut" } } }
  ]
}
```

Older objects use `{ "name": "closed", "clip": "closed", "onEnd": "open" }` with a flat `interactions` list (`{ "trigger": "click_in_range", "action": { "kind": "goto", "state": "open" } }` or `{ "kind": "cycle" }`; triggers `walk_on`, `walk_off`, `click_in_range`). They still play the same way.

## Characters, wearables and rigs

`zones` retains the editor's `{ "zones": [...] }` map. A zone contains `id`, `name`, `layer`, `size: {x,y,z}` and `track: [{ "pos": {x,y,z} }, ...]` for each frame. The `headshot` box marks the character's chat portrait crop on frame 0. Keep it inside the character canvas. Preserve legacy attachment zones when editing older items.

New wearables are overlays of the full host character grid, frame for frame. Match the host's dimensions and poses; put only garment voxels in the wearable. The host drives animation, so a wearable does not define its own clips. Pull the owned host prototype to generate matching frames, or use the editor's wearable host picker for the initial setup.

`skeleton` and `voxel_binding_b64` preserve the editor's optional rig. A skeleton is `{ "bones": [...], "animations": [...] }`, with animations optional. At most 32 bones are supported by the editor. Each bone has `id`, `name`, `parent_id` (another bone id or null), `position: [x,y,z]` in local voxel units, `rest_rotation: [rx,ry,rz]` in Euler XYZ radians, `length` in voxels, and `color` as integer `0xRRGGBB`. Bone ids are unique and parent references must be acyclic. An animation has `id`, `name`, `duration_ms`, and `tracks: [{ "bone_id": "arm", "keyframes": [{ "t": 0, "rotation": [0,0,0] }] }]`. Bone animation time is local to the authoring rig; saved pose frames still use the 100 ms world tick. Binding is canonical base64 of exactly `size.x*size.y*size.z` bytes, one bone index plus one per cell (0 = unbound), in x-fast order. These are authoring aids; precomputed complete poses do not require a skeleton. Preserve the skeleton map and binding together when round-tripping an existing rig. The runtime uses the saved poses.

## Binary source v3

The CLI compiles local sparse JSON to the canonical editor source. All integers below are little-endian:

| Field | Bytes |
| --- | --- |
| schema = 3 | u16 |
| size.x, size.y, size.z | three u16 |
| palette length N | u16 |
| RGB palette | 3*N bytes |
| rotations (1 or 4) | u8 |
| frame count | u16 |
| each frame: duration_ms, RLE byte length, RLE | u32, u32, then bytes |

RLE is repeated `(runLength:u8, paletteIndex:u8)` pairs. Runs are 1–255; adjacent equal colors may be split. Each frame expands to exactly `x*y*z` bytes; every index is in the palette. No zero-length runs, odd lengths, overflow or underflow. Static legacy frames may carry duration 0; a local pull normalizes to 100. Runtime surface schema `0x8003` and database split-storage sources cannot be authored or uploaded.

Optional layers trailer: ASCII `EGLY`, version `u8=1`, then for each frame `layerCount:u8`, `activeLayer:u8`. Each layer is `visible:u8` (0/1), UTF-8 name byte length `u8`, name bytes, RLE byte length `u32`, then a complete RLE grid. Composite frames precede the trailer and must match visible layers. Hidden layers survive a pull/push. No trailing data after the last layer.

The single-file CLI exports `BLUEPRINT_SCHEMA`, `BLUEPRINT_RULES`, `encodeBlueprint(document)` and `decodeBlueprint(source)` for Node scripts. Importing it does not run a command. `encodeBlueprint` returns the HTTP source/metadata payload; `decodeBlueprint` reads that payload back to local editable JSON. This code ships with the CLI, so generation and validation can run entirely offline.

