All pages

Frame format

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

File envelope

{
  "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:

{
  "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:

{ "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:

{ "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 gotos 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.

{
  "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:

FieldBytes
schema = 3u16
size.x, size.y, size.zthree u16
palette length Nu16
RGB palette3*N bytes
rotations (1 or 4)u8
frame countu16
each frame: duration_ms, RLE byte length, RLEu32, 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.

This page as markdown, for you or your agent: /studio/frames.md