# The Blank recipe, version 1

A Blank recipe is a small JSON file that describes a 3D world. It lists the parts of the world,
their shapes and materials, and how they move. People and AI agents write recipes. The Blank
engine reads a recipe, builds the 3D model, renders it and can export it as a standard 3D file.

This page is the open specification. Anyone may read, write and generate recipes, and build tools
that use them. See the [license](LICENSE.md).

## A whole recipe

```json
{
  "version": 1,
  "revision": 0,
  "name": "A small lantern",
  "budget": {"triangles": 2000, "nodes": 16, "lights": 1, "particles": 24},
  "materials": {"stone": {"color": "#AFC3C6", "roughness": 0.85},
                "magic": {"color": "#38E0C3", "emission": 0.9}},
  "rhythms": {"glow": {"period": 5, "direction": [0, 0, 1], "wavelength": 30}},
  "parts": [
    {"id": "base", "material": "stone", "position": [0, 0.2, 0],
     "geometry": {"kind": "cone", "radius": 0.4, "top_radius": 0.3, "height": 0.4, "segments": 7}},
    {"id": "heart", "parent": "base", "material": "magic", "position": [0, 0.8, 0],
     "geometry": {"kind": "ico_sphere", "radius": 0.15, "subdivisions": 1},
     "animation": {"rhythm": "glow", "lift": 0.06, "pulse": 0.05},
     "light": {"color": [0.25, 1, 0.8], "intensity": 0.9, "range": 3.5}}
  ]
}
```

## Top level

| Field | Required | Meaning |
|---|---|---|
| `version` | yes | Always `1`. |
| `revision` | yes | Non-negative integer; every accepted edit increments it by one. |
| `name` | no | Display name. |
| `budget` | yes | Upper limits the world must respect (below). |
| `materials` | yes | 1–32 named materials. |
| `parts` | yes | The objects of the world, at least one. |
| `rhythms` | no | Up to 16 shared drivers that parts follow. |
| `lighting` | no | World light: `ambient`, `sun`, `bloom`, `bloom_threshold`, `exposure`, `fog`. |
| `environment` | no | `background` as linear `[r, g, b]` and `grid` as a boolean. |

Budgets: `triangles` 1–20,000, `nodes` 1–256, `lights` 0–8, `particles` 0–1,024. If a world goes
over its budget, Blank rejects it. It does not simplify it for you. A recipe is at most 512 KiB.

Identifiers (part, material and rhythm names) match `[a-z][a-z0-9_-]{0,63}`.
Distances are metres in a right-handed space with +Y up. Angles are radians.

## Materials

`color` is `"#RRGGBB"` in sRGB. Optional `roughness` and `metallic` are 0–1. Optional `emission`
is 0–8 and makes a surface glow in its own colour. All surfaces are opaque.

## Parts

Each part needs an `id`, a `geometry` and a `material`. The `id` never changes. Optional fields:
`name`, `parent` (another part's id), local `position`, `rotation` (XYZ radians), positive
`scale`, `solid`, `animation`, `light` and `effects`. A child moves with its parent.
Blank rejects a part that is its own ancestor, and any name that points to nothing.

### Shapes

| `kind` | Parameters |
|---|---|
| `box` | `size: [x, y, z]`, optional `bevel` |
| `cone` | `radius`, optional `top_radius`, `height`, `segments` 3–32 |
| `ico_sphere` | `radius`, `subdivisions` 1–3 |
| `torus` | `radius`, `tube`, `segments` 3–64, `sides` 3–12 |
| `loft` | `segments` 3–32 and 2–16 `rings`, each with `center`, `radius`, optional `phase` |
| `prism` | 3–32 XY `outline` points, `depth`, optional `bevel` |

Boxes, cones, spheres, tori and prisms are centred on the part's origin. Loft rings use the
centres you give them. All meshes are low-poly with flat shading.

### Light and effects

`light` adds a point light: `color` as linear `[r, g, b]`, `intensity` and `range` in metres.
`effects` adds up to eight particle effects, such as floating sparks:
`{"id", "count", "seed", "size", "intensity", "color", "emitter": {"kind": "sphere", "radius",
"thickness"}, "motion": {"orbit", "wander", "speed"}, "pulse": {"depth", "rate"}}`.

## Motion and rhythms

A **rhythm** is a named beat that many parts can follow, such as a breeze or a glow. When many
parts follow one rhythm, they move together. These shared rhythms are what make a world feel alive.

```json
"rhythms": {"breeze": {"period": 7, "direction": [1, 0, 0.35], "wavelength": 9}}
```

`period` is the length of one cycle in seconds (0.5–120). `direction` is the way the rhythm
travels. `wavelength` (0.5–1000 m) is how far apart two parts must be to move in step again.
Optional `phase` shifts the whole rhythm in time. A part follows a rhythm through its `animation`:

```json
"animation": {"rhythm": "breeze", "sway": 0.08, "lift": 0.02, "pulse": 0.05, "delay": 0.1}
```

- `sway` moves the part along the rhythm's direction and `lift` moves it up and down, in metres (±10).
- `pulse` makes it grow and shrink a little (0–0.45); `delay` makes it respond later (0–1 cycle).
- `glow` makes a glowing part breathe: it dims by that fraction (0–1) and returns, its light too.

Each part's timing depends on where it is in the world, so you can see the breeze move across the
scene. A part with no rhythm and no motion of its own stays still.

`bend` (radians, ±0.6) makes a part lean the way the rhythm travels while its base stays in place.
Use it for grass, stems and trees. It bends from the bottom of its shape unless you set `pivot`
`[x, y, z]`, in metres from the part's position, in its parent's frame. Children bend with their
parent, so a leaning trunk carries its crown. It needs a rhythm whose `direction` is not straight up.

```json
"animation": {"rhythm": "breeze", "bend": 0.2, "delay": 0.1}
```

Instead of a rhythm, a part can have its own motion: `angular_velocity` `[x, y, z]` radians per second,
`amplitude` `[x, y, z]` metres, `period` seconds, `phase` radians, `pulse`, `glow`, and `swing` `[x, y, z]`
peak lean in radians about `pivot`.

## Precise edits

To change a world, you do not rewrite the recipe. You send an edit transaction. It names the
revision it starts from and lists the changes:

```json
{"base_revision": 2, "edits": [
  {"part": "crown-top", "set": {"scale": [1.1, 0.65, 0.84]}},
  {"material": "gold", "set": {"emission": 0.8}},
  {"rhythm": "breeze", "set": {"period": 10}},
  {"add_part": {"id": "seed", "parent": "crown-top", "material": "gold",
    "geometry": {"kind": "ico_sphere", "radius": 0.05, "subdivisions": 1}}},
  {"remove_part": "old-branch"}
]}
```

- Each edit changes exactly one part, material or rhythm.
- Setting a rhythm name that does not exist yet adds it. `remove_rhythm` removes one.
- Nested values merge. Changing a geometry `kind` replaces the old shape.
- Removing a part keeps its children. Removing a rhythm that parts still follow fails.
- A transaction holds 1–64 edits. Either all of them apply or none do. Blank checks the whole
  result, adds one to `revision`, and rejects a transaction whose `base_revision` is out of date.

## Safety and limits

A recipe cannot contain scripts, load other files or include its own meshes. Every value has a
limit, and Blank checks the whole recipe before it builds anything. This makes a recipe safe to
accept from anyone, including an AI.
