# Feature examples: 2D, input, audio, UI, physics

Sindri AI knowledge pack, file `12-examples-2d.md`. Complete, working example projects, each showing one capability. Every file is shown whole, with its path.

Start with `00-start-here.md` if you have not read it. Every fact here is copied from the Sindri repository; nothing in this file is a guess.

## Contents of this file

- **Input Actions** (`examples/input`)
- **Sound Mixer** (`examples/audio`)
- **Camera Lab** (`examples/camera`)
- **Tween Lab** (`examples/tween`)
- **Sequence Stage** (`examples/sequence`)
- **Spatial Query Lab** (`examples/spatial`)
- **Weave Control Room** (`examples/ui`)
- **Physics Playground** (`examples/physics`)
- **Sprite Masks** (`examples/sprite-masks`)
- **Sprite Shaders** (`examples/sprite-shaders`)

---

## Example: Input Actions (`examples/input`)

Every text file of the project follows, whole, under its path relative to the project folder. JSON files are shown with short arrays on one line; whitespace does not matter to Sindri.

### Input Actions

A feature example for input actions. Open `sindri.toml` in the editor or run
the exported project in a WebGPU browser. Pages publishes it at
`https://sindri.vardir.no/examples/input/`.

The ship reads what the player means rather than what they pressed. The
scene's `sindri.input.actions` component declares two actions: `move`, a
direction bound to WASD, the arrow keys and a pad's d-pad, and `boost`, a
button bound to Space and a pad's South button. `Action.vector("move")` and
`Action.held("boost")` answer to whichever is being used.

**REBIND BOOST** (or K) waits for the next key or pad button and makes it
boost's first binding with `Action.rebind`; **ADD BOOST KEY** adds one beside
the others. The labels list each action's bindings as `Action.bindings` reads
them, so a rebinding shows up at once. Escape cancels.

The layout is `assets/ui/input.weave`: the bindings across the top and the
controls along the bottom. The script reads where those were laid out with
`Ui.position` and `Ui.size` and sizes the ship's arena to the room between
them, so a phone held upright gets a tall arena and a desktop a wide one.

All behaviour is in `assets/input.decay`. `game/tests/the_mixer_and_input_demos_work.rs`
moves the ship, boosts it, rebinds boost to J and boosts with J; the browser
smoke does the same through a page.

See the scripting contract (in `03-scripting-guide.md`).

### `assets/input.decay`

```rust
// Everything here reads what the player means -- `move` and `boost` -- rather
// than which key they pressed. The scene's `sindri.input.actions` says what
// each is bound to, and the buttons below rebind `boost` while it runs.
script InputDemo {
    // Which binding of `boost` the next key goes to, or -1 when not waiting.
    var waiting: number = -1.0;

    fn update(dt: number) {
        if this.waiting < 0.0 {
            if Ui.is_pressed(World.find("rebind")) || Input.just_pressed("K") {
                listen(0.0);
            } else if Ui.is_pressed(World.find("add")) {
                listen(Action.bindings("boost").length);
            }
        } else {
            capture();
        }
        Ui.set_text(World.find("move-label"), "MOVE   " + listed("move"));
        Ui.set_text(World.find("boost-label"), "BOOST   " + listed("boost"));
        fit_arena();
    }

    // The arena fills the room the layout left between the bindings and the
    // controls, read in overlay units (half the screen's height is one) and
    // turned into the world's, so a phone held upright gets a tall arena and
    // a desktop a wide one.
    fn fit_arena() {
        let bindings = World.find("bindings");
        let status = World.find("status");
        let upper = Ui.position(bindings).y - Ui.size(bindings).y * 0.5;
        let lower = Ui.position(status).y + Ui.size(status).y * 0.5;
        var high: number = 12.0;
        if Viewport.aspect < 1.0 { high = 12.0 / Viewport.aspect; }
        let unit = high * 0.5;
        let gap = 0.04;
        let wide = Ui.size(bindings).x;
        let arena = World.find("arena");
        arena.transform.scale = Vec3(wide * unit, max(0.5, (upper - lower - gap * 2.0) * unit), 1.0);
        arena.transform.position = Vec3(0.0, (upper + lower) * 0.5 * unit, -0.5);
    }

    fn listen(index: number) {
        this.waiting = index;
        Ui.set_text(World.find("status"), "Press a key or a pad button for BOOST. Escape cancels.");
        print("Input waiting");
    }

    // The next key, mouse button or pad button becomes the binding. The
    // click that asked is not it, so the mouse is skipped.
    fn capture() {
        let pressed = Action.last_pressed();
        if pressed == "" || pressed.starts_with("mouse.") { return; }
        if pressed == "key.Escape" {
            Ui.set_text(World.find("status"), "Rebinding cancelled.");
        } else {
            Action.rebind("boost", this.waiting, pressed);
            Ui.set_text(World.find("status"), "BOOST is now on " + pressed + ".");
            print("Input rebound " + pressed);
        }
        this.waiting = -1.0;
    }

    // Each binding as its text, short enough to read: `key.` is understood,
    // and a pad is said as a pad.
    fn listed(action: String) -> String {
        var said = "";
        for binding in Action.bindings(action) {
            if said != "" { said = said + "   "; }
            said = said + binding.replace("key.", "").replace("gamepad.", "pad ");
        }
        return said;
    }
}

script Ship {
    @export let speed: number = 3.0;

    fn update(dt: number) {
        let way = Action.vector("move");
        var pace = this.speed;
        this.shape.fill = Color(0.18, 0.82, 1.0, 1.0);
        if Action.held("boost") {
            pace = this.speed * 2.5;
            this.shape.fill = Color(1.0, 0.52, 0.22, 1.0);
        }
        if Action.pressed("boost") { print("Input boost"); }
        // Kept inside the arena, whatever shape the screen gave it.
        let arena = World.find("arena");
        let reach_x = arena.transform.scale.x * 0.5 - 0.3;
        let reach_y = arena.transform.scale.y * 0.5 - 0.3;
        let centre_y = arena.transform.position.y;
        let p = this.transform.position;
        this.transform.position = Vec3(
            max(-reach_x, min(reach_x, p.x + way.x * pace * dt)),
            max(centre_y - reach_y, min(centre_y + reach_y, p.y + way.y * pace * dt)),
            p.z
        );
    }
}
```

### `assets/input.scene`

```json
{
  "format_version": 10,
  "metadata": {
    "name": "Input Actions"
  },
  "entities": [
    {
      "id": "camera",
      "name": "camera",
      "transform_3d": {
        "position": [0, 0, 10],
        "scale": [1, 1, 1]
      },
      "components": {
        "sindri.camera": {
          "projection": "orthographic",
          "vertical_size": 12,
          "near": 0.1,
          "far": 100,
          "fit": "shorter"
        }
      }
    },
    {
      "id": "environment",
      "name": "environment",
      "components": {
        "sindri.environment": {
          "background": [0.004, 0.009, 0.018, 1.0]
        }
      }
    },
    {
      "id": "controls",
      "name": "controls",
      "components": {
        "sindri.input.actions": {
          "actions": [
            {
              "name": "move",
              "kind": "vector",
              "bindings": [
                {
                  "vector": {
                    "up": "key.W",
                    "down": "key.S",
                    "left": "key.A",
                    "right": "key.D"
                  }
                },
                {
                  "vector": {
                    "up": "key.ArrowUp",
                    "down": "key.ArrowDown",
                    "left": "key.ArrowLeft",
                    "right": "key.ArrowRight"
                  }
                },
                {
                  "vector": {
                    "up": "gamepad.dpad_up",
                    "down": "gamepad.dpad_down",
                    "left": "gamepad.dpad_left",
                    "right": "gamepad.dpad_right"
                  }
                }
              ]
            },
            {
              "name": "boost",
              "kind": "button",
              "bindings": ["key.Space", "gamepad.south"]
            }
          ]
        },
        "sindri.script": {
          "source": "input.decay",
          "script": "InputDemo",
          "properties": {}
        }
      }
    },
    {
      "id": "arena",
      "name": "arena",
      "transform_3d": {
        "position": [0, 0, -0.5],
        "scale": [10, 6, 1]
      },
      "components": {
        "sindri.shape": {
          "kind": "rect",
          "fill": [0.03, 0.06, 0.1, 1],
          "stroke": [0.15, 0.35, 0.5, 1],
          "stroke_width": 0.01,
          "layer": 0
        }
      }
    },
    {
      "id": "ship",
      "name": "ship",
      "transform_3d": {
        "position": [0, 0, 0],
        "scale": [0.6, 0.6, 1]
      },
      "components": {
        "sindri.shape": {
          "kind": "ellipse",
          "fill": [0.18, 0.82, 1, 1],
          "stroke": [0.9, 0.96, 1, 1],
          "stroke_width": 0.05,
          "layer": 4
        },
        "sindri.script": {
          "source": "input.decay",
          "script": "Ship",
          "properties": {}
        }
      }
    },
    {
      "id": "room",
      "name": "room",
      "transform_3d": {
        "scale": [3.3, 1.9, 1.0]
      },
      "components": {
        "weave.style": {
          "classes": ["room"]
        },
        "sindri.ui.layout": {
          "direction": "column",
          "spacing": 0.02,
          "justify": "space_between",
          "align": "center"
        }
      }
    },
    {
      "id": "top",
      "name": "top",
      "parent": "room",
      "transform_3d": {
        "scale": [3.0, 0.4, 1.0]
      },
      "components": {
        "weave.style": {
          "classes": ["top"]
        },
        "sindri.ui.layout": {
          "direction": "column",
          "spacing": 0.01,
          "justify": "start",
          "align": "center"
        }
      }
    },
    {
      "id": "title",
      "name": "title",
      "parent": "top",
      "transform_3d": {
        "scale": [1.0, 0.08, 1.0]
      },
      "components": {
        "weave.style": {
          "classes": ["heading"]
        },
        "sindri.ui.text": {
          "text": "INPUT ACTIONS",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.035,
          "bounds": [1.0, 0.08],
          "anchor": "center",
          "layer": 20,
          "line_align": "center"
        }
      }
    },
    {
      "id": "bindings",
      "name": "bindings",
      "parent": "top",
      "transform_3d": {
        "scale": [1.6, 0.2, 1.0]
      },
      "components": {
        "weave.style": {
          "classes": ["bindings"]
        },
        "sindri.ui.shape": {
          "kind": "rect",
          "fill": [0.05, 0.09, 0.14, 1.0],
          "anchor": "center",
          "layer": 10
        },
        "sindri.ui.layout": {
          "direction": "column",
          "spacing": 0.01,
          "justify": "start",
          "align": "center"
        }
      }
    },
    {
      "id": "move-label",
      "name": "move-label",
      "parent": "bindings",
      "transform_3d": {
        "scale": [1.0, 0.08, 1.0]
      },
      "components": {
        "weave.style": {
          "classes": ["binding"]
        },
        "sindri.ui.text": {
          "text": "move: ",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.035,
          "bounds": [1.0, 0.08],
          "anchor": "center",
          "layer": 20,
          "line_align": "left"
        }
      }
    },
    {
      "id": "boost-label",
      "name": "boost-label",
      "parent": "bindings",
      "transform_3d": {
        "scale": [1.0, 0.08, 1.0]
      },
      "components": {
        "weave.style": {
          "classes": ["binding"]
        },
        "sindri.ui.text": {
          "text": "boost: ",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.035,
          "bounds": [1.0, 0.08],
          "anchor": "center",
          "layer": 20,
          "line_align": "left"
        }
      }
    },
    {
      "id": "bottom",
      "name": "bottom",
      "parent": "room",
      "transform_3d": {
        "scale": [3.0, 0.3, 1.0]
      },
      "components": {
        "weave.style": {
          "classes": ["bottom"]
        },
        "sindri.ui.layout": {
          "direction": "column",
          "spacing": 0.01,
          "justify": "end",
          "align": "center"
        }
      }
    },
    {
      "id": "status",
      "name": "status",
      "parent": "bottom",
      "transform_3d": {
        "scale": [1.0, 0.08, 1.0]
      },
      "components": {
        "weave.style": {
          "classes": ["status"]
        },
        "sindri.ui.text": {
          "text": "Move with WASD, the arrows or a pad. Boost to go faster.",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.035,
          "bounds": [1.0, 0.08],
          "anchor": "center",
          "layer": 20,
          "line_align": "center"
        }
      }
    },
    {
      "id": "buttons",
      "name": "buttons",
      "parent": "bottom",
      "transform_3d": {
        "scale": [1.3, 0.14, 1.0]
      },
      "components": {
        "weave.style": {
          "classes": ["buttons"]
        },
        "sindri.ui.layout": {
          "direction": "row",
          "spacing": 0.04,
          "justify": "center",
          "align": "center"
        }
      }
    },
    {
      "id": "rebind",
      "name": "rebind",
      "parent": "buttons",
      "transform_3d": {
        "scale": [0.5, 0.12, 1.0]
      },
      "components": {
        "weave.style": {
          "classes": ["action"]
        },
        "sindri.ui.shape": {
          "kind": "rect",
          "fill": [0.05, 0.09, 0.14, 1.0],
          "anchor": "center",
          "layer": 11
        },
        "sindri.ui.button": {
          "label": "REBIND BOOST",
          "disabled": false,
          "autofocus": false
        },
        "sindri.ui.layout": {
          "direction": "row",
          "spacing": 0.0,
          "justify": "center",
          "align": "center"
        }
      }
    },
    {
      "id": "rebind-label",
      "name": "rebind-label",
      "parent": "rebind",
      "transform_3d": {
        "scale": [1.0, 0.08, 1.0]
      },
      "components": {
        "weave.style": {
          "classes": ["action-label"]
        },
        "sindri.ui.text": {
          "text": "REBIND BOOST",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.035,
          "bounds": [1.0, 0.08],
          "anchor": "center",
          "layer": 20,
          "line_align": "center"
        }
      }
    },
    {
      "id": "add",
      "name": "add",
      "parent": "buttons",
      "transform_3d": {
        "scale": [0.5, 0.12, 1.0]
      },
      "components": {
        "weave.style": {
          "classes": ["action"]
        },
        "sindri.ui.shape": {
          "kind": "rect",
          "fill": [0.05, 0.09, 0.14, 1.0],
          "anchor": "center",
          "layer": 11
        },
        "sindri.ui.button": {
          "label": "ADD BOOST KEY",
          "disabled": false,
          "autofocus": false
        },
        "sindri.ui.layout": {
          "direction": "row",
          "spacing": 0.0,
          "justify": "center",
          "align": "center"
        }
      }
    },
    {
      "id": "add-label",
      "name": "add-label",
      "parent": "add",
      "transform_3d": {
        "scale": [1.0, 0.08, 1.0]
      },
      "components": {
        "weave.style": {
          "classes": ["action-label"]
        },
        "sindri.ui.text": {
          "text": "ADD BOOST KEY",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.035,
          "bounds": [1.0, 0.08],
          "anchor": "center",
          "layer": 20,
          "line_align": "center"
        }
      }
    }
  ]
}
```

### `assets/ui/input.weave`

```css
/* Input Actions: the bindings across the top, the controls along the bottom,
   and the ship's arena between them in the world. Stacked on a phone. */
:root {
    --ink: #e6f0f7;
    --muted: #8aa1b6;
    --accent: #5fe3cf;
    --panel: #0b1522cc;
    --line: #22384c;
    --control: #0f1c2b;
}

sindri.ui.text { color: var(--ink); text-wrap: word; }

.room { width: 100vw; height: 100vh; padding: 18px; direction: column; justify-content: space-between; align-items: center; }
.top { width: 100%; height: 150px; gap: 10px; direction: column; justify-content: start; align-items: center; }
.heading { width: 100%; height: 40px; font-size: 30px; letter-spacing: 4px; text-align: center; }
.bindings {
    width: 92vw; max-width: 720px; height: 92px; padding: 10px 16px; gap: 6px;
    direction: column; justify-content: center; align-items: start;
    background: var(--panel); border-color: var(--line); border-width: 1px; border-radius: 12px;
}
.binding { width: 100%; height: 34px; font-size: 13px; line-height: 17px; color: var(--muted); text-align: start; }

.bottom { width: 100%; height: 120px; gap: 10px; direction: column; justify-content: end; align-items: center; }
.status { width: 92vw; max-width: 720px; height: 40px; font-size: 15px; line-height: 20px; color: var(--accent); text-align: center; }
.buttons { width: 92vw; max-width: 520px; height: 56px; direction: row; gap: 16px; justify-content: center; align-items: center; }
.action {
    width: 48%; height: 48px; direction: row; justify-content: center; align-items: center;
    background: var(--control); border-color: var(--line); border-width: 1px; border-radius: 10px;
}
.action:hover { background: #142638; border-color: #3b5a73; }
.action:active { background: #0f3b3a; border-color: var(--accent); }
.action:focus { border-color: var(--accent); border-width: 2px; }
.action-label { width: 100%; height: 20px; font-size: 15px; letter-spacing: 2px; text-align: center; }

@media (orientation: portrait) {
    .room { padding: 14px; }
    .top { height: 200px; }
    .heading { height: 34px; font-size: 24px; letter-spacing: 3px; }
    .bindings { height: 124px; padding: 10px 12px; }
    .binding { height: 50px; font-size: 12px; line-height: 16px; }
    .bottom { height: 130px; }
    .action-label { font-size: 13px; letter-spacing: 1px; }
}

@media (orientation: landscape) and (max-height: 460px) {
    .room { padding: 8px; }
    .top { height: 110px; gap: 6px; }
    .heading { height: 28px; font-size: 20px; }
    .bindings { height: 72px; padding: 6px 12px; gap: 2px; }
    .binding { height: 32px; font-size: 11px; line-height: 15px; }
    .bottom { height: 88px; gap: 6px; }
    .status { height: 22px; font-size: 13px; }
    .buttons { height: 44px; }
    .action { height: 40px; }
}
```

### `sindri.toml`

```toml
format_version = 1
[project]
name = "Input Actions"
main_scene = "assets/input.scene"
[assets]
include = ["fonts/Fonts-OFL.txt", "ui/input.weave"]
```

### Binary files in this project

`assets/fonts/ChakraPetch-Regular.ttf`, `assets/fonts/Fonts-OFL.txt`


---

## Example: Sound Mixer (`examples/audio`)

Every text file of the project follows, whole, under its path relative to the project folder. JSON files are shown with short arrays on one line; whitespace does not matter to Sindri.

### Sound Mixer

A feature example for audio buses. Open `sindri.toml` in the editor or run the
exported project in a WebGPU browser. Pages publishes it at
`https://sindri.vardir.no/examples/audio/`.

Every sound plays through a bus, and every bus through `master`. The theme is
an authored looping `sindri.audio.source`, so it plays on `music`; the buttons
play effects with `Audio.play` and `Audio.play_on("effects", …)`. Each slider
calls `Audio.set_volume` for its bus, which changes what is already playing as
well as what plays next, and its fill and percentage read the bus back with
`Audio.volume`. Drag a slider, tap along it, or use the arrows: up and down
choose a slider, left and right move it a step. Browsers start audio on the
first click or key.

The layout is `assets/ui/mixer.weave`: one centred column and a panel of bus
rows, with rules for a phone held upright and a phone on its side.

All behaviour is in `assets/mixer.decay`. `game/tests/the_mixer_and_input_demos_work.rs`
moves the buses by keyboard and by touch and plays an effect through its bus;
the platform's `AudioMixer` tests prove the volumes reach live voices.

See the scripting contract (in `03-scripting-guide.md`).

### `assets/mixer.decay`

```rust
// A volume per bus. The looping theme is authored on the music bus (a looping
// source with no bus named is music); the buttons play effects. Each slider
// sets its bus at once, for what is playing and for what plays next.
script Mixer {
    fn start() {
        show("master");
        show("music");
        show("effects");
    }

    fn update(dt: number) {
        follow("master");
        follow("music");
        follow("effects");
        if Ui.is_pressed(World.find("shot")) {
            Audio.play("audio/shot.wav", 0.8);
            say("A shot, on the effects bus at " + percent(Audio.volume("effects")) + ".");
        }
        if Ui.is_pressed(World.find("boom")) {
            Audio.play_on("effects", "audio/boss.wav", 0.7);
            say("A boom, on the effects bus at " + percent(Audio.volume("effects")) + ".");
        }
    }

    // A slider the person moved sets its bus, and the click that answers is
    // played through the effects bus, so turning effects down is heard.
    fn follow(bus: String) {
        let slider = World.find(bus);
        if !Ui.slider_changed(slider) { return; }
        Audio.set_volume(bus, Ui.slider_value(slider));
        Audio.play("audio/click.wav", 0.4);
        show(bus);
        say(bus + " is at " + percent(Audio.volume(bus)) + ".");
    }

    // The fill and the readout follow the bus, as `Audio.volume` reads it.
    fn show(bus: String) {
        let volume = Audio.volume(bus);
        Ui.set_fill(World.find(bus + "-fill"), volume);
        Ui.set_text(World.find(bus + "-value"), percent(volume));
    }

    fn percent(volume: number) -> String {
        return (volume * 100.0).fixed(0) + "%";
    }

    fn say(text: String) {
        Ui.set_text(World.find("status"), text);
        print("Mixer " + text);
    }
}
```

### `assets/mixer.scene`

```json
{
  "format_version": 10,
  "metadata": {
    "name": "Sound Mixer"
  },
  "entities": [
    {
      "id": "camera",
      "name": "camera",
      "transform_3d": {
        "position": [0, 0, 10],
        "scale": [1, 1, 1]
      },
      "components": {
        "sindri.camera": {
          "projection": "orthographic",
          "vertical_size": 12,
          "near": 0.1,
          "far": 100,
          "fit": "shorter"
        }
      }
    },
    {
      "id": "environment",
      "name": "environment",
      "components": {
        "sindri.environment": {
          "background": [0.004, 0.009, 0.018, 1.0]
        }
      }
    },
    {
      "id": "theme",
      "name": "theme",
      "components": {
        "sindri.audio.source": {
          "clip": "audio/theme.wav",
          "autoplay": true,
          "looping": true,
          "volume": 0.6
        },
        "sindri.script": {
          "source": "mixer.decay",
          "script": "Mixer",
          "properties": {}
        }
      }
    },
    {
      "id": "room",
      "name": "room",
      "transform_3d": {
        "scale": [3.3, 1.9, 1.0]
      },
      "components": {
        "weave.style": {
          "classes": ["room"]
        },
        "sindri.ui.layout": {
          "direction": "column",
          "spacing": 0.04,
          "justify": "center",
          "align": "center"
        }
      }
    },
    {
      "id": "title",
      "name": "title",
      "parent": "room",
      "transform_3d": {
        "scale": [1.0, 0.08, 1.0]
      },
      "components": {
        "weave.style": {
          "classes": ["heading"]
        },
        "sindri.ui.text": {
          "text": "SOUND MIXER",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.035,
          "bounds": [1.0, 0.08],
          "anchor": "center",
          "layer": 20,
          "line_align": "center"
        }
      }
    },
    {
      "id": "subtitle",
      "name": "subtitle",
      "parent": "room",
      "transform_3d": {
        "scale": [1.0, 0.08, 1.0]
      },
      "components": {
        "weave.style": {
          "classes": ["subtitle"]
        },
        "sindri.ui.text": {
          "text": "Every sound plays through a bus; every bus through master.",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.035,
          "bounds": [1.0, 0.08],
          "anchor": "center",
          "layer": 20,
          "line_align": "center"
        }
      }
    },
    {
      "id": "panel",
      "name": "panel",
      "parent": "room",
      "transform_3d": {
        "scale": [1.4, 0.9, 1.0]
      },
      "components": {
        "weave.style": {
          "classes": ["panel"]
        },
        "sindri.ui.shape": {
          "kind": "rect",
          "fill": [0.05, 0.09, 0.14, 1.0],
          "anchor": "center",
          "layer": 10
        },
        "sindri.ui.layout": {
          "direction": "column",
          "spacing": 0.03,
          "justify": "start",
          "align": "center"
        }
      }
    },
    {
      "id": "master-row",
      "name": "master-row",
      "parent": "panel",
      "transform_3d": {
        "scale": [1.3, 0.14, 1.0]
      },
      "components": {
        "weave.style": {
          "classes": ["mix-row"]
        },
        "sindri.ui.layout": {
          "direction": "row",
          "spacing": 0.03,
          "justify": "space_between",
          "align": "center"
        }
      }
    },
    {
      "id": "master-label",
      "name": "master-label",
      "parent": "master-row",
      "transform_3d": {
        "scale": [1.0, 0.08, 1.0]
      },
      "components": {
        "weave.style": {
          "classes": ["row-label"]
        },
        "sindri.ui.text": {
          "text": "MASTER",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.035,
          "bounds": [1.0, 0.08],
          "anchor": "center",
          "layer": 20,
          "line_align": "left"
        }
      }
    },
    {
      "id": "master",
      "name": "master",
      "parent": "master-row",
      "transform_3d": {
        "scale": [0.8, 0.08, 1.0]
      },
      "components": {
        "weave.style": {
          "classes": ["track"]
        },
        "sindri.ui.shape": {
          "kind": "rect",
          "fill": [0.05, 0.09, 0.14, 1.0],
          "anchor": "center",
          "layer": 11
        },
        "sindri.ui.slider": {
          "label": "Master volume",
          "min": 0.0,
          "max": 1.0,
          "step": 0.05,
          "value": 1.0,
          "disabled": false,
          "autofocus": true
        },
        "sindri.ui.layout": {
          "direction": "row",
          "spacing": 0.0,
          "justify": "start",
          "align": "center"
        }
      }
    },
    {
      "id": "master-fill",
      "name": "master-fill",
      "parent": "master",
      "transform_3d": {
        "scale": [0.8, 0.08, 1.0]
      },
      "components": {
        "weave.style": {
          "classes": ["fill"]
        },
        "sindri.ui.image": {
          "texture": "textures/white.png",
          "anchor": "center",
          "layer": 12,
          "tint": [0.37, 0.89, 0.81, 0.85],
          "fill": {
            "amount": 1.0,
            "from": "left"
          }
        }
      }
    },
    {
      "id": "master-value",
      "name": "master-value",
      "parent": "master-row",
      "transform_3d": {
        "scale": [1.0, 0.08, 1.0]
      },
      "components": {
        "weave.style": {
          "classes": ["row-value"]
        },
        "sindri.ui.text": {
          "text": "100%",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.035,
          "bounds": [1.0, 0.08],
          "anchor": "center",
          "layer": 20,
          "line_align": "right"
        }
      }
    },
    {
      "id": "music-row",
      "name": "music-row",
      "parent": "panel",
      "transform_3d": {
        "scale": [1.3, 0.14, 1.0]
      },
      "components": {
        "weave.style": {
          "classes": ["mix-row"]
        },
        "sindri.ui.layout": {
          "direction": "row",
          "spacing": 0.03,
          "justify": "space_between",
          "align": "center"
        }
      }
    },
    {
      "id": "music-label",
      "name": "music-label",
      "parent": "music-row",
      "transform_3d": {
        "scale": [1.0, 0.08, 1.0]
      },
      "components": {
        "weave.style": {
          "classes": ["row-label"]
        },
        "sindri.ui.text": {
          "text": "MUSIC",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.035,
          "bounds": [1.0, 0.08],
          "anchor": "center",
          "layer": 20,
          "line_align": "left"
        }
      }
    },
    {
      "id": "music",
      "name": "music",
      "parent": "music-row",
      "transform_3d": {
        "scale": [0.8, 0.08, 1.0]
      },
      "components": {
        "weave.style": {
          "classes": ["track"]
        },
        "sindri.ui.shape": {
          "kind": "rect",
          "fill": [0.05, 0.09, 0.14, 1.0],
          "anchor": "center",
          "layer": 11
        },
        "sindri.ui.slider": {
          "label": "Music volume",
          "min": 0.0,
          "max": 1.0,
          "step": 0.05,
          "value": 1.0,
          "disabled": false,
          "autofocus": false
        },
        "sindri.ui.layout": {
          "direction": "row",
          "spacing": 0.0,
          "justify": "start",
          "align": "center"
        }
      }
    },
    {
      "id": "music-fill",
      "name": "music-fill",
      "parent": "music",
      "transform_3d": {
        "scale": [0.8, 0.08, 1.0]
      },
      "components": {
        "weave.style": {
          "classes": ["fill"]
        },
        "sindri.ui.image": {
          "texture": "textures/white.png",
          "anchor": "center",
          "layer": 12,
          "tint": [0.37, 0.89, 0.81, 0.85],
          "fill": {
            "amount": 1.0,
            "from": "left"
          }
        }
      }
    },
    {
      "id": "music-value",
      "name": "music-value",
      "parent": "music-row",
      "transform_3d": {
        "scale": [1.0, 0.08, 1.0]
      },
      "components": {
        "weave.style": {
          "classes": ["row-value"]
        },
        "sindri.ui.text": {
          "text": "100%",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.035,
          "bounds": [1.0, 0.08],
          "anchor": "center",
          "layer": 20,
          "line_align": "right"
        }
      }
    },
    {
      "id": "effects-row",
      "name": "effects-row",
      "parent": "panel",
      "transform_3d": {
        "scale": [1.3, 0.14, 1.0]
      },
      "components": {
        "weave.style": {
          "classes": ["mix-row"]
        },
        "sindri.ui.layout": {
          "direction": "row",
          "spacing": 0.03,
          "justify": "space_between",
          "align": "center"
        }
      }
    },
    {
      "id": "effects-label",
      "name": "effects-label",
      "parent": "effects-row",
      "transform_3d": {
        "scale": [1.0, 0.08, 1.0]
      },
      "components": {
        "weave.style": {
          "classes": ["row-label"]
        },
        "sindri.ui.text": {
          "text": "EFFECTS",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.035,
          "bounds": [1.0, 0.08],
          "anchor": "center",
          "layer": 20,
          "line_align": "left"
        }
      }
    },
    {
      "id": "effects",
      "name": "effects",
      "parent": "effects-row",
      "transform_3d": {
        "scale": [0.8, 0.08, 1.0]
      },
      "components": {
        "weave.style": {
          "classes": ["track"]
        },
        "sindri.ui.shape": {
          "kind": "rect",
          "fill": [0.05, 0.09, 0.14, 1.0],
          "anchor": "center",
          "layer": 11
        },
        "sindri.ui.slider": {
          "label": "Effects volume",
          "min": 0.0,
          "max": 1.0,
          "step": 0.05,
          "value": 1.0,
          "disabled": false,
          "autofocus": false
        },
        "sindri.ui.layout": {
          "direction": "row",
          "spacing": 0.0,
          "justify": "start",
          "align": "center"
        }
      }
    },
    {
      "id": "effects-fill",
      "name": "effects-fill",
      "parent": "effects",
      "transform_3d": {
        "scale": [0.8, 0.08, 1.0]
      },
      "components": {
        "weave.style": {
          "classes": ["fill"]
        },
        "sindri.ui.image": {
          "texture": "textures/white.png",
          "anchor": "center",
          "layer": 12,
          "tint": [0.37, 0.89, 0.81, 0.85],
          "fill": {
            "amount": 1.0,
            "from": "left"
          }
        }
      }
    },
    {
      "id": "effects-value",
      "name": "effects-value",
      "parent": "effects-row",
      "transform_3d": {
        "scale": [1.0, 0.08, 1.0]
      },
      "components": {
        "weave.style": {
          "classes": ["row-value"]
        },
        "sindri.ui.text": {
          "text": "100%",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.035,
          "bounds": [1.0, 0.08],
          "anchor": "center",
          "layer": 20,
          "line_align": "right"
        }
      }
    },
    {
      "id": "buttons",
      "name": "buttons",
      "parent": "panel",
      "transform_3d": {
        "scale": [1.3, 0.14, 1.0]
      },
      "components": {
        "weave.style": {
          "classes": ["buttons"]
        },
        "sindri.ui.layout": {
          "direction": "row",
          "spacing": 0.04,
          "justify": "center",
          "align": "center"
        }
      }
    },
    {
      "id": "shot",
      "name": "shot",
      "parent": "buttons",
      "transform_3d": {
        "scale": [0.5, 0.12, 1.0]
      },
      "components": {
        "weave.style": {
          "classes": ["action"]
        },
        "sindri.ui.shape": {
          "kind": "rect",
          "fill": [0.05, 0.09, 0.14, 1.0],
          "anchor": "center",
          "layer": 11
        },
        "sindri.ui.button": {
          "label": "PLAY SHOT",
          "disabled": false,
          "autofocus": false
        },
        "sindri.ui.layout": {
          "direction": "row",
          "spacing": 0.0,
          "justify": "center",
          "align": "center"
        }
      }
    },
    {
      "id": "shot-label",
      "name": "shot-label",
      "parent": "shot",
      "transform_3d": {
        "scale": [1.0, 0.08, 1.0]
      },
      "components": {
        "weave.style": {
          "classes": ["action-label"]
        },
        "sindri.ui.text": {
          "text": "PLAY SHOT",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.035,
          "bounds": [1.0, 0.08],
          "anchor": "center",
          "layer": 20,
          "line_align": "center"
        }
      }
    },
    {
      "id": "boom",
      "name": "boom",
      "parent": "buttons",
      "transform_3d": {
        "scale": [0.5, 0.12, 1.0]
      },
      "components": {
        "weave.style": {
          "classes": ["action"]
        },
        "sindri.ui.shape": {
          "kind": "rect",
          "fill": [0.05, 0.09, 0.14, 1.0],
          "anchor": "center",
          "layer": 11
        },
        "sindri.ui.button": {
          "label": "PLAY BOOM",
          "disabled": false,
          "autofocus": false
        },
        "sindri.ui.layout": {
          "direction": "row",
          "spacing": 0.0,
          "justify": "center",
          "align": "center"
        }
      }
    },
    {
      "id": "boom-label",
      "name": "boom-label",
      "parent": "boom",
      "transform_3d": {
        "scale": [1.0, 0.08, 1.0]
      },
      "components": {
        "weave.style": {
          "classes": ["action-label"]
        },
        "sindri.ui.text": {
          "text": "PLAY BOOM",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.035,
          "bounds": [1.0, 0.08],
          "anchor": "center",
          "layer": 20,
          "line_align": "center"
        }
      }
    },
    {
      "id": "status",
      "name": "status",
      "parent": "room",
      "transform_3d": {
        "scale": [1.0, 0.08, 1.0]
      },
      "components": {
        "weave.style": {
          "classes": ["status"]
        },
        "sindri.ui.text": {
          "text": "Drag a slider, or use the arrows: up and down choose, left and right set.",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.035,
          "bounds": [1.0, 0.08],
          "anchor": "center",
          "layer": 20,
          "line_align": "center"
        }
      }
    },
    {
      "id": "hint",
      "name": "hint",
      "parent": "room",
      "transform_3d": {
        "scale": [1.0, 0.08, 1.0]
      },
      "components": {
        "weave.style": {
          "classes": ["hint"]
        },
        "sindri.ui.text": {
          "text": "The theme loops on the music bus. The buttons play effects.",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.035,
          "bounds": [1.0, 0.08],
          "anchor": "center",
          "layer": 20,
          "line_align": "center"
        }
      }
    }
  ]
}
```

### `assets/ui/mixer.weave`

```css
/* Sound Mixer: one column, a panel of bus rows, sized for a desktop and
   stacked tighter on a phone. Decay writes values; this decides the look. */
:root {
    --ink: #e6f0f7;
    --muted: #8aa1b6;
    --accent: #5fe3cf;
    --panel: #0b1522;
    --well: #07101b;
    --line: #22384c;
    --control: #0f1c2b;
}

sindri.ui.text { color: var(--ink); text-wrap: word; }

.room { width: 100vw; height: 100vh; padding: 24px; gap: 18px; direction: column; justify-content: center; align-items: center; }
.heading { width: 100%; height: 40px; font-size: 30px; letter-spacing: 4px; text-align: center; }
.subtitle { width: 100%; max-width: 560px; height: 22px; font-size: 14px; color: var(--muted); text-align: center; }

.panel {
    width: 92vw; max-width: 560px; height: 272px; padding: 20px; gap: 14px;
    direction: column; justify-content: start; align-items: center;
    background: var(--panel); border-color: var(--line); border-width: 1px; border-radius: 14px;
}
.mix-row { width: 100%; height: 44px; direction: row; justify-content: space-between; align-items: center; }
.row-label { width: 22%; height: 22px; font-size: 15px; letter-spacing: 2px; color: var(--muted); text-align: start; }
.row-value { width: 14%; height: 22px; font-size: 15px; color: var(--accent); text-align: end; }
.track {
    width: 60%; height: 28px; padding: 3px;
    direction: row; justify-content: start; align-items: center;
    background: var(--well); border-color: var(--line); border-width: 1px; border-radius: 8px;
}
.track:hover { border-color: #3b5a73; }
.track:focus { border-color: var(--accent); border-width: 2px; }
.fill { width: 100%; height: 100%; }

.buttons { width: 100%; height: 56px; direction: row; gap: 16px; justify-content: center; align-items: center; }
.action {
    width: 46%; height: 48px; direction: row; justify-content: center; align-items: center;
    background: var(--control); border-color: var(--line); border-width: 1px; border-radius: 10px;
}
.action:hover { background: #142638; border-color: #3b5a73; }
.action:active { background: #0f3b3a; border-color: var(--accent); }
.action:focus { border-color: var(--accent); border-width: 2px; }
.action-label { width: 100%; height: 20px; font-size: 15px; letter-spacing: 2px; text-align: center; }

.status { width: 92vw; max-width: 560px; height: 40px; font-size: 15px; line-height: 20px; color: var(--accent); text-align: center; }
.hint { width: 92vw; max-width: 560px; height: 36px; font-size: 12px; line-height: 18px; color: var(--muted); text-align: center; }

@media (orientation: portrait) {
    .room { padding: 16px; gap: 14px; }
    .heading { height: 34px; font-size: 24px; letter-spacing: 3px; }
    .subtitle { height: 36px; font-size: 13px; line-height: 18px; }
    .panel { height: 256px; padding: 14px; gap: 12px; }
    .row-label { width: 26%; font-size: 13px; letter-spacing: 1px; }
    .track { width: 54%; height: 30px; }
    .row-value { width: 16%; font-size: 13px; }
    .action-label { font-size: 13px; letter-spacing: 1px; }
}

@media (orientation: landscape) and (max-height: 460px) {
    .room { padding: 10px; gap: 8px; }
    .heading { height: 30px; font-size: 22px; }
    .panel { height: 212px; padding: 12px; gap: 8px; }
    .mix-row { height: 36px; }
    .buttons { height: 46px; }
    .action { height: 40px; }
    .status { height: 22px; font-size: 13px; }
    .hint { height: 18px; font-size: 11px; }
}
```

### `sindri.toml`

```toml
format_version = 1
[project]
name = "Sound Mixer"
main_scene = "assets/mixer.scene"
[assets]
include = ["fonts/Fonts-OFL.txt", "ui/mixer.weave", "audio/shot.wav", "audio/boss.wav", "audio/click.wav"]
```

### Binary files in this project

`assets/audio/boss.wav`, `assets/audio/click.wav`, `assets/audio/shot.wav`, `assets/audio/theme.wav`, `assets/fonts/ChakraPetch-Regular.ttf`, `assets/fonts/Fonts-OFL.txt`, `assets/textures/white.png`


---

## Example: Camera Lab (`examples/camera`)

Every text file of the project follows, whole, under its path relative to the project folder. JSON files are shown with short arrays on one line; whitespace does not matter to Sindri.

### Camera Lab

An interactive feature example for the engine-owned camera behavior. Open this
directory as a Sindri project, or run `cargo run -p sindri-camera-demo --bin camera`.
The published Pages route is `/examples/camera/`.

Move with the arrow keys or hold the four on-screen direction buttons. Start
the automatic tour to take the target beyond the camera's confinement rectangle.
Manual movement stops the tour.

| Control | Key | What changes |
| --- | --- | --- |
| Follow | F | Follow the target, or hold the current view |
| Bounds | B | Confine the camera center to X [-8, 8], Y [-4, 4] |
| Shake | K | Enable or disable impact motion |
| Smooth | S | Switch smoothing between 5 and 1000; the speed cap stays 8 |
| Zone | Z | Switch the full-width/full-height dead zone between 2.5 × 1.5 and zero |
| Tour | T | Start or stop the rectangular movement route |
| Impact | Space | Add 1 trauma, with strength 0.3, frequency 30 and decay 1.6 |
| Reset | R | Restore default modes and return the target to the origin |

Reset lets the camera settle through its normal follow behavior; it does not
teleport or overwrite the engine's shake state. With a dead zone, settling need
not put the camera exactly at the target.

The cyan rectangle is parented to the rendered camera and shows the dead zone
in world units. Amber dashes and corner markers show **camera-center limits**,
not a guarantee that the viewport stays inside an arena. The outer gray frame
marks the movement area's edge. The coordinate readout samples the camera
before that fixed step's engine camera update.

Gameplay, mode selection and screen controls live in `assets/camera-demo.decay`.
Follow, dead-zone handling, smoothing, confinement and shake are performed by
`update_camera_behaviors`. Rust supplies hosting, UI hit-testing and rendering.
The exported project uses the existing shared browser runner. The packaged
font and Weave media rules keep the browser interface readable on phones.

The native tests run the authored scene and scripts, including touch input.
CI exports both project-subpath and custom-domain routes, exercises controls
in desktop and phone browsers, and captures their rendered output.

### `assets/camera-demo.decay`

```rust
// Decay selects modes; the engine performs camera motion and shake.
script CameraDemo {
    @export let speed: number = 5.0;
    var follow: bool = true;
    var bounds: bool = true;
    var shake: bool = true;
    var smooth: bool = true;
    var zone: bool = true;
    var tour: bool = false;
    var leg: number = 0.0;

    fn start() { configure(); }
    fn configure() {
        if this.follow {
            Camera.follow(this.entity);
            Camera.dead_zone(0.0, 0.0);
            if this.zone { Camera.dead_zone(2.5, 1.5); }
            Camera.smoothing(1000.0);
            if this.smooth { Camera.smoothing(5.0); }
            Camera.max_speed(8.0);
        } else { Camera.clear_follow(); }
        if this.bounds { Camera.bounds(-8.0, -4.0, 8.0, 4.0); }
        else { Camera.clear_bounds(); }
        Camera.shake(0.0, 30.0, 1.6);
        if this.shake { Camera.shake(0.3, 30.0, 1.6); }
        World.set_active(World.find("dead-zone"), this.follow && this.zone);
        World.set_active(World.find("camera-bounds"), this.bounds);
        Ui.set_text(World.find("follow-label"), "FOLLOW OFF");
        if this.follow { Ui.set_text(World.find("follow-label"), "FOLLOW ON"); }
        Ui.set_text(World.find("bounds-label"), "BOUNDS OFF");
        if this.bounds { Ui.set_text(World.find("bounds-label"), "BOUNDS ON"); }
        Ui.set_text(World.find("shake-label"), "SHAKE OFF");
        if this.shake { Ui.set_text(World.find("shake-label"), "SHAKE ON"); }
        Ui.set_text(World.find("smooth-label"), "SNAPPY");
        if this.smooth { Ui.set_text(World.find("smooth-label"), "SMOOTH"); }
        Ui.set_text(World.find("zone-label"), "ZONE OFF");
        if this.zone { Ui.set_text(World.find("zone-label"), "ZONE ON"); }
        var smoothing = 1000.0;
        if this.smooth { smoothing = 5.0; }
        var zone = "0 x 0";
        if this.zone { zone = "2.5 x 1.5"; }
        Ui.set_text(World.find("values"), "SMOOTH " + smoothing + " / ZONE " + zone + " / CAP 8");
        Ui.set_text(World.find("tour-label"), "START TOUR");
        if this.tour { Ui.set_text(World.find("tour-label"), "STOP TOUR"); }
    }
    fn update(dt: number) {
        controls();
        var x = Input.axis("ArrowLeft", "ArrowRight");
        var y = Input.axis("ArrowDown", "ArrowUp");
        if Ui.is_held(World.find("left")) { x -= 1.0; }
        if Ui.is_held(World.find("right")) { x += 1.0; }
        if Ui.is_held(World.find("up")) { y += 1.0; }
        if Ui.is_held(World.find("down")) { y -= 1.0; }
        if x != 0.0 || y != 0.0 {
            if this.tour { this.tour = false; configure(); }
        } else if this.tour {
            if this.leg == 0.0 {
                x = 1.0;
                if this.transform.position.x >= 12.0 { this.leg = 1.0; }
            } else if this.leg == 1.0 {
                y = 1.0;
                if this.transform.position.y >= 6.0 { this.leg = 2.0; }
            } else if this.leg == 2.0 {
                x = -1.0;
                if this.transform.position.x <= -12.0 { this.leg = 3.0; }
            } else {
                y = -1.0;
                if this.transform.position.y <= -6.0 { this.leg = 0.0; }
            }
        }
        this.transform.position.x = clamp(this.transform.position.x + x * speed * dt, -13.0, 13.0);
        this.transform.position.y = clamp(this.transform.position.y + y * speed * dt, -7.0, 7.0);
        let p = this.transform.world_position;
        let camera = World.find("Gameplay Camera");
        let c = camera.transform.world_position;
        Ui.set_text(World.find("position-label"), "TARGET " + p.x.fixed(1) + ", " + p.y.fixed(1) + "   CAMERA " + c.x.fixed(1) + ", " + c.y.fixed(1));
    }
    fn controls() {
        var changed = false;
        if Input.just_pressed("F") || Ui.is_pressed(World.find("follow")) { this.follow = !this.follow; changed = true; }
        if Input.just_pressed("B") || Ui.is_pressed(World.find("bounds")) { this.bounds = !this.bounds; changed = true; }
        if Input.just_pressed("K") || Ui.is_pressed(World.find("shake")) { this.shake = !this.shake; changed = true; }
        if Input.just_pressed("S") || Ui.is_pressed(World.find("smooth")) { this.smooth = !this.smooth; changed = true; }
        if Input.just_pressed("Z") || Ui.is_pressed(World.find("zone")) { this.zone = !this.zone; changed = true; }
        if Input.just_pressed("T") || Ui.is_pressed(World.find("tour")) { this.tour = !this.tour; this.leg = 0.0; changed = true; }
        if Input.just_pressed("R") || Ui.is_pressed(World.find("reset")) {
            this.follow = true; this.bounds = true; this.shake = true;
            this.smooth = true; this.zone = true; this.tour = false; this.leg = 0.0;
            this.transform.position = Vec3(0.0, 0.0, 0.0);
            changed = true;
        }
        if changed { configure(); print("Camera demo modes changed"); }
        if Input.just_pressed("Space") || Ui.is_pressed(World.find("impact")) {
            if this.shake { Camera.add_trauma(1.0); print("Camera demo impact"); }
        }
    }
}
script CameraLayout {
    @export let x: number = 0.0;
    @export let width: number = 1.0;
    @export let button: bool = false;
    fn update(dt: number) {
        let span = min(1.0, Viewport.aspect);
        this.transform.position.x = this.x * span;
        if this.button { this.transform.scale.x = this.width * span; }
    }
}
```

### `assets/demo.scene`

```json
{
  "format_version": 10,
  "metadata": {
    "name": "Camera Lab"
  },
  "entities": [
    {
      "id": "camera",
      "name": "Gameplay Camera",
      "transform_3d": {
        "position": [0, 0, 10],
        "rotation": [0, 0, 0, 1],
        "scale": [1, 1, 1]
      },
      "components": {
        "sindri.camera": {
          "projection": "orthographic",
          "vertical_size": 8,
          "near": 0.1,
          "far": 100,
          "fit": "shorter"
        },
        "sindri.camera.behavior": {
          "follow": {
            "target": "target",
            "offset": [0, 0, 0],
            "dead_zone": [2.5, 1.5],
            "smoothing": 5,
            "max_speed": 8
          },
          "confine": {
            "min": [-8, -4],
            "max": [8, 4]
          },
          "shake": {
            "trauma": 0,
            "strength": 0.3,
            "decay": 1.6,
            "frequency": 30,
            "phase": 0
          }
        }
      }
    },
    {
      "id": "target",
      "name": "Moving Target",
      "transform_3d": {
        "position": [0, 0, 0],
        "rotation": [0, 0, 0, 1],
        "scale": [0.65, 0.65, 1]
      },
      "components": {
        "sindri.shape": {
          "kind": "polygon",
          "count": 4,
          "fill": [0.18, 0.82, 1, 1],
          "stroke": [0.8, 0.97, 1, 1],
          "stroke_width": 0.08,
          "layer": 4
        },
        "sindri.script": {
          "source": "camera-demo.decay",
          "script": "CameraDemo",
          "properties": {
            "speed": 5
          }
        }
      }
    },
    {
      "id": "world-grid",
      "name": "World Grid",
      "transform_3d": {
        "position": [0, 0, -1],
        "rotation": [0, 0, 0, 1],
        "scale": [28, 16, 1]
      },
      "components": {
        "sindri.shape": {
          "kind": "grid",
          "count": 28,
          "fill": [0, 0, 0, 0],
          "stroke": [0.035, 0.06, 0.09, 0.7],
          "stroke_width": 0.001,
          "layer": 0
        }
      }
    },
    {
      "id": "dead-zone",
      "name": "dead-zone",
      "transform_3d": {
        "position": [0, 0, -9],
        "scale": [2.5, 1.5, 1]
      },
      "components": {
        "sindri.shape": {
          "kind": "rect",
          "stroke": [0.2, 0.9, 1, 1],
          "stroke_width": 0.018,
          "fill": [0.1, 0.7, 1, 0.06],
          "layer": 6
        }
      },
      "parent": "camera"
    },
    {
      "id": "camera-bounds",
      "name": "camera-bounds",
      "transform_3d": {
        "position": [0, 0, -0.3],
        "scale": [16, 8, 1]
      },
      "components": {
        "sindri.shape": {
          "kind": "rect",
          "stroke": [1, 0.63, 0.24, 1],
          "stroke_width": 0.0015,
          "dashes": 32,
          "layer": 2
        }
      }
    },
    {
      "id": "world-edge",
      "name": "world-edge",
      "transform_3d": {
        "position": [0, 0, -0.5],
        "scale": [26, 14, 1]
      },
      "components": {
        "sindri.shape": {
          "kind": "rect",
          "stroke": [0.4, 0.5, 0.65, 1],
          "stroke_width": 0.001,
          "layer": 1
        }
      }
    },
    {
      "id": "bound-marker-0",
      "name": "bound-marker-0",
      "transform_3d": {
        "position": [-8, -4, 0],
        "scale": [0.18, 0.18, 1]
      },
      "components": {
        "sindri.shape": {
          "kind": "ellipse",
          "fill": [1, 0.63, 0.24, 1],
          "layer": 3
        }
      }
    },
    {
      "id": "bound-marker-1",
      "name": "bound-marker-1",
      "transform_3d": {
        "position": [-8, 4, 0],
        "scale": [0.18, 0.18, 1]
      },
      "components": {
        "sindri.shape": {
          "kind": "ellipse",
          "fill": [1, 0.63, 0.24, 1],
          "layer": 3
        }
      }
    },
    {
      "id": "bound-marker-2",
      "name": "bound-marker-2",
      "transform_3d": {
        "position": [8, -4, 0],
        "scale": [0.18, 0.18, 1]
      },
      "components": {
        "sindri.shape": {
          "kind": "ellipse",
          "fill": [1, 0.63, 0.24, 1],
          "layer": 3
        }
      }
    },
    {
      "id": "bound-marker-3",
      "name": "bound-marker-3",
      "transform_3d": {
        "position": [8, 4, 0],
        "scale": [0.18, 0.18, 1]
      },
      "components": {
        "sindri.shape": {
          "kind": "ellipse",
          "fill": [1, 0.63, 0.24, 1],
          "layer": 3
        }
      }
    },
    {
      "id": "title",
      "name": "title",
      "transform_3d": {
        "position": [0, 0.9, 0],
        "scale": [1, 1, 1]
      },
      "components": {
        "sindri.ui.text": {
          "text": "CAMERA LAB",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.085,
          "bounds": [1.7, 0.08],
          "anchor": "center",
          "layer": 20
        },
        "sindri.script": {
          "source": "camera-demo.decay",
          "script": "CameraLayout",
          "properties": {
            "x": 0
          }
        }
      }
    },
    {
      "id": "subtitle",
      "name": "subtitle",
      "transform_3d": {
        "position": [0, 0.79, 0],
        "scale": [1, 1, 1]
      },
      "components": {
        "sindri.ui.text": {
          "text": "FOLLOW / CONFINE / SHAKE",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.037,
          "bounds": [1.7, 0.08],
          "anchor": "center",
          "layer": 20
        },
        "sindri.script": {
          "source": "camera-demo.decay",
          "script": "CameraLayout",
          "properties": {
            "x": 0
          }
        }
      }
    },
    {
      "id": "legend",
      "name": "legend",
      "transform_3d": {
        "position": [0, 0.7, 0],
        "scale": [1, 1, 1]
      },
      "components": {
        "sindri.ui.text": {
          "text": "CYAN: DEAD ZONE   AMBER: CAMERA LIMIT",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.031,
          "bounds": [1.7, 0.08],
          "anchor": "center",
          "layer": 20
        },
        "sindri.script": {
          "source": "camera-demo.decay",
          "script": "CameraLayout",
          "properties": {
            "x": 0
          }
        }
      }
    },
    {
      "id": "values",
      "name": "values",
      "transform_3d": {
        "position": [0, 0.62, 0],
        "scale": [1, 1, 1]
      },
      "components": {
        "sindri.ui.text": {
          "text": "ZONE 2.5 x 1.5  /  SPEED CAP 8",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.03,
          "bounds": [1.7, 0.08],
          "anchor": "center",
          "layer": 20
        },
        "sindri.script": {
          "source": "camera-demo.decay",
          "script": "CameraLayout",
          "properties": {
            "x": 0
          }
        }
      }
    },
    {
      "id": "position-label",
      "name": "position-label",
      "transform_3d": {
        "position": [0, -0.34, 0],
        "scale": [1, 1, 1]
      },
      "components": {
        "sindri.ui.text": {
          "text": "TARGET 0, 0   CAMERA 0, 0",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.032,
          "bounds": [1.7, 0.08],
          "anchor": "center",
          "layer": 20
        },
        "sindri.script": {
          "source": "camera-demo.decay",
          "script": "CameraLayout",
          "properties": {
            "x": 0
          }
        }
      }
    },
    {
      "id": "tour",
      "name": "tour",
      "transform_3d": {
        "position": [-0.31, 0.48, 0],
        "scale": [0.56, 0.1, 1]
      },
      "components": {
        "sindri.ui.shape": {
          "kind": "rect",
          "fill": [0.035, 0.075, 0.12, 0.96],
          "stroke": [0.15, 0.35, 0.5, 1],
          "stroke_width": 0.02,
          "corner_radius": 0.12,
          "anchor": "center",
          "layer": 10
        },
        "sindri.ui.button": {
          "label": "START TOUR"
        },
        "sindri.script": {
          "source": "camera-demo.decay",
          "script": "CameraLayout",
          "properties": {
            "x": -0.31,
            "width": 0.56,
            "button": true
          }
        }
      }
    },
    {
      "id": "tour-label",
      "name": "tour-label",
      "transform_3d": {
        "position": [-0.31, 0.48, 0],
        "scale": [1, 1, 1]
      },
      "components": {
        "sindri.ui.text": {
          "text": "START TOUR",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.038,
          "bounds": [0.56, 0.08],
          "anchor": "center",
          "layer": 20
        },
        "sindri.script": {
          "source": "camera-demo.decay",
          "script": "CameraLayout",
          "properties": {
            "x": -0.31
          }
        }
      }
    },
    {
      "id": "impact",
      "name": "impact",
      "transform_3d": {
        "position": [0.31, 0.48, 0],
        "scale": [0.56, 0.1, 1]
      },
      "components": {
        "sindri.ui.shape": {
          "kind": "rect",
          "fill": [0.035, 0.075, 0.12, 0.96],
          "stroke": [0.15, 0.35, 0.5, 1],
          "stroke_width": 0.02,
          "corner_radius": 0.12,
          "anchor": "center",
          "layer": 10
        },
        "sindri.ui.button": {
          "label": "IMPACT"
        },
        "sindri.script": {
          "source": "camera-demo.decay",
          "script": "CameraLayout",
          "properties": {
            "x": 0.31,
            "width": 0.56,
            "button": true
          }
        }
      }
    },
    {
      "id": "impact-label",
      "name": "impact-label",
      "transform_3d": {
        "position": [0.31, 0.48, 0],
        "scale": [1, 1, 1]
      },
      "components": {
        "sindri.ui.text": {
          "text": "IMPACT",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.038,
          "bounds": [0.56, 0.08],
          "anchor": "center",
          "layer": 20
        },
        "sindri.script": {
          "source": "camera-demo.decay",
          "script": "CameraLayout",
          "properties": {
            "x": 0.31
          }
        }
      }
    },
    {
      "id": "follow",
      "name": "follow",
      "transform_3d": {
        "position": [-0.6, -0.49, 0],
        "scale": [0.55, 0.1, 1]
      },
      "components": {
        "sindri.ui.shape": {
          "kind": "rect",
          "fill": [0.035, 0.075, 0.12, 0.96],
          "stroke": [0.15, 0.35, 0.5, 1],
          "stroke_width": 0.02,
          "corner_radius": 0.12,
          "anchor": "center",
          "layer": 10
        },
        "sindri.ui.button": {
          "label": "FOLLOW ON"
        },
        "sindri.script": {
          "source": "camera-demo.decay",
          "script": "CameraLayout",
          "properties": {
            "x": -0.6,
            "width": 0.55,
            "button": true
          }
        }
      }
    },
    {
      "id": "follow-label",
      "name": "follow-label",
      "transform_3d": {
        "position": [-0.6, -0.49, 0],
        "scale": [1, 1, 1]
      },
      "components": {
        "sindri.ui.text": {
          "text": "FOLLOW ON",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.038,
          "bounds": [0.55, 0.08],
          "anchor": "center",
          "layer": 20
        },
        "sindri.script": {
          "source": "camera-demo.decay",
          "script": "CameraLayout",
          "properties": {
            "x": -0.6
          }
        }
      }
    },
    {
      "id": "bounds",
      "name": "bounds",
      "transform_3d": {
        "position": [0, -0.49, 0],
        "scale": [0.55, 0.1, 1]
      },
      "components": {
        "sindri.ui.shape": {
          "kind": "rect",
          "fill": [0.035, 0.075, 0.12, 0.96],
          "stroke": [0.15, 0.35, 0.5, 1],
          "stroke_width": 0.02,
          "corner_radius": 0.12,
          "anchor": "center",
          "layer": 10
        },
        "sindri.ui.button": {
          "label": "BOUNDS ON"
        },
        "sindri.script": {
          "source": "camera-demo.decay",
          "script": "CameraLayout",
          "properties": {
            "x": 0,
            "width": 0.55,
            "button": true
          }
        }
      }
    },
    {
      "id": "bounds-label",
      "name": "bounds-label",
      "transform_3d": {
        "position": [0, -0.49, 0],
        "scale": [1, 1, 1]
      },
      "components": {
        "sindri.ui.text": {
          "text": "BOUNDS ON",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.038,
          "bounds": [0.55, 0.08],
          "anchor": "center",
          "layer": 20
        },
        "sindri.script": {
          "source": "camera-demo.decay",
          "script": "CameraLayout",
          "properties": {
            "x": 0
          }
        }
      }
    },
    {
      "id": "shake",
      "name": "shake",
      "transform_3d": {
        "position": [0.6, -0.49, 0],
        "scale": [0.55, 0.1, 1]
      },
      "components": {
        "sindri.ui.shape": {
          "kind": "rect",
          "fill": [0.035, 0.075, 0.12, 0.96],
          "stroke": [0.15, 0.35, 0.5, 1],
          "stroke_width": 0.02,
          "corner_radius": 0.12,
          "anchor": "center",
          "layer": 10
        },
        "sindri.ui.button": {
          "label": "SHAKE ON"
        },
        "sindri.script": {
          "source": "camera-demo.decay",
          "script": "CameraLayout",
          "properties": {
            "x": 0.6,
            "width": 0.55,
            "button": true
          }
        }
      }
    },
    {
      "id": "shake-label",
      "name": "shake-label",
      "transform_3d": {
        "position": [0.6, -0.49, 0],
        "scale": [1, 1, 1]
      },
      "components": {
        "sindri.ui.text": {
          "text": "SHAKE ON",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.038,
          "bounds": [0.55, 0.08],
          "anchor": "center",
          "layer": 20
        },
        "sindri.script": {
          "source": "camera-demo.decay",
          "script": "CameraLayout",
          "properties": {
            "x": 0.6
          }
        }
      }
    },
    {
      "id": "smooth",
      "name": "smooth",
      "transform_3d": {
        "position": [-0.6, -0.64, 0],
        "scale": [0.55, 0.1, 1]
      },
      "components": {
        "sindri.ui.shape": {
          "kind": "rect",
          "fill": [0.035, 0.075, 0.12, 0.96],
          "stroke": [0.15, 0.35, 0.5, 1],
          "stroke_width": 0.02,
          "corner_radius": 0.12,
          "anchor": "center",
          "layer": 10
        },
        "sindri.ui.button": {
          "label": "SMOOTH"
        },
        "sindri.script": {
          "source": "camera-demo.decay",
          "script": "CameraLayout",
          "properties": {
            "x": -0.6,
            "width": 0.55,
            "button": true
          }
        }
      }
    },
    {
      "id": "smooth-label",
      "name": "smooth-label",
      "transform_3d": {
        "position": [-0.6, -0.64, 0],
        "scale": [1, 1, 1]
      },
      "components": {
        "sindri.ui.text": {
          "text": "SMOOTH",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.038,
          "bounds": [0.55, 0.08],
          "anchor": "center",
          "layer": 20
        },
        "sindri.script": {
          "source": "camera-demo.decay",
          "script": "CameraLayout",
          "properties": {
            "x": -0.6
          }
        }
      }
    },
    {
      "id": "zone",
      "name": "zone",
      "transform_3d": {
        "position": [0, -0.64, 0],
        "scale": [0.55, 0.1, 1]
      },
      "components": {
        "sindri.ui.shape": {
          "kind": "rect",
          "fill": [0.035, 0.075, 0.12, 0.96],
          "stroke": [0.15, 0.35, 0.5, 1],
          "stroke_width": 0.02,
          "corner_radius": 0.12,
          "anchor": "center",
          "layer": 10
        },
        "sindri.ui.button": {
          "label": "ZONE ON"
        },
        "sindri.script": {
          "source": "camera-demo.decay",
          "script": "CameraLayout",
          "properties": {
            "x": 0,
            "width": 0.55,
            "button": true
          }
        }
      }
    },
    {
      "id": "zone-label",
      "name": "zone-label",
      "transform_3d": {
        "position": [0, -0.64, 0],
        "scale": [1, 1, 1]
      },
      "components": {
        "sindri.ui.text": {
          "text": "ZONE ON",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.038,
          "bounds": [0.55, 0.08],
          "anchor": "center",
          "layer": 20
        },
        "sindri.script": {
          "source": "camera-demo.decay",
          "script": "CameraLayout",
          "properties": {
            "x": 0
          }
        }
      }
    },
    {
      "id": "reset",
      "name": "reset",
      "transform_3d": {
        "position": [0.6, -0.64, 0],
        "scale": [0.55, 0.1, 1]
      },
      "components": {
        "sindri.ui.shape": {
          "kind": "rect",
          "fill": [0.035, 0.075, 0.12, 0.96],
          "stroke": [0.15, 0.35, 0.5, 1],
          "stroke_width": 0.02,
          "corner_radius": 0.12,
          "anchor": "center",
          "layer": 10
        },
        "sindri.ui.button": {
          "label": "RESET"
        },
        "sindri.script": {
          "source": "camera-demo.decay",
          "script": "CameraLayout",
          "properties": {
            "x": 0.6,
            "width": 0.55,
            "button": true
          }
        }
      }
    },
    {
      "id": "reset-label",
      "name": "reset-label",
      "transform_3d": {
        "position": [0.6, -0.64, 0],
        "scale": [1, 1, 1]
      },
      "components": {
        "sindri.ui.text": {
          "text": "RESET",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.038,
          "bounds": [0.55, 0.08],
          "anchor": "center",
          "layer": 20
        },
        "sindri.script": {
          "source": "camera-demo.decay",
          "script": "CameraLayout",
          "properties": {
            "x": 0.6
          }
        }
      }
    },
    {
      "id": "left",
      "name": "left",
      "transform_3d": {
        "position": [-0.66, -0.8, 0],
        "scale": [0.4, 0.1, 1]
      },
      "components": {
        "sindri.ui.shape": {
          "kind": "rect",
          "fill": [0.035, 0.075, 0.12, 0.96],
          "stroke": [0.15, 0.35, 0.5, 1],
          "stroke_width": 0.02,
          "corner_radius": 0.12,
          "anchor": "center",
          "layer": 10
        },
        "sindri.ui.button": {
          "label": "LEFT"
        },
        "sindri.script": {
          "source": "camera-demo.decay",
          "script": "CameraLayout",
          "properties": {
            "x": -0.66,
            "width": 0.4,
            "button": true
          }
        }
      }
    },
    {
      "id": "left-label",
      "name": "left-label",
      "transform_3d": {
        "position": [-0.66, -0.8, 0],
        "scale": [1, 1, 1]
      },
      "components": {
        "sindri.ui.text": {
          "text": "LEFT",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.038,
          "bounds": [0.4, 0.08],
          "anchor": "center",
          "layer": 20
        },
        "sindri.script": {
          "source": "camera-demo.decay",
          "script": "CameraLayout",
          "properties": {
            "x": -0.66
          }
        }
      }
    },
    {
      "id": "up",
      "name": "up",
      "transform_3d": {
        "position": [-0.22, -0.8, 0],
        "scale": [0.4, 0.1, 1]
      },
      "components": {
        "sindri.ui.shape": {
          "kind": "rect",
          "fill": [0.035, 0.075, 0.12, 0.96],
          "stroke": [0.15, 0.35, 0.5, 1],
          "stroke_width": 0.02,
          "corner_radius": 0.12,
          "anchor": "center",
          "layer": 10
        },
        "sindri.ui.button": {
          "label": "UP"
        },
        "sindri.script": {
          "source": "camera-demo.decay",
          "script": "CameraLayout",
          "properties": {
            "x": -0.22,
            "width": 0.4,
            "button": true
          }
        }
      }
    },
    {
      "id": "up-label",
      "name": "up-label",
      "transform_3d": {
        "position": [-0.22, -0.8, 0],
        "scale": [1, 1, 1]
      },
      "components": {
        "sindri.ui.text": {
          "text": "UP",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.038,
          "bounds": [0.4, 0.08],
          "anchor": "center",
          "layer": 20
        },
        "sindri.script": {
          "source": "camera-demo.decay",
          "script": "CameraLayout",
          "properties": {
            "x": -0.22
          }
        }
      }
    },
    {
      "id": "down",
      "name": "down",
      "transform_3d": {
        "position": [0.22, -0.8, 0],
        "scale": [0.4, 0.1, 1]
      },
      "components": {
        "sindri.ui.shape": {
          "kind": "rect",
          "fill": [0.035, 0.075, 0.12, 0.96],
          "stroke": [0.15, 0.35, 0.5, 1],
          "stroke_width": 0.02,
          "corner_radius": 0.12,
          "anchor": "center",
          "layer": 10
        },
        "sindri.ui.button": {
          "label": "DOWN"
        },
        "sindri.script": {
          "source": "camera-demo.decay",
          "script": "CameraLayout",
          "properties": {
            "x": 0.22,
            "width": 0.4,
            "button": true
          }
        }
      }
    },
    {
      "id": "down-label",
      "name": "down-label",
      "transform_3d": {
        "position": [0.22, -0.8, 0],
        "scale": [1, 1, 1]
      },
      "components": {
        "sindri.ui.text": {
          "text": "DOWN",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.038,
          "bounds": [0.4, 0.08],
          "anchor": "center",
          "layer": 20
        },
        "sindri.script": {
          "source": "camera-demo.decay",
          "script": "CameraLayout",
          "properties": {
            "x": 0.22
          }
        }
      }
    },
    {
      "id": "right",
      "name": "right",
      "transform_3d": {
        "position": [0.66, -0.8, 0],
        "scale": [0.4, 0.1, 1]
      },
      "components": {
        "sindri.ui.shape": {
          "kind": "rect",
          "fill": [0.035, 0.075, 0.12, 0.96],
          "stroke": [0.15, 0.35, 0.5, 1],
          "stroke_width": 0.02,
          "corner_radius": 0.12,
          "anchor": "center",
          "layer": 10
        },
        "sindri.ui.button": {
          "label": "RIGHT"
        },
        "sindri.script": {
          "source": "camera-demo.decay",
          "script": "CameraLayout",
          "properties": {
            "x": 0.66,
            "width": 0.4,
            "button": true
          }
        }
      }
    },
    {
      "id": "right-label",
      "name": "right-label",
      "transform_3d": {
        "position": [0.66, -0.8, 0],
        "scale": [1, 1, 1]
      },
      "components": {
        "sindri.ui.text": {
          "text": "RIGHT",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.038,
          "bounds": [0.4, 0.08],
          "anchor": "center",
          "layer": 20
        },
        "sindri.script": {
          "source": "camera-demo.decay",
          "script": "CameraLayout",
          "properties": {
            "x": 0.66
          }
        }
      }
    },
    {
      "id": "hint",
      "name": "hint",
      "transform_3d": {
        "position": [0, -0.93, 0],
        "scale": [1, 1, 1]
      },
      "components": {
        "sindri.ui.text": {
          "text": "ARROWS move / SPACE impact / R reset",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.03,
          "bounds": [1.75, 0.08],
          "anchor": "center",
          "layer": 20
        },
        "sindri.script": {
          "source": "camera-demo.decay",
          "script": "CameraLayout",
          "properties": {
            "x": 0
          }
        }
      }
    }
  ]
}
```

### `assets/ui/camera.weave`

```css
#title { font-size: 26px; }
#subtitle, #legend, #values, #position-label, #hint { font-size: 11px; }
#follow-label, #bounds-label, #shake-label, #smooth-label, #zone-label,
#reset-label, #tour-label, #impact-label,
#left-label, #up-label, #down-label, #right-label { font-size: 12px; }
@media (min-height: 700px) {
    #title { font-size: 30px; }
    #subtitle, #legend, #values, #position-label, #hint { font-size: 12px; }
    #follow-label, #bounds-label, #shake-label, #smooth-label, #zone-label,
    #reset-label, #tour-label, #impact-label,
    #left-label, #up-label, #down-label, #right-label { font-size: 14px; }
}
```

### `sindri.toml`

```toml
format_version = 1
[project]
name = "Camera Lab"
main_scene = "assets/demo.scene"
[assets]
include = ["fonts/Fonts-OFL.txt", "ui/camera.weave"]
```

### Binary files in this project

`assets/fonts/ChakraPetch-Regular.ttf`, `assets/fonts/Fonts-OFL.txt`


---

## Example: Tween Lab (`examples/tween`)

Every text file of the project follows, whole, under its path relative to the project folder. JSON files are shown with short arrays on one line; whitespace does not matter to Sindri.

### Tween Lab

A feature example for managed Decay gameplay tweening. Open `sindri.toml` in
the editor or run the exported project in a WebGPU browser. Pages publishes it
at `https://sindri.vardir.no/examples/tween/`.

Five rows compare linear, ease, ease-in, ease-out and ease-in-out timing while
moving, scaling, rotating and changing colour. Buttons work with mouse/touch;
Space resumes, P pauses, R restarts, C cancels, and V replaces the destination
from the displayed value. Cancellation holds the value; use Restart to replay.
The first pass runs automatically and holds at completion.

The sixth row composes tweens: an orange marker crosses, waits half a second
and comes back, because its return is `Tween.after` the crossing with a
`Tween.set_delay`, while a `Tween.set_yoyo` scale that `Tween.set_loops` plays
for ever makes it breathe. The same controls pause, resume, cancel and restart
it.

Labels and controls use `assets/ui/tween.weave`: pixel font sizes keep desktop
text readable, and media rules increase them for taller screens. The scene
provides the fallback presentation.

The project uses the ordinary browser export host. All demo behaviour is in
`assets/scripts/tween-demo.decay`; no bespoke Rust game loop or JavaScript
animation implements its tweens. The source is small enough to copy into a game.
`crates/sindri-decay/tests/tween_demo.rs` opens, validates, compiles and plays it;
CI also exports and runs it through the browser smoke on desktop and portrait
viewports, observing animation, pause and each playback control.

See the gameplay contract (in `03-scripting-guide.md`). Weave's
CSS-style UI animation authoring is a separate surface sharing easing math.

### `assets/scripts/tween-demo.decay`

```rust
// A feature example: managed playback, with game decisions entirely in Decay.
state Demo {
    var action: number = 0.0;
    var revision: number = 0.0;
}
script TweenDemo {
    @export let easing: String = "linear";
    var movement: Vec3Tween = null;
    var size: Vec3Tween = null;
    var tint: ColorTween = null;
    var turn: NumberTween = null;
    var revision: number = 0.0;
    var right: bool = true;
    var span: number = 1.8;

    fn start() {
        this.span = min(2.6, Viewport.aspect * 2.6);
        let position = this.transform.position;
        this.transform.position = Vec3(-this.span, position.y, position.z);
        create();
    }
    fn create() {
        var target = this.span;
        if !this.right { target = -this.span; }
        let position = this.transform.position;
        this.movement = Tween.vec3(position, Vec3(target, position.y, position.z), 2.5, this.easing);
        this.size = Tween.vec3(this.transform.scale, Vec3(0.55, 0.55, 1.0), 2.5, this.easing);
        this.tint = Tween.color(this.shape.fill, Color(1.0, 0.52, 0.22, 1.0), 2.5, this.easing);
        this.turn = Tween.number(this.transform.rotation_z, 6.2831853, 2.5, this.easing);
    }
    fn update(dt: number) {
        if this.revision != Demo.revision {
            this.revision = Demo.revision;
            control();
        }
        this.transform.position = Tween.vec3_value(this.movement);
        this.transform.scale = Tween.vec3_value(this.size);
        this.shape.fill = Tween.color_value(this.tint);
        this.transform.rotation_z = Tween.number_value(this.turn);
    }
    fn control() {
        if Demo.action == 1.0 {
            Tween.resume(this.movement); Tween.resume(this.size);
            Tween.resume(this.tint); Tween.resume(this.turn);
        } else if Demo.action == 2.0 {
            Tween.pause(this.movement); Tween.pause(this.size);
            Tween.pause(this.tint); Tween.pause(this.turn);
        } else if Demo.action == 3.0 {
            Tween.restart(this.movement); Tween.restart(this.size);
            Tween.restart(this.tint); Tween.restart(this.turn);
        } else if Demo.action == 4.0 {
            Tween.cancel(this.movement); Tween.cancel(this.size);
            Tween.cancel(this.tint); Tween.cancel(this.turn);
        } else {
            // Replacement starts at the currently displayed value.
            Tween.dispose(this.movement); Tween.dispose(this.size);
            Tween.dispose(this.tint); Tween.dispose(this.turn);
            this.right = !this.right;
            create();
        }
    }
}
script TweenControls {
    fn update(dt: number) {
        if Input.just_pressed("P") { request(2.0); }
        if Input.just_pressed("R") { request(3.0); }
        if Input.just_pressed("C") { request(4.0); }
        if Input.just_pressed("V") { request(5.0); }
        if Input.just_pressed("Space") { request(1.0); }
    }
    fn request(action: number) {
        Demo.action = action;
        Demo.revision += 1.0;
        print("Tween demo action " + action);
    }
}
script TweenButton {
    @export let action: number = 1.0;
    fn update(dt: number) {
        if Ui.is_pressed(this.entity) {
            Demo.action = this.action;
            Demo.revision += 1.0;
            print("Tween demo action " + this.action);
        }
    }
}
// Composition: across and back as a sequence, the return waiting half a
// second after the crossing, while a yoyo makes the marker breathe for ever.
script TweenSequence {
    var across: Vec3Tween = null;
    var back: Vec3Tween = null;
    var breathe: Vec3Tween = null;
    var revision: number = 0.0;
    var span: number = 1.8;

    fn start() {
        this.span = min(2.6, Viewport.aspect * 2.6);
        create();
    }
    fn create() {
        let y = this.transform.position.y;
        let left = Vec3(-this.span, y, 0.0);
        let right = Vec3(this.span, y, 0.0);
        this.across = Tween.vec3(left, right, 1.2, "ease-in-out");
        this.back = Tween.vec3(right, left, 1.2, "ease-in-out");
        Tween.set_delay(this.back, 0.5);
        Tween.after(this.back, this.across);
        this.breathe = Tween.vec3(Vec3(0.23, 0.23, 1.0), Vec3(0.34, 0.34, 1.0), 0.4, "ease-in-out");
        Tween.set_loops(this.breathe, 0.0);
        Tween.set_yoyo(this.breathe, true);
    }
    fn update(dt: number) {
        if this.revision != Demo.revision {
            this.revision = Demo.revision;
            control();
        }
        if Tween.is_done(this.across) {
            this.transform.position = Tween.vec3_value(this.back);
        } else {
            this.transform.position = Tween.vec3_value(this.across);
        }
        this.transform.scale = Tween.vec3_value(this.breathe);
    }
    fn control() {
        if Demo.action == 1.0 {
            Tween.resume(this.across); Tween.resume(this.back); Tween.resume(this.breathe);
        } else if Demo.action == 2.0 {
            Tween.pause(this.across); Tween.pause(this.back); Tween.pause(this.breathe);
        } else if Demo.action == 4.0 {
            Tween.cancel(this.across); Tween.cancel(this.back); Tween.cancel(this.breathe);
        } else {
            // Restart and replace both play the sequence again from the left.
            Tween.restart(this.across); Tween.restart(this.back); Tween.restart(this.breathe);
        }
    }
}
```

### `assets/tween.scene`

```json
{
  "format_version": 10,
  "metadata": {
    "name": "Tween Lab"
  },
  "entities": [
    {
      "id": "camera",
      "name": "camera",
      "transform_3d": {
        "position": [0, 0, 10],
        "scale": [1, 1, 1]
      },
      "components": {
        "sindri.camera": {
          "projection": "orthographic",
          "vertical_size": 8,
          "near": 0.1,
          "far": 100,
          "fit": "height"
        }
      }
    },
    {
      "id": "controls",
      "name": "controls",
      "transform_3d": {
        "position": [0, 0, 0],
        "scale": [1, 1, 1]
      },
      "components": {
        "sindri.script": {
          "source": "scripts/tween-demo.decay",
          "script": "TweenControls"
        }
      }
    },
    {
      "id": "title",
      "name": "title",
      "transform_3d": {
        "position": [0, 0.85, 0],
        "scale": [1, 1, 1]
      },
      "components": {
        "sindri.ui.text": {
          "text": "TWEEN LAB",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.085,
          "bounds": [1, 0.1],
          "anchor": "center",
          "layer": 20
        }
      }
    },
    {
      "id": "subtitle",
      "name": "subtitle",
      "transform_3d": {
        "position": [0, 0.74, 0],
        "scale": [1, 1, 1]
      },
      "components": {
        "sindri.ui.text": {
          "text": "MOVE / SCALE / COLOR / ROTATE",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.04,
          "bounds": [1, 0.1],
          "anchor": "center",
          "layer": 20
        }
      }
    },
    {
      "id": "track-linear",
      "name": "track-linear",
      "transform_3d": {
        "position": [0, 1.7, -0.1],
        "scale": [5.5, 0.012, 1]
      },
      "components": {
        "sindri.shape": {
          "kind": "polygon",
          "count": 4,
          "fill": [0.15, 0.22, 0.3, 1],
          "stroke": [0, 0, 0, 0],
          "layer": 0
        }
      }
    },
    {
      "id": "sample-linear",
      "name": "sample-linear",
      "transform_3d": {
        "position": [0, 1.7, 0],
        "scale": [0.23, 0.23, 1]
      },
      "components": {
        "sindri.shape": {
          "kind": "polygon",
          "count": 4,
          "fill": [0.18, 0.82, 1, 1],
          "stroke": [0.9, 0.96, 1, 1],
          "stroke_width": 0.05,
          "layer": 4
        },
        "sindri.script": {
          "source": "scripts/tween-demo.decay",
          "script": "TweenDemo",
          "properties": {
            "easing": "linear"
          }
        }
      }
    },
    {
      "id": "label-linear",
      "name": "label-linear",
      "transform_3d": {
        "position": [0.06, 0.49, 0],
        "scale": [1, 1, 1]
      },
      "components": {
        "sindri.ui.text": {
          "text": "linear",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.055,
          "bounds": [0.7, 0.07],
          "anchor": "left",
          "layer": 20
        }
      }
    },
    {
      "id": "track-ease",
      "name": "track-ease",
      "transform_3d": {
        "position": [0, 0.8999999999999999, -0.1],
        "scale": [5.5, 0.012, 1]
      },
      "components": {
        "sindri.shape": {
          "kind": "polygon",
          "count": 4,
          "fill": [0.15, 0.22, 0.3, 1],
          "stroke": [0, 0, 0, 0],
          "layer": 0
        }
      }
    },
    {
      "id": "sample-ease",
      "name": "sample-ease",
      "transform_3d": {
        "position": [0, 0.8999999999999999, 0],
        "scale": [0.23, 0.23, 1]
      },
      "components": {
        "sindri.shape": {
          "kind": "polygon",
          "count": 4,
          "fill": [0.18, 0.82, 1, 1],
          "stroke": [0.9, 0.96, 1, 1],
          "stroke_width": 0.05,
          "layer": 4
        },
        "sindri.script": {
          "source": "scripts/tween-demo.decay",
          "script": "TweenDemo",
          "properties": {
            "easing": "ease"
          }
        }
      }
    },
    {
      "id": "label-ease",
      "name": "label-ease",
      "transform_3d": {
        "position": [0.06, 0.29, 0],
        "scale": [1, 1, 1]
      },
      "components": {
        "sindri.ui.text": {
          "text": "ease",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.055,
          "bounds": [0.7, 0.07],
          "anchor": "left",
          "layer": 20
        }
      }
    },
    {
      "id": "track-ease-in",
      "name": "track-ease-in",
      "transform_3d": {
        "position": [0, 0.09999999999999987, -0.1],
        "scale": [5.5, 0.012, 1]
      },
      "components": {
        "sindri.shape": {
          "kind": "polygon",
          "count": 4,
          "fill": [0.15, 0.22, 0.3, 1],
          "stroke": [0, 0, 0, 0],
          "layer": 0
        }
      }
    },
    {
      "id": "sample-ease-in",
      "name": "sample-ease-in",
      "transform_3d": {
        "position": [0, 0.09999999999999987, 0],
        "scale": [0.23, 0.23, 1]
      },
      "components": {
        "sindri.shape": {
          "kind": "polygon",
          "count": 4,
          "fill": [0.18, 0.82, 1, 1],
          "stroke": [0.9, 0.96, 1, 1],
          "stroke_width": 0.05,
          "layer": 4
        },
        "sindri.script": {
          "source": "scripts/tween-demo.decay",
          "script": "TweenDemo",
          "properties": {
            "easing": "ease-in"
          }
        }
      }
    },
    {
      "id": "label-ease-in",
      "name": "label-ease-in",
      "transform_3d": {
        "position": [0.06, 0.08999999999999997, 0],
        "scale": [1, 1, 1]
      },
      "components": {
        "sindri.ui.text": {
          "text": "ease-in",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.055,
          "bounds": [0.7, 0.07],
          "anchor": "left",
          "layer": 20
        }
      }
    },
    {
      "id": "track-ease-out",
      "name": "track-ease-out",
      "transform_3d": {
        "position": [0, -0.7000000000000004, -0.1],
        "scale": [5.5, 0.012, 1]
      },
      "components": {
        "sindri.shape": {
          "kind": "polygon",
          "count": 4,
          "fill": [0.15, 0.22, 0.3, 1],
          "stroke": [0, 0, 0, 0],
          "layer": 0
        }
      }
    },
    {
      "id": "sample-ease-out",
      "name": "sample-ease-out",
      "transform_3d": {
        "position": [0, -0.7000000000000004, 0],
        "scale": [0.23, 0.23, 1]
      },
      "components": {
        "sindri.shape": {
          "kind": "polygon",
          "count": 4,
          "fill": [0.18, 0.82, 1, 1],
          "stroke": [0.9, 0.96, 1, 1],
          "stroke_width": 0.05,
          "layer": 4
        },
        "sindri.script": {
          "source": "scripts/tween-demo.decay",
          "script": "TweenDemo",
          "properties": {
            "easing": "ease-out"
          }
        }
      }
    },
    {
      "id": "label-ease-out",
      "name": "label-ease-out",
      "transform_3d": {
        "position": [0.06, -0.1100000000000001, 0],
        "scale": [1, 1, 1]
      },
      "components": {
        "sindri.ui.text": {
          "text": "ease-out",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.055,
          "bounds": [0.7, 0.07],
          "anchor": "left",
          "layer": 20
        }
      }
    },
    {
      "id": "track-ease-in-out",
      "name": "track-ease-in-out",
      "transform_3d": {
        "position": [0, -1.5000000000000002, -0.1],
        "scale": [5.5, 0.012, 1]
      },
      "components": {
        "sindri.shape": {
          "kind": "polygon",
          "count": 4,
          "fill": [0.15, 0.22, 0.3, 1],
          "stroke": [0, 0, 0, 0],
          "layer": 0
        }
      }
    },
    {
      "id": "sample-ease-in-out",
      "name": "sample-ease-in-out",
      "transform_3d": {
        "position": [0, -1.5000000000000002, 0],
        "scale": [0.23, 0.23, 1]
      },
      "components": {
        "sindri.shape": {
          "kind": "polygon",
          "count": 4,
          "fill": [0.18, 0.82, 1, 1],
          "stroke": [0.9, 0.96, 1, 1],
          "stroke_width": 0.05,
          "layer": 4
        },
        "sindri.script": {
          "source": "scripts/tween-demo.decay",
          "script": "TweenDemo",
          "properties": {
            "easing": "ease-in-out"
          }
        }
      }
    },
    {
      "id": "label-ease-in-out",
      "name": "label-ease-in-out",
      "transform_3d": {
        "position": [0.06, -0.31000000000000005, 0],
        "scale": [1, 1, 1]
      },
      "components": {
        "sindri.ui.text": {
          "text": "ease-in-out",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.055,
          "bounds": [0.7, 0.07],
          "anchor": "left",
          "layer": 20
        }
      }
    },
    {
      "id": "track-sequence",
      "name": "track-sequence",
      "transform_3d": {
        "position": [0, -2.3, -0.1],
        "scale": [5.5, 0.012, 1]
      },
      "components": {
        "sindri.shape": {
          "kind": "polygon",
          "count": 4,
          "fill": [0.15, 0.22, 0.3, 1],
          "stroke": [0, 0, 0, 0],
          "layer": 0
        }
      }
    },
    {
      "id": "sample-sequence",
      "name": "sample-sequence",
      "transform_3d": {
        "position": [0, -2.3, 0],
        "scale": [0.23, 0.23, 1]
      },
      "components": {
        "sindri.shape": {
          "kind": "polygon",
          "count": 4,
          "fill": [1.0, 0.52, 0.22, 1],
          "stroke": [0.9, 0.96, 1, 1],
          "stroke_width": 0.05,
          "layer": 4
        },
        "sindri.script": {
          "source": "scripts/tween-demo.decay",
          "script": "TweenSequence",
          "properties": {}
        }
      }
    },
    {
      "id": "label-sequence",
      "name": "label-sequence",
      "transform_3d": {
        "position": [0.06, -0.51, 0],
        "scale": [1, 1, 1]
      },
      "components": {
        "sindri.ui.text": {
          "text": "sequence + yoyo",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.055,
          "bounds": [0.7, 0.07],
          "anchor": "left",
          "layer": 20
        }
      }
    },
    {
      "id": "button-play",
      "name": "button-play",
      "transform_3d": {
        "position": [-0.3, -0.64, 0],
        "scale": [0.27, 0.09, 1]
      },
      "components": {
        "sindri.ui.shape": {
          "kind": "rect",
          "fill": [0.15, 0.23, 0.33, 1],
          "anchor": "center",
          "layer": 10
        },
        "sindri.ui.button": {
          "label": "PLAY"
        },
        "sindri.script": {
          "source": "scripts/tween-demo.decay",
          "script": "TweenButton",
          "properties": {
            "action": 1
          }
        }
      }
    },
    {
      "id": "button-play-label",
      "name": "button-play-label",
      "transform_3d": {
        "position": [-0.3, -0.64, 0],
        "scale": [1, 1, 1]
      },
      "components": {
        "sindri.ui.text": {
          "text": "PLAY",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.04,
          "bounds": [0.25, 0.08],
          "anchor": "center",
          "layer": 20
        }
      }
    },
    {
      "id": "button-pause",
      "name": "button-pause",
      "transform_3d": {
        "position": [0, -0.64, 0],
        "scale": [0.27, 0.09, 1]
      },
      "components": {
        "sindri.ui.shape": {
          "kind": "rect",
          "fill": [0.15, 0.23, 0.33, 1],
          "anchor": "center",
          "layer": 10
        },
        "sindri.ui.button": {
          "label": "PAUSE"
        },
        "sindri.script": {
          "source": "scripts/tween-demo.decay",
          "script": "TweenButton",
          "properties": {
            "action": 2
          }
        }
      }
    },
    {
      "id": "button-pause-label",
      "name": "button-pause-label",
      "transform_3d": {
        "position": [0, -0.64, 0],
        "scale": [1, 1, 1]
      },
      "components": {
        "sindri.ui.text": {
          "text": "PAUSE",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.04,
          "bounds": [0.25, 0.08],
          "anchor": "center",
          "layer": 20
        }
      }
    },
    {
      "id": "button-restart",
      "name": "button-restart",
      "transform_3d": {
        "position": [0.3, -0.64, 0],
        "scale": [0.27, 0.09, 1]
      },
      "components": {
        "sindri.ui.shape": {
          "kind": "rect",
          "fill": [0.15, 0.23, 0.33, 1],
          "anchor": "center",
          "layer": 10
        },
        "sindri.ui.button": {
          "label": "RESTART"
        },
        "sindri.script": {
          "source": "scripts/tween-demo.decay",
          "script": "TweenButton",
          "properties": {
            "action": 3
          }
        }
      }
    },
    {
      "id": "button-restart-label",
      "name": "button-restart-label",
      "transform_3d": {
        "position": [0.3, -0.64, 0],
        "scale": [1, 1, 1]
      },
      "components": {
        "sindri.ui.text": {
          "text": "RESTART",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.04,
          "bounds": [0.25, 0.08],
          "anchor": "center",
          "layer": 20
        }
      }
    },
    {
      "id": "button-cancel",
      "name": "button-cancel",
      "transform_3d": {
        "position": [-0.15, -0.77, 0],
        "scale": [0.27, 0.09, 1]
      },
      "components": {
        "sindri.ui.shape": {
          "kind": "rect",
          "fill": [0.15, 0.23, 0.33, 1],
          "anchor": "center",
          "layer": 10
        },
        "sindri.ui.button": {
          "label": "CANCEL"
        },
        "sindri.script": {
          "source": "scripts/tween-demo.decay",
          "script": "TweenButton",
          "properties": {
            "action": 4
          }
        }
      }
    },
    {
      "id": "button-cancel-label",
      "name": "button-cancel-label",
      "transform_3d": {
        "position": [-0.15, -0.77, 0],
        "scale": [1, 1, 1]
      },
      "components": {
        "sindri.ui.text": {
          "text": "CANCEL",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.04,
          "bounds": [0.25, 0.08],
          "anchor": "center",
          "layer": 20
        }
      }
    },
    {
      "id": "button-replace",
      "name": "button-replace",
      "transform_3d": {
        "position": [0.15, -0.77, 0],
        "scale": [0.27, 0.09, 1]
      },
      "components": {
        "sindri.ui.shape": {
          "kind": "rect",
          "fill": [0.15, 0.23, 0.33, 1],
          "anchor": "center",
          "layer": 10
        },
        "sindri.ui.button": {
          "label": "REPLACE"
        },
        "sindri.script": {
          "source": "scripts/tween-demo.decay",
          "script": "TweenButton",
          "properties": {
            "action": 5
          }
        }
      }
    },
    {
      "id": "button-replace-label",
      "name": "button-replace-label",
      "transform_3d": {
        "position": [0.15, -0.77, 0],
        "scale": [1, 1, 1]
      },
      "components": {
        "sindri.ui.text": {
          "text": "REPLACE",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.04,
          "bounds": [0.25, 0.08],
          "anchor": "center",
          "layer": 20
        }
      }
    },
    {
      "id": "hint",
      "name": "hint",
      "transform_3d": {
        "position": [0, -0.92, 0],
        "scale": [1, 1, 1]
      },
      "components": {
        "sindri.ui.text": {
          "text": "SPACE: PLAY   P: PAUSE   R: RESTART   C: CANCEL   V: REPLACE",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.03,
          "bounds": [1.1, 0.08],
          "anchor": "center",
          "layer": 20
        }
      }
    }
  ]
}
```

### `assets/ui/tween.weave`

```css
/* Presentation stays CSS-inspired; Decay owns gameplay tween playback. */
#title { font-size: 28px; }
#subtitle { font-size: 12px; }
#label-linear, #label-ease, #label-ease-in, #label-ease-out, #label-ease-in-out {
    font-size: 16px;
}
#button-play-label, #button-pause-label, #button-restart-label,
#button-cancel-label, #button-replace-label {
    font-size: 14px;
}
#hint { font-size: 10px; }

@media (min-height: 700px) {
    #title { font-size: 32px; }
    #label-linear, #label-ease, #label-ease-in, #label-ease-out, #label-ease-in-out {
        font-size: 18px;
    }
    #button-play-label, #button-pause-label, #button-restart-label,
    #button-cancel-label, #button-replace-label {
        font-size: 16px;
    }
    #hint { font-size: 11px; }
}
```

### `sindri.toml`

```toml
format_version = 1
[project]
name = "Tween Lab"
main_scene = "assets/tween.scene"
[assets]
include = ["fonts/Fonts-OFL.txt", "ui/tween.weave"]
```

### Binary files in this project

`assets/fonts/ChakraPetch-Regular.ttf`, `assets/fonts/Fonts-OFL.txt`


---

## Example: Sequence Stage (`examples/sequence`)

Every text file of the project follows, whole, under its path relative to the project folder. JSON files are shown with short arrays on one line; whitespace does not matter to Sindri.

### Sequence Stage

A feature example for sequences and the editor's Timeline. Open `sindri.toml`
in the editor or run the exported project in a WebGPU browser.

The Director entity carries a `sindri.sequence` with two sequences. **intro**
plays when the scene starts: the camera starts close and pulls back as the
ship flies in and swings upright, the ring blooms around it, and the title and
hint fade up. The camera is not a child of the Director; its track names it
from the top of the scene, `/camera`, which is how one director drives
anything in a scene. Its cues play a launch
sound, a landing click and mark the moment it is ready. **idle** loops after
it: the ship bobs and the ring breathes. Space or a tap replays the intro.

Select the Director and open the Timeline panel to see both. Every track is
authored there, not in a script: `assets/scripts/director.decay` only says
which sequence plays and prints the cues it hears with `Sequence.cued`. The
scene is saved at the intro's finished look, so the editor shows the stage;
turn on the Timeline's preview and click the ruler to see any moment of it.

`game/tests/the_sequence_demo_works.rs` opens the project, compiles its
script and plays it through the session every export runs: the ship lands,
the title fades up, the stage moves on to its idle loop, and Space replays.

See the Sequence surface (in `03-scripting-guide.md`).

### `assets/scripts/director.decay`

```rust
// A feature example: choreography authored in the Timeline, driven by a
// script that only says which sequence plays and listens for its cues.
script Director {
    var beats: number = 0.0;

    fn update(dt: number) {
        if Sequence.cued(this.entity, "launch") { print("Sequence cue launch"); }
        if Sequence.cued(this.entity, "landed") { print("Sequence cue landed"); }
        if Sequence.cued(this.entity, "ready") { print("Sequence cue ready"); }
        if Sequence.cued(this.entity, "beat") {
            this.beats += 1.0;
            print("Sequence idle beat " + this.beats);
        }
        // Once the intro has played out, the stage idles on a loop.
        if Sequence.playing(this.entity) == "intro" && Sequence.is_finished(this.entity) {
            Sequence.play(this.entity, "idle");
        }
        if Input.just_pressed("Space") || Pointer.just_pressed("Left") {
            Sequence.play(this.entity, "intro");
            Sequence.restart(this.entity);
            print("Sequence replay");
        }
    }
}
```

### `assets/sequence.scene`

```json
{
  "format_version": 10,
  "metadata": {
    "name": "Sequence Stage"
  },
  "entities": [
    {
      "id": "camera",
      "name": "camera",
      "transform_3d": {
        "position": [0, 0, 10],
        "scale": [1, 1, 1]
      },
      "components": {
        "sindri.camera": {
          "projection": "orthographic",
          "vertical_size": 8,
          "near": 0.1,
          "far": 100,
          "fit": "height"
        }
      }
    },
    {
      "id": "director",
      "name": "Director",
      "transform_3d": {
        "position": [0, 0, 0],
        "scale": [1, 1, 1]
      },
      "components": {
        "sindri.script": {
          "source": "scripts/director.decay",
          "script": "Director"
        },
        "sindri.sequence": {
          "sequences": {
            "intro": {
              "duration": 2.4,
              "looping": false,
              "tracks": [
                {
                  "target": "/camera",
                  "property": "sindri.camera/vertical_size",
                  "keys": [
                    {
                      "time": 0.0,
                      "value": 4.0,
                      "ease": "ease-in-out"
                    },
                    {
                      "time": 1.4,
                      "value": 8.0,
                      "ease": "linear"
                    }
                  ]
                },
                {
                  "target": "Ship",
                  "property": "position.x",
                  "keys": [
                    {
                      "time": 0,
                      "value": -7,
                      "ease": "ease-out"
                    },
                    {
                      "time": 1.2,
                      "value": 0,
                      "ease": "linear"
                    }
                  ]
                },
                {
                  "target": "Ship",
                  "property": "rotation",
                  "keys": [
                    {
                      "time": 0,
                      "value": -90,
                      "ease": "ease-out"
                    },
                    {
                      "time": 1.2,
                      "value": 0,
                      "ease": "linear"
                    }
                  ]
                },
                {
                  "target": "Ring",
                  "property": "scale",
                  "keys": [
                    {
                      "time": 1.2,
                      "value": 0.0,
                      "ease": "ease-out"
                    },
                    {
                      "time": 1.6,
                      "value": 2.6,
                      "ease": "ease-in-out"
                    },
                    {
                      "time": 2.0,
                      "value": 2.2,
                      "ease": "linear"
                    }
                  ]
                },
                {
                  "target": "Ring",
                  "property": "sindri.shape/stroke.3",
                  "keys": [
                    {
                      "time": 1.2,
                      "value": 1.0,
                      "ease": "linear"
                    },
                    {
                      "time": 2.0,
                      "value": 0.35,
                      "ease": "linear"
                    }
                  ]
                },
                {
                  "target": "Title",
                  "property": "sindri.ui.text/color.3",
                  "keys": [
                    {
                      "time": 0.6,
                      "value": 0.0,
                      "ease": "ease-in"
                    },
                    {
                      "time": 1.4,
                      "value": 1.0,
                      "ease": "linear"
                    }
                  ]
                },
                {
                  "target": "Title",
                  "property": "position.y",
                  "keys": [
                    {
                      "time": 0.6,
                      "value": 0.95,
                      "ease": "ease-out"
                    },
                    {
                      "time": 1.4,
                      "value": 0.85,
                      "ease": "linear"
                    }
                  ]
                },
                {
                  "target": "Hint",
                  "property": "sindri.ui.text/color.3",
                  "keys": [
                    {
                      "time": 2.0,
                      "value": 0.0,
                      "ease": "linear"
                    },
                    {
                      "time": 2.4,
                      "value": 0.8,
                      "ease": "linear"
                    }
                  ]
                }
              ],
              "cues": [
                {
                  "time": 0.0,
                  "name": "launch",
                  "sound": {
                    "clip": "audio/shot.wav",
                    "volume": 0.6
                  }
                },
                {
                  "time": 1.2,
                  "name": "landed",
                  "sound": {
                    "clip": "audio/click.wav",
                    "volume": 0.9
                  }
                },
                {
                  "time": 2.4,
                  "name": "ready"
                }
              ]
            },
            "idle": {
              "duration": 2.0,
              "looping": true,
              "tracks": [
                {
                  "target": "Ship",
                  "property": "position.y",
                  "keys": [
                    {
                      "time": 0,
                      "value": 0,
                      "ease": "ease-in-out"
                    },
                    {
                      "time": 1.0,
                      "value": 0.25,
                      "ease": "ease-in-out"
                    },
                    {
                      "time": 2.0,
                      "value": 0,
                      "ease": "linear"
                    }
                  ]
                },
                {
                  "target": "Ring",
                  "property": "scale",
                  "keys": [
                    {
                      "time": 0,
                      "value": 2.2,
                      "ease": "ease-in-out"
                    },
                    {
                      "time": 1.0,
                      "value": 2.4,
                      "ease": "ease-in-out"
                    },
                    {
                      "time": 2.0,
                      "value": 2.2,
                      "ease": "linear"
                    }
                  ]
                }
              ],
              "cues": [
                {
                  "time": 0.0,
                  "name": "beat"
                }
              ]
            }
          },
          "playing": "intro",
          "speed": 1.0
        }
      }
    },
    {
      "id": "ring",
      "name": "Ring",
      "transform_3d": {
        "position": [0, 0, -0.1],
        "scale": [2.2, 2.2, 1]
      },
      "components": {
        "sindri.shape": {
          "kind": "polygon",
          "count": 24,
          "fill": [0, 0, 0, 0],
          "stroke": [0.32, 0.86, 0.78, 1],
          "stroke_width": 0.06,
          "layer": 2
        }
      },
      "parent": "director"
    },
    {
      "id": "ship",
      "name": "Ship",
      "transform_3d": {
        "position": [0, 0, 0],
        "scale": [0.9, 0.9, 1]
      },
      "components": {
        "sindri.shape": {
          "kind": "polygon",
          "count": 3,
          "fill": [0.95, 0.64, 0.24, 1],
          "stroke": [1, 0.92, 0.78, 1],
          "stroke_width": 0.06,
          "layer": 6
        }
      },
      "parent": "director"
    },
    {
      "id": "title",
      "name": "Title",
      "transform_3d": {
        "position": [0, 0.95, 0],
        "scale": [1, 1, 1]
      },
      "components": {
        "sindri.ui.text": {
          "text": "SEQUENCE STAGE",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.085,
          "bounds": [1, 0.12],
          "anchor": "center",
          "color": [0.93, 0.92, 0.88, 1.0],
          "layer": 20
        }
      },
      "parent": "director"
    },
    {
      "id": "hint",
      "name": "Hint",
      "transform_3d": {
        "position": [0, 0.12, 0],
        "scale": [1, 1, 1]
      },
      "components": {
        "sindri.ui.text": {
          "text": "SPACE OR TAP TO REPLAY",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.04,
          "bounds": [1, 0.12],
          "anchor": "center",
          "color": [0.6, 0.66, 0.7, 0.8],
          "layer": 20
        }
      },
      "parent": "director"
    }
  ]
}
```

### `sindri.toml`

```toml
format_version = 1
[project]
name = "Sequence Stage"
main_scene = "assets/sequence.scene"
[assets]
include = ["fonts/Fonts-OFL.txt", "audio/shot.wav", "audio/click.wav"]
```

### Binary files in this project

`assets/audio/click.wav`, `assets/audio/shot.wav`, `assets/fonts/ChakraPetch-Regular.ttf`, `assets/fonts/Fonts-OFL.txt`


---

## Example: Spatial Query Lab (`examples/spatial`)

Every text file of the project follows, whole, under its path relative to the project folder. JSON files are shown with short arrays on one line; whitespace does not matter to Sindri.

### Spatial Query Lab

Open this directory as a Sindri project. Pages publishes it at
`/examples/spatial/`; the directory and homepage link it.

Move the cyan query origin with the arrow keys or hold an on-screen direction
button. The dashed circle shows the radius. Yellow is the nearest active
entity with the selected tag, even when it lies outside the radius. Green
targets are inside the radius. The bottom readout lists the actual query
snapshot in nearest-first order.

| Control | Key | Action |
| --- | --- | --- |
| Radius - / + | Q / E | Adjust by 0.5, bounded to [0, 3.5] |
| Tag | T | Cycle enemy, ally and a missing tag |
| A | A | Toggle target A's active state |
| Parent | P | Toggle the parent of C and D |
| Rotate C/D | O | Rotate their parent by 45 degrees |
| Reset | R | Restore origin, radius, tag, active state and parent rotation |

A and B begin equally distant; A appears first in stable world order. E sits
exactly on the initial radius boundary and is included. C and D are children
of a translated, rotated, scaled parent: the queries compare their composed
world positions. Gray untagged markers and letter labels stay visible when
their target or parent is inactive, so exclusion can be observed.

A tagged entity with no transform is also authored in the scene and omitted
from both spatial results. Selecting the missing tag shows `nearest: null`
and `within_radius: []`. Reducing radius to zero leaves nearest meaningful
while the radius snapshot is empty.

The gameplay calls `World.nearest(tag, Vec3)` and
`World.within_radius(tag, Vec3, radius)` directly. It does not implement another
distance scan or sort. Results use active authored tags, inclusive radius
boundaries, nearest-first order and stable world-order ties. Radius snapshots
refuse more than 8192 results; negative and NaN radii are errors. Positive
infinity is an unbounded query, though this UI deliberately exposes finite
radii. See the Decay scripting reference for the full contract.

The native regression opens the authored scene, compiles the scripts and
exercises ordering, boundary inclusion, parent transforms, active filtering,
null/empty results, reset and phone touch controls. CI also exports and plays
desktop and phone WebGPU builds under project and custom-domain base paths.
Spatial indexing, cone/box queries and physics queries are outside this demo.

### `assets/spatial.decay`

```rust
// The query APIs decide membership and order; Decay presents their snapshots.
script SpatialDemo {
    var radius: number = 2.0;
    var tag: String = "enemy";
    var a_active: bool = true;
    var parent_active: bool = true;
    var angle: number = 1.5707963;
    var report: bool = true;

    fn update(dt: number) {
        controls();
        var x = Input.axis("ArrowLeft", "ArrowRight");
        var y = Input.axis("ArrowDown", "ArrowUp");
        if Ui.is_held(World.find("left")) { x -= 1.0; }
        if Ui.is_held(World.find("right")) { x += 1.0; }
        if Ui.is_held(World.find("up")) { y += 1.0; }
        if Ui.is_held(World.find("down")) { y -= 1.0; }
        this.transform.position.x = clamp(this.transform.position.x + x * 2.5 * dt, -3.2, 3.2);
        this.transform.position.y = clamp(this.transform.position.y + y * 2.5 * dt, -2.2, 2.2);
        let ring = World.find("radius-ring");
        ring.transform.scale = Vec3(this.radius * 2.0, this.radius * 2.0, 1.0);
        // Styling known drawn samples must not touch the transformless tagged sample.
        let drawn: List<Entity> = [World.find("A"), World.find("B"), World.find("C"), World.find("D"), World.find("E")];
        for target in drawn { target.shape.fill = Color(1.0, 0.48, 0.2, 1.0); }
        let ally = World.find("F");
        ally.shape.fill = Color(0.3, 0.55, 1.0, 1.0);
        let origin = this.transform.world_position;
        let closest: Entity = World.nearest(this.tag, origin);
        let nearby: List<Entity> = World.within_radius(this.tag, origin, this.radius);
        var ordered = "";
        var rank: number = 1.0;
        for target in nearby {
            target.shape.fill = Color(0.4, 0.85, 0.4, 1.0);
            ordered = ordered + rank + ":" + name(target) + "  ";
            rank += 1.0;
        }
        var nearest_name = "null";
        Ui.set_text(World.find("nearest-label"), "nearest: null");
        if closest != null {
            nearest_name = name(closest);
            closest.shape.fill = Color(1.0, 0.85, 0.2, 1.0);
            Ui.set_text(World.find("nearest-label"), "nearest: " + name(closest));
        }
        if nearby.length == 0.0 { ordered = "within_radius: []"; }
        Ui.set_text(World.find("results-label"), ordered);
        Ui.set_text(World.find("query-label"), this.tag + " / radius " + this.radius.fixed(1) + " / " + nearby.length + " inside");
        if this.report {
            print("Spatial demo nearest " + nearest_name + " / " + ordered);
            this.report = false;
        }
    }
    fn name(target: Entity) -> String {
        if target == World.find("A") { return "A"; }
        if target == World.find("B") { return "B"; }
        if target == World.find("C") { return "C"; }
        if target == World.find("D") { return "D"; }
        if target == World.find("E") { return "E"; }
        return "F";
    }
    fn controls() {
        var changed = false;
        if Input.just_pressed("Q") || Ui.is_pressed(World.find("less")) { this.radius = max(0.0, this.radius - 0.5); changed = true; }
        if Input.just_pressed("E") || Ui.is_pressed(World.find("more")) { this.radius = min(3.5, this.radius + 0.5); changed = true; }
        if Input.just_pressed("T") || Ui.is_pressed(World.find("tag")) {
            if this.tag == "enemy" { this.tag = "ally"; }
            else if this.tag == "ally" { this.tag = "missing"; }
            else { this.tag = "enemy"; }
            changed = true;
        }
        if Input.just_pressed("A") || Ui.is_pressed(World.find("active")) { this.a_active = !this.a_active; changed = true; }
        if Input.just_pressed("P") || Ui.is_pressed(World.find("parent")) { this.parent_active = !this.parent_active; changed = true; }
        if Input.just_pressed("O") || Ui.is_pressed(World.find("rotate")) { this.angle += 0.7853982; changed = true; }
        if Input.just_pressed("R") || Ui.is_pressed(World.find("reset")) {
            this.radius = 2.0; this.tag = "enemy"; this.a_active = true; this.parent_active = true;
            this.angle = 1.5707963; this.transform.position = Vec3(0.0, 0.0, 0.0); changed = true;
        }
        if changed {
            this.report = true;
            World.set_active(World.find("A"), this.a_active);
            let parent = World.find("target-parent");
            World.set_active(parent, this.parent_active);
            parent.transform.rotation_z = this.angle;
            Ui.set_text(World.find("tag-label"), "TAG " + this.tag);
            Ui.set_text(World.find("active-label"), "A OFF");
            if this.a_active { Ui.set_text(World.find("active-label"), "A ON"); }
            Ui.set_text(World.find("parent-label"), "PARENT OFF");
            if this.parent_active { Ui.set_text(World.find("parent-label"), "PARENT ON"); }
            print("Spatial demo controls changed");
        }
    }
}
// Ghosts remain visible when a query target (or its parent) is inactive.
// Their labels use the same canonical world position as the queries.
script SpatialMarker {
    @export let target: String = "A";
    fn update(dt: number) {
        let entity = World.find(this.target);
        this.transform.position = entity.transform.world_position;
        let label = World.find(this.target + "-label");
        let p = entity.transform.world_position;
        let scale = min(1.0, Viewport.aspect) / 6.0;
        label.transform.position = Vec3(p.x * scale, p.y * scale + 0.06, 0.0);
    }
}
script SpatialLayout {
    @export let x: number = 0.0;
    @export let width: number = 1.0;
    @export let button: bool = false;
    fn update(dt: number) {
        let span = min(1.0, Viewport.aspect);
        this.transform.position.x = this.x * span;
        if this.button { this.transform.scale.x = this.width * span; }
    }
}
```

### `assets/spatial.scene`

```json
{
  "format_version": 10,
  "metadata": {
    "name": "Spatial Query Lab"
  },
  "entities": [
    {
      "id": "camera",
      "name": "camera",
      "components": {
        "sindri.camera": {
          "projection": "orthographic",
          "vertical_size": 12,
          "near": 0.1,
          "far": 100,
          "fit": "shorter"
        }
      },
      "transform_3d": {
        "position": [0, 0, 10],
        "scale": [1, 1, 1]
      }
    },
    {
      "id": "probe",
      "name": "probe",
      "components": {
        "sindri.script": {
          "source": "spatial.decay",
          "script": "SpatialDemo"
        }
      },
      "transform_3d": {
        "position": [0, 0, 0],
        "scale": [1, 1, 1]
      }
    },
    {
      "id": "probe-dot",
      "name": "probe-dot",
      "components": {
        "sindri.shape": {
          "kind": "ellipse",
          "fill": [0.2, 0.9, 1, 1],
          "layer": 5
        }
      },
      "transform_3d": {
        "position": [0, 0, 0.1],
        "scale": [0.16, 0.16, 1]
      },
      "parent": "probe"
    },
    {
      "id": "radius-ring",
      "name": "radius-ring",
      "components": {
        "sindri.shape": {
          "kind": "ellipse",
          "fill": [0.1, 0.7, 1, 0.025],
          "stroke": [0.1, 0.65, 0.8, 0.8],
          "stroke_width": 0.006,
          "dashes": 48,
          "layer": 1
        }
      },
      "transform_3d": {
        "position": [0, 0, -0.1],
        "scale": [4, 4, 1]
      },
      "parent": "probe"
    },
    {
      "id": "grid",
      "name": "grid",
      "components": {
        "sindri.shape": {
          "kind": "grid",
          "count": 8,
          "stroke": [0.035, 0.06, 0.09, 0.7],
          "stroke_width": 0.003,
          "layer": 0
        }
      },
      "transform_3d": {
        "position": [0, 0, -1],
        "scale": [8, 8, 1]
      }
    },
    {
      "id": "A",
      "name": "A",
      "components": {
        "sindri.tags": {
          "tags": ["enemy"]
        },
        "sindri.shape": {
          "kind": "ellipse",
          "fill": [1, 0.48, 0.2, 1],
          "layer": 3
        }
      },
      "transform_3d": {
        "position": [-1.5, 0, 0],
        "scale": [0.45, 0.45, 1]
      }
    },
    {
      "id": "B",
      "name": "B",
      "components": {
        "sindri.tags": {
          "tags": ["enemy"]
        },
        "sindri.shape": {
          "kind": "ellipse",
          "fill": [1, 0.48, 0.2, 1],
          "layer": 3
        }
      },
      "transform_3d": {
        "position": [1.5, 0, 0],
        "scale": [0.45, 0.45, 1]
      }
    },
    {
      "id": "target-parent",
      "name": "target-parent",
      "components": {},
      "transform_3d": {
        "position": [2, 0.5, 0],
        "scale": [2, 2, 1],
        "rotation": [0, 0, 0.70710678, 0.70710678]
      }
    },
    {
      "id": "C",
      "name": "C",
      "components": {
        "sindri.tags": {
          "tags": ["enemy"]
        },
        "sindri.shape": {
          "kind": "ellipse",
          "fill": [1, 0.48, 0.2, 1],
          "layer": 3
        }
      },
      "transform_3d": {
        "position": [-1, 0, 0],
        "scale": [0.225, 0.225, 1]
      },
      "parent": "target-parent"
    },
    {
      "id": "D",
      "name": "D",
      "components": {
        "sindri.tags": {
          "tags": ["enemy"]
        },
        "sindri.shape": {
          "kind": "ellipse",
          "fill": [1, 0.48, 0.2, 1],
          "layer": 3
        }
      },
      "transform_3d": {
        "position": [0.5, 0.5, 0],
        "scale": [0.225, 0.225, 1]
      },
      "parent": "target-parent"
    },
    {
      "id": "E",
      "name": "E",
      "components": {
        "sindri.tags": {
          "tags": ["enemy"]
        },
        "sindri.shape": {
          "kind": "ellipse",
          "fill": [1, 0.48, 0.2, 1],
          "layer": 3
        }
      },
      "transform_3d": {
        "position": [0, -2, 0],
        "scale": [0.45, 0.45, 1]
      }
    },
    {
      "id": "F",
      "name": "F",
      "components": {
        "sindri.tags": {
          "tags": ["ally"]
        },
        "sindri.shape": {
          "kind": "ellipse",
          "fill": [0.3, 0.55, 1, 1],
          "layer": 3
        }
      },
      "transform_3d": {
        "position": [0, 2.2, 0],
        "scale": [0.45, 0.45, 1]
      }
    },
    {
      "id": "nonspatial",
      "name": "nonspatial",
      "components": {
        "sindri.tags": {
          "tags": ["enemy"]
        }
      }
    },
    {
      "id": "A-ghost",
      "name": "A-ghost",
      "components": {
        "sindri.shape": {
          "kind": "ellipse",
          "stroke": [0.3, 0.35, 0.4, 0.8],
          "stroke_width": 0.05,
          "layer": 2
        },
        "sindri.script": {
          "source": "spatial.decay",
          "script": "SpatialMarker",
          "properties": {
            "target": "A"
          }
        }
      },
      "transform_3d": {
        "position": [0, 0, 0],
        "scale": [0.55, 0.55, 1]
      }
    },
    {
      "id": "A-label",
      "name": "A-label",
      "components": {
        "sindri.ui.text": {
          "text": "A",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.05,
          "bounds": [0.15, 0.08],
          "anchor": "center",
          "layer": 20
        }
      },
      "transform_3d": {
        "position": [0, 0, 0],
        "scale": [1, 1, 1]
      }
    },
    {
      "id": "B-ghost",
      "name": "B-ghost",
      "components": {
        "sindri.shape": {
          "kind": "ellipse",
          "stroke": [0.3, 0.35, 0.4, 0.8],
          "stroke_width": 0.05,
          "layer": 2
        },
        "sindri.script": {
          "source": "spatial.decay",
          "script": "SpatialMarker",
          "properties": {
            "target": "B"
          }
        }
      },
      "transform_3d": {
        "position": [0, 0, 0],
        "scale": [0.55, 0.55, 1]
      }
    },
    {
      "id": "B-label",
      "name": "B-label",
      "components": {
        "sindri.ui.text": {
          "text": "B",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.05,
          "bounds": [0.15, 0.08],
          "anchor": "center",
          "layer": 20
        }
      },
      "transform_3d": {
        "position": [0, 0, 0],
        "scale": [1, 1, 1]
      }
    },
    {
      "id": "C-ghost",
      "name": "C-ghost",
      "components": {
        "sindri.shape": {
          "kind": "ellipse",
          "stroke": [0.3, 0.35, 0.4, 0.8],
          "stroke_width": 0.05,
          "layer": 2
        },
        "sindri.script": {
          "source": "spatial.decay",
          "script": "SpatialMarker",
          "properties": {
            "target": "C"
          }
        }
      },
      "transform_3d": {
        "position": [0, 0, 0],
        "scale": [0.55, 0.55, 1]
      }
    },
    {
      "id": "C-label",
      "name": "C-label",
      "components": {
        "sindri.ui.text": {
          "text": "C",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.05,
          "bounds": [0.15, 0.08],
          "anchor": "center",
          "layer": 20
        }
      },
      "transform_3d": {
        "position": [0, 0, 0],
        "scale": [1, 1, 1]
      }
    },
    {
      "id": "D-ghost",
      "name": "D-ghost",
      "components": {
        "sindri.shape": {
          "kind": "ellipse",
          "stroke": [0.3, 0.35, 0.4, 0.8],
          "stroke_width": 0.05,
          "layer": 2
        },
        "sindri.script": {
          "source": "spatial.decay",
          "script": "SpatialMarker",
          "properties": {
            "target": "D"
          }
        }
      },
      "transform_3d": {
        "position": [0, 0, 0],
        "scale": [0.55, 0.55, 1]
      }
    },
    {
      "id": "D-label",
      "name": "D-label",
      "components": {
        "sindri.ui.text": {
          "text": "D",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.05,
          "bounds": [0.15, 0.08],
          "anchor": "center",
          "layer": 20
        }
      },
      "transform_3d": {
        "position": [0, 0, 0],
        "scale": [1, 1, 1]
      }
    },
    {
      "id": "E-ghost",
      "name": "E-ghost",
      "components": {
        "sindri.shape": {
          "kind": "ellipse",
          "stroke": [0.3, 0.35, 0.4, 0.8],
          "stroke_width": 0.05,
          "layer": 2
        },
        "sindri.script": {
          "source": "spatial.decay",
          "script": "SpatialMarker",
          "properties": {
            "target": "E"
          }
        }
      },
      "transform_3d": {
        "position": [0, 0, 0],
        "scale": [0.55, 0.55, 1]
      }
    },
    {
      "id": "E-label",
      "name": "E-label",
      "components": {
        "sindri.ui.text": {
          "text": "E",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.05,
          "bounds": [0.15, 0.08],
          "anchor": "center",
          "layer": 20
        }
      },
      "transform_3d": {
        "position": [0, 0, 0],
        "scale": [1, 1, 1]
      }
    },
    {
      "id": "F-ghost",
      "name": "F-ghost",
      "components": {
        "sindri.shape": {
          "kind": "ellipse",
          "stroke": [0.3, 0.35, 0.4, 0.8],
          "stroke_width": 0.05,
          "layer": 2
        },
        "sindri.script": {
          "source": "spatial.decay",
          "script": "SpatialMarker",
          "properties": {
            "target": "F"
          }
        }
      },
      "transform_3d": {
        "position": [0, 0, 0],
        "scale": [0.55, 0.55, 1]
      }
    },
    {
      "id": "F-label",
      "name": "F-label",
      "components": {
        "sindri.ui.text": {
          "text": "F",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.05,
          "bounds": [0.15, 0.08],
          "anchor": "center",
          "layer": 20
        }
      },
      "transform_3d": {
        "position": [0, 0, 0],
        "scale": [1, 1, 1]
      }
    },
    {
      "id": "title",
      "name": "title",
      "components": {
        "sindri.ui.text": {
          "text": "SPATIAL QUERY LAB",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.08,
          "bounds": [1.8, 0.08],
          "anchor": "center",
          "layer": 20
        },
        "sindri.script": {
          "source": "spatial.decay",
          "script": "SpatialLayout",
          "properties": {
            "x": 0
          }
        }
      },
      "transform_3d": {
        "position": [0, 0.9, 0],
        "scale": [1, 1, 1]
      }
    },
    {
      "id": "subtitle",
      "name": "subtitle",
      "components": {
        "sindri.ui.text": {
          "text": "World.nearest / World.within_radius",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.035,
          "bounds": [1.75, 0.08],
          "anchor": "center",
          "layer": 20
        },
        "sindri.script": {
          "source": "spatial.decay",
          "script": "SpatialLayout",
          "properties": {
            "x": 0
          }
        }
      },
      "transform_3d": {
        "position": [0, 0.79, 0],
        "scale": [1, 1, 1]
      }
    },
    {
      "id": "legend",
      "name": "legend",
      "components": {
        "sindri.ui.text": {
          "text": "YELLOW nearest / GREEN inside / GRAY ghosts",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.029,
          "bounds": [1.8, 0.08],
          "anchor": "center",
          "layer": 20
        },
        "sindri.script": {
          "source": "spatial.decay",
          "script": "SpatialLayout",
          "properties": {
            "x": 0
          }
        }
      },
      "transform_3d": {
        "position": [0, 0.69, 0],
        "scale": [1, 1, 1]
      }
    },
    {
      "id": "query-label",
      "name": "query-label",
      "components": {
        "sindri.ui.text": {
          "text": "enemy / radius 2.0 / 4 inside",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.033,
          "bounds": [1.8, 0.08],
          "anchor": "center",
          "layer": 20
        },
        "sindri.script": {
          "source": "spatial.decay",
          "script": "SpatialLayout",
          "properties": {
            "x": 0
          }
        }
      },
      "transform_3d": {
        "position": [0, 0.6, 0],
        "scale": [1, 1, 1]
      }
    },
    {
      "id": "nearest-label",
      "name": "nearest-label",
      "components": {
        "sindri.ui.text": {
          "text": "nearest: A",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.038,
          "bounds": [1.7, 0.08],
          "anchor": "center",
          "layer": 20
        },
        "sindri.script": {
          "source": "spatial.decay",
          "script": "SpatialLayout",
          "properties": {
            "x": 0
          }
        }
      },
      "transform_3d": {
        "position": [0, 0.51, 0],
        "scale": [1, 1, 1]
      }
    },
    {
      "id": "results-label",
      "name": "results-label",
      "components": {
        "sindri.ui.text": {
          "text": "1:A  2:B  3:D  4:E",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.037,
          "bounds": [1.75, 0.08],
          "anchor": "center",
          "layer": 20
        },
        "sindri.script": {
          "source": "spatial.decay",
          "script": "SpatialLayout",
          "properties": {
            "x": 0
          }
        }
      },
      "transform_3d": {
        "position": [0, -0.42, 0],
        "scale": [1, 1, 1]
      }
    },
    {
      "id": "less",
      "name": "less",
      "components": {
        "sindri.ui.shape": {
          "kind": "rect",
          "fill": [0.035, 0.075, 0.12, 0.96],
          "stroke": [0.15, 0.35, 0.5, 1],
          "stroke_width": 0.02,
          "corner_radius": 0.12,
          "anchor": "center",
          "layer": 10
        },
        "sindri.ui.button": {
          "label": "RADIUS -"
        },
        "sindri.script": {
          "source": "spatial.decay",
          "script": "SpatialLayout",
          "properties": {
            "x": -0.6,
            "width": 0.55,
            "button": true
          }
        }
      },
      "transform_3d": {
        "position": [-0.6, -0.54, 0],
        "scale": [0.55, 0.1, 1]
      }
    },
    {
      "id": "less-label",
      "name": "less-label",
      "components": {
        "sindri.ui.text": {
          "text": "RADIUS -",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.038,
          "bounds": [0.55, 0.08],
          "anchor": "center",
          "layer": 20
        },
        "sindri.script": {
          "source": "spatial.decay",
          "script": "SpatialLayout",
          "properties": {
            "x": -0.6
          }
        }
      },
      "transform_3d": {
        "position": [-0.6, -0.54, 0],
        "scale": [1, 1, 1]
      }
    },
    {
      "id": "tag",
      "name": "tag",
      "components": {
        "sindri.ui.shape": {
          "kind": "rect",
          "fill": [0.035, 0.075, 0.12, 0.96],
          "stroke": [0.15, 0.35, 0.5, 1],
          "stroke_width": 0.02,
          "corner_radius": 0.12,
          "anchor": "center",
          "layer": 10
        },
        "sindri.ui.button": {
          "label": "TAG enemy"
        },
        "sindri.script": {
          "source": "spatial.decay",
          "script": "SpatialLayout",
          "properties": {
            "x": 0,
            "width": 0.55,
            "button": true
          }
        }
      },
      "transform_3d": {
        "position": [0, -0.54, 0],
        "scale": [0.55, 0.1, 1]
      }
    },
    {
      "id": "tag-label",
      "name": "tag-label",
      "components": {
        "sindri.ui.text": {
          "text": "TAG enemy",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.038,
          "bounds": [0.55, 0.08],
          "anchor": "center",
          "layer": 20
        },
        "sindri.script": {
          "source": "spatial.decay",
          "script": "SpatialLayout",
          "properties": {
            "x": 0
          }
        }
      },
      "transform_3d": {
        "position": [0, -0.54, 0],
        "scale": [1, 1, 1]
      }
    },
    {
      "id": "more",
      "name": "more",
      "components": {
        "sindri.ui.shape": {
          "kind": "rect",
          "fill": [0.035, 0.075, 0.12, 0.96],
          "stroke": [0.15, 0.35, 0.5, 1],
          "stroke_width": 0.02,
          "corner_radius": 0.12,
          "anchor": "center",
          "layer": 10
        },
        "sindri.ui.button": {
          "label": "RADIUS +"
        },
        "sindri.script": {
          "source": "spatial.decay",
          "script": "SpatialLayout",
          "properties": {
            "x": 0.6,
            "width": 0.55,
            "button": true
          }
        }
      },
      "transform_3d": {
        "position": [0.6, -0.54, 0],
        "scale": [0.55, 0.1, 1]
      }
    },
    {
      "id": "more-label",
      "name": "more-label",
      "components": {
        "sindri.ui.text": {
          "text": "RADIUS +",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.038,
          "bounds": [0.55, 0.08],
          "anchor": "center",
          "layer": 20
        },
        "sindri.script": {
          "source": "spatial.decay",
          "script": "SpatialLayout",
          "properties": {
            "x": 0.6
          }
        }
      },
      "transform_3d": {
        "position": [0.6, -0.54, 0],
        "scale": [1, 1, 1]
      }
    },
    {
      "id": "active",
      "name": "active",
      "components": {
        "sindri.ui.shape": {
          "kind": "rect",
          "fill": [0.035, 0.075, 0.12, 0.96],
          "stroke": [0.15, 0.35, 0.5, 1],
          "stroke_width": 0.02,
          "corner_radius": 0.12,
          "anchor": "center",
          "layer": 10
        },
        "sindri.ui.button": {
          "label": "A ON"
        },
        "sindri.script": {
          "source": "spatial.decay",
          "script": "SpatialLayout",
          "properties": {
            "x": -0.6,
            "width": 0.55,
            "button": true
          }
        }
      },
      "transform_3d": {
        "position": [-0.6, -0.69, 0],
        "scale": [0.55, 0.1, 1]
      }
    },
    {
      "id": "active-label",
      "name": "active-label",
      "components": {
        "sindri.ui.text": {
          "text": "A ON",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.038,
          "bounds": [0.55, 0.08],
          "anchor": "center",
          "layer": 20
        },
        "sindri.script": {
          "source": "spatial.decay",
          "script": "SpatialLayout",
          "properties": {
            "x": -0.6
          }
        }
      },
      "transform_3d": {
        "position": [-0.6, -0.69, 0],
        "scale": [1, 1, 1]
      }
    },
    {
      "id": "parent",
      "name": "parent",
      "components": {
        "sindri.ui.shape": {
          "kind": "rect",
          "fill": [0.035, 0.075, 0.12, 0.96],
          "stroke": [0.15, 0.35, 0.5, 1],
          "stroke_width": 0.02,
          "corner_radius": 0.12,
          "anchor": "center",
          "layer": 10
        },
        "sindri.ui.button": {
          "label": "PARENT ON"
        },
        "sindri.script": {
          "source": "spatial.decay",
          "script": "SpatialLayout",
          "properties": {
            "x": 0,
            "width": 0.55,
            "button": true
          }
        }
      },
      "transform_3d": {
        "position": [0, -0.69, 0],
        "scale": [0.55, 0.1, 1]
      }
    },
    {
      "id": "parent-label",
      "name": "parent-label",
      "components": {
        "sindri.ui.text": {
          "text": "PARENT ON",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.038,
          "bounds": [0.55, 0.08],
          "anchor": "center",
          "layer": 20
        },
        "sindri.script": {
          "source": "spatial.decay",
          "script": "SpatialLayout",
          "properties": {
            "x": 0
          }
        }
      },
      "transform_3d": {
        "position": [0, -0.69, 0],
        "scale": [1, 1, 1]
      }
    },
    {
      "id": "rotate",
      "name": "rotate",
      "components": {
        "sindri.ui.shape": {
          "kind": "rect",
          "fill": [0.035, 0.075, 0.12, 0.96],
          "stroke": [0.15, 0.35, 0.5, 1],
          "stroke_width": 0.02,
          "corner_radius": 0.12,
          "anchor": "center",
          "layer": 10
        },
        "sindri.ui.button": {
          "label": "ROTATE C/D"
        },
        "sindri.script": {
          "source": "spatial.decay",
          "script": "SpatialLayout",
          "properties": {
            "x": 0.6,
            "width": 0.55,
            "button": true
          }
        }
      },
      "transform_3d": {
        "position": [0.6, -0.69, 0],
        "scale": [0.55, 0.1, 1]
      }
    },
    {
      "id": "rotate-label",
      "name": "rotate-label",
      "components": {
        "sindri.ui.text": {
          "text": "ROTATE C/D",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.038,
          "bounds": [0.55, 0.08],
          "anchor": "center",
          "layer": 20
        },
        "sindri.script": {
          "source": "spatial.decay",
          "script": "SpatialLayout",
          "properties": {
            "x": 0.6
          }
        }
      },
      "transform_3d": {
        "position": [0.6, -0.69, 0],
        "scale": [1, 1, 1]
      }
    },
    {
      "id": "left",
      "name": "left",
      "components": {
        "sindri.ui.shape": {
          "kind": "rect",
          "fill": [0.035, 0.075, 0.12, 0.96],
          "stroke": [0.15, 0.35, 0.5, 1],
          "stroke_width": 0.02,
          "corner_radius": 0.12,
          "anchor": "center",
          "layer": 10
        },
        "sindri.ui.button": {
          "label": "LEFT"
        },
        "sindri.script": {
          "source": "spatial.decay",
          "script": "SpatialLayout",
          "properties": {
            "x": -0.7,
            "width": 0.31,
            "button": true
          }
        }
      },
      "transform_3d": {
        "position": [-0.7, -0.84, 0],
        "scale": [0.31, 0.1, 1]
      }
    },
    {
      "id": "left-label",
      "name": "left-label",
      "components": {
        "sindri.ui.text": {
          "text": "LEFT",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.038,
          "bounds": [0.31, 0.08],
          "anchor": "center",
          "layer": 20
        },
        "sindri.script": {
          "source": "spatial.decay",
          "script": "SpatialLayout",
          "properties": {
            "x": -0.7
          }
        }
      },
      "transform_3d": {
        "position": [-0.7, -0.84, 0],
        "scale": [1, 1, 1]
      }
    },
    {
      "id": "up",
      "name": "up",
      "components": {
        "sindri.ui.shape": {
          "kind": "rect",
          "fill": [0.035, 0.075, 0.12, 0.96],
          "stroke": [0.15, 0.35, 0.5, 1],
          "stroke_width": 0.02,
          "corner_radius": 0.12,
          "anchor": "center",
          "layer": 10
        },
        "sindri.ui.button": {
          "label": "UP"
        },
        "sindri.script": {
          "source": "spatial.decay",
          "script": "SpatialLayout",
          "properties": {
            "x": -0.35,
            "width": 0.31,
            "button": true
          }
        }
      },
      "transform_3d": {
        "position": [-0.35, -0.84, 0],
        "scale": [0.31, 0.1, 1]
      }
    },
    {
      "id": "up-label",
      "name": "up-label",
      "components": {
        "sindri.ui.text": {
          "text": "UP",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.038,
          "bounds": [0.31, 0.08],
          "anchor": "center",
          "layer": 20
        },
        "sindri.script": {
          "source": "spatial.decay",
          "script": "SpatialLayout",
          "properties": {
            "x": -0.35
          }
        }
      },
      "transform_3d": {
        "position": [-0.35, -0.84, 0],
        "scale": [1, 1, 1]
      }
    },
    {
      "id": "reset",
      "name": "reset",
      "components": {
        "sindri.ui.shape": {
          "kind": "rect",
          "fill": [0.035, 0.075, 0.12, 0.96],
          "stroke": [0.15, 0.35, 0.5, 1],
          "stroke_width": 0.02,
          "corner_radius": 0.12,
          "anchor": "center",
          "layer": 10
        },
        "sindri.ui.button": {
          "label": "RESET"
        },
        "sindri.script": {
          "source": "spatial.decay",
          "script": "SpatialLayout",
          "properties": {
            "x": 0,
            "width": 0.31,
            "button": true
          }
        }
      },
      "transform_3d": {
        "position": [0, -0.84, 0],
        "scale": [0.31, 0.1, 1]
      }
    },
    {
      "id": "reset-label",
      "name": "reset-label",
      "components": {
        "sindri.ui.text": {
          "text": "RESET",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.038,
          "bounds": [0.31, 0.08],
          "anchor": "center",
          "layer": 20
        },
        "sindri.script": {
          "source": "spatial.decay",
          "script": "SpatialLayout",
          "properties": {
            "x": 0
          }
        }
      },
      "transform_3d": {
        "position": [0, -0.84, 0],
        "scale": [1, 1, 1]
      }
    },
    {
      "id": "down",
      "name": "down",
      "components": {
        "sindri.ui.shape": {
          "kind": "rect",
          "fill": [0.035, 0.075, 0.12, 0.96],
          "stroke": [0.15, 0.35, 0.5, 1],
          "stroke_width": 0.02,
          "corner_radius": 0.12,
          "anchor": "center",
          "layer": 10
        },
        "sindri.ui.button": {
          "label": "DOWN"
        },
        "sindri.script": {
          "source": "spatial.decay",
          "script": "SpatialLayout",
          "properties": {
            "x": 0.35,
            "width": 0.31,
            "button": true
          }
        }
      },
      "transform_3d": {
        "position": [0.35, -0.84, 0],
        "scale": [0.31, 0.1, 1]
      }
    },
    {
      "id": "down-label",
      "name": "down-label",
      "components": {
        "sindri.ui.text": {
          "text": "DOWN",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.038,
          "bounds": [0.31, 0.08],
          "anchor": "center",
          "layer": 20
        },
        "sindri.script": {
          "source": "spatial.decay",
          "script": "SpatialLayout",
          "properties": {
            "x": 0.35
          }
        }
      },
      "transform_3d": {
        "position": [0.35, -0.84, 0],
        "scale": [1, 1, 1]
      }
    },
    {
      "id": "right",
      "name": "right",
      "components": {
        "sindri.ui.shape": {
          "kind": "rect",
          "fill": [0.035, 0.075, 0.12, 0.96],
          "stroke": [0.15, 0.35, 0.5, 1],
          "stroke_width": 0.02,
          "corner_radius": 0.12,
          "anchor": "center",
          "layer": 10
        },
        "sindri.ui.button": {
          "label": "RIGHT"
        },
        "sindri.script": {
          "source": "spatial.decay",
          "script": "SpatialLayout",
          "properties": {
            "x": 0.7,
            "width": 0.31,
            "button": true
          }
        }
      },
      "transform_3d": {
        "position": [0.7, -0.84, 0],
        "scale": [0.31, 0.1, 1]
      }
    },
    {
      "id": "right-label",
      "name": "right-label",
      "components": {
        "sindri.ui.text": {
          "text": "RIGHT",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.038,
          "bounds": [0.31, 0.08],
          "anchor": "center",
          "layer": 20
        },
        "sindri.script": {
          "source": "spatial.decay",
          "script": "SpatialLayout",
          "properties": {
            "x": 0.7
          }
        }
      },
      "transform_3d": {
        "position": [0.7, -0.84, 0],
        "scale": [1, 1, 1]
      }
    },
    {
      "id": "hint",
      "name": "hint",
      "components": {
        "sindri.ui.text": {
          "text": "ARROWS move / R reset / C,D share a parent",
          "font": "fonts/ChakraPetch-Regular.ttf",
          "font_size": 0.03,
          "bounds": [1.8, 0.08],
          "anchor": "center",
          "layer": 20
        },
        "sindri.script": {
          "source": "spatial.decay",
          "script": "SpatialLayout",
          "properties": {
            "x": 0
          }
        }
      },
      "transform_3d": {
        "position": [0, -0.96, 0],
        "scale": [1, 1, 1]
      }
    }
  ]
}
```

### `assets/ui/spatial.weave`

```css
#title { font-size: 26px; }
#subtitle, #legend, #query-label, #nearest-label, #results-label, #hint { font-size: 12px; }
#less-label, #more-label, #tag-label, #active-label, #parent-label,
#rotate-label, #reset-label, #left-label, #up-label, #down-label, #right-label {
    font-size: 12px;
}
#A-label, #B-label, #C-label, #D-label, #E-label, #F-label { font-size: 16px; }
@media (min-height: 700px) {
    #title { font-size: 30px; }
    #less-label, #more-label, #tag-label, #active-label, #parent-label,
    #rotate-label, #reset-label, #left-label, #up-label, #down-label, #right-label {
        font-size: 14px;
    }
}
```

### `sindri.toml`

```toml
format_version = 1
[project]
name = "Spatial Query Lab"
main_scene = "assets/spatial.scene"
[assets]
include = ["fonts/Fonts-OFL.txt", "ui/spatial.weave"]
```

### Binary files in this project

`assets/fonts/ChakraPetch-Regular.ttf`, `assets/fonts/Fonts-OFL.txt`


---

## Example: Weave Control Room (`examples/ui`)

Every text file of the project follows, whole, under its path relative to the project folder. JSON files are shown with short arrays on one line; whitespace does not matter to Sindri.

### Weave Control Room

Feature example for shared screen widgets: switches, a checkbox, a locked
control, a difficulty radio group, a sector dropdown, a single-line callsign
field with a caret and selection, keyboard and gamepad focus, and a clipped
vertical scroll archive. Interaction and value presentation are Decay; styling
is Weave. Open this project in the editor or export it with
`sindri-export examples/ui`.

Use Tab/Shift+Tab or the arrow keys (or a pad's d-pad) to move focus,
Space/Enter (or South) to press, and Escape (or East) to back out. In the
callsign field, Left/Right/Home/End move the caret, Shift selects, Backspace
and Delete erase, Ctrl or Cmd with A/C/X/V select all, copy, cut and paste,
and Enter submits. In a browser the field also takes IME composition, a paste
and a phone's on-screen keyboard. Scroll the archive with a wheel or trackpad,
or drag it.

### `assets/ui/widgets.weave`

```css
/* Weave Control Room: every size, color and state the widgets show.
   Decay writes values and words; this decides how they look. */

:root {
    --ink: #e6f0f7;
    --muted: #8aa1b6;
    --accent: #5fe3cf;
    --accent-deep: #0f3b3a;
    --panel: #0b1522;
    --well: #07101b;
    --line: #22384c;
    --control: #0f1c2b;
    --warm: #f0b35a;
}

sindri.ui.text { color: var(--ink); }
/* Wrapped to its box, so a label set to start lines up with its box's edge. */
.control-label, .input-text, .row-label, .row-number, .section-title, .small, .dropdown-label, .option-label { text-wrap: word; }

.room {
    width: 100vw;
    height: 100vh;
    padding: 26px;
    direction: column;
    gap: 14px;
    justify-content: start;
    align-items: center;
}

.masthead {
    width: 100%;
    height: 56px;
    direction: column;
    gap: 4px;
    justify-content: center;
    align-items: center;
}
.heading { width: 100%; height: 34px; font-size: 28px; letter-spacing: 4px; text-align: center; }
.subtitle { width: 100%; height: 22px; font-size: 14px; color: var(--muted); text-align: center; }

.deck {
    width: 100%;
    height: 368px;
    direction: row;
    gap: 28px;
    justify-content: center;
    align-items: start;
}

.panel {
    width: 46%;
    max-width: 460px;
    height: 368px;
    padding: 14px;
    direction: column;
    gap: 8px;
    justify-content: start;
    align-items: center;
    background: var(--panel);
    border-color: var(--line);
    border-width: 1px;
    border-radius: 14px;
}
.section-title { width: 100%; height: 26px; font-size: 14px; letter-spacing: 3px; color: var(--accent); text-align: start; }
.small { width: 100%; height: 18px; font-size: 11px; color: var(--muted); text-align: start; }
.readout { width: 100%; height: 20px; font-size: 12px; letter-spacing: 2px; color: var(--warm); text-align: start; text-wrap: word; }

/* Toggle, checkbox and field share one control shape. */
.control {
    width: 100%;
    height: 38px;
    padding: 0px 14px;
    direction: row;
    justify-content: space-between;
    align-items: center;
    background: var(--control);
    border-color: var(--line);
    border-width: 1px;
    border-radius: 9px;
}
.control:hover { background: #142638; border-color: #3b5a73; }
.control:focus { border-color: var(--accent); border-width: 2px; }
.control:disabled { background: #0a121c; border-color: #17232f; }
.control-label { width: 72%; height: 22px; font-size: 15px; text-align: start; }
.control:disabled .control-label { color: #4d6072; }

.track { width: 46px; height: 24px; background: #1d2e40; border-radius: 12px; border-color: #2f4658; border-width: 1px; }
.knob { width: 18px; height: 18px; x: -11px; background: var(--muted); border-radius: 9px; }
.switch:checked .track { background: var(--accent-deep); border-color: var(--accent); }
.switch:checked .knob { x: 11px; background: var(--accent); }
.switch:disabled .knob { background: #33485a; }

.box { width: 24px; height: 24px; background: #0a1520; border-color: #3b5a73; border-width: 2px; border-radius: 5px; }
.tick { width: 12px; height: 12px; background: transparent; border-radius: 2px; }
.checkbox:checked .box { border-color: var(--accent); }
.checkbox:checked .tick { background: var(--accent); }

.input { justify-content: start; background: var(--well); }
.input-text { width: 100%; height: 22px; font-size: 15px; color: var(--ink); text-align: start; }

/* A radio group: three chips, the chosen one lit. */
.radio-row { width: 100%; height: 38px; direction: row; justify-content: space-between; align-items: center; }
.radio-label { width: 30%; }
.chip { width: 21%; height: 32px; background: var(--control); border-color: var(--line); border-width: 1px; border-radius: 16px; }
.chip:hover { border-color: #3b5a73; }
.chip:focus { border-color: var(--accent); border-width: 2px; }
.chip:checked { background: var(--accent-deep); border-color: var(--accent); }
.chip-label { width: 100%; height: 18px; font-size: 13px; color: var(--muted); text-align: center; }
.chip:checked .chip-label { color: var(--accent); }

/* A dropdown: the header, and the list the engine shows while it is open. */
.dropdown { justify-content: start; }
.dropdown:open { border-color: var(--accent); }
.dropdown-label { width: 82%; height: 22px; font-size: 15px; text-align: start; }
.chevron { width: 20px; height: 20px; x: 180px; font-size: 14px; color: var(--muted); text-align: center; }
.dropdown:open .chevron { color: var(--accent); }
.dropdown-list {
    width: 100%; height: 186px; y: -114px;
    padding: 6px; gap: 4px;
    background: #0a1524; border-color: var(--accent); border-width: 1px; border-radius: 9px;
    box-shadow: 0px 12px 28px #000000b0;
}
.option { width: 100%; height: 30px; flex-shrink: 0; background: transparent; border-radius: 6px; }
.option:hover, .option:focus { background: #17334a; }
.option:checked { background: var(--accent-deep); }
.option-label { width: 90%; height: 18px; font-size: 14px; text-align: start; }
.option:checked .option-label { color: var(--accent); }

/* The archive: a clipped list that is as tall as its rows. */
.scroll {
    width: 100%;
    height: 270px;
    padding: 6px;
    gap: 5px;
    background: var(--well);
    border-color: var(--line);
    border-width: 1px;
    border-radius: 8px;
}
.archive-row {
    width: 100%;
    height: 36px;
    padding: 0px 12px;
    flex-shrink: 0;
    gap: 14px;
    background: #10202f;
    border-color: #1c3043;
    border-width: 1px;
    border-radius: 6px;
}
.archive-row:hover { background: #17334a; border-color: #3f7a8a; }
.archive-row:active { background: var(--accent-deep); border-color: var(--accent); }
.archive-row:disabled { background: #0b141e; border-color: #141f2a; }
.row-number { width: 28px; height: 18px; font-size: 12px; color: var(--accent); text-align: start; }
.row-label { width: 78%; height: 18px; font-size: 14px; text-align: start; }
.archive-row:disabled .row-number, .archive-row:disabled .row-label { color: #45596b; }

.status { width: 100%; height: 26px; font-size: 16px; letter-spacing: 1px; color: var(--accent); text-align: center; }
.footer { width: 100%; height: 18px; font-size: 11px; letter-spacing: 1px; color: var(--muted); text-align: center; }

/* Phones: the two panels stack, and everything takes the width. */
@media (orientation: portrait) {
    .room { padding: 16px; gap: 10px; }
    .masthead { height: 54px; }
    .heading { height: 30px; font-size: 21px; letter-spacing: 2px; }
    .subtitle { height: 18px; font-size: 12px; }
    .deck { height: 652px; direction: column; gap: 10px; align-items: center; }
    .panel { width: 100%; padding: 12px; gap: 6px; }
    .settings { height: 336px; }
    .inventory { height: 306px; }
    .scroll { height: 234px; }
    .chevron { x: 140px; }
    .control-label { font-size: 14px; }
    .status { height: 22px; font-size: 13px; }
    .footer { height: 16px; font-size: 9px; letter-spacing: 0px; }
}

/* Short landscape: a phone on its side. Tighter rows, same two panels. */
@media (orientation: landscape) and (max-height: 460px) {
    .room { padding: 10px; gap: 6px; }
    .masthead { height: 40px; gap: 0px; }
    .heading { height: 26px; font-size: 20px; }
    .subtitle { height: 14px; font-size: 11px; }
    .deck { height: 262px; }
    .panel { height: 262px; padding: 8px; gap: 3px; }
    .radio-row { height: 26px; }
    .chip { height: 22px; }
    .chip-label { font-size: 11px; }
    .dropdown-list { height: 150px; y: -92px; }
    .option { height: 24px; }
    .option-label { font-size: 12px; }
    .section-title { height: 18px; font-size: 12px; }
    .control { height: 26px; }
    .control-label { font-size: 13px; }
    .scroll { height: 196px; }
    .archive-row { height: 30px; }
    .small { height: 14px; font-size: 10px; }
    .readout { height: 14px; font-size: 10px; }
    .status { height: 18px; font-size: 12px; }
    .footer { height: 14px; font-size: 9px; }
}
```

### `assets/ui.decay`

```rust
// The Control Room's behaviour: what each widget's value says, and what
// happens when the person changes one. Weave owns every size, color and
// state; nothing here positions or paints anything.

script WidgetDemo {
    var assist: Entity = null;
    var telemetry: Entity = null;
    var callsign: Entity = null;
    var archive: Entity = null;
    var sector: Entity = null;
    var sectors: List<String> = ["Outer Drift", "Ember Belt", "Violet Wake", "Null Lattice", "Core Approach"];
    var difficulties: List<String> = ["cadet", "pilot", "ace"];

    fn start() {
        this.assist = World.find("toggle");
        this.telemetry = World.find("checkbox");
        this.callsign = World.find("callsign");
        this.archive = World.find("archive");
        this.sector = World.find("sector");
        show_switches();
        show_callsign();
        print("UI demo ready");
    }

    fn update(dt: number) {
        if Ui.changed(this.assist) {
            print("UI toggle changed " + Ui.is_checked(this.assist));
            show_switches();
        }
        if Ui.changed(this.telemetry) {
            print("UI checkbox changed " + Ui.is_checked(this.telemetry));
            show_switches();
        }
        // Locked, so this never fires; the browser smoke listens for it.
        if Ui.changed(World.find("autopilot")) {
            print("UI autopilot changed");
        }
        if Ui.changed(this.callsign) {
            print("UI text changed " + Ui.input_text(this.callsign));
        }
        if Ui.submitted(this.callsign) {
            let name = Ui.input_text(this.callsign);
            if name == "" {
                status("CALLSIGN / type one first");
            } else {
                status("CALLSIGN / " + name + " confirmed");
            }
            print("UI submitted " + name);
        }
        if Ui.changed(this.sector) {
            let name = this.sectors[Ui.selected(this.sector)];
            Ui.set_text(World.find("sector-text"), "Sector  / " + name);
            print("UI sector chosen " + name);
        }
        for d in this.difficulties {
            let chip = World.find("difficulty-" + d);
            if Ui.changed(chip) && Ui.is_checked(chip) {
                print("UI difficulty " + d);
            }
        }
        if Ui.changed(this.archive) {
            print("UI archive scrolled " + Ui.scroll_offset(this.archive).fixed(2));
        }
        // Every step, because focus comes and goes without a change event:
        // the caret shows while the field has the keyboard.
        show_callsign();
        show_focus();
    }

    fn show_focus() {
        var focused = "none";
        if Ui.is_focused(this.assist) {
            focused = "flight assist";
        } else if Ui.is_focused(this.telemetry) {
            focused = "mission telemetry";
        } else if Ui.is_focused(this.callsign) {
            focused = "callsign / typing";
        } else if Ui.is_focused(this.sector) {
            focused = "sector";
        } else if Ui.is_focused(World.find("difficulty-cadet")) {
            focused = "difficulty / cadet";
        } else if Ui.is_focused(World.find("difficulty-pilot")) {
            focused = "difficulty / pilot";
        } else if Ui.is_focused(World.find("difficulty-ace")) {
            focused = "difficulty / ace";
        } else {
            for i in 0..19 {
                if Ui.is_focused(World.find("row-" + i)) {
                    focused = "archive row " + (i + 1);
                }
            }
        }
        Ui.set_text(World.find("focus-readout"), "FOCUS / " + focused);
    }

    fn show_switches() {
        if Ui.is_checked(this.assist) {
            Ui.set_text(World.find("toggle-text"), "Flight assist  / on");
        } else {
            Ui.set_text(World.find("toggle-text"), "Flight assist  / off");
        }
        if Ui.is_checked(this.telemetry) {
            Ui.set_text(World.find("checkbox-text"), "Mission telemetry  / logging");
        } else {
            Ui.set_text(World.find("checkbox-text"), "Mission telemetry  / silent");
        }
    }

    // The callsign as typed, with a caret where the person is typing, or
    // the selection in brackets.
    fn show_callsign() {
        let name = Ui.input_text(this.callsign);
        let label = World.find("callsign-text");
        if !Ui.is_focused(this.callsign) {
            if name == "" { Ui.set_text(label, "Your callsign"); }
            else { Ui.set_text(label, name); }
            return;
        }
        let start = Ui.selection_start(this.callsign);
        let end = Ui.selection_end(this.callsign);
        let before = name.slice(0, start);
        let after = name.slice(end, name.length);
        if start == end {
            Ui.set_text(label, before + "|" + after);
        } else {
            Ui.set_text(label, before + "[" + name.slice(start, end) + "]" + after);
        }
    }


    fn status(message: String) {
        Ui.set_text(World.find("status"), message);
    }
}

script ArchiveRow {
    @export let module: String = "";

    fn update(dt: number) {
        if Ui.is_pressed(this.entity) {
            Ui.set_text(World.find("status"), "SELECTED / " + this.module);
            print("UI selected " + this.module);
        }
    }
}
```

### `assets/widgets.scene`

```json
{
  "format_version": 10,
  "metadata": {"name": "Weave Control Room"},
  "entities": [
    {"id": "controller", "name": "controller", "components": {"sindri.script": {"source": "ui.decay", "script": "WidgetDemo", "properties": {}}}},
    {"id": "environment", "name": "environment", "components": {"sindri.environment": {"background": [0.004, 0.009, 0.018, 1.0]}}},
    {"id": "room", "name": "room", "transform_3d": {"scale": [3.3, 1.9, 1.0]}, "components": {"weave.style": {"classes": ["room"]}, "sindri.ui.layout": {"direction": "column", "spacing": 0.04, "justify": "start", "align": "center"}}},
    {"id": "masthead", "name": "masthead", "parent": "room", "transform_3d": {"scale": [3.2, 0.2, 1.0]}, "components": {"weave.style": {"classes": ["masthead"]}, "sindri.ui.layout": {"direction": "column", "spacing": 0.0, "justify": "center", "align": "center"}}},
    {"id": "heading", "name": "heading", "parent": "masthead", "transform_3d": {"scale": [2.0, 0.1, 1.0]}, "components": {"weave.style": {"classes": ["heading"]}, "sindri.ui.text": {"text": "WEAVE / CONTROL ROOM", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.06, "bounds": [1.0, 0.08], "anchor": "center", "layer": 20, "line_align": "center"}}},
    {"id": "subtitle", "name": "subtitle", "parent": "masthead", "transform_3d": {"scale": [2.0, 0.07, 1.0]}, "components": {"weave.style": {"classes": ["subtitle"]}, "sindri.ui.text": {"text": "Engine widgets, styled by Weave, driven by Decay.", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.035, "bounds": [1.0, 0.08], "anchor": "center", "layer": 20, "line_align": "center"}}},
    {"id": "deck", "name": "deck", "parent": "room", "transform_3d": {"scale": [3.2, 1.3, 1.0]}, "components": {"weave.style": {"classes": ["deck"]}, "sindri.ui.layout": {"direction": "row", "spacing": 0.08, "justify": "center", "align": "start"}}},
    {"id": "settings", "name": "settings", "parent": "deck", "transform_3d": {"scale": [1.4, 1.3, 1.0]}, "components": {"weave.style": {"classes": ["panel", "settings"]}, "sindri.ui.shape": {"kind": "rect", "fill": [0.05, 0.09, 0.14, 1.0], "anchor": "center", "layer": 10}, "sindri.ui.layout": {"direction": "column", "spacing": 0.03, "justify": "start", "align": "center"}}},
    {"id": "settings-heading", "name": "settings-heading", "parent": "settings", "transform_3d": {"scale": [1.3, 0.08, 1.0]}, "components": {"weave.style": {"classes": ["section-title"]}, "sindri.ui.text": {"text": "01 / FLIGHT SETTINGS", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.035, "bounds": [1.0, 0.08], "anchor": "center", "layer": 20, "line_align": "center"}}},
    {"id": "toggle", "name": "toggle", "parent": "settings", "transform_3d": {"scale": [1.3, 0.14, 1.0]}, "components": {"weave.style": {"classes": ["control", "switch"]}, "sindri.ui.shape": {"kind": "rect", "fill": [0.05, 0.09, 0.14, 1.0], "anchor": "center", "layer": 11}, "sindri.ui.toggle": {"label": "Flight assist", "checked": true, "disabled": false, "autofocus": true}, "sindri.ui.layout": {"direction": "row", "spacing": 0.03, "justify": "space_between", "align": "center"}}},
    {"id": "toggle-text", "name": "toggle-text", "parent": "toggle", "transform_3d": {"scale": [0.9, 0.08, 1.0]}, "components": {"weave.style": {"classes": ["control-label"]}, "sindri.ui.text": {"text": "Flight assist", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.034, "bounds": [0.9, 0.08], "anchor": "center", "layer": 20, "line_align": "left"}}},
    {"id": "toggle-track", "name": "toggle-track", "parent": "toggle", "transform_3d": {"scale": [0.16, 0.07, 1.0]}, "components": {"weave.style": {"classes": ["track"]}, "sindri.ui.shape": {"kind": "rect", "fill": [0.05, 0.09, 0.14, 1.0], "anchor": "center", "layer": 12}}},
    {"id": "toggle-knob", "name": "toggle-knob", "parent": "toggle-track", "transform_3d": {"scale": [0.055, 0.055, 1.0]}, "components": {"weave.style": {"classes": ["knob"]}, "sindri.ui.shape": {"kind": "rect", "fill": [0.05, 0.09, 0.14, 1.0], "anchor": "center", "layer": 13}}},
    {"id": "checkbox", "name": "checkbox", "parent": "settings", "transform_3d": {"scale": [1.3, 0.14, 1.0]}, "components": {"weave.style": {"classes": ["control", "checkbox"]}, "sindri.ui.shape": {"kind": "rect", "fill": [0.05, 0.09, 0.14, 1.0], "anchor": "center", "layer": 11}, "sindri.ui.toggle": {"label": "Mission telemetry", "checked": false, "disabled": false, "autofocus": false}, "sindri.ui.layout": {"direction": "row", "spacing": 0.03, "justify": "space_between", "align": "center"}}},
    {"id": "checkbox-text", "name": "checkbox-text", "parent": "checkbox", "transform_3d": {"scale": [0.9, 0.08, 1.0]}, "components": {"weave.style": {"classes": ["control-label"]}, "sindri.ui.text": {"text": "Mission telemetry", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.034, "bounds": [0.9, 0.08], "anchor": "center", "layer": 20, "line_align": "left"}}},
    {"id": "checkbox-box", "name": "checkbox-box", "parent": "checkbox", "transform_3d": {"scale": [0.07, 0.07, 1.0]}, "components": {"weave.style": {"classes": ["box"]}, "sindri.ui.shape": {"kind": "rect", "fill": [0.05, 0.09, 0.14, 1.0], "anchor": "center", "layer": 12}}},
    {"id": "checkbox-tick", "name": "checkbox-tick", "parent": "checkbox-box", "transform_3d": {"scale": [0.035, 0.035, 1.0]}, "components": {"weave.style": {"classes": ["tick"]}, "sindri.ui.shape": {"kind": "rect", "fill": [0.05, 0.09, 0.14, 1.0], "anchor": "center", "layer": 13}}},
    {"id": "autopilot", "name": "autopilot", "parent": "settings", "transform_3d": {"scale": [1.3, 0.14, 1.0]}, "components": {"weave.style": {"classes": ["control", "switch", "locked"]}, "sindri.ui.shape": {"kind": "rect", "fill": [0.05, 0.09, 0.14, 1.0], "anchor": "center", "layer": 11}, "sindri.ui.toggle": {"label": "Autopilot", "checked": false, "disabled": true, "autofocus": false}, "sindri.ui.layout": {"direction": "row", "spacing": 0.03, "justify": "space_between", "align": "center"}}},
    {"id": "autopilot-text", "name": "autopilot-text", "parent": "autopilot", "transform_3d": {"scale": [0.9, 0.08, 1.0]}, "components": {"weave.style": {"classes": ["control-label"]}, "sindri.ui.text": {"text": "Autopilot  / locked", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.034, "bounds": [0.9, 0.08], "anchor": "center", "layer": 20, "line_align": "left"}}},
    {"id": "autopilot-track", "name": "autopilot-track", "parent": "autopilot", "transform_3d": {"scale": [0.16, 0.07, 1.0]}, "components": {"weave.style": {"classes": ["track"]}, "sindri.ui.shape": {"kind": "rect", "fill": [0.05, 0.09, 0.14, 1.0], "anchor": "center", "layer": 12}}},
    {"id": "autopilot-knob", "name": "autopilot-knob", "parent": "autopilot-track", "transform_3d": {"scale": [0.055, 0.055, 1.0]}, "components": {"weave.style": {"classes": ["knob"]}, "sindri.ui.shape": {"kind": "rect", "fill": [0.05, 0.09, 0.14, 1.0], "anchor": "center", "layer": 13}}},
    {"id": "difficulty", "name": "difficulty", "parent": "settings", "transform_3d": {"scale": [1.3, 0.14, 1.0]}, "components": {"weave.style": {"classes": ["radio-row"]}, "sindri.ui.layout": {"direction": "row", "spacing": 0.02, "justify": "space_between", "align": "center"}}},
    {"id": "difficulty-text", "name": "difficulty-text", "parent": "difficulty", "transform_3d": {"scale": [0.4, 0.08, 1.0]}, "components": {"weave.style": {"classes": ["control-label", "radio-label"]}, "sindri.ui.text": {"text": "Difficulty", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.034, "bounds": [0.4, 0.08], "anchor": "center", "layer": 20, "line_align": "left"}}},
    {"id": "difficulty-cadet", "name": "difficulty-cadet", "parent": "difficulty", "transform_3d": {"scale": [0.27, 0.12, 1.0]}, "components": {"weave.style": {"classes": ["chip"]}, "sindri.ui.shape": {"kind": "rect", "fill": [0.05, 0.09, 0.14, 1.0], "anchor": "center", "layer": 11}, "sindri.ui.toggle": {"label": "Cadet", "checked": false, "group": "difficulty"}}},
    {"id": "difficulty-cadet-text", "name": "difficulty-cadet-text", "parent": "difficulty-cadet", "transform_3d": {"scale": [0.25, 0.07, 1.0]}, "components": {"weave.style": {"classes": ["chip-label"]}, "sindri.ui.text": {"text": "Cadet", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.032, "bounds": [0.25, 0.07], "anchor": "center", "layer": 20, "line_align": "center"}}},
    {"id": "difficulty-pilot", "name": "difficulty-pilot", "parent": "difficulty", "transform_3d": {"scale": [0.27, 0.12, 1.0]}, "components": {"weave.style": {"classes": ["chip"]}, "sindri.ui.shape": {"kind": "rect", "fill": [0.05, 0.09, 0.14, 1.0], "anchor": "center", "layer": 11}, "sindri.ui.toggle": {"label": "Pilot", "checked": true, "group": "difficulty"}}},
    {"id": "difficulty-pilot-text", "name": "difficulty-pilot-text", "parent": "difficulty-pilot", "transform_3d": {"scale": [0.25, 0.07, 1.0]}, "components": {"weave.style": {"classes": ["chip-label"]}, "sindri.ui.text": {"text": "Pilot", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.032, "bounds": [0.25, 0.07], "anchor": "center", "layer": 20, "line_align": "center"}}},
    {"id": "difficulty-ace", "name": "difficulty-ace", "parent": "difficulty", "transform_3d": {"scale": [0.27, 0.12, 1.0]}, "components": {"weave.style": {"classes": ["chip"]}, "sindri.ui.shape": {"kind": "rect", "fill": [0.05, 0.09, 0.14, 1.0], "anchor": "center", "layer": 11}, "sindri.ui.toggle": {"label": "Ace", "checked": false, "group": "difficulty"}}},
    {"id": "difficulty-ace-text", "name": "difficulty-ace-text", "parent": "difficulty-ace", "transform_3d": {"scale": [0.25, 0.07, 1.0]}, "components": {"weave.style": {"classes": ["chip-label"]}, "sindri.ui.text": {"text": "Ace", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.032, "bounds": [0.25, 0.07], "anchor": "center", "layer": 20, "line_align": "center"}}},
    {"id": "sector", "name": "sector", "parent": "settings", "transform_3d": {"scale": [1.3, 0.14, 1.0]}, "components": {"weave.style": {"classes": ["control", "dropdown"]}, "sindri.ui.shape": {"kind": "rect", "fill": [0.05, 0.09, 0.14, 1.0], "anchor": "center", "layer": 11}, "sindri.ui.dropdown": {"label": "Sector", "selected": 0}}},
    {"id": "sector-text", "name": "sector-text", "parent": "sector", "transform_3d": {"scale": [1.0, 0.08, 1.0]}, "components": {"weave.style": {"classes": ["dropdown-label"]}, "sindri.ui.text": {"text": "Sector  / Outer Drift", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.034, "bounds": [1.0, 0.08], "anchor": "center", "layer": 20, "line_align": "left"}}},
    {"id": "sector-chevron", "name": "sector-chevron", "parent": "sector", "transform_3d": {"scale": [0.08, 0.08, 1.0]}, "components": {"weave.style": {"classes": ["chevron"]}, "sindri.ui.text": {"text": "v", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.034, "bounds": [0.08, 0.08], "anchor": "center", "layer": 20, "line_align": "center"}}},
    {"id": "sector-list", "name": "sector-list", "parent": "sector", "transform_3d": {"scale": [1.3, 0.62, 1.0]}, "components": {"weave.style": {"classes": ["dropdown-list"]}, "sindri.ui.shape": {"kind": "rect", "fill": [0.05, 0.09, 0.14, 1.0], "anchor": "center", "layer": 60}, "sindri.ui.layout": {"direction": "column", "spacing": 0.01, "justify": "start", "align": "center"}}},
    {"id": "sector-0", "name": "sector-0", "parent": "sector-list", "transform_3d": {"scale": [1.24, 0.1, 1.0]}, "components": {"weave.style": {"classes": ["option"]}, "sindri.ui.shape": {"kind": "rect", "fill": [0.05, 0.09, 0.14, 1.0], "anchor": "center", "layer": 61}, "sindri.ui.option": {"label": "Outer Drift"}}},
    {"id": "sector-0-text", "name": "sector-0-text", "parent": "sector-0", "transform_3d": {"scale": [1.1, 0.07, 1.0]}, "components": {"weave.style": {"classes": ["option-label"]}, "sindri.ui.text": {"text": "Outer Drift", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.032, "bounds": [1.1, 0.07], "anchor": "center", "layer": 62, "line_align": "left"}}},
    {"id": "sector-1", "name": "sector-1", "parent": "sector-list", "transform_3d": {"scale": [1.24, 0.1, 1.0]}, "components": {"weave.style": {"classes": ["option"]}, "sindri.ui.shape": {"kind": "rect", "fill": [0.05, 0.09, 0.14, 1.0], "anchor": "center", "layer": 61}, "sindri.ui.option": {"label": "Ember Belt"}}},
    {"id": "sector-1-text", "name": "sector-1-text", "parent": "sector-1", "transform_3d": {"scale": [1.1, 0.07, 1.0]}, "components": {"weave.style": {"classes": ["option-label"]}, "sindri.ui.text": {"text": "Ember Belt", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.032, "bounds": [1.1, 0.07], "anchor": "center", "layer": 62, "line_align": "left"}}},
    {"id": "sector-2", "name": "sector-2", "parent": "sector-list", "transform_3d": {"scale": [1.24, 0.1, 1.0]}, "components": {"weave.style": {"classes": ["option"]}, "sindri.ui.shape": {"kind": "rect", "fill": [0.05, 0.09, 0.14, 1.0], "anchor": "center", "layer": 61}, "sindri.ui.option": {"label": "Violet Wake"}}},
    {"id": "sector-2-text", "name": "sector-2-text", "parent": "sector-2", "transform_3d": {"scale": [1.1, 0.07, 1.0]}, "components": {"weave.style": {"classes": ["option-label"]}, "sindri.ui.text": {"text": "Violet Wake", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.032, "bounds": [1.1, 0.07], "anchor": "center", "layer": 62, "line_align": "left"}}},
    {"id": "sector-3", "name": "sector-3", "parent": "sector-list", "transform_3d": {"scale": [1.24, 0.1, 1.0]}, "components": {"weave.style": {"classes": ["option"]}, "sindri.ui.shape": {"kind": "rect", "fill": [0.05, 0.09, 0.14, 1.0], "anchor": "center", "layer": 61}, "sindri.ui.option": {"label": "Null Lattice"}}},
    {"id": "sector-3-text", "name": "sector-3-text", "parent": "sector-3", "transform_3d": {"scale": [1.1, 0.07, 1.0]}, "components": {"weave.style": {"classes": ["option-label"]}, "sindri.ui.text": {"text": "Null Lattice", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.032, "bounds": [1.1, 0.07], "anchor": "center", "layer": 62, "line_align": "left"}}},
    {"id": "sector-4", "name": "sector-4", "parent": "sector-list", "transform_3d": {"scale": [1.24, 0.1, 1.0]}, "components": {"weave.style": {"classes": ["option"]}, "sindri.ui.shape": {"kind": "rect", "fill": [0.05, 0.09, 0.14, 1.0], "anchor": "center", "layer": 61}, "sindri.ui.option": {"label": "Core Approach"}}},
    {"id": "sector-4-text", "name": "sector-4-text", "parent": "sector-4", "transform_3d": {"scale": [1.1, 0.07, 1.0]}, "components": {"weave.style": {"classes": ["option-label"]}, "sindri.ui.text": {"text": "Core Approach", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.032, "bounds": [1.1, 0.07], "anchor": "center", "layer": 62, "line_align": "left"}}},
    {"id": "callsign", "name": "callsign", "parent": "settings", "transform_3d": {"scale": [1.3, 0.14, 1.0]}, "components": {"weave.style": {"classes": ["control", "input"]}, "sindri.ui.shape": {"kind": "rect", "fill": [0.05, 0.09, 0.14, 1.0], "anchor": "center", "layer": 11}, "sindri.ui.text_input": {"label": "Callsign", "value": "", "placeholder": "Your callsign", "max_length": 18}, "sindri.ui.layout": {"direction": "row", "spacing": 0.0, "justify": "start", "align": "center"}}},
    {"id": "callsign-text", "name": "callsign-text", "parent": "callsign", "transform_3d": {"scale": [1.2, 0.08, 1.0]}, "components": {"weave.style": {"classes": ["input-text"]}, "sindri.ui.text": {"text": "Your callsign", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.034, "bounds": [1.2, 0.08], "anchor": "center", "layer": 20, "line_align": "left"}}},
    {"id": "focus-readout", "name": "focus-readout", "parent": "settings", "transform_3d": {"scale": [1.3, 0.06, 1.0]}, "components": {"weave.style": {"classes": ["readout"]}, "sindri.ui.text": {"text": "FOCUS / none", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.03, "bounds": [1.0, 0.08], "anchor": "center", "layer": 20, "line_align": "center"}}},
    {"id": "inventory", "name": "inventory", "parent": "deck", "transform_3d": {"scale": [1.4, 1.3, 1.0]}, "components": {"weave.style": {"classes": ["panel", "inventory"]}, "sindri.ui.shape": {"kind": "rect", "fill": [0.05, 0.09, 0.14, 1.0], "anchor": "center", "layer": 10}, "sindri.ui.layout": {"direction": "column", "spacing": 0.03, "justify": "start", "align": "center"}}},
    {"id": "inventory-heading", "name": "inventory-heading", "parent": "inventory", "transform_3d": {"scale": [1.3, 0.08, 1.0]}, "components": {"weave.style": {"classes": ["section-title"]}, "sindri.ui.text": {"text": "02 / MODULE ARCHIVE", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.035, "bounds": [1.0, 0.08], "anchor": "center", "layer": 20, "line_align": "center"}}},
    {"id": "archive", "name": "archive", "parent": "inventory", "transform_3d": {"scale": [1.3, 0.9, 1.0]}, "components": {"weave.style": {"classes": ["scroll"]}, "sindri.ui.shape": {"kind": "rect", "fill": [0.05, 0.09, 0.14, 1.0], "anchor": "center", "layer": 11}, "sindri.ui.scroll": {"label": "Module archive", "content_height": 0.0, "offset": 0.0}, "sindri.ui.layout": {"direction": "column", "spacing": 0.015, "justify": "start", "align": "center"}}},
    {"id": "row-0", "name": "row-0", "parent": "archive", "transform_3d": {"scale": [1.2, 0.11, 1.0]}, "components": {"weave.style": {"classes": ["archive-row"]}, "sindri.ui.shape": {"kind": "rect", "fill": [0.05, 0.09, 0.14, 1.0], "anchor": "center", "layer": 12}, "sindri.ui.button": {"label": "Guidance Kernel", "disabled": false}, "sindri.script": {"source": "ui.decay", "script": "ArchiveRow", "properties": {"module": "Guidance Kernel"}}, "sindri.ui.layout": {"direction": "row", "spacing": 0.04, "justify": "start", "align": "center"}}},
    {"id": "row-number-0", "name": "row-number-0", "parent": "row-0", "transform_3d": {"scale": [0.12, 0.07, 1.0]}, "components": {"weave.style": {"classes": ["row-number"]}, "sindri.ui.text": {"text": "01", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.03, "bounds": [0.12, 0.07], "anchor": "center", "layer": 20, "line_align": "left"}}},
    {"id": "row-text-0", "name": "row-text-0", "parent": "row-0", "transform_3d": {"scale": [0.9, 0.07, 1.0]}, "components": {"weave.style": {"classes": ["row-label"]}, "sindri.ui.text": {"text": "Guidance Kernel", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.032, "bounds": [0.9, 0.07], "anchor": "center", "layer": 20, "line_align": "left"}}},
    {"id": "row-1", "name": "row-1", "parent": "archive", "transform_3d": {"scale": [1.2, 0.11, 1.0]}, "components": {"weave.style": {"classes": ["archive-row"]}, "sindri.ui.shape": {"kind": "rect", "fill": [0.05, 0.09, 0.14, 1.0], "anchor": "center", "layer": 12}, "sindri.ui.button": {"label": "Arc Imprint", "disabled": false}, "sindri.script": {"source": "ui.decay", "script": "ArchiveRow", "properties": {"module": "Arc Imprint"}}, "sindri.ui.layout": {"direction": "row", "spacing": 0.04, "justify": "start", "align": "center"}}},
    {"id": "row-number-1", "name": "row-number-1", "parent": "row-1", "transform_3d": {"scale": [0.12, 0.07, 1.0]}, "components": {"weave.style": {"classes": ["row-number"]}, "sindri.ui.text": {"text": "02", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.03, "bounds": [0.12, 0.07], "anchor": "center", "layer": 20, "line_align": "left"}}},
    {"id": "row-text-1", "name": "row-text-1", "parent": "row-1", "transform_3d": {"scale": [0.9, 0.07, 1.0]}, "components": {"weave.style": {"classes": ["row-label"]}, "sindri.ui.text": {"text": "Arc Imprint", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.032, "bounds": [0.9, 0.07], "anchor": "center", "layer": 20, "line_align": "left"}}},
    {"id": "row-2", "name": "row-2", "parent": "archive", "transform_3d": {"scale": [1.2, 0.11, 1.0]}, "components": {"weave.style": {"classes": ["archive-row"]}, "sindri.ui.shape": {"kind": "rect", "fill": [0.05, 0.09, 0.14, 1.0], "anchor": "center", "layer": 12}, "sindri.ui.button": {"label": "Nova Imprint", "disabled": false}, "sindri.script": {"source": "ui.decay", "script": "ArchiveRow", "properties": {"module": "Nova Imprint"}}, "sindri.ui.layout": {"direction": "row", "spacing": 0.04, "justify": "start", "align": "center"}}},
    {"id": "row-number-2", "name": "row-number-2", "parent": "row-2", "transform_3d": {"scale": [0.12, 0.07, 1.0]}, "components": {"weave.style": {"classes": ["row-number"]}, "sindri.ui.text": {"text": "03", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.03, "bounds": [0.12, 0.07], "anchor": "center", "layer": 20, "line_align": "left"}}},
    {"id": "row-text-2", "name": "row-text-2", "parent": "row-2", "transform_3d": {"scale": [0.9, 0.07, 1.0]}, "components": {"weave.style": {"classes": ["row-label"]}, "sindri.ui.text": {"text": "Nova Imprint", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.032, "bounds": [0.9, 0.07], "anchor": "center", "layer": 20, "line_align": "left"}}},
    {"id": "row-3", "name": "row-3", "parent": "archive", "transform_3d": {"scale": [1.2, 0.11, 1.0]}, "components": {"weave.style": {"classes": ["archive-row"]}, "sindri.ui.shape": {"kind": "rect", "fill": [0.05, 0.09, 0.14, 1.0], "anchor": "center", "layer": 12}, "sindri.ui.button": {"label": "Gravity Anchor", "disabled": false}, "sindri.script": {"source": "ui.decay", "script": "ArchiveRow", "properties": {"module": "Gravity Anchor"}}, "sindri.ui.layout": {"direction": "row", "spacing": 0.04, "justify": "start", "align": "center"}}},
    {"id": "row-number-3", "name": "row-number-3", "parent": "row-3", "transform_3d": {"scale": [0.12, 0.07, 1.0]}, "components": {"weave.style": {"classes": ["row-number"]}, "sindri.ui.text": {"text": "04", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.03, "bounds": [0.12, 0.07], "anchor": "center", "layer": 20, "line_align": "left"}}},
    {"id": "row-text-3", "name": "row-text-3", "parent": "row-3", "transform_3d": {"scale": [0.9, 0.07, 1.0]}, "components": {"weave.style": {"classes": ["row-label"]}, "sindri.ui.text": {"text": "Gravity Anchor", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.032, "bounds": [0.9, 0.07], "anchor": "center", "layer": 20, "line_align": "left"}}},
    {"id": "row-4", "name": "row-4", "parent": "archive", "transform_3d": {"scale": [1.2, 0.11, 1.0]}, "components": {"weave.style": {"classes": ["archive-row"]}, "sindri.ui.shape": {"kind": "rect", "fill": [0.05, 0.09, 0.14, 1.0], "anchor": "center", "layer": 12}, "sindri.ui.button": {"label": "Prism Imprint", "disabled": false}, "sindri.script": {"source": "ui.decay", "script": "ArchiveRow", "properties": {"module": "Prism Imprint"}}, "sindri.ui.layout": {"direction": "row", "spacing": 0.04, "justify": "start", "align": "center"}}},
    {"id": "row-number-4", "name": "row-number-4", "parent": "row-4", "transform_3d": {"scale": [0.12, 0.07, 1.0]}, "components": {"weave.style": {"classes": ["row-number"]}, "sindri.ui.text": {"text": "05", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.03, "bounds": [0.12, 0.07], "anchor": "center", "layer": 20, "line_align": "left"}}},
    {"id": "row-text-4", "name": "row-text-4", "parent": "row-4", "transform_3d": {"scale": [0.9, 0.07, 1.0]}, "components": {"weave.style": {"classes": ["row-label"]}, "sindri.ui.text": {"text": "Prism Imprint", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.032, "bounds": [0.9, 0.07], "anchor": "center", "layer": 20, "line_align": "left"}}},
    {"id": "row-5", "name": "row-5", "parent": "archive", "transform_3d": {"scale": [1.2, 0.11, 1.0]}, "components": {"weave.style": {"classes": ["archive-row"]}, "sindri.ui.shape": {"kind": "rect", "fill": [0.05, 0.09, 0.14, 1.0], "anchor": "center", "layer": 12}, "sindri.ui.button": {"label": "Trimmed Hull", "disabled": false}, "sindri.script": {"source": "ui.decay", "script": "ArchiveRow", "properties": {"module": "Trimmed Hull"}}, "sindri.ui.layout": {"direction": "row", "spacing": 0.04, "justify": "start", "align": "center"}}},
    {"id": "row-number-5", "name": "row-number-5", "parent": "row-5", "transform_3d": {"scale": [0.12, 0.07, 1.0]}, "components": {"weave.style": {"classes": ["row-number"]}, "sindri.ui.text": {"text": "06", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.03, "bounds": [0.12, 0.07], "anchor": "center", "layer": 20, "line_align": "left"}}},
    {"id": "row-text-5", "name": "row-text-5", "parent": "row-5", "transform_3d": {"scale": [0.9, 0.07, 1.0]}, "components": {"weave.style": {"classes": ["row-label"]}, "sindri.ui.text": {"text": "Trimmed Hull", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.032, "bounds": [0.9, 0.07], "anchor": "center", "layer": 20, "line_align": "left"}}},
    {"id": "row-6", "name": "row-6", "parent": "archive", "transform_3d": {"scale": [1.2, 0.11, 1.0]}, "components": {"weave.style": {"classes": ["archive-row"]}, "sindri.ui.shape": {"kind": "rect", "fill": [0.05, 0.09, 0.14, 1.0], "anchor": "center", "layer": 12}, "sindri.ui.button": {"label": "Second Opinion", "disabled": false}, "sindri.script": {"source": "ui.decay", "script": "ArchiveRow", "properties": {"module": "Second Opinion"}}, "sindri.ui.layout": {"direction": "row", "spacing": 0.04, "justify": "start", "align": "center"}}},
    {"id": "row-number-6", "name": "row-number-6", "parent": "row-6", "transform_3d": {"scale": [0.12, 0.07, 1.0]}, "components": {"weave.style": {"classes": ["row-number"]}, "sindri.ui.text": {"text": "07", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.03, "bounds": [0.12, 0.07], "anchor": "center", "layer": 20, "line_align": "left"}}},
    {"id": "row-text-6", "name": "row-text-6", "parent": "row-6", "transform_3d": {"scale": [0.9, 0.07, 1.0]}, "components": {"weave.style": {"classes": ["row-label"]}, "sindri.ui.text": {"text": "Second Opinion", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.032, "bounds": [0.9, 0.07], "anchor": "center", "layer": 20, "line_align": "left"}}},
    {"id": "row-7", "name": "row-7", "parent": "archive", "transform_3d": {"scale": [1.2, 0.11, 1.0]}, "components": {"weave.style": {"classes": ["archive-row"]}, "sindri.ui.shape": {"kind": "rect", "fill": [0.05, 0.09, 0.14, 1.0], "anchor": "center", "layer": 12}, "sindri.ui.button": {"label": "Ion Lens", "disabled": false}, "sindri.script": {"source": "ui.decay", "script": "ArchiveRow", "properties": {"module": "Ion Lens"}}, "sindri.ui.layout": {"direction": "row", "spacing": 0.04, "justify": "start", "align": "center"}}},
    {"id": "row-number-7", "name": "row-number-7", "parent": "row-7", "transform_3d": {"scale": [0.12, 0.07, 1.0]}, "components": {"weave.style": {"classes": ["row-number"]}, "sindri.ui.text": {"text": "08", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.03, "bounds": [0.12, 0.07], "anchor": "center", "layer": 20, "line_align": "left"}}},
    {"id": "row-text-7", "name": "row-text-7", "parent": "row-7", "transform_3d": {"scale": [0.9, 0.07, 1.0]}, "components": {"weave.style": {"classes": ["row-label"]}, "sindri.ui.text": {"text": "Ion Lens", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.032, "bounds": [0.9, 0.07], "anchor": "center", "layer": 20, "line_align": "left"}}},
    {"id": "row-8", "name": "row-8", "parent": "archive", "transform_3d": {"scale": [1.2, 0.11, 1.0]}, "components": {"weave.style": {"classes": ["archive-row"]}, "sindri.ui.shape": {"kind": "rect", "fill": [0.05, 0.09, 0.14, 1.0], "anchor": "center", "layer": 12}, "sindri.ui.button": {"label": "Flux Capacitor", "disabled": false}, "sindri.script": {"source": "ui.decay", "script": "ArchiveRow", "properties": {"module": "Flux Capacitor"}}, "sindri.ui.layout": {"direction": "row", "spacing": 0.04, "justify": "start", "align": "center"}}},
    {"id": "row-number-8", "name": "row-number-8", "parent": "row-8", "transform_3d": {"scale": [0.12, 0.07, 1.0]}, "components": {"weave.style": {"classes": ["row-number"]}, "sindri.ui.text": {"text": "09", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.03, "bounds": [0.12, 0.07], "anchor": "center", "layer": 20, "line_align": "left"}}},
    {"id": "row-text-8", "name": "row-text-8", "parent": "row-8", "transform_3d": {"scale": [0.9, 0.07, 1.0]}, "components": {"weave.style": {"classes": ["row-label"]}, "sindri.ui.text": {"text": "Flux Capacitor", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.032, "bounds": [0.9, 0.07], "anchor": "center", "layer": 20, "line_align": "left"}}},
    {"id": "row-9", "name": "row-9", "parent": "archive", "transform_3d": {"scale": [1.2, 0.11, 1.0]}, "components": {"weave.style": {"classes": ["archive-row", "locked"]}, "sindri.ui.shape": {"kind": "rect", "fill": [0.05, 0.09, 0.14, 1.0], "anchor": "center", "layer": 12}, "sindri.ui.button": {"label": "Classified", "disabled": true}, "sindri.script": {"source": "ui.decay", "script": "ArchiveRow", "properties": {"module": "Classified"}}, "sindri.ui.layout": {"direction": "row", "spacing": 0.04, "justify": "start", "align": "center"}}},
    {"id": "row-number-9", "name": "row-number-9", "parent": "row-9", "transform_3d": {"scale": [0.12, 0.07, 1.0]}, "components": {"weave.style": {"classes": ["row-number"]}, "sindri.ui.text": {"text": "10", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.03, "bounds": [0.12, 0.07], "anchor": "center", "layer": 20, "line_align": "left"}}},
    {"id": "row-text-9", "name": "row-text-9", "parent": "row-9", "transform_3d": {"scale": [0.9, 0.07, 1.0]}, "components": {"weave.style": {"classes": ["row-label"]}, "sindri.ui.text": {"text": "Classified  / locked", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.032, "bounds": [0.9, 0.07], "anchor": "center", "layer": 20, "line_align": "left"}}},
    {"id": "row-10", "name": "row-10", "parent": "archive", "transform_3d": {"scale": [1.2, 0.11, 1.0]}, "components": {"weave.style": {"classes": ["archive-row"]}, "sindri.ui.shape": {"kind": "rect", "fill": [0.05, 0.09, 0.14, 1.0], "anchor": "center", "layer": 12}, "sindri.ui.button": {"label": "Phase Shell", "disabled": false}, "sindri.script": {"source": "ui.decay", "script": "ArchiveRow", "properties": {"module": "Phase Shell"}}, "sindri.ui.layout": {"direction": "row", "spacing": 0.04, "justify": "start", "align": "center"}}},
    {"id": "row-number-10", "name": "row-number-10", "parent": "row-10", "transform_3d": {"scale": [0.12, 0.07, 1.0]}, "components": {"weave.style": {"classes": ["row-number"]}, "sindri.ui.text": {"text": "11", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.03, "bounds": [0.12, 0.07], "anchor": "center", "layer": 20, "line_align": "left"}}},
    {"id": "row-text-10", "name": "row-text-10", "parent": "row-10", "transform_3d": {"scale": [0.9, 0.07, 1.0]}, "components": {"weave.style": {"classes": ["row-label"]}, "sindri.ui.text": {"text": "Phase Shell", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.032, "bounds": [0.9, 0.07], "anchor": "center", "layer": 20, "line_align": "left"}}},
    {"id": "row-11", "name": "row-11", "parent": "archive", "transform_3d": {"scale": [1.2, 0.11, 1.0]}, "components": {"weave.style": {"classes": ["archive-row"]}, "sindri.ui.shape": {"kind": "rect", "fill": [0.05, 0.09, 0.14, 1.0], "anchor": "center", "layer": 12}, "sindri.ui.button": {"label": "Afterburner", "disabled": false}, "sindri.script": {"source": "ui.decay", "script": "ArchiveRow", "properties": {"module": "Afterburner"}}, "sindri.ui.layout": {"direction": "row", "spacing": 0.04, "justify": "start", "align": "center"}}},
    {"id": "row-number-11", "name": "row-number-11", "parent": "row-11", "transform_3d": {"scale": [0.12, 0.07, 1.0]}, "components": {"weave.style": {"classes": ["row-number"]}, "sindri.ui.text": {"text": "12", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.03, "bounds": [0.12, 0.07], "anchor": "center", "layer": 20, "line_align": "left"}}},
    {"id": "row-text-11", "name": "row-text-11", "parent": "row-11", "transform_3d": {"scale": [0.9, 0.07, 1.0]}, "components": {"weave.style": {"classes": ["row-label"]}, "sindri.ui.text": {"text": "Afterburner", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.032, "bounds": [0.9, 0.07], "anchor": "center", "layer": 20, "line_align": "left"}}},
    {"id": "row-12", "name": "row-12", "parent": "archive", "transform_3d": {"scale": [1.2, 0.11, 1.0]}, "components": {"weave.style": {"classes": ["archive-row"]}, "sindri.ui.shape": {"kind": "rect", "fill": [0.05, 0.09, 0.14, 1.0], "anchor": "center", "layer": 12}, "sindri.ui.button": {"label": "Pulse Coil", "disabled": false}, "sindri.script": {"source": "ui.decay", "script": "ArchiveRow", "properties": {"module": "Pulse Coil"}}, "sindri.ui.layout": {"direction": "row", "spacing": 0.04, "justify": "start", "align": "center"}}},
    {"id": "row-number-12", "name": "row-number-12", "parent": "row-12", "transform_3d": {"scale": [0.12, 0.07, 1.0]}, "components": {"weave.style": {"classes": ["row-number"]}, "sindri.ui.text": {"text": "13", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.03, "bounds": [0.12, 0.07], "anchor": "center", "layer": 20, "line_align": "left"}}},
    {"id": "row-text-12", "name": "row-text-12", "parent": "row-12", "transform_3d": {"scale": [0.9, 0.07, 1.0]}, "components": {"weave.style": {"classes": ["row-label"]}, "sindri.ui.text": {"text": "Pulse Coil", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.032, "bounds": [0.9, 0.07], "anchor": "center", "layer": 20, "line_align": "left"}}},
    {"id": "row-13", "name": "row-13", "parent": "archive", "transform_3d": {"scale": [1.2, 0.11, 1.0]}, "components": {"weave.style": {"classes": ["archive-row"]}, "sindri.ui.shape": {"kind": "rect", "fill": [0.05, 0.09, 0.14, 1.0], "anchor": "center", "layer": 12}, "sindri.ui.button": {"label": "Neutron Core", "disabled": false}, "sindri.script": {"source": "ui.decay", "script": "ArchiveRow", "properties": {"module": "Neutron Core"}}, "sindri.ui.layout": {"direction": "row", "spacing": 0.04, "justify": "start", "align": "center"}}},
    {"id": "row-number-13", "name": "row-number-13", "parent": "row-13", "transform_3d": {"scale": [0.12, 0.07, 1.0]}, "components": {"weave.style": {"classes": ["row-number"]}, "sindri.ui.text": {"text": "14", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.03, "bounds": [0.12, 0.07], "anchor": "center", "layer": 20, "line_align": "left"}}},
    {"id": "row-text-13", "name": "row-text-13", "parent": "row-13", "transform_3d": {"scale": [0.9, 0.07, 1.0]}, "components": {"weave.style": {"classes": ["row-label"]}, "sindri.ui.text": {"text": "Neutron Core", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.032, "bounds": [0.9, 0.07], "anchor": "center", "layer": 20, "line_align": "left"}}},
    {"id": "row-14", "name": "row-14", "parent": "archive", "transform_3d": {"scale": [1.2, 0.11, 1.0]}, "components": {"weave.style": {"classes": ["archive-row"]}, "sindri.ui.shape": {"kind": "rect", "fill": [0.05, 0.09, 0.14, 1.0], "anchor": "center", "layer": 12}, "sindri.ui.button": {"label": "Deflector", "disabled": false}, "sindri.script": {"source": "ui.decay", "script": "ArchiveRow", "properties": {"module": "Deflector"}}, "sindri.ui.layout": {"direction": "row", "spacing": 0.04, "justify": "start", "align": "center"}}},
    {"id": "row-number-14", "name": "row-number-14", "parent": "row-14", "transform_3d": {"scale": [0.12, 0.07, 1.0]}, "components": {"weave.style": {"classes": ["row-number"]}, "sindri.ui.text": {"text": "15", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.03, "bounds": [0.12, 0.07], "anchor": "center", "layer": 20, "line_align": "left"}}},
    {"id": "row-text-14", "name": "row-text-14", "parent": "row-14", "transform_3d": {"scale": [0.9, 0.07, 1.0]}, "components": {"weave.style": {"classes": ["row-label"]}, "sindri.ui.text": {"text": "Deflector", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.032, "bounds": [0.9, 0.07], "anchor": "center", "layer": 20, "line_align": "left"}}},
    {"id": "row-15", "name": "row-15", "parent": "archive", "transform_3d": {"scale": [1.2, 0.11, 1.0]}, "components": {"weave.style": {"classes": ["archive-row"]}, "sindri.ui.shape": {"kind": "rect", "fill": [0.05, 0.09, 0.14, 1.0], "anchor": "center", "layer": 12}, "sindri.ui.button": {"label": "Target Matrix", "disabled": false}, "sindri.script": {"source": "ui.decay", "script": "ArchiveRow", "properties": {"module": "Target Matrix"}}, "sindri.ui.layout": {"direction": "row", "spacing": 0.04, "justify": "start", "align": "center"}}},
    {"id": "row-number-15", "name": "row-number-15", "parent": "row-15", "transform_3d": {"scale": [0.12, 0.07, 1.0]}, "components": {"weave.style": {"classes": ["row-number"]}, "sindri.ui.text": {"text": "16", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.03, "bounds": [0.12, 0.07], "anchor": "center", "layer": 20, "line_align": "left"}}},
    {"id": "row-text-15", "name": "row-text-15", "parent": "row-15", "transform_3d": {"scale": [0.9, 0.07, 1.0]}, "components": {"weave.style": {"classes": ["row-label"]}, "sindri.ui.text": {"text": "Target Matrix", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.032, "bounds": [0.9, 0.07], "anchor": "center", "layer": 20, "line_align": "left"}}},
    {"id": "row-16", "name": "row-16", "parent": "archive", "transform_3d": {"scale": [1.2, 0.11, 1.0]}, "components": {"weave.style": {"classes": ["archive-row"]}, "sindri.ui.shape": {"kind": "rect", "fill": [0.05, 0.09, 0.14, 1.0], "anchor": "center", "layer": 12}, "sindri.ui.button": {"label": "Void Engine", "disabled": false}, "sindri.script": {"source": "ui.decay", "script": "ArchiveRow", "properties": {"module": "Void Engine"}}, "sindri.ui.layout": {"direction": "row", "spacing": 0.04, "justify": "start", "align": "center"}}},
    {"id": "row-number-16", "name": "row-number-16", "parent": "row-16", "transform_3d": {"scale": [0.12, 0.07, 1.0]}, "components": {"weave.style": {"classes": ["row-number"]}, "sindri.ui.text": {"text": "17", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.03, "bounds": [0.12, 0.07], "anchor": "center", "layer": 20, "line_align": "left"}}},
    {"id": "row-text-16", "name": "row-text-16", "parent": "row-16", "transform_3d": {"scale": [0.9, 0.07, 1.0]}, "components": {"weave.style": {"classes": ["row-label"]}, "sindri.ui.text": {"text": "Void Engine", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.032, "bounds": [0.9, 0.07], "anchor": "center", "layer": 20, "line_align": "left"}}},
    {"id": "row-17", "name": "row-17", "parent": "archive", "transform_3d": {"scale": [1.2, 0.11, 1.0]}, "components": {"weave.style": {"classes": ["archive-row"]}, "sindri.ui.shape": {"kind": "rect", "fill": [0.05, 0.09, 0.14, 1.0], "anchor": "center", "layer": 12}, "sindri.ui.button": {"label": "Last Light", "disabled": false}, "sindri.script": {"source": "ui.decay", "script": "ArchiveRow", "properties": {"module": "Last Light"}}, "sindri.ui.layout": {"direction": "row", "spacing": 0.04, "justify": "start", "align": "center"}}},
    {"id": "row-number-17", "name": "row-number-17", "parent": "row-17", "transform_3d": {"scale": [0.12, 0.07, 1.0]}, "components": {"weave.style": {"classes": ["row-number"]}, "sindri.ui.text": {"text": "18", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.03, "bounds": [0.12, 0.07], "anchor": "center", "layer": 20, "line_align": "left"}}},
    {"id": "row-text-17", "name": "row-text-17", "parent": "row-17", "transform_3d": {"scale": [0.9, 0.07, 1.0]}, "components": {"weave.style": {"classes": ["row-label"]}, "sindri.ui.text": {"text": "Last Light", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.032, "bounds": [0.9, 0.07], "anchor": "center", "layer": 20, "line_align": "left"}}},
    {"id": "row-18", "name": "row-18", "parent": "archive", "transform_3d": {"scale": [1.2, 0.11, 1.0]}, "components": {"weave.style": {"classes": ["archive-row"]}, "sindri.ui.shape": {"kind": "rect", "fill": [0.05, 0.09, 0.14, 1.0], "anchor": "center", "layer": 12}, "sindri.ui.button": {"label": "Aegis", "disabled": false}, "sindri.script": {"source": "ui.decay", "script": "ArchiveRow", "properties": {"module": "Aegis"}}, "sindri.ui.layout": {"direction": "row", "spacing": 0.04, "justify": "start", "align": "center"}}},
    {"id": "row-number-18", "name": "row-number-18", "parent": "row-18", "transform_3d": {"scale": [0.12, 0.07, 1.0]}, "components": {"weave.style": {"classes": ["row-number"]}, "sindri.ui.text": {"text": "19", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.03, "bounds": [0.12, 0.07], "anchor": "center", "layer": 20, "line_align": "left"}}},
    {"id": "row-text-18", "name": "row-text-18", "parent": "row-18", "transform_3d": {"scale": [0.9, 0.07, 1.0]}, "components": {"weave.style": {"classes": ["row-label"]}, "sindri.ui.text": {"text": "Aegis", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.032, "bounds": [0.9, 0.07], "anchor": "center", "layer": 20, "line_align": "left"}}},
    {"id": "scroll-help", "name": "scroll-help", "parent": "inventory", "transform_3d": {"scale": [1.3, 0.06, 1.0]}, "components": {"weave.style": {"classes": ["small"]}, "sindri.ui.text": {"text": "Wheel, drag or swipe through all 19 modules.", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.028, "bounds": [1.0, 0.08], "anchor": "center", "layer": 20, "line_align": "center"}}},
    {"id": "status", "name": "status", "parent": "room", "transform_3d": {"scale": [3.0, 0.08, 1.0]}, "components": {"weave.style": {"classes": ["status"]}, "sindri.ui.text": {"text": "READY / Select a control", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.036, "bounds": [1.0, 0.08], "anchor": "center", "layer": 20, "line_align": "center"}}},
    {"id": "footer", "name": "footer", "parent": "room", "transform_3d": {"scale": [3.0, 0.06, 1.0]}, "components": {"weave.style": {"classes": ["footer"]}, "sindri.ui.text": {"text": "TAB or ARROWS / focus    SPACE / press    ENTER / submit    ESC / back", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.026, "bounds": [1.0, 0.08], "anchor": "center", "layer": 20, "line_align": "center"}}}
  ]
}
```

### `sindri.toml`

```toml
format_version = 1

[project]
name = "Weave Control Room"
main_scene = "assets/widgets.scene"

[assets]
include = ["ui/widgets.weave"]
```

### Binary files in this project

`assets/fonts/ChakraPetch-Regular.ttf`, `assets/fonts/Fonts-OFL.txt`


---

## Example: Physics Playground (`examples/physics`)

Every text file of the project follows, whole, under its path relative to the project folder. JSON files are shown with short arrays on one line; whitespace does not matter to Sindri.

### Physics Playground

A feature example and a toybox: one room full of contraptions that show what
Sindri's physics can do, and tools to poke them with. Authored entirely in
scene data, Decay and Weave. Open `sindri.toml` in the editor or export it
with `sindri-export examples/physics`.

The design and its checklist are in
`docs/physics-playground.md` (`docs/physics-playground.md` in the Sindri repository). The scene,
prefabs and material profiles are written by
`scripts/physics_playground/generate.py`; edit the numbers there and run it
again rather than editing the generated files.

#### What is in the room

- **Wrecking ball**: wind it up with a hinge position motor, let it swing
  through a castle of wood, glass and steel, or cut the rope.
- **Gantry crane**: drive the trolley, lower the hook, grab whatever is under
  it with a joint made at runtime, and drop it somewhere worse.
- **Cannon gallery**: aim and fire through hanging glass panes; switch
  continuous collision off and watch shells ghost straight through.
- **Bumper pit**: flippers, bumpers that kick, and a ball lift that carries
  what drains back up to the top.
- **Material lab**: ice, wood and rubber race down one ramp; four pads show how
  high each material bounces.
- **Seesaw and trampoline**: drop the anvil, launch the ball, retune the
  trampoline's springs.
- **Test track**: a robot that climbs steps, rides a lift it calls by standing
  on it, drops through one-way planks, shoves crates and kicks them.
- **Voxel Quarry** (K, or 3D QUARRY): the 3D annex, a scene of its own. Crates
  on a voxel floor; click the ground to dig, DIG UNDER to drop every crate into
  a pit of its own, DROP CRATES for more, and a laser firing a 3D ray down.
- **Domino run**: fourteen dominoes, and at the end a red button that sets off
  DROP EVERYTHING. A hundred balls can knock them over too.

#### Controls

| Key | Button | What it does |
| --- | --- | --- |
| G / B / N / P | GRAB, BLAST, SPAWN, PROBE | Choose what the pointer does. N again picks what SPAWN drops; P again picks the probe's query. |
| L | MASK | Which layers the probe sees. |
| Q / E | < > | Fly to the previous or next contraption, or back to the whole room. |
| 1-4 | The four action buttons | Work the selected contraption. |
| A / D, W, S, F | LEFT, RIGHT, JUMP, KICK | Drive the robot on the test track. |
| X | DROP EVERYTHING | Every joint lets go. Again puts them back. |
| O | 100 BALLS | Pours a hundred balls in from the ceiling. |
| V | GRAVITY | Earth, moon, zero-g, upside down, sideways. |
| I | DEBUG | Outlines by state, contacts and normals, velocities, and every joint from anchor to anchor. |
| R | RESET | Resets the selected contraption, or the whole room. |
| K | 3D QUARRY | Goes to the Voxel Quarry, and back. |

The probe is dragged: press where a query starts and let go where it points.
It casts a ray, a circle or a box, or counts what overlaps a circle, and the
readout under the title says what it hit, how far, and which way the surface
faces.

`game/tests/the_physics_playground_works/` plays it through the same session
the browser runs, and the browser smoke tests play it on desktop and phone.

### `assets/materials/clay.profile`

```json
{
  "format_version": 1,
  "name": "Clay",
  "type": "physics_material",
  "values": {
    "friction": 0.9,
    "restitution": 0.0
  }
}
```

### `assets/materials/ice.profile`

```json
{
  "format_version": 1,
  "name": "Ice",
  "type": "physics_material",
  "values": {
    "friction": 0.01,
    "restitution": 0.05
  }
}
```

### `assets/materials/rubber.profile`

```json
{
  "format_version": 1,
  "name": "Rubber",
  "type": "physics_material",
  "values": {
    "friction": 1.2,
    "restitution": 0.92
  }
}
```

### `assets/materials/steel.profile`

```json
{
  "format_version": 1,
  "name": "Steel",
  "type": "physics_material",
  "values": {
    "friction": 0.3,
    "restitution": 0.45
  }
}
```

### `assets/materials/wood.profile`

```json
{
  "format_version": 1,
  "name": "Wood",
  "type": "physics_material",
  "values": {
    "friction": 0.45,
    "restitution": 0.2
  }
}
```

### `assets/playground.scene`

Omitted: 197 KB of level data, too large to be a useful pattern.

### `assets/prefabs/anvil.prefab`

```json
{
  "format_version": 1,
  "metadata": {
    "name": "Anvil"
  },
  "entities": [
    {
      "id": "anvil",
      "name": "Anvil",
      "transform_3d": {
        "position": [0, 0, 0.3],
        "rotation": [0.0, 0.0, 0.0, 1.0],
        "scale": [1.1, 0.8, 1.0]
      },
      "components": {
        "sindri.shape": {
          "kind": "rect",
          "count": 6.0,
          "fill": [0.04667, 0.05951, 0.08866, 1.0],
          "stroke": [0.32314, 0.38643, 0.49693, 1.0],
          "stroke_width": 0.07,
          "corner_radius": 0.0,
          "dashes": 0.0,
          "dash_duty": 0.5,
          "sweep_start": 0.0,
          "sweep_turns": 1.0,
          "blend": "over",
          "layer": 7
        },
        "sindri.physics2d.collider": {
          "pieces": [
            {
              "shape": {
                "shape": "box",
                "half_extents": [0.55, 0.4]
              },
              "offset": [0.0, 0.0],
              "rotation": 0.0,
              "sensor": false,
              "layers": {
                "memberships": 2,
                "filter": 191
              },
              "friction": 0.7,
              "restitution": 0.0
            }
          ]
        },
        "sindri.physics2d.rigid_body": {
          "kind": "dynamic",
          "pose": {
            "position": [0, 0],
            "rotation": 0
          },
          "linear_velocity": [0, 0],
          "angular_velocity": 0,
          "gravity_scale": 1.0,
          "linear_damping": 0.0,
          "angular_damping": 0.1,
          "lock_rotation": false,
          "continuous_collision": true
        },
        "sindri.tags": {
          "tags": ["loose"]
        }
      }
    }
  ]
}
```

### `assets/prefabs/ball.prefab`

```json
{
  "format_version": 1,
  "metadata": {
    "name": "Ball"
  },
  "entities": [
    {
      "id": "ball",
      "name": "Ball",
      "transform_3d": {
        "position": [0, 0, 0.3],
        "rotation": [0.0, 0.0, 0.0, 1.0],
        "scale": [1.0, 1.0, 1.0]
      },
      "components": {
        "sindri.shape": {
          "kind": "ellipse",
          "count": 6.0,
          "fill": [0.07227, 0.58408, 0.87137, 1.0],
          "stroke": [0.50888, 0.89627, 1.0, 1.0],
          "stroke_width": 0.07,
          "corner_radius": 0.0,
          "dashes": 0.0,
          "dash_duty": 0.5,
          "sweep_start": 0.0,
          "sweep_turns": 1.0,
          "blend": "over",
          "layer": 7
        },
        "sindri.physics2d.collider": {
          "pieces": [
            {
              "shape": {
                "shape": "circle",
                "radius": 0.3
              },
              "offset": [0.0, 0.0],
              "rotation": 0.0,
              "sensor": false,
              "layers": {
                "memberships": 4,
                "filter": 191
              },
              "friction": 0.3,
              "restitution": 0.6
            }
          ]
        },
        "sindri.physics2d.rigid_body": {
          "kind": "dynamic",
          "pose": {
            "position": [0, 0],
            "rotation": 0
          },
          "linear_velocity": [0, 0],
          "angular_velocity": 0,
          "gravity_scale": 1.0,
          "linear_damping": 0.02,
          "angular_damping": 0.05,
          "lock_rotation": false,
          "continuous_collision": false
        },
        "sindri.tags": {
          "tags": ["loose", "round"]
        }
      }
    }
  ]
}
```

### `assets/prefabs/bowling-ball.prefab`

```json
{
  "format_version": 1,
  "metadata": {
    "name": "Bowling ball"
  },
  "entities": [
    {
      "id": "bowling-ball",
      "name": "Bowling ball",
      "transform_3d": {
        "position": [0, 0, 0.3],
        "rotation": [0.0, 0.0, 0.0, 1.0],
        "scale": [1.0, 1.0, 1.0]
      },
      "components": {
        "sindri.shape": {
          "kind": "ellipse",
          "count": 6.0,
          "fill": [0.10462, 0.02315, 0.2384, 1.0],
          "stroke": [0.57112, 0.20508, 1.0, 1.0],
          "stroke_width": 0.07,
          "corner_radius": 0.0,
          "dashes": 0.0,
          "dash_duty": 0.5,
          "sweep_start": 0.0,
          "sweep_turns": 1.0,
          "blend": "over",
          "layer": 7
        },
        "sindri.physics2d.collider": {
          "pieces": [
            {
              "shape": {
                "shape": "circle",
                "radius": 0.55
              },
              "offset": [0.0, 0.0],
              "rotation": 0.0,
              "sensor": false,
              "layers": {
                "memberships": 4,
                "filter": 191
              },
              "friction": 0.2,
              "restitution": 0.15
            }
          ]
        },
        "sindri.physics2d.rigid_body": {
          "kind": "dynamic",
          "pose": {
            "position": [0, 0],
            "rotation": 0
          },
          "linear_velocity": [0, 0],
          "angular_velocity": 0,
          "gravity_scale": 1.0,
          "linear_damping": 0.0,
          "angular_damping": 0.02,
          "lock_rotation": false,
          "continuous_collision": true
        },
        "sindri.tags": {
          "tags": ["loose", "round"]
        }
      }
    }
  ]
}
```

### `assets/prefabs/crate.prefab`

```json
{
  "format_version": 1,
  "metadata": {
    "name": "Crate"
  },
  "entities": [
    {
      "id": "crate",
      "name": "Crate",
      "transform_3d": {
        "position": [0, 0, 0.3],
        "rotation": [0.0, 0.0, 0.0, 1.0],
        "scale": [1.0, 1.0, 1.0]
      },
      "components": {
        "sindri.shape": {
          "kind": "rect",
          "count": 6.0,
          "fill": [0.58408, 0.25818, 0.07819, 1.0],
          "stroke": [0.88792, 0.54572, 0.25818, 1.0],
          "stroke_width": 0.07,
          "corner_radius": 0.0,
          "dashes": 0.0,
          "dash_duty": 0.5,
          "sweep_start": 0.0,
          "sweep_turns": 1.0,
          "blend": "over",
          "layer": 7
        },
        "sindri.physics2d.collider": {
          "pieces": [
            {
              "shape": {
                "shape": "box",
                "half_extents": [0.45, 0.45]
              },
              "offset": [0.0, 0.0],
              "rotation": 0.0,
              "sensor": false,
              "layers": {
                "memberships": 2,
                "filter": 191
              },
              "friction": 0.6,
              "restitution": 0.05
            }
          ]
        },
        "sindri.physics2d.rigid_body": {
          "kind": "dynamic",
          "pose": {
            "position": [0, 0],
            "rotation": 0
          },
          "linear_velocity": [0, 0],
          "angular_velocity": 0,
          "gravity_scale": 1.0,
          "linear_damping": 0.05,
          "angular_damping": 0.05,
          "lock_rotation": false,
          "continuous_collision": false
        },
        "sindri.tags": {
          "tags": ["loose"]
        }
      }
    }
  ]
}
```

### `assets/prefabs/crate3d.prefab`

```json
{
  "format_version": 1,
  "metadata": {
    "name": "Crate 3D"
  },
  "entities": [
    {
      "id": "crate3d",
      "name": "Crate 3D",
      "transform_3d": {
        "position": [0, 0, 0],
        "rotation": [0.0, 0.0, 0.0, 1.0],
        "scale": [1.0, 1.0, 1.0]
      },
      "components": {
        "sindri.mesh": {
          "layer": 0,
          "primitive": "cube",
          "texture": "textures/crate.png"
        },
        "sindri.physics3d.rigid_body": {
          "kind": "dynamic",
          "position": [0, 0, 0],
          "rotation": [0, 0, 0, 1],
          "linear_velocity": [0, 0, 0],
          "angular_velocity": [0, 0, 0],
          "gravity_scale": 1.0,
          "linear_damping": 0.05,
          "angular_damping": 0.1,
          "lock_rotation": false
        },
        "sindri.physics3d.collider": {
          "pieces": [
            {
              "shape": {
                "shape": "box",
                "half_extents": [0.5, 0.5, 0.5]
              },
              "offset": [0, 0, 0],
              "rotation": [0, 0, 0, 1],
              "sensor": false,
              "layers": {
                "memberships": 4294967295,
                "filter": 4294967295
              },
              "friction": 0.7,
              "restitution": 0.1
            }
          ]
        },
        "sindri.tags": {
          "tags": ["crate", "dropped"]
        }
      }
    }
  ]
}
```

### `assets/prefabs/debug-bar.prefab`

```json
{
  "format_version": 1,
  "metadata": {
    "name": "debug-bar"
  },
  "entities": [
    {
      "id": "debug-bar",
      "name": "debug-bar",
      "transform_3d": {
        "position": [0, 0, 0.3],
        "rotation": [0.0, 0.0, 0.0, 1.0],
        "scale": [1.0, 1.0, 1.0]
      },
      "components": {
        "sindri.shape": {
          "kind": "rect",
          "count": 6.0,
          "fill": [1.0, 1.0, 1.0, 1.0],
          "stroke": [0.0, 0.0, 0.0, 0.0],
          "stroke_width": 0.0,
          "corner_radius": 0.0,
          "dashes": 0.0,
          "dash_duty": 0.5,
          "sweep_start": 0.0,
          "sweep_turns": 1.0,
          "blend": "over",
          "layer": 56
        }
      }
    }
  ]
}
```

### `assets/prefabs/debug-dot.prefab`

```json
{
  "format_version": 1,
  "metadata": {
    "name": "debug-dot"
  },
  "entities": [
    {
      "id": "debug-dot",
      "name": "debug-dot",
      "transform_3d": {
        "position": [0, 0, 0.3],
        "rotation": [0.0, 0.0, 0.0, 1.0],
        "scale": [1.0, 1.0, 1.0]
      },
      "components": {
        "sindri.shape": {
          "kind": "ellipse",
          "count": 6.0,
          "fill": [1.0, 1.0, 1.0, 1.0],
          "stroke": [0.0, 0.0, 0.0, 0.0],
          "stroke_width": 0.0,
          "corner_radius": 0.0,
          "dashes": 0.0,
          "dash_duty": 0.5,
          "sweep_start": 0.0,
          "sweep_turns": 1.0,
          "blend": "over",
          "layer": 56
        }
      }
    }
  ]
}
```

### `assets/prefabs/debug-rect.prefab`

```json
{
  "format_version": 1,
  "metadata": {
    "name": "debug-rect"
  },
  "entities": [
    {
      "id": "debug-rect",
      "name": "debug-rect",
      "transform_3d": {
        "position": [0, 0, 0.3],
        "rotation": [0.0, 0.0, 0.0, 1.0],
        "scale": [1.0, 1.0, 1.0]
      },
      "components": {
        "sindri.shape": {
          "kind": "rect",
          "count": 6.0,
          "fill": [0.0, 0.0, 0.0, 0.0],
          "stroke": [1.0, 1.0, 1.0, 1.0],
          "stroke_width": 0.06,
          "corner_radius": 0.0,
          "dashes": 0.0,
          "dash_duty": 0.5,
          "sweep_start": 0.0,
          "sweep_turns": 1.0,
          "blend": "over",
          "layer": 56
        }
      }
    }
  ]
}
```

### `assets/prefabs/debug-ring.prefab`

```json
{
  "format_version": 1,
  "metadata": {
    "name": "debug-ring"
  },
  "entities": [
    {
      "id": "debug-ring",
      "name": "debug-ring",
      "transform_3d": {
        "position": [0, 0, 0.3],
        "rotation": [0.0, 0.0, 0.0, 1.0],
        "scale": [1.0, 1.0, 1.0]
      },
      "components": {
        "sindri.shape": {
          "kind": "ellipse",
          "count": 6.0,
          "fill": [0.0, 0.0, 0.0, 0.0],
          "stroke": [1.0, 1.0, 1.0, 1.0],
          "stroke_width": 0.08,
          "corner_radius": 0.0,
          "dashes": 0.0,
          "dash_duty": 0.5,
          "sweep_start": 0.0,
          "sweep_turns": 1.0,
          "blend": "over",
          "layer": 56
        }
      }
    }
  ]
}
```

### `assets/prefabs/hand.prefab`

```json
{
  "format_version": 1,
  "metadata": {
    "name": "Hand"
  },
  "entities": [
    {
      "id": "hand",
      "name": "Hand",
      "transform_3d": {
        "position": [0, 0, 0.3],
        "rotation": [0.0, 0.0, 0.0, 1.0],
        "scale": [0.35, 0.35, 1.0]
      },
      "components": {
        "sindri.shape": {
          "kind": "ellipse",
          "count": 6.0,
          "fill": [1.0, 0.6376, 0.13287, 0.9],
          "stroke": [1.0, 1.0, 1.0, 1.0],
          "stroke_width": 0.18,
          "corner_radius": 0.0,
          "dashes": 0.0,
          "dash_duty": 0.5,
          "sweep_start": 0.0,
          "sweep_turns": 1.0,
          "blend": "over",
          "layer": 61
        },
        "sindri.physics2d.collider": {
          "pieces": [
            {
              "shape": {
                "shape": "circle",
                "radius": 0.05
              },
              "offset": [0.0, 0.0],
              "rotation": 0.0,
              "sensor": false,
              "layers": {
                "memberships": 64,
                "filter": 0
              },
              "friction": 0.0,
              "restitution": 0.0
            }
          ]
        },
        "sindri.physics2d.rigid_body": {
          "kind": "kinematic_velocity",
          "pose": {
            "position": [0, 0],
            "rotation": 0
          },
          "linear_velocity": [0, 0],
          "angular_velocity": 0,
          "gravity_scale": 1.0,
          "linear_damping": 0.0,
          "angular_damping": 0.0,
          "lock_rotation": false,
          "continuous_collision": false
        }
      }
    }
  ]
}
```

### `assets/prefabs/plank.prefab`

```json
{
  "format_version": 1,
  "metadata": {
    "name": "Plank"
  },
  "entities": [
    {
      "id": "plank",
      "name": "Plank",
      "transform_3d": {
        "position": [0, 0, 0.3],
        "rotation": [0.0, 0.0, 0.0, 1.0],
        "scale": [1.0, 1.0, 1.0]
      },
      "components": {
        "sindri.shape": {
          "kind": "rect",
          "count": 6.0,
          "fill": [0.69387, 0.35153, 0.13287, 1.0],
          "stroke": [0.92158, 0.64448, 0.35153, 1.0],
          "stroke_width": 0.07,
          "corner_radius": 0.0,
          "dashes": 0.0,
          "dash_duty": 0.5,
          "sweep_start": 0.0,
          "sweep_turns": 1.0,
          "blend": "over",
          "layer": 7
        },
        "sindri.physics2d.collider": {
          "pieces": [
            {
              "shape": {
                "shape": "box",
                "half_extents": [1.4, 0.14]
              },
              "offset": [0.0, 0.0],
              "rotation": 0.0,
              "sensor": false,
              "layers": {
                "memberships": 2,
                "filter": 191
              },
              "friction": 0.6,
              "restitution": 0.05
            }
          ]
        },
        "sindri.physics2d.rigid_body": {
          "kind": "dynamic",
          "pose": {
            "position": [0, 0],
            "rotation": 0
          },
          "linear_velocity": [0, 0],
          "angular_velocity": 0,
          "gravity_scale": 1.0,
          "linear_damping": 0.05,
          "angular_damping": 0.05,
          "lock_rotation": false,
          "continuous_collision": false
        },
        "sindri.tags": {
          "tags": ["loose"]
        }
      }
    }
  ]
}
```

### `assets/prefabs/shell.prefab`

```json
{
  "format_version": 1,
  "metadata": {
    "name": "Shell"
  },
  "entities": [
    {
      "id": "shell",
      "name": "Shell",
      "transform_3d": {
        "position": [0, 0, 0.3],
        "rotation": [0.0, 0.0, 0.0, 1.0],
        "scale": [0.24, 0.24, 1.0]
      },
      "components": {
        "sindri.shape": {
          "kind": "ellipse",
          "count": 6.0,
          "fill": [1.0, 0.72306, 0.09531, 1.0],
          "stroke": [1.0, 1.0, 1.0, 1.0],
          "stroke_width": 0.25,
          "corner_radius": 0.0,
          "dashes": 0.0,
          "dash_duty": 0.5,
          "sweep_start": 0.0,
          "sweep_turns": 1.0,
          "blend": "over",
          "layer": 9
        },
        "sindri.physics2d.collider": {
          "pieces": [
            {
              "shape": {
                "shape": "circle",
                "radius": 0.12
              },
              "offset": [0.0, 0.0],
              "rotation": 0.0,
              "sensor": false,
              "layers": {
                "memberships": 16,
                "filter": 39
              },
              "friction": 0.3,
              "restitution": 0.2
            }
          ]
        },
        "sindri.physics2d.rigid_body": {
          "kind": "dynamic",
          "pose": {
            "position": [0, 0],
            "rotation": 0
          },
          "linear_velocity": [0, 0],
          "angular_velocity": 0,
          "gravity_scale": 1.0,
          "linear_damping": 0.0,
          "angular_damping": 0.0,
          "lock_rotation": false,
          "continuous_collision": true
        },
        "sindri.script": {
          "source": "scripts/toys.decay",
          "script": "Shell",
          "properties": {}
        },
        "sindri.tags": {
          "tags": ["loose", "shell", "round"]
        }
      }
    }
  ]
}
```

### `assets/quarry.scene`

```json
{
  "format_version": 10,
  "metadata": {"name": "Voxel Quarry"},
  "entities": [
    {"id": "q-camera", "name": "Quarry Camera", "transform_3d": {"position": [9.0, 13.5, 10.5], "rotation": [-0.202846, 0.361857, 0.080991, 0.906285], "scale": [1.0, 1.0, 1.0]}, "components": {"sindri.camera": {"projection": "perspective", "vertical_fov_degrees": 45.0, "near": 0.1, "far": 200.0}}},
    {"id": "q-sun", "name": "Quarry Sun", "transform_3d": {"position": [0, 30, 0], "rotation": [-0.416852, 0.401068, 0.212271, 0.787604], "scale": [1.0, 1.0, 1.0]}, "components": {"sindri.light": {"kind": "directional", "color": [1.0, 0.93, 0.8], "intensity": 1.1}}},
    {"id": "q-environment", "name": "Quarry Environment", "transform_3d": {"position": [0.0, 0.0, 0.0], "rotation": [0.0, 0.0, 0.0, 1.0], "scale": [1.0, 1.0, 1.0]}, "components": {"sindri.environment": {"background": [0.01229, 0.0331, 0.0648, 1.0], "ambient_intensity": 0.45}}},
    {"id": "q-physics", "name": "Quarry Physics", "transform_3d": {"position": [0.0, 0.0, 0.0], "rotation": [0.0, 0.0, 0.0, 1.0], "scale": [1.0, 1.0, 1.0]}, "components": {"sindri.physics3d.world": {"gravity": [0.0, -16.0, 0.0], "layers": ["solid"]}}},
    {"id": "q-floor", "name": "Quarry Floor", "transform_3d": {"position": [0.0, 0.0, 0.0], "rotation": [0.0, 0.0, 0.0, 1.0], "scale": [1.0, 1.0, 1.0]}, "components": {"sindri.voxel_world": {"blocks": "quarry.tileset", "generator": {"kind": "layered_terrain", "base_height": 6, "height_variation": 0, "surface_voxel": "grass", "subsurface_voxel": "dirt", "deep_voxel": "stone", "subsurface_depth": 3}, "render_radius": 2, "vertical_radius": 1, "focus": [0, 0, 0], "edits": [{"at": [-9, 7, -9], "block": "stone"}, {"at": [-9, 8, -9], "block": "stone"}, {"at": [-9, 9, -9], "block": "stone"}, {"at": [-9, 10, -9], "block": "stone"}, {"at": [-9, 7, -8], "block": "stone"}, {"at": [-9, 8, -8], "block": "stone"}, {"at": [-9, 9, -8], "block": "stone"}, {"at": [-9, 7, -7], "block": "stone"}, {"at": [-9, 8, -7], "block": "stone"}, {"at": [-9, 7, -6], "block": "stone"}, {"at": [-8, 7, -9], "block": "stone"}, {"at": [-8, 8, -9], "block": "stone"}, {"at": [-8, 9, -9], "block": "stone"}, {"at": [-8, 10, -9], "block": "stone"}, {"at": [-8, 7, -8], "block": "stone"}, {"at": [-8, 8, -8], "block": "stone"}, {"at": [-8, 9, -8], "block": "stone"}, {"at": [-8, 7, -7], "block": "stone"}, {"at": [-8, 8, -7], "block": "stone"}, {"at": [-8, 7, -6], "block": "stone"}, {"at": [-7, 7, -9], "block": "stone"}, {"at": [-7, 8, -9], "block": "stone"}, {"at": [-7, 9, -9], "block": "stone"}, {"at": [-7, 10, -9], "block": "stone"}, {"at": [-7, 7, -8], "block": "stone"}, {"at": [-7, 8, -8], "block": "stone"}, {"at": [-7, 9, -8], "block": "stone"}, {"at": [-7, 7, -7], "block": "stone"}, {"at": [-7, 8, -7], "block": "stone"}, {"at": [-7, 7, -6], "block": "stone"}, {"at": [-6, 7, -9], "block": "stone"}, {"at": [-6, 8, -9], "block": "stone"}, {"at": [-6, 9, -9], "block": "stone"}, {"at": [-6, 10, -9], "block": "stone"}, {"at": [-6, 7, -8], "block": "stone"}, {"at": [-6, 8, -8], "block": "stone"}, {"at": [-6, 9, -8], "block": "stone"}, {"at": [-6, 7, -7], "block": "stone"}, {"at": [-6, 8, -7], "block": "stone"}, {"at": [-6, 7, -6], "block": "stone"}, {"at": [-5, 7, -9], "block": "stone"}, {"at": [-5, 8, -9], "block": "stone"}, {"at": [-5, 9, -9], "block": "stone"}, {"at": [-5, 10, -9], "block": "stone"}, {"at": [-5, 7, -8], "block": "stone"}, {"at": [-5, 8, -8], "block": "stone"}, {"at": [-5, 9, -8], "block": "stone"}, {"at": [-5, 7, -7], "block": "stone"}, {"at": [-5, 8, -7], "block": "stone"}, {"at": [-5, 7, -6], "block": "stone"}, {"at": [-4, 7, -9], "block": "stone"}, {"at": [-4, 8, -9], "block": "stone"}, {"at": [-4, 9, -9], "block": "stone"}, {"at": [-4, 10, -9], "block": "stone"}, {"at": [-4, 7, -8], "block": "stone"}, {"at": [-4, 8, -8], "block": "stone"}, {"at": [-4, 9, -8], "block": "stone"}, {"at": [-4, 7, -7], "block": "stone"}, {"at": [-4, 8, -7], "block": "stone"}, {"at": [-4, 7, -6], "block": "stone"}, {"at": [-3, 7, -9], "block": "stone"}, {"at": [-3, 8, -9], "block": "stone"}, {"at": [-3, 9, -9], "block": "stone"}, {"at": [-3, 10, -9], "block": "stone"}, {"at": [-3, 7, -8], "block": "stone"}, {"at": [-3, 8, -8], "block": "stone"}, {"at": [-3, 9, -8], "block": "stone"}, {"at": [-3, 7, -7], "block": "stone"}, {"at": [-3, 8, -7], "block": "stone"}, {"at": [-3, 7, -6], "block": "stone"}, {"at": [-2, 7, -9], "block": "stone"}, {"at": [-2, 8, -9], "block": "stone"}, {"at": [-2, 9, -9], "block": "stone"}, {"at": [-2, 10, -9], "block": "stone"}, {"at": [-2, 7, -8], "block": "stone"}, {"at": [-2, 8, -8], "block": "stone"}, {"at": [-2, 9, -8], "block": "stone"}, {"at": [-2, 7, -7], "block": "stone"}, {"at": [-2, 8, -7], "block": "stone"}, {"at": [-2, 7, -6], "block": "stone"}, {"at": [-1, 7, -9], "block": "stone"}, {"at": [-1, 8, -9], "block": "stone"}, {"at": [-1, 9, -9], "block": "stone"}, {"at": [-1, 10, -9], "block": "stone"}, {"at": [-1, 7, -8], "block": "stone"}, {"at": [-1, 8, -8], "block": "stone"}, {"at": [-1, 9, -8], "block": "stone"}, {"at": [-1, 7, -7], "block": "stone"}, {"at": [-1, 8, -7], "block": "stone"}, {"at": [-1, 7, -6], "block": "stone"}, {"at": [0, 7, -9], "block": "stone"}, {"at": [0, 8, -9], "block": "stone"}, {"at": [0, 9, -9], "block": "stone"}, {"at": [0, 10, -9], "block": "stone"}, {"at": [0, 7, -8], "block": "stone"}, {"at": [0, 8, -8], "block": "stone"}, {"at": [0, 9, -8], "block": "stone"}, {"at": [0, 7, -7], "block": "stone"}, {"at": [0, 8, -7], "block": "stone"}, {"at": [0, 7, -6], "block": "stone"}, {"at": [1, 7, -9], "block": "stone"}, {"at": [1, 8, -9], "block": "stone"}, {"at": [1, 9, -9], "block": "stone"}, {"at": [1, 10, -9], "block": "stone"}, {"at": [1, 7, -8], "block": "stone"}, {"at": [1, 8, -8], "block": "stone"}, {"at": [1, 9, -8], "block": "stone"}, {"at": [1, 7, -7], "block": "stone"}, {"at": [1, 8, -7], "block": "stone"}, {"at": [1, 7, -6], "block": "stone"}, {"at": [2, 7, -9], "block": "stone"}, {"at": [2, 8, -9], "block": "stone"}, {"at": [2, 9, -9], "block": "stone"}, {"at": [2, 10, -9], "block": "stone"}, {"at": [2, 7, -8], "block": "stone"}, {"at": [2, 8, -8], "block": "stone"}, {"at": [2, 9, -8], "block": "stone"}, {"at": [2, 7, -7], "block": "stone"}, {"at": [2, 8, -7], "block": "stone"}, {"at": [2, 7, -6], "block": "stone"}, {"at": [3, 7, -9], "block": "stone"}, {"at": [3, 8, -9], "block": "stone"}, {"at": [3, 9, -9], "block": "stone"}, {"at": [3, 10, -9], "block": "stone"}, {"at": [3, 7, -8], "block": "stone"}, {"at": [3, 8, -8], "block": "stone"}, {"at": [3, 9, -8], "block": "stone"}, {"at": [3, 7, -7], "block": "stone"}, {"at": [3, 8, -7], "block": "stone"}, {"at": [3, 7, -6], "block": "stone"}, {"at": [4, 7, -9], "block": "stone"}, {"at": [4, 8, -9], "block": "stone"}, {"at": [4, 9, -9], "block": "stone"}, {"at": [4, 10, -9], "block": "stone"}, {"at": [4, 7, -8], "block": "stone"}, {"at": [4, 8, -8], "block": "stone"}, {"at": [4, 9, -8], "block": "stone"}, {"at": [4, 7, -7], "block": "stone"}, {"at": [4, 8, -7], "block": "stone"}, {"at": [4, 7, -6], "block": "stone"}, {"at": [5, 7, -9], "block": "stone"}, {"at": [5, 8, -9], "block": "stone"}, {"at": [5, 9, -9], "block": "stone"}, {"at": [5, 10, -9], "block": "stone"}, {"at": [5, 7, -8], "block": "stone"}, {"at": [5, 8, -8], "block": "stone"}, {"at": [5, 9, -8], "block": "stone"}, {"at": [5, 7, -7], "block": "stone"}, {"at": [5, 8, -7], "block": "stone"}, {"at": [5, 7, -6], "block": "stone"}, {"at": [6, 7, -9], "block": "stone"}, {"at": [6, 8, -9], "block": "stone"}, {"at": [6, 9, -9], "block": "stone"}, {"at": [6, 10, -9], "block": "stone"}, {"at": [6, 7, -8], "block": "stone"}, {"at": [6, 8, -8], "block": "stone"}, {"at": [6, 9, -8], "block": "stone"}, {"at": [6, 7, -7], "block": "stone"}, {"at": [6, 8, -7], "block": "stone"}, {"at": [6, 7, -6], "block": "stone"}, {"at": [7, 7, -9], "block": "stone"}, {"at": [7, 8, -9], "block": "stone"}, {"at": [7, 9, -9], "block": "stone"}, {"at": [7, 10, -9], "block": "stone"}, {"at": [7, 7, -8], "block": "stone"}, {"at": [7, 8, -8], "block": "stone"}, {"at": [7, 9, -8], "block": "stone"}, {"at": [7, 7, -7], "block": "stone"}, {"at": [7, 8, -7], "block": "stone"}, {"at": [7, 7, -6], "block": "stone"}, {"at": [8, 7, -9], "block": "stone"}, {"at": [8, 8, -9], "block": "stone"}, {"at": [8, 9, -9], "block": "stone"}, {"at": [8, 10, -9], "block": "stone"}, {"at": [8, 7, -8], "block": "stone"}, {"at": [8, 8, -8], "block": "stone"}, {"at": [8, 9, -8], "block": "stone"}, {"at": [8, 7, -7], "block": "stone"}, {"at": [8, 8, -7], "block": "stone"}, {"at": [8, 7, -6], "block": "stone"}, {"at": [9, 7, -9], "block": "stone"}, {"at": [9, 8, -9], "block": "stone"}, {"at": [9, 9, -9], "block": "stone"}, {"at": [9, 10, -9], "block": "stone"}, {"at": [9, 7, -8], "block": "stone"}, {"at": [9, 8, -8], "block": "stone"}, {"at": [9, 9, -8], "block": "stone"}, {"at": [9, 7, -7], "block": "stone"}, {"at": [9, 8, -7], "block": "stone"}, {"at": [9, 7, -6], "block": "stone"}, {"at": [6, 7, -5], "block": "stone"}, {"at": [7, 7, -5], "block": "stone"}, {"at": [7, 8, -5], "block": "stone"}, {"at": [8, 7, -5], "block": "stone"}, {"at": [8, 8, -5], "block": "stone"}, {"at": [8, 9, -5], "block": "stone"}, {"at": [9, 7, -5], "block": "stone"}, {"at": [9, 8, -5], "block": "stone"}, {"at": [9, 9, -5], "block": "stone"}, {"at": [9, 10, -5], "block": "stone"}, {"at": [6, 7, -4], "block": "stone"}, {"at": [7, 7, -4], "block": "stone"}, {"at": [7, 8, -4], "block": "stone"}, {"at": [8, 7, -4], "block": "stone"}, {"at": [8, 8, -4], "block": "stone"}, {"at": [8, 9, -4], "block": "stone"}, {"at": [9, 7, -4], "block": "stone"}, {"at": [9, 8, -4], "block": "stone"}, {"at": [9, 9, -4], "block": "stone"}, {"at": [9, 10, -4], "block": "stone"}, {"at": [6, 7, -3], "block": "stone"}, {"at": [7, 7, -3], "block": "stone"}, {"at": [7, 8, -3], "block": "stone"}, {"at": [8, 7, -3], "block": "stone"}, {"at": [8, 8, -3], "block": "stone"}, {"at": [8, 9, -3], "block": "stone"}, {"at": [9, 7, -3], "block": "stone"}, {"at": [9, 8, -3], "block": "stone"}, {"at": [9, 9, -3], "block": "stone"}, {"at": [9, 10, -3], "block": "stone"}, {"at": [6, 7, -2], "block": "stone"}, {"at": [7, 7, -2], "block": "stone"}, {"at": [7, 8, -2], "block": "stone"}, {"at": [8, 7, -2], "block": "stone"}, {"at": [8, 8, -2], "block": "stone"}, {"at": [8, 9, -2], "block": "stone"}, {"at": [9, 7, -2], "block": "stone"}, {"at": [9, 8, -2], "block": "stone"}, {"at": [9, 9, -2], "block": "stone"}, {"at": [9, 10, -2], "block": "stone"}, {"at": [6, 7, -1], "block": "stone"}, {"at": [7, 7, -1], "block": "stone"}, {"at": [7, 8, -1], "block": "stone"}, {"at": [8, 7, -1], "block": "stone"}, {"at": [8, 8, -1], "block": "stone"}, {"at": [8, 9, -1], "block": "stone"}, {"at": [9, 7, -1], "block": "stone"}, {"at": [9, 8, -1], "block": "stone"}, {"at": [9, 9, -1], "block": "stone"}, {"at": [9, 10, -1], "block": "stone"}, {"at": [6, 7, 0], "block": "stone"}, {"at": [7, 7, 0], "block": "stone"}, {"at": [7, 8, 0], "block": "stone"}, {"at": [8, 7, 0], "block": "stone"}, {"at": [8, 8, 0], "block": "stone"}, {"at": [8, 9, 0], "block": "stone"}, {"at": [9, 7, 0], "block": "stone"}, {"at": [9, 8, 0], "block": "stone"}, {"at": [9, 9, 0], "block": "stone"}, {"at": [9, 10, 0], "block": "stone"}, {"at": [6, 7, 1], "block": "stone"}, {"at": [7, 7, 1], "block": "stone"}, {"at": [7, 8, 1], "block": "stone"}, {"at": [8, 7, 1], "block": "stone"}, {"at": [8, 8, 1], "block": "stone"}, {"at": [8, 9, 1], "block": "stone"}, {"at": [9, 7, 1], "block": "stone"}, {"at": [9, 8, 1], "block": "stone"}, {"at": [9, 9, 1], "block": "stone"}, {"at": [9, 10, 1], "block": "stone"}, {"at": [6, 7, 2], "block": "stone"}, {"at": [7, 7, 2], "block": "stone"}, {"at": [7, 8, 2], "block": "stone"}, {"at": [8, 7, 2], "block": "stone"}, {"at": [8, 8, 2], "block": "stone"}, {"at": [8, 9, 2], "block": "stone"}, {"at": [9, 7, 2], "block": "stone"}, {"at": [9, 8, 2], "block": "stone"}, {"at": [9, 9, 2], "block": "stone"}, {"at": [9, 10, 2], "block": "stone"}, {"at": [6, 7, 3], "block": "stone"}, {"at": [7, 7, 3], "block": "stone"}, {"at": [7, 8, 3], "block": "stone"}, {"at": [8, 7, 3], "block": "stone"}, {"at": [8, 8, 3], "block": "stone"}, {"at": [8, 9, 3], "block": "stone"}, {"at": [9, 7, 3], "block": "stone"}, {"at": [9, 8, 3], "block": "stone"}, {"at": [9, 9, 3], "block": "stone"}, {"at": [9, 10, 3], "block": "stone"}, {"at": [6, 7, 4], "block": "stone"}, {"at": [7, 7, 4], "block": "stone"}, {"at": [7, 8, 4], "block": "stone"}, {"at": [8, 7, 4], "block": "stone"}, {"at": [8, 8, 4], "block": "stone"}, {"at": [8, 9, 4], "block": "stone"}, {"at": [9, 7, 4], "block": "stone"}, {"at": [9, 8, 4], "block": "stone"}, {"at": [9, 9, 4], "block": "stone"}, {"at": [9, 10, 4], "block": "stone"}, {"at": [6, 7, 5], "block": "stone"}, {"at": [7, 7, 5], "block": "stone"}, {"at": [7, 8, 5], "block": "stone"}, {"at": [8, 7, 5], "block": "stone"}, {"at": [8, 8, 5], "block": "stone"}, {"at": [8, 9, 5], "block": "stone"}, {"at": [9, 7, 5], "block": "stone"}, {"at": [9, 8, 5], "block": "stone"}, {"at": [9, 9, 5], "block": "stone"}, {"at": [9, 10, 5], "block": "stone"}, {"at": [6, 7, 6], "block": "stone"}, {"at": [7, 7, 6], "block": "stone"}, {"at": [7, 8, 6], "block": "stone"}, {"at": [8, 7, 6], "block": "stone"}, {"at": [8, 8, 6], "block": "stone"}, {"at": [8, 9, 6], "block": "stone"}, {"at": [9, 7, 6], "block": "stone"}, {"at": [9, 8, 6], "block": "stone"}, {"at": [9, 9, 6], "block": "stone"}, {"at": [9, 10, 6], "block": "stone"}, {"at": [6, 7, 7], "block": "stone"}, {"at": [7, 7, 7], "block": "stone"}, {"at": [7, 8, 7], "block": "stone"}, {"at": [8, 7, 7], "block": "stone"}, {"at": [8, 8, 7], "block": "stone"}, {"at": [8, 9, 7], "block": "stone"}, {"at": [9, 7, 7], "block": "stone"}, {"at": [9, 8, 7], "block": "stone"}, {"at": [9, 9, 7], "block": "stone"}, {"at": [9, 10, 7], "block": "stone"}, {"at": [6, 7, 8], "block": "stone"}, {"at": [7, 7, 8], "block": "stone"}, {"at": [7, 8, 8], "block": "stone"}, {"at": [8, 7, 8], "block": "stone"}, {"at": [8, 8, 8], "block": "stone"}, {"at": [8, 9, 8], "block": "stone"}, {"at": [9, 7, 8], "block": "stone"}, {"at": [9, 8, 8], "block": "stone"}, {"at": [9, 9, 8], "block": "stone"}, {"at": [9, 10, 8], "block": "stone"}, {"at": [6, 7, 9], "block": "stone"}, {"at": [7, 7, 9], "block": "stone"}, {"at": [7, 8, 9], "block": "stone"}, {"at": [8, 7, 9], "block": "stone"}, {"at": [8, 8, 9], "block": "stone"}, {"at": [8, 9, 9], "block": "stone"}, {"at": [9, 7, 9], "block": "stone"}, {"at": [9, 8, 9], "block": "stone"}, {"at": [9, 9, 9], "block": "stone"}, {"at": [9, 10, 9], "block": "stone"}, {"at": [-8, 6, 4], "block": "sand"}, {"at": [-8, 6, 5], "block": "sand"}, {"at": [-8, 6, 6], "block": "sand"}, {"at": [-8, 6, 7], "block": "sand"}, {"at": [-7, 6, 4], "block": "sand"}, {"at": [-7, 6, 5], "block": "sand"}, {"at": [-7, 6, 6], "block": "sand"}, {"at": [-7, 6, 7], "block": "sand"}, {"at": [-6, 6, 4], "block": "sand"}, {"at": [-6, 6, 5], "block": "sand"}, {"at": [-6, 6, 6], "block": "sand"}, {"at": [-6, 6, 7], "block": "sand"}, {"at": [-5, 6, 4], "block": "sand"}, {"at": [-5, 6, 5], "block": "sand"}, {"at": [-5, 6, 6], "block": "sand"}, {"at": [-5, 6, 7], "block": "sand"}]}, "sindri.physics3d.voxel_collider": {"friction": 0.8, "layers": {"filter": 4294967295, "memberships": 4294967295}, "margin": 2.0, "restitution": 0.0}}},
    {"id": "q-crate-0", "name": "q-crate-0", "transform_3d": {"position": [-2.5, 7.5, 0.5], "rotation": [0.0, 0.0, 0.0, 1.0], "scale": [1.0, 1.0, 1.0]}, "components": {"sindri.mesh": {"layer": 0, "primitive": "cube", "texture": "textures/steel.png"}, "sindri.physics3d.rigid_body": {"kind": "dynamic", "position": [0, 0, 0], "rotation": [0, 0, 0, 1], "linear_velocity": [0, 0, 0], "angular_velocity": [0, 0, 0], "gravity_scale": 1.0, "linear_damping": 0.05, "angular_damping": 0.1, "lock_rotation": false}, "sindri.physics3d.collider": {"pieces": [{"shape": {"shape": "box", "half_extents": [0.5, 0.5, 0.5]}, "offset": [0, 0, 0], "rotation": [0, 0, 0, 1], "sensor": false, "layers": {"memberships": 4294967295, "filter": 4294967295}, "friction": 0.7, "restitution": 0.1}]}, "sindri.tags": {"tags": ["crate"]}, "sindri.script": {"source": "scripts/quarry.decay", "script": "QuarryCrate", "properties": {}}}},
    {"id": "q-crate-1", "name": "q-crate-1", "transform_3d": {"position": [-2.5, 8.5, 0.5], "rotation": [0.0, 0.0, 0.0, 1.0], "scale": [1.0, 1.0, 1.0]}, "components": {"sindri.mesh": {"layer": 0, "primitive": "cube", "texture": "textures/crate.png"}, "sindri.physics3d.rigid_body": {"kind": "dynamic", "position": [0, 0, 0], "rotation": [0, 0, 0, 1], "linear_velocity": [0, 0, 0], "angular_velocity": [0, 0, 0], "gravity_scale": 1.0, "linear_damping": 0.05, "angular_damping": 0.1, "lock_rotation": false}, "sindri.physics3d.collider": {"pieces": [{"shape": {"shape": "box", "half_extents": [0.5, 0.5, 0.5]}, "offset": [0, 0, 0], "rotation": [0, 0, 0, 1], "sensor": false, "layers": {"memberships": 4294967295, "filter": 4294967295}, "friction": 0.7, "restitution": 0.1}]}, "sindri.tags": {"tags": ["crate"]}, "sindri.script": {"source": "scripts/quarry.decay", "script": "QuarryCrate", "properties": {}}}},
    {"id": "q-crate-2", "name": "q-crate-2", "transform_3d": {"position": [-2.5, 9.5, 0.5], "rotation": [0.0, 0.0, 0.0, 1.0], "scale": [1.0, 1.0, 1.0]}, "components": {"sindri.mesh": {"layer": 0, "primitive": "cube", "texture": "textures/crate.png"}, "sindri.physics3d.rigid_body": {"kind": "dynamic", "position": [0, 0, 0], "rotation": [0, 0, 0, 1], "linear_velocity": [0, 0, 0], "angular_velocity": [0, 0, 0], "gravity_scale": 1.0, "linear_damping": 0.05, "angular_damping": 0.1, "lock_rotation": false}, "sindri.physics3d.collider": {"pieces": [{"shape": {"shape": "box", "half_extents": [0.5, 0.5, 0.5]}, "offset": [0, 0, 0], "rotation": [0, 0, 0, 1], "sensor": false, "layers": {"memberships": 4294967295, "filter": 4294967295}, "friction": 0.7, "restitution": 0.1}]}, "sindri.tags": {"tags": ["crate"]}, "sindri.script": {"source": "scripts/quarry.decay", "script": "QuarryCrate", "properties": {}}}},
    {"id": "q-crate-3", "name": "q-crate-3", "transform_3d": {"position": [-2.5, 10.5, 0.5], "rotation": [0.0, 0.0, 0.0, 1.0], "scale": [1.0, 1.0, 1.0]}, "components": {"sindri.mesh": {"layer": 0, "primitive": "cube", "texture": "textures/crate.png"}, "sindri.physics3d.rigid_body": {"kind": "dynamic", "position": [0, 0, 0], "rotation": [0, 0, 0, 1], "linear_velocity": [0, 0, 0], "angular_velocity": [0, 0, 0], "gravity_scale": 1.0, "linear_damping": 0.05, "angular_damping": 0.1, "lock_rotation": false}, "sindri.physics3d.collider": {"pieces": [{"shape": {"shape": "box", "half_extents": [0.5, 0.5, 0.5]}, "offset": [0, 0, 0], "rotation": [0, 0, 0, 1], "sensor": false, "layers": {"memberships": 4294967295, "filter": 4294967295}, "friction": 0.7, "restitution": 0.1}]}, "sindri.tags": {"tags": ["crate"]}, "sindri.script": {"source": "scripts/quarry.decay", "script": "QuarryCrate", "properties": {}}}},
    {"id": "q-crate-4", "name": "q-crate-4", "transform_3d": {"position": [-2.5, 11.5, 0.5], "rotation": [0.0, 0.0, 0.0, 1.0], "scale": [1.0, 1.0, 1.0]}, "components": {"sindri.mesh": {"layer": 0, "primitive": "cube", "texture": "textures/steel.png"}, "sindri.physics3d.rigid_body": {"kind": "dynamic", "position": [0, 0, 0], "rotation": [0, 0, 0, 1], "linear_velocity": [0, 0, 0], "angular_velocity": [0, 0, 0], "gravity_scale": 1.0, "linear_damping": 0.05, "angular_damping": 0.1, "lock_rotation": false}, "sindri.physics3d.collider": {"pieces": [{"shape": {"shape": "box", "half_extents": [0.5, 0.5, 0.5]}, "offset": [0, 0, 0], "rotation": [0, 0, 0, 1], "sensor": false, "layers": {"memberships": 4294967295, "filter": 4294967295}, "friction": 0.7, "restitution": 0.1}]}, "sindri.tags": {"tags": ["crate"]}, "sindri.script": {"source": "scripts/quarry.decay", "script": "QuarryCrate", "properties": {}}}},
    {"id": "q-crate-5", "name": "q-crate-5", "transform_3d": {"position": [1.0, 7.5, 3.5], "rotation": [0.0, 0.0, 0.0, 1.0], "scale": [1.0, 1.0, 1.0]}, "components": {"sindri.mesh": {"layer": 0, "primitive": "cube", "texture": "textures/crate.png"}, "sindri.physics3d.rigid_body": {"kind": "dynamic", "position": [0, 0, 0], "rotation": [0, 0, 0, 1], "linear_velocity": [0, 0, 0], "angular_velocity": [0, 0, 0], "gravity_scale": 1.0, "linear_damping": 0.05, "angular_damping": 0.1, "lock_rotation": false}, "sindri.physics3d.collider": {"pieces": [{"shape": {"shape": "box", "half_extents": [0.5, 0.5, 0.5]}, "offset": [0, 0, 0], "rotation": [0, 0, 0, 1], "sensor": false, "layers": {"memberships": 4294967295, "filter": 4294967295}, "friction": 0.7, "restitution": 0.1}]}, "sindri.tags": {"tags": ["crate"]}, "sindri.script": {"source": "scripts/quarry.decay", "script": "QuarryCrate", "properties": {}}}},
    {"id": "q-crate-6", "name": "q-crate-6", "transform_3d": {"position": [2.05, 7.5, 3.5], "rotation": [0.0, 0.0, 0.0, 1.0], "scale": [1.0, 1.0, 1.0]}, "components": {"sindri.mesh": {"layer": 0, "primitive": "cube", "texture": "textures/crate.png"}, "sindri.physics3d.rigid_body": {"kind": "dynamic", "position": [0, 0, 0], "rotation": [0, 0, 0, 1], "linear_velocity": [0, 0, 0], "angular_velocity": [0, 0, 0], "gravity_scale": 1.0, "linear_damping": 0.05, "angular_damping": 0.1, "lock_rotation": false}, "sindri.physics3d.collider": {"pieces": [{"shape": {"shape": "box", "half_extents": [0.5, 0.5, 0.5]}, "offset": [0, 0, 0], "rotation": [0, 0, 0, 1], "sensor": false, "layers": {"memberships": 4294967295, "filter": 4294967295}, "friction": 0.7, "restitution": 0.1}]}, "sindri.tags": {"tags": ["crate"]}, "sindri.script": {"source": "scripts/quarry.decay", "script": "QuarryCrate", "properties": {}}}},
    {"id": "q-crate-7", "name": "q-crate-7", "transform_3d": {"position": [3.1, 7.5, 3.5], "rotation": [0.0, 0.0, 0.0, 1.0], "scale": [1.0, 1.0, 1.0]}, "components": {"sindri.mesh": {"layer": 0, "primitive": "cube", "texture": "textures/crate.png"}, "sindri.physics3d.rigid_body": {"kind": "dynamic", "position": [0, 0, 0], "rotation": [0, 0, 0, 1], "linear_velocity": [0, 0, 0], "angular_velocity": [0, 0, 0], "gravity_scale": 1.0, "linear_damping": 0.05, "angular_damping": 0.1, "lock_rotation": false}, "sindri.physics3d.collider": {"pieces": [{"shape": {"shape": "box", "half_extents": [0.5, 0.5, 0.5]}, "offset": [0, 0, 0], "rotation": [0, 0, 0, 1], "sensor": false, "layers": {"memberships": 4294967295, "filter": 4294967295}, "friction": 0.7, "restitution": 0.1}]}, "sindri.tags": {"tags": ["crate"]}, "sindri.script": {"source": "scripts/quarry.decay", "script": "QuarryCrate", "properties": {}}}},
    {"id": "q-crate-8", "name": "q-crate-8", "transform_3d": {"position": [1.52, 8.5, 3.5], "rotation": [0.0, 0.0, 0.0, 1.0], "scale": [1.0, 1.0, 1.0]}, "components": {"sindri.mesh": {"layer": 0, "primitive": "cube", "texture": "textures/steel.png"}, "sindri.physics3d.rigid_body": {"kind": "dynamic", "position": [0, 0, 0], "rotation": [0, 0, 0, 1], "linear_velocity": [0, 0, 0], "angular_velocity": [0, 0, 0], "gravity_scale": 1.0, "linear_damping": 0.05, "angular_damping": 0.1, "lock_rotation": false}, "sindri.physics3d.collider": {"pieces": [{"shape": {"shape": "box", "half_extents": [0.5, 0.5, 0.5]}, "offset": [0, 0, 0], "rotation": [0, 0, 0, 1], "sensor": false, "layers": {"memberships": 4294967295, "filter": 4294967295}, "friction": 0.7, "restitution": 0.1}]}, "sindri.tags": {"tags": ["crate"]}, "sindri.script": {"source": "scripts/quarry.decay", "script": "QuarryCrate", "properties": {}}}},
    {"id": "q-crate-9", "name": "q-crate-9", "transform_3d": {"position": [2.57, 8.5, 3.5], "rotation": [0.0, 0.0, 0.0, 1.0], "scale": [1.0, 1.0, 1.0]}, "components": {"sindri.mesh": {"layer": 0, "primitive": "cube", "texture": "textures/crate.png"}, "sindri.physics3d.rigid_body": {"kind": "dynamic", "position": [0, 0, 0], "rotation": [0, 0, 0, 1], "linear_velocity": [0, 0, 0], "angular_velocity": [0, 0, 0], "gravity_scale": 1.0, "linear_damping": 0.05, "angular_damping": 0.1, "lock_rotation": false}, "sindri.physics3d.collider": {"pieces": [{"shape": {"shape": "box", "half_extents": [0.5, 0.5, 0.5]}, "offset": [0, 0, 0], "rotation": [0, 0, 0, 1], "sensor": false, "layers": {"memberships": 4294967295, "filter": 4294967295}, "friction": 0.7, "restitution": 0.1}]}, "sindri.tags": {"tags": ["crate"]}, "sindri.script": {"source": "scripts/quarry.decay", "script": "QuarryCrate", "properties": {}}}},
    {"id": "q-crate-10", "name": "q-crate-10", "transform_3d": {"position": [2.04, 9.5, 3.5], "rotation": [0.0, 0.0, 0.0, 1.0], "scale": [1.0, 1.0, 1.0]}, "components": {"sindri.mesh": {"layer": 0, "primitive": "cube", "texture": "textures/crate.png"}, "sindri.physics3d.rigid_body": {"kind": "dynamic", "position": [0, 0, 0], "rotation": [0, 0, 0, 1], "linear_velocity": [0, 0, 0], "angular_velocity": [0, 0, 0], "gravity_scale": 1.0, "linear_damping": 0.05, "angular_damping": 0.1, "lock_rotation": false}, "sindri.physics3d.collider": {"pieces": [{"shape": {"shape": "box", "half_extents": [0.5, 0.5, 0.5]}, "offset": [0, 0, 0], "rotation": [0, 0, 0, 1], "sensor": false, "layers": {"memberships": 4294967295, "filter": 4294967295}, "friction": 0.7, "restitution": 0.1}]}, "sindri.tags": {"tags": ["crate"]}, "sindri.script": {"source": "scripts/quarry.decay", "script": "QuarryCrate", "properties": {}}}},
    {"id": "q-crate-11", "name": "q-crate-11", "transform_3d": {"position": [-6.0, 7.5, -3.0], "rotation": [0.0, 0.0, 0.0, 1.0], "scale": [1.0, 1.0, 1.0]}, "components": {"sindri.mesh": {"layer": 0, "primitive": "cube", "texture": "textures/crate.png"}, "sindri.physics3d.rigid_body": {"kind": "dynamic", "position": [0, 0, 0], "rotation": [0, 0, 0, 1], "linear_velocity": [0, 0, 0], "angular_velocity": [0, 0, 0], "gravity_scale": 1.0, "linear_damping": 0.05, "angular_damping": 0.1, "lock_rotation": false}, "sindri.physics3d.collider": {"pieces": [{"shape": {"shape": "box", "half_extents": [0.5, 0.5, 0.5]}, "offset": [0, 0, 0], "rotation": [0, 0, 0, 1], "sensor": false, "layers": {"memberships": 4294967295, "filter": 4294967295}, "friction": 0.7, "restitution": 0.1}]}, "sindri.tags": {"tags": ["crate"]}, "sindri.script": {"source": "scripts/quarry.decay", "script": "QuarryCrate", "properties": {}}}},
    {"id": "q-crate-12", "name": "q-crate-12", "transform_3d": {"position": [-4.4, 7.5, -3.0], "rotation": [0.0, 0.0, 0.0, 1.0], "scale": [1.0, 1.0, 1.0]}, "components": {"sindri.mesh": {"layer": 0, "primitive": "cube", "texture": "textures/steel.png"}, "sindri.physics3d.rigid_body": {"kind": "dynamic", "position": [0, 0, 0], "rotation": [0, 0, 0, 1], "linear_velocity": [0, 0, 0], "angular_velocity": [0, 0, 0], "gravity_scale": 1.0, "linear_damping": 0.05, "angular_damping": 0.1, "lock_rotation": false}, "sindri.physics3d.collider": {"pieces": [{"shape": {"shape": "box", "half_extents": [0.5, 0.5, 0.5]}, "offset": [0, 0, 0], "rotation": [0, 0, 0, 1], "sensor": false, "layers": {"memberships": 4294967295, "filter": 4294967295}, "friction": 0.7, "restitution": 0.1}]}, "sindri.tags": {"tags": ["crate"]}, "sindri.script": {"source": "scripts/quarry.decay", "script": "QuarryCrate", "properties": {}}}},
    {"id": "q-crate-13", "name": "q-crate-13", "transform_3d": {"position": [-2.8, 7.5, -3.0], "rotation": [0.0, 0.0, 0.0, 1.0], "scale": [1.0, 1.0, 1.0]}, "components": {"sindri.mesh": {"layer": 0, "primitive": "cube", "texture": "textures/crate.png"}, "sindri.physics3d.rigid_body": {"kind": "dynamic", "position": [0, 0, 0], "rotation": [0, 0, 0, 1], "linear_velocity": [0, 0, 0], "angular_velocity": [0, 0, 0], "gravity_scale": 1.0, "linear_damping": 0.05, "angular_damping": 0.1, "lock_rotation": false}, "sindri.physics3d.collider": {"pieces": [{"shape": {"shape": "box", "half_extents": [0.5, 0.5, 0.5]}, "offset": [0, 0, 0], "rotation": [0, 0, 0, 1], "sensor": false, "layers": {"memberships": 4294967295, "filter": 4294967295}, "friction": 0.7, "restitution": 0.1}]}, "sindri.tags": {"tags": ["crate"]}, "sindri.script": {"source": "scripts/quarry.decay", "script": "QuarryCrate", "properties": {}}}},
    {"id": "q-crate-14", "name": "q-crate-14", "transform_3d": {"position": [-1.1999999999999993, 7.5, -3.0], "rotation": [0.0, 0.0, 0.0, 1.0], "scale": [1.0, 1.0, 1.0]}, "components": {"sindri.mesh": {"layer": 0, "primitive": "cube", "texture": "textures/crate.png"}, "sindri.physics3d.rigid_body": {"kind": "dynamic", "position": [0, 0, 0], "rotation": [0, 0, 0, 1], "linear_velocity": [0, 0, 0], "angular_velocity": [0, 0, 0], "gravity_scale": 1.0, "linear_damping": 0.05, "angular_damping": 0.1, "lock_rotation": false}, "sindri.physics3d.collider": {"pieces": [{"shape": {"shape": "box", "half_extents": [0.5, 0.5, 0.5]}, "offset": [0, 0, 0], "rotation": [0, 0, 0, 1], "sensor": false, "layers": {"memberships": 4294967295, "filter": 4294967295}, "friction": 0.7, "restitution": 0.1}]}, "sindri.tags": {"tags": ["crate"]}, "sindri.script": {"source": "scripts/quarry.decay", "script": "QuarryCrate", "properties": {}}}},
    {"id": "q-laser", "name": "q-laser", "transform_3d": {"position": [0, 14, 0.5], "rotation": [0.0, 0.0, 0.0, 1.0], "scale": [0.06, 1.0, 0.06]}, "components": {"sindri.mesh": {"layer": 0, "primitive": "cube", "texture": "textures/laser.png"}}},
    {"id": "q-laser-hit", "name": "q-laser-hit", "transform_3d": {"position": [0, 7, 0.5], "rotation": [0.0, 0.0, 0.0, 1.0], "scale": [0.3, 0.3, 0.3]}, "components": {"sindri.mesh": {"layer": 0, "primitive": "cube", "texture": "textures/laser.png"}}},
    {"id": "q-director", "name": "Quarry Director", "transform_3d": {"position": [0.0, 0.0, 0.0], "rotation": [0.0, 0.0, 0.0, 1.0], "scale": [1.0, 1.0, 1.0]}, "components": {"sindri.script": {"source": "scripts/quarry.decay", "script": "Quarry", "properties": {"crate_prefab": "prefabs/crate3d.prefab"}}}},
    {"id": "q-hud", "name": "q-hud", "transform_3d": {"position": [0.0, 0.0, 0.0], "rotation": [0.0, 0.0, 0.0, 1.0], "scale": [3.4, 2.0, 1.0]}, "components": {"weave.style": {"classes": ["hud"]}, "sindri.ui.layout": {"direction": "column", "spacing": 0.02, "justify": "space_between", "align": "center"}}},
    {"id": "q-top", "name": "q-top", "parent": "q-hud", "transform_3d": {"position": [0.0, 0.0, 0.0], "rotation": [0.0, 0.0, 0.0, 1.0], "scale": [3.3, 0.2, 1.0]}, "components": {"weave.style": {"classes": ["top"]}, "sindri.ui.layout": {"direction": "row", "spacing": 0.02, "justify": "space_between", "align": "start"}}},
    {"id": "q-brand", "name": "q-brand", "parent": "q-top", "transform_3d": {"position": [0.0, 0.0, 0.0], "rotation": [0.0, 0.0, 0.0, 1.0], "scale": [1.0, 0.2, 1.0]}, "components": {"weave.style": {"classes": ["brand"]}, "sindri.ui.layout": {"direction": "column", "spacing": 0.02, "justify": "start", "align": "start"}}},
    {"id": "q-title", "name": "q-title", "parent": "q-brand", "transform_3d": {"position": [0.0, 0.0, 0.0], "rotation": [0.0, 0.0, 0.0, 1.0], "scale": [1.0, 0.08, 1.0]}, "components": {"weave.style": {"classes": ["title"]}, "sindri.ui.text": {"text": "VOXEL QUARRY", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.0667, "bounds": [1.0, 0.08], "anchor": "center", "layer": 40, "line_align": "center"}}},
    {"id": "q-stats", "name": "q-stats", "parent": "q-brand", "transform_3d": {"position": [0.0, 0.0, 0.0], "rotation": [0.0, 0.0, 0.0, 1.0], "scale": [1.2, 0.05, 1.0]}, "components": {"weave.style": {"classes": ["stats"]}, "sindri.ui.text": {"text": "", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.0306, "bounds": [1.2, 0.05], "anchor": "center", "layer": 40, "line_align": "center"}}},
    {"id": "q-readout", "name": "q-readout", "parent": "q-brand", "transform_3d": {"position": [0.0, 0.0, 0.0], "rotation": [0.0, 0.0, 0.0, 1.0], "scale": [1.6, 0.08, 1.0]}, "components": {"weave.style": {"classes": ["readout"]}, "sindri.ui.text": {"text": "", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.0306, "bounds": [1.6, 0.08], "anchor": "center", "layer": 40, "line_align": "center"}}},
    {"id": "q-globals", "name": "q-globals", "parent": "q-top", "transform_3d": {"position": [0.0, 0.0, 0.0], "rotation": [0.0, 0.0, 0.0, 1.0], "scale": [2.2, 0.1, 1.0]}, "components": {"weave.style": {"classes": ["globals"]}, "sindri.ui.layout": {"direction": "row", "spacing": 0.02, "justify": "end", "align": "center"}}},
    {"id": "q-back", "name": "q-back", "parent": "q-globals", "transform_3d": {"position": [0.0, 0.0, 0.0], "rotation": [0.0, 0.0, 0.0, 1.0], "scale": [0.3, 0.09, 1.0]}, "components": {"weave.style": {"classes": ["button", "loud"]}, "sindri.ui.shape": {"kind": "rect", "fill": [0.00605, 0.01681, 0.03689, 1.0], "anchor": "center", "layer": 31}, "sindri.ui.button": {"label": "< THE ROOM", "autofocus": false, "disabled": false}, "sindri.ui.layout": {"direction": "row", "spacing": 0.02, "justify": "center", "align": "center"}}},
    {"id": "q-back-label", "name": "q-back-label", "parent": "q-back", "transform_3d": {"position": [0.0, 0.0, 0.0], "rotation": [0.0, 0.0, 0.0, 1.0], "scale": [0.3, 0.05, 1.0]}, "components": {"weave.style": {"classes": ["button-label"]}, "sindri.ui.text": {"text": "< THE ROOM", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.0361, "bounds": [0.3, 0.05], "anchor": "center", "layer": 40, "line_align": "center"}}},
    {"id": "q-reset", "name": "q-reset", "parent": "q-globals", "transform_3d": {"position": [0.0, 0.0, 0.0], "rotation": [0.0, 0.0, 0.0, 1.0], "scale": [0.22, 0.09, 1.0]}, "components": {"weave.style": {"classes": ["button"]}, "sindri.ui.shape": {"kind": "rect", "fill": [0.00605, 0.01681, 0.03689, 1.0], "anchor": "center", "layer": 31}, "sindri.ui.button": {"label": "RESET", "autofocus": false, "disabled": false}, "sindri.ui.layout": {"direction": "row", "spacing": 0.02, "justify": "center", "align": "center"}}},
    {"id": "q-reset-label", "name": "q-reset-label", "parent": "q-reset", "transform_3d": {"position": [0.0, 0.0, 0.0], "rotation": [0.0, 0.0, 0.0, 1.0], "scale": [0.22, 0.05, 1.0]}, "components": {"weave.style": {"classes": ["button-label"]}, "sindri.ui.text": {"text": "RESET", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.0361, "bounds": [0.22, 0.05], "anchor": "center", "layer": 40, "line_align": "center"}}},
    {"id": "q-bottom", "name": "q-bottom", "parent": "q-hud", "transform_3d": {"position": [0.0, 0.0, 0.0], "rotation": [0.0, 0.0, 0.0, 1.0], "scale": [3.3, 0.4, 1.0]}, "components": {"weave.style": {"classes": ["bottom"]}, "sindri.ui.layout": {"direction": "column", "spacing": 0.02, "justify": "end", "align": "center"}}},
    {"id": "q-dock", "name": "q-dock", "parent": "q-bottom", "transform_3d": {"position": [0.0, 0.0, 0.0], "rotation": [0.0, 0.0, 0.0, 1.0], "scale": [3.3, 0.11, 1.0]}, "components": {"weave.style": {"classes": ["dock"]}, "sindri.ui.layout": {"direction": "row", "spacing": 0.02, "justify": "center", "align": "center"}}},
    {"id": "q-actions", "name": "q-actions", "parent": "q-dock", "transform_3d": {"position": [0.0, 0.0, 0.0], "rotation": [0.0, 0.0, 0.0, 1.0], "scale": [3.0, 0.1, 1.0]}, "components": {"weave.style": {"classes": ["actions"]}, "sindri.ui.layout": {"direction": "row", "spacing": 0.02, "justify": "center", "align": "center"}}},
    {"id": "q-act-1", "name": "q-act-1", "parent": "q-actions", "transform_3d": {"position": [0.0, 0.0, 0.0], "rotation": [0.0, 0.0, 0.0, 1.0], "scale": [0.36, 0.09, 1.0]}, "components": {"weave.style": {"classes": ["button", "action"]}, "sindri.ui.shape": {"kind": "rect", "fill": [0.00605, 0.01681, 0.03689, 1.0], "anchor": "center", "layer": 31}, "sindri.ui.button": {"label": "DROP CRATES", "autofocus": false, "disabled": false}, "sindri.ui.layout": {"direction": "row", "spacing": 0.02, "justify": "center", "align": "center"}}},
    {"id": "q-act-1-label", "name": "q-act-1-label", "parent": "q-act-1", "transform_3d": {"position": [0.0, 0.0, 0.0], "rotation": [0.0, 0.0, 0.0, 1.0], "scale": [0.36, 0.05, 1.0]}, "components": {"weave.style": {"classes": ["button-label"]}, "sindri.ui.text": {"text": "DROP CRATES", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.0361, "bounds": [0.36, 0.05], "anchor": "center", "layer": 40, "line_align": "center"}}},
    {"id": "q-act-2", "name": "q-act-2", "parent": "q-actions", "transform_3d": {"position": [0.0, 0.0, 0.0], "rotation": [0.0, 0.0, 0.0, 1.0], "scale": [0.36, 0.09, 1.0]}, "components": {"weave.style": {"classes": ["button", "action"]}, "sindri.ui.shape": {"kind": "rect", "fill": [0.00605, 0.01681, 0.03689, 1.0], "anchor": "center", "layer": 31}, "sindri.ui.button": {"label": "DIG UNDER", "autofocus": false, "disabled": false}, "sindri.ui.layout": {"direction": "row", "spacing": 0.02, "justify": "center", "align": "center"}}},
    {"id": "q-act-2-label", "name": "q-act-2-label", "parent": "q-act-2", "transform_3d": {"position": [0.0, 0.0, 0.0], "rotation": [0.0, 0.0, 0.0, 1.0], "scale": [0.36, 0.05, 1.0]}, "components": {"weave.style": {"classes": ["button-label"]}, "sindri.ui.text": {"text": "DIG UNDER", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.0361, "bounds": [0.36, 0.05], "anchor": "center", "layer": 40, "line_align": "center"}}},
    {"id": "q-act-3", "name": "q-act-3", "parent": "q-actions", "transform_3d": {"position": [0.0, 0.0, 0.0], "rotation": [0.0, 0.0, 0.0, 1.0], "scale": [0.36, 0.09, 1.0]}, "components": {"weave.style": {"classes": ["button", "action"]}, "sindri.ui.shape": {"kind": "rect", "fill": [0.00605, 0.01681, 0.03689, 1.0], "anchor": "center", "layer": 31}, "sindri.ui.button": {"label": "SHOVE", "autofocus": false, "disabled": false}, "sindri.ui.layout": {"direction": "row", "spacing": 0.02, "justify": "center", "align": "center"}}},
    {"id": "q-act-3-label", "name": "q-act-3-label", "parent": "q-act-3", "transform_3d": {"position": [0.0, 0.0, 0.0], "rotation": [0.0, 0.0, 0.0, 1.0], "scale": [0.36, 0.05, 1.0]}, "components": {"weave.style": {"classes": ["button-label"]}, "sindri.ui.text": {"text": "SHOVE", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.0361, "bounds": [0.36, 0.05], "anchor": "center", "layer": 40, "line_align": "center"}}},
    {"id": "q-act-4", "name": "q-act-4", "parent": "q-actions", "transform_3d": {"position": [0.0, 0.0, 0.0], "rotation": [0.0, 0.0, 0.0, 1.0], "scale": [0.36, 0.09, 1.0]}, "components": {"weave.style": {"classes": ["button", "action"]}, "sindri.ui.shape": {"kind": "rect", "fill": [0.00605, 0.01681, 0.03689, 1.0], "anchor": "center", "layer": 31}, "sindri.ui.button": {"label": "STOP LASER", "autofocus": false, "disabled": false}, "sindri.ui.layout": {"direction": "row", "spacing": 0.02, "justify": "center", "align": "center"}}},
    {"id": "q-act-4-label", "name": "q-act-4-label", "parent": "q-act-4", "transform_3d": {"position": [0.0, 0.0, 0.0], "rotation": [0.0, 0.0, 0.0, 1.0], "scale": [0.36, 0.05, 1.0]}, "components": {"weave.style": {"classes": ["button-label"]}, "sindri.ui.text": {"text": "STOP LASER", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.0361, "bounds": [0.36, 0.05], "anchor": "center", "layer": 40, "line_align": "center"}}},
    {"id": "q-hint", "name": "q-hint", "parent": "q-bottom", "transform_3d": {"position": [0.0, 0.0, 0.0], "rotation": [0.0, 0.0, 0.0, 1.0], "scale": [2.6, 0.05, 1.0]}, "components": {"weave.style": {"classes": ["hint"]}, "sindri.ui.text": {"text": "Tap or click the ground to dig a block. 1-4 work the quarry; K goes back to the room.", "font": "fonts/ChakraPetch-Regular.ttf", "font_size": 0.0306, "bounds": [2.6, 0.05], "anchor": "center", "layer": 40, "line_align": "center"}}}
  ]
}
```

### `assets/quarry.tileset`

```json
{
  "format_version": 1,
  "tiles": {
    "grass": {
      "faces": {
        "top": {
          "sprite": "textures/grass.png",
          "size": [1.0, 1.0]
        },
        "bottom": {
          "sprite": "textures/dirt.png",
          "size": [1.0, 1.0]
        },
        "north": {
          "sprite": "textures/grass-side.png",
          "size": [1.0, 1.0]
        },
        "south": {
          "sprite": "textures/grass-side.png",
          "size": [1.0, 1.0]
        },
        "east": {
          "sprite": "textures/grass-side.png",
          "size": [1.0, 1.0]
        },
        "west": {
          "sprite": "textures/grass-side.png",
          "size": [1.0, 1.0]
        }
      }
    },
    "dirt": {
      "faces": {
        "top": {
          "sprite": "textures/dirt.png",
          "size": [1.0, 1.0]
        },
        "bottom": {
          "sprite": "textures/dirt.png",
          "size": [1.0, 1.0]
        },
        "north": {
          "sprite": "textures/dirt.png",
          "size": [1.0, 1.0]
        },
        "south": {
          "sprite": "textures/dirt.png",
          "size": [1.0, 1.0]
        },
        "east": {
          "sprite": "textures/dirt.png",
          "size": [1.0, 1.0]
        },
        "west": {
          "sprite": "textures/dirt.png",
          "size": [1.0, 1.0]
        }
      }
    },
    "stone": {
      "faces": {
        "top": {
          "sprite": "textures/stone.png",
          "size": [1.0, 1.0]
        },
        "bottom": {
          "sprite": "textures/stone.png",
          "size": [1.0, 1.0]
        },
        "north": {
          "sprite": "textures/stone.png",
          "size": [1.0, 1.0]
        },
        "south": {
          "sprite": "textures/stone.png",
          "size": [1.0, 1.0]
        },
        "east": {
          "sprite": "textures/stone.png",
          "size": [1.0, 1.0]
        },
        "west": {
          "sprite": "textures/stone.png",
          "size": [1.0, 1.0]
        }
      }
    },
    "sand": {
      "faces": {
        "top": {
          "sprite": "textures/sand.png",
          "size": [1.0, 1.0]
        },
        "bottom": {
          "sprite": "textures/sand.png",
          "size": [1.0, 1.0]
        },
        "north": {
          "sprite": "textures/sand.png",
          "size": [1.0, 1.0]
        },
        "south": {
          "sprite": "textures/sand.png",
          "size": [1.0, 1.0]
        },
        "east": {
          "sprite": "textures/sand.png",
          "size": [1.0, 1.0]
        },
        "west": {
          "sprite": "textures/sand.png",
          "size": [1.0, 1.0]
        }
      }
    }
  }
}
```

### `assets/scripts/lab.decay`

```rust
// The probe tool and the debug overlay: the playground's instruments.
//
// The probe is the old query lab grown up: drag from where the query starts to
// where it points, and it casts a ray, a circle or a box that way, or asks what
// overlaps a circle that big, through the layers the mask lets in. The overlay
// draws over the room without replacing it: outlines coloured by what each
// body is doing, velocity arrows, solver contacts with their normals, and every
// authored joint from anchor to anchor.

// One authored joint, as the generator wrote it: the overlay cannot read a
// joint's anchors, only whether it is on.
struct JointLink {
    owner: String,
    first: String,
    second: String,
    first_anchor: Vec2,
    second_anchor: Vec2,
}

shared fn probe_name(index: number) -> String {
    if index == 1.0 { return "CIRCLE CAST"; }
    if index == 2.0 { return "BOX CAST"; }
    if index == 3.0 { return "AREA"; }
    return "RAY";
}

shared fn mask_name(index: number) -> String {
    if index == 1.0 { return "SOLIDS"; }
    if index == 2.0 { return "PROPS"; }
    if index == 3.0 { return "BALLS"; }
    return "EVERYTHING";
}

shared fn probe_mask(index: number) -> number {
    if index == 1.0 { return Physics.mask(["static"]); }
    if index == 2.0 { return Physics.mask(["props", "glass", "shells"]); }
    if index == 3.0 { return Physics.mask(["balls"]); }
    return Physics.mask(["static", "props", "balls", "robot", "shells", "glass", "machines"]);
}

// What a probe says it found, from the tags the room gives things.
shared fn describe(thing: Entity) -> String {
    if World.has_tag(thing, "robot") { return "the robot"; }
    if World.has_tag(thing, "domino") { return "a domino"; }
    if World.has_tag(thing, "glass") { return "a glass pane"; }
    if World.has_tag(thing, "shell") { return "a shell"; }
    if World.has_tag(thing, "button") { return "the red button"; }
    if World.has_tag(thing, "platform") { return "the lift"; }
    if World.has_tag(thing, "round") && (World.has_tag(thing, "body") || World.has_tag(thing, "loose")) { return "a ball"; }
    if World.has_tag(thing, "body") || World.has_tag(thing, "loose") { return "a body"; }
    if World.has_tag(thing, "one-way") { return "a one-way plank"; }
    return "the room";
}

shared fn pair(v: Vec2) -> String {
    return "(" + v.x.fixed(2) + ", " + v.y.fixed(2) + ")";
}

// Where a body-local anchor is in the world.
shared fn anchored(thing: Entity, anchor: Vec2) -> Vec2 {
    let at = thing.transform.world_position;
    let turn = thing.transform.rotation_z;
    let c = cos(turn);
    let s = sin(turn);
    return Vec2(at.x + anchor.x * c - anchor.y * s, at.y + anchor.x * s + anchor.y * c);
}

script Probe {
    @export let ring: Prefab;
    var origin: Vec2 = Vec2(0.0, 0.0);
    var aim: Vec2 = Vec2(4.0, 0.0);
    var dragging: bool = false;
    var showing: bool = false;
    var marks: List<Entity> = [];
    var marked: number = 0.0;
    // What the probe found, for the readout under the title.
    var text: String = "";

    fn update(dt: number) {
        let probing = Game.tool == 3.0;
        World.set_active(World.find("btn-mask"), probing);
        if !probing {
            if this.showing { hide(); }
            this.text = "";
            return;
        }
        if Input.just_pressed("L") || Ui.is_pressed(World.find("btn-mask")) {
            Game.probe_mask = (Game.probe_mask + 1.0) % 4.0;
        }
        Ui.set_text(World.find("btn-mask-label"), "MASK: " + mask_name(Game.probe_mask));
        let director = Director.on(World.find("Director"));
        let at = director.view_center + Pointer.overlay * (director.view_height * 0.5);
        if Pointer.just_pressed("Left") && !Pointer.over_ui && Pointer.inside {
            this.origin = at;
            this.aim = at;
            this.dragging = true;
            this.showing = true;
        }
        if this.dragging {
            this.aim = at;
            if !Pointer.is_down("Left") { this.dragging = false; }
        }
        if !this.showing {
            this.text = probe_name(Game.probe) + " / " + mask_name(Game.probe_mask) + " / drag from where it starts to where it points";
            return;
        }
        cast();
    }

    fn cast() {
        let delta = this.aim - this.origin;
        let length = delta.length;
        var direction = Vec2(1.0, 0.0);
        if length > 0.01 { direction = delta.normalized; }
        let reach = max(0.05, length);
        let mask = probe_mask(Game.probe_mask);
        let kind = probe_name(Game.probe) + " / " + mask_name(Game.probe_mask);
        Game.queries += 1.0;
        if Game.probe == 3.0 {
            area(max(0.3, length), mask, kind);
            return;
        }
        World.set_active(World.find("probe-area"), false);
        hide_marks(0.0);
        let turn = atan2(direction.y, direction.x);
        var hit: RayHit2d? = null;
        if Game.probe == 0.0 { hit = Physics.raycast(this.origin, direction, reach, mask, false, null); }
        if Game.probe == 1.0 { hit = Physics.cast_circle(this.origin, 0.5, direction, reach, mask, false, null); }
        if Game.probe == 2.0 { hit = Physics.cast_box(this.origin, Vec2(0.6, 0.35), turn, direction, reach, mask, false, null); }
        var end = this.aim;
        if length <= 0.01 { end = this.origin + direction * reach; }
        let found = hit;
        if found != null { end = this.origin + direction * found.distance; }
        line(World.find("probe-line"), this.origin, end, 0.06);
        World.set_active(World.find("probe-line"), true);
        ghost(end, turn);
        let dot = World.find("probe-hit");
        let normal = World.find("probe-normal");
        if found != null {
            dot.transform.position = Vec3(found.point.x, found.point.y, 1.9);
            line(normal, found.point, found.point + found.normal * 1.4, 0.07);
            World.set_active(dot, true);
            World.set_active(normal, true);
            this.text = kind + " / " + describe(found.entity) + " at " + found.distance.fixed(2) + " u / normal " + pair(found.normal);
        } else {
            World.set_active(dot, false);
            World.set_active(normal, false);
            this.text = kind + " / nothing within " + reach.fixed(2) + " u";
        }
    }

    // The shape a cast sweeps, drawn where it stops.
    fn ghost(at: Vec2, turn: number) {
        let round = World.find("probe-ghost-circle");
        let square = World.find("probe-ghost-box");
        World.set_active(round, Game.probe == 1.0);
        World.set_active(square, Game.probe == 2.0);
        round.transform.position = Vec3(at.x, at.y, 1.9);
        square.transform.position = Vec3(at.x, at.y, 1.9);
        square.transform.rotation_z = turn;
    }

    fn area(radius: number, mask: number, kind: String) {
        World.set_active(World.find("probe-line"), false);
        World.set_active(World.find("probe-hit"), false);
        World.set_active(World.find("probe-normal"), false);
        World.set_active(World.find("probe-ghost-circle"), false);
        World.set_active(World.find("probe-ghost-box"), false);
        let zone = World.find("probe-area");
        zone.transform.position = Vec3(this.origin.x, this.origin.y, 1.9);
        zone.transform.scale = Vec3(radius * 2.0, radius * 2.0, 1.0);
        World.set_active(zone, true);
        let found = Physics.overlap_circle(this.origin, radius, mask, true, null);
        var count = 0.0;
        for thing in found {
            if count < 48.0 {
                var mark: Entity? = null;
                if count < this.marks.length {
                    mark = this.marks[count];
                } else {
                    let made = World.spawn(this.ring);
                    this.marks.push(made);
                    mark = made;
                }
                let shown = mark ?? thing;
                let at = thing.transform.world_position;
                let size = max(thing.transform.scale.x, thing.transform.scale.y);
                shown.transform.position = Vec3(at.x, at.y, 1.95);
                shown.transform.scale = Vec3(min(size, 3.0) + 0.3, min(size, 3.0) + 0.3, 1.0);
                World.set_active(shown, true);
            }
            count += 1.0;
        }
        hide_marks(min(count, 48.0));
        this.text = kind + " / " + count + " within " + radius.fixed(2) + " u";
    }

    fn hide_marks(from: number) {
        var index = from;
        while index < this.marked {
            World.set_active(this.marks[index], false);
            index += 1.0;
        }
        this.marked = from;
    }

    fn hide() {
        this.showing = false;
        this.dragging = false;
        for name in ["probe-line", "probe-hit", "probe-normal", "probe-ghost-circle", "probe-ghost-box", "probe-area"] {
            World.set_active(World.find(name), false);
        }
        hide_marks(0.0);
    }
}

script DebugOverlay {
    @export let outline: Prefab;
    @export let ring: Prefab;
    @export let bar: Prefab;
    @export let dot: Prefab;
    @export let links: List<JointLink> = [];

    var rects: List<Entity> = [];
    var rings: List<Entity> = [];
    var bars: List<Entity> = [];
    var dots: List<Entity> = [];
    // How many of each kind were drawn this frame, and the frame before:
    // 0 outline, 1 ring, 2 bar, 3 dot.
    var used: List<number> = [0.0, 0.0, 0.0, 0.0];
    var before: List<number> = [0.0, 0.0, 0.0, 0.0];
    var shown: bool = false;
    var text: String = "";

    fn update(dt: number) {
        let layer = World.find("debug-layer");
        if !Game.debug {
            if this.shown {
                World.set_active(layer, false);
                this.shown = false;
            }
            this.text = "";
            return;
        }
        if !this.shown {
            World.set_active(layer, true);
            this.shown = true;
        }
        this.before = this.used;
        this.used = [0.0, 0.0, 0.0, 0.0];
        let director = Director.on(World.find("Director"));
        let half = Vec2(director.view_height * max(0.3, Viewport.aspect) * 0.5, director.view_height * 0.5);
        let things = Physics.overlap_box(director.view_center, half, 0.0, 4294967295.0, true, null);
        var awake = 0.0;
        var touching = 0.0;
        var drawn = 0.0;
        for thing in things {
            if drawn < 260.0 {
                drawn += 1.0;
                let movable = World.has_tag(thing, "body") || World.has_tag(thing, "loose");
                var color = Color(0.35, 0.55, 0.8, 0.7);
                if movable {
                    let velocity = Vec2(Physics.velocity_x(thing), Physics.velocity_y(thing));
                    let speed = velocity.length;
                    color = Color(0.55, 0.6, 0.7, 0.8);
                    if speed > 0.05 {
                        awake += 1.0;
                        color = Color(0.3, 1.0, 0.6, 0.95);
                    }
                    let at = thing.transform.world_position;
                    if speed > 0.4 {
                        let tip = Vec2(at.x, at.y) + velocity * 0.12;
                        stroke(Vec2(at.x, at.y), tip, 0.06, Color(1.0, 0.85, 0.3, 0.95));
                        spot(tip, 0.16, Color(1.0, 0.85, 0.3, 0.95));
                    }
                    for contact in Physics.contacts(thing) {
                        if touching < 180.0 {
                            touching += 1.0;
                            let push = 0.25 + min(1.0, contact.normal_impulse * 0.6);
                            spot(contact.point, 0.14, Color(1.0, 0.3, 0.4, 1.0));
                            stroke(contact.point, contact.point + contact.normal * push, 0.04, Color(1.0, 0.45, 0.5, 0.9));
                        }
                    }
                }
                if World.has_tag(thing, "sensor") { color = Color(0.85, 1.0, 0.3, 0.9); }
                trace(thing, color);
            }
        }
        var joined = 0.0;
        for link in this.links {
            let owner = World.find(link.owner);
            let first = World.find(link.first);
            let second = World.find(link.second);
            if owner != null && first != null && second != null {
                var color = Color(1.0, 0.35, 0.35, 0.9);
                if Physics.joint_enabled(owner) {
                    color = Color(0.4, 1.0, 0.85, 0.9);
                    joined += 1.0;
                }
                let a = anchored(first, link.first_anchor);
                let b = anchored(second, link.second_anchor);
                if a.distance(b) > 0.05 { stroke(a, b, 0.05, color); }
                spot(a, 0.22, color);
                spot(b, 0.22, color);
            }
        }
        tidy();
        this.text = things.length + " in view / " + awake + " awake / " + touching + " contacts / " + joined + " of " + this.links.length + " joints on";
    }

    // An outline over a collider, the size the thing is drawn.
    fn trace(thing: Entity, color: Color) {
        var size = thing.transform.scale;
        if World.has_tag(thing, "robot") { size = Vec3(0.68, 1.18, 1.0); }
        var mark = take(0.0);
        if World.has_tag(thing, "round") { mark = take(1.0); }
        let at = thing.transform.world_position;
        mark.transform.position = Vec3(at.x, at.y, 1.8);
        mark.transform.rotation_z = thing.transform.rotation_z;
        mark.transform.scale = Vec3(max(0.05, abs(size.x)), max(0.05, abs(size.y)), 1.0);
        mark.shape.stroke = color;
        mark.shape.stroke_width = min(0.45, 0.1 / max(0.05, min(abs(size.x), abs(size.y))));
    }

    fn stroke(from: Vec2, to: Vec2, width: number, color: Color) {
        let mark = take(2.0);
        line(mark, from, to, width);
        mark.transform.position.z = 1.85;
        mark.shape.fill = color;
    }

    fn spot(at: Vec2, size: number, color: Color) {
        let mark = take(3.0);
        mark.transform.position = Vec3(at.x, at.y, 1.9);
        mark.transform.scale = Vec3(size, size, 1.0);
        mark.shape.fill = color;
    }

    // The next free mark of a kind: 0 outline, 1 ring, 2 bar, 3 dot. A mark
    // hidden last frame is switched back on; the pool only grows.
    fn take(kind: number) -> Entity {
        var pool = this.rects;
        var prefab = this.outline;
        if kind == 1.0 { pool = this.rings; prefab = this.ring; }
        if kind == 2.0 { pool = this.bars; prefab = this.bar; }
        if kind == 3.0 { pool = this.dots; prefab = this.dot; }
        let index = this.used[kind];
        this.used[kind] = index + 1.0;
        if index < pool.length {
            let mark = pool[index];
            if index >= this.before[kind] { World.set_active(mark, true); }
            return mark;
        }
        let made = World.spawn_child(prefab, World.find("debug-layer"));
        if kind == 0.0 { this.rects.push(made); }
        if kind == 1.0 { this.rings.push(made); }
        if kind == 2.0 { this.bars.push(made); }
        if kind == 3.0 { this.dots.push(made); }
        return made;
    }

    // Marks drawn last frame and not this one go dark.
    fn tidy() {
        hide_from(this.rects, this.used[0], this.before[0]);
        hide_from(this.rings, this.used[1], this.before[1]);
        hide_from(this.bars, this.used[2], this.before[2]);
        hide_from(this.dots, this.used[3], this.before[3]);
    }

    fn hide_from(pool: List<Entity>, from: number, to: number) {
        var index = from;
        while index < to && index < pool.length {
            World.set_active(pool[index], false);
            index += 1.0;
        }
    }
}
```

### `assets/scripts/parts.decay`

```rust
// What every authored part of a toy knows: where the scene put it, so a reset
// can put it back. Toys are scene entities rather than prefabs because an
// authored joint can only name bodies in its own scene or prefab, and the
// crane must be able to pick up anything the room was built with.

script Part {
    // Which toy this part belongs to; 0 is the room itself.
    @export let toy: number = 0.0;

    var home: Vec3 = Vec3(0.0, 0.0, 0.0);
    var home_turn: number = 0.0;

    fn start() {
        this.home = this.transform.position;
        this.home_turn = this.transform.rotation_z;
    }

    on ResetToy(toy) {
        if toy == 0.0 || toy == this.toy { put_back(); }
    }

    fn put_back() {
        this.transform.position = this.home;
        this.transform.rotation_z = this.home_turn;
        Physics.set_velocity(this.entity, 0.0, 0.0);
        Physics.set_angular_velocity(this.entity, 0.0);
    }
}

// A joint owner: DROP EVERYTHING turns it off, a reset turns it back on.
script JointPart {
    @export let toy: number = 0.0;

    on ResetToy(toy) {
        if toy == 0.0 || toy == this.toy { Physics.set_joint_enabled(this.entity, true); }
    }
}
```

### `assets/scripts/playground.decay`

```rust
// The playground's director: the camera, the pointer's tools, the global
// buttons and the toy selector. Every toy is its own script; this one only
// says which toy is selected and passes on what the player asked of it.

state Game {
    var toy: number = 0.0;
    var tool: number = 0.0;
    var debug: bool = false;
    var gravity: number = 0.0;
    var chaos: bool = false;
    var spawned: number = 0.0;
    var blasts: number = 0.0;
    var grabs: number = 0.0;
    // The probe's query (0 ray, 1 circle cast, 2 box cast, 3 area) and mask.
    var probe: number = 0.0;
    var probe_mask: number = 0.0;
    var queries: number = 0.0;
}

// A toy was asked to do one of its four things: 1 to 4.
event ToyAction(toy: number, action: number);
// A toy, or 0 for the whole room, goes back to how the scene authored it.
event ResetToy(toy: number);
// DROP EVERYTHING: every joint lets go, every motor stops.
event DropEverything(on: bool);

const TOOLS: number = 4.0;
const TOYS: number = 8.0;

shared fn toy_name(toy: number) -> String {
    if toy == 1.0 { return "WRECKING BALL"; }
    if toy == 2.0 { return "GANTRY CRANE"; }
    if toy == 3.0 { return "CANNON GALLERY"; }
    if toy == 4.0 { return "BUMPER PIT"; }
    if toy == 5.0 { return "MATERIAL LAB"; }
    if toy == 6.0 { return "SEESAW + TRAMPOLINE"; }
    if toy == 7.0 { return "TEST TRACK"; }
    if toy == 8.0 { return "DOMINO RUN"; }
    return "THE WHOLE ROOM";
}

// What a toy's four buttons say, comma-free so it fits a chip.
shared fn action_name(toy: number, action: number) -> String {
    if toy == 1.0 {
        if action == 1.0 { return "WIND UP"; }
        if action == 2.0 { return "LET GO"; }
        if action == 3.0 { return "CUT ROPE"; }
        return "REBUILD";
    }
    if toy == 2.0 {
        if action == 1.0 { return "< TROLLEY"; }
        if action == 2.0 { return "TROLLEY >"; }
        if action == 3.0 { return "HOIST"; }
        return "GRAB / DROP";
    }
    if toy == 3.0 {
        if action == 1.0 { return "AIM UP"; }
        if action == 2.0 { return "AIM DOWN"; }
        if action == 3.0 { return "FIRE"; }
        return "CCD";
    }
    if toy == 4.0 {
        if action == 1.0 { return "LEFT FLIP"; }
        if action == 2.0 { return "RIGHT FLIP"; }
        if action == 3.0 { return "LIFT"; }
        return "MULTIBALL";
    }
    if toy == 5.0 {
        if action == 1.0 { return "RACE"; }
        if action == 2.0 { return "BOUNCE TEST"; }
        if action == 3.0 { return "TILT"; }
        return "RESET LAB";
    }
    if toy == 6.0 {
        if action == 1.0 { return "DROP ANVIL"; }
        if action == 2.0 { return "LOAD BALL"; }
        if action == 3.0 { return "STIFFER"; }
        return "SOFTER";
    }
    if toy == 7.0 {
        if action == 1.0 { return "< LEFT"; }
        if action == 2.0 { return "RIGHT >"; }
        if action == 3.0 { return "JUMP"; }
        return "KICK";
    }
    if toy == 8.0 {
        if action == 1.0 { return "PUSH"; }
        if action == 2.0 { return "SET UP"; }
        if action == 3.0 { return "PRESS IT"; }
        return "ARM / DISARM";
    }
    if action == 1.0 { return "DROP EVERYTHING"; }
    if action == 2.0 { return "100 BALLS"; }
    if action == 3.0 { return "BLAST ALL"; }
    return "CLEAN UP";
}

// Where the camera looks for each toy: centre and how much height it shows.
shared fn toy_view(toy: number) -> Vec3 {
    if toy == 1.0 { return Vec3(-20.0, 7.0, 15.0); }
    if toy == 2.0 { return Vec3(-16.0, 6.5, 17.0); }
    if toy == 3.0 { return Vec3(18.0, -7.5, 14.0); }
    if toy == 4.0 { return Vec3(21.0, 4.0, 22.0); }
    if toy == 5.0 { return Vec3(-23.0, -6.5, 15.0); }
    if toy == 6.0 { return Vec3(-9.5, -8.0, 13.0); }
    if toy == 7.0 { return Vec3(3.0, -4.2, 12.5); }
    if toy == 8.0 { return Vec3(2.6, 4.0, 10.0); }
    return Vec3(0.0, 0.0, 0.0);
}

// One line under the dock saying what to try; a toy may replace it with a
// live readout while it is selected.
shared fn toy_hint(toy: number) -> String {
    if toy == 1.0 { return "Wind it up, let it go. Cut the rope mid-swing."; }
    if toy == 2.0 { return "Hold the arrows to drive. HOIST lowers onto whatever is below; GRAB ties it on."; }
    if toy == 3.0 { return "Hold aim, hold FIRE. Turn CCD off and watch shells pass through the glass."; }
    if toy == 4.0 { return "Flip with Z and M. LIFT carries the balls back up to the top."; }
    if toy == 5.0 { return "Same ramp, same push: ice, wood and rubber do not agree."; }
    if toy == 6.0 { return "Drop the anvil on the seesaw. Tune the trampoline's springs."; }
    if toy == 7.0 { return "A/D or arrows walk, W or Space jumps, S drops through planks, F kicks. Ride the lift."; }
    if toy == 8.0 { return "Push the first domino. The last one lands on the red button, and the red button means it."; }
    return "Grab, throw, blast, spawn. Q and E visit each contraption.";
}

shared fn gravity_name(index: number) -> String {
    if index == 1.0 { return "MOON"; }
    if index == 2.0 { return "ZERO-G"; }
    if index == 3.0 { return "UPSIDE DOWN"; }
    if index == 4.0 { return "SIDEWAYS"; }
    return "EARTH";
}

// Which way is down for each GRAVITY setting, in units per second squared.
shared fn gravity_pull(index: number) -> Vec2 {
    if index == 1.0 { return Vec2(0.0, -3.0); }
    if index == 2.0 { return Vec2(0.0, 0.0); }
    if index == 3.0 { return Vec2(0.0, 16.0); }
    if index == 4.0 { return Vec2(16.0, 0.0); }
    return Vec2(0.0, -16.0);
}

shared fn tool_name(index: number) -> String {
    if index == 1.0 { return "BLAST"; }
    if index == 2.0 { return "SPAWN"; }
    if index == 3.0 { return "PROBE"; }
    return "GRAB";
}

// Everything the pointer's tools may pick up, push or probe: what moves.
shared fn movable_mask() -> number {
    return Physics.mask(["props", "balls", "shells", "glass"]);
}

script Director {
    @export let ball: Prefab;
    @export let crate_box: Prefab;
    @export let plank: Prefab;
    @export let anvil: Prefab;
    @export let bowling: Prefab;
    @export let hand_prefab: Prefab;

    // The room the overview fits, in world units.
    @export let room_width: number = 62.0;
    @export let room_height: number = 30.0;

    var view_center: Vec2 = Vec2(0.0, 0.0);
    var view_height: number = 30.0;
    var item: number = 0.0;
    var balls_owed: number = 0.0;
    var spawned: List<Entity> = [];
    var hand: Entity? = null;
    var held: Entity? = null;
    var hold_offset: Vec2 = Vec2(0.0, 0.0);
    var started: bool = false;
    var frames: number = 0.0;
    // The clear band of screen between the bars, in overlay units.
    var band_low: number = -0.62;
    var band_high: number = 0.82;

    fn start() {
        refresh_labels();
    }

    fn update(dt: number) {
        if !this.started {
            this.started = true;
            let goal = target_view();
            this.view_center = Vec2(goal.x, goal.y);
            this.view_height = goal.z;
        }
        keys();
        buttons();
        measure_band();
        follow_view(dt);
        pointer(dt);
        rain();
        stats();
    }

    // What the camera wants to show, as a centre and a height: a toy, or the
    // whole room, fitted into the band of screen the bars leave clear.
    fn target_view() -> Vec3 {
        let band = max(0.6, this.band_high - this.band_low);
        let middle = (this.band_high + this.band_low) * 0.5;
        if Game.toy != 0.0 {
            let view = toy_view(Game.toy);
            let height = view.z * 2.0 / (band * 0.96);
            return Vec3(view.x, view.y - middle * height * 0.5, height);
        }
        let aspect = max(0.3, Viewport.aspect);
        let height = max(this.room_height * 2.0 / (band * 0.97), this.room_width / aspect * 1.02);
        return Vec3(0.0, 0.0 - middle * height * 0.5, height);
    }

    // The band between the top bar and the bottom dock, in overlay units,
    // read from where Weave laid them out: the bars are fixed in pixels, so
    // on a small screen they cover more of the room.
    fn measure_band() {
        // Weave lays the bars out a few frames after the scene comes on.
        if this.frames < 10.0 { return; }
        let low = World.find("toy-prev");
        let high = World.find("btn-drop");
        this.band_low = Ui.position(low).y + Ui.size(low).y * 0.5;
        this.band_high = Ui.position(high).y - Ui.size(high).y * 0.5;
    }

    fn follow_view(dt: number) {
        let goal = target_view();
        let blend = 1.0 - exp(-6.0 * dt);
        this.view_center = this.view_center.lerp(Vec2(goal.x, goal.y), blend);
        this.view_height = lerp(this.view_height, goal.z, blend);
        let camera = World.find("Camera");
        camera.transform.position.x = this.view_center.x;
        camera.transform.position.y = this.view_center.y;
        Camera.orthographic_size(camera, this.view_height);
    }

    // The pointer in the world: the overlay is two units tall and centred, and
    // the camera shows view_height units of height around view_center.
    fn pointer_world() -> Vec2 {
        return this.view_center + Pointer.overlay * (this.view_height * 0.5);
    }

    fn keys() {
        if Input.just_pressed("G") { choose_tool(0.0); }
        if Input.just_pressed("B") { choose_tool(1.0); }
        if Input.just_pressed("N") {
            if Game.tool == 2.0 { this.item = (this.item + 1.0) % 5.0; }
            choose_tool(2.0);
        }
        if Input.just_pressed("P") {
            if Game.tool == 3.0 { Game.probe = (Game.probe + 1.0) % 4.0; }
            choose_tool(3.0);
        }
        if Input.just_pressed("Q") { choose_toy(Game.toy - 1.0); }
        if Input.just_pressed("E") { choose_toy(Game.toy + 1.0); }
        if Input.just_pressed("X") { drop_everything(); }
        if Input.just_pressed("O") { this.balls_owed += 100.0; }
        if Input.just_pressed("V") { cycle_gravity(); }
        if Input.just_pressed("I") { toggle_debug(); }
        if Input.just_pressed("R") { reset(); }
        if Input.just_pressed("K") { to_quarry(); }
        if Input.just_pressed("Digit1") { act(1.0); }
        if Input.just_pressed("Digit2") { act(2.0); }
        if Input.just_pressed("Digit3") { act(3.0); }
        if Input.just_pressed("Digit4") { act(4.0); }
    }

    fn buttons() {
        if Ui.is_pressed(World.find("btn-drop")) { drop_everything(); }
        if Ui.is_pressed(World.find("btn-balls")) { this.balls_owed += 100.0; }
        if Ui.is_pressed(World.find("btn-gravity")) { cycle_gravity(); }
        if Ui.is_pressed(World.find("btn-debug")) { toggle_debug(); }
        if Ui.is_pressed(World.find("btn-reset")) { reset(); }
        if Ui.is_pressed(World.find("btn-quarry")) { to_quarry(); }
        if Ui.is_pressed(World.find("tool-grab")) { choose_tool(0.0); }
        if Ui.is_pressed(World.find("tool-blast")) { choose_tool(1.0); }
        if Ui.is_pressed(World.find("tool-spawn")) {
            if Game.tool == 2.0 { this.item = (this.item + 1.0) % 5.0; }
            choose_tool(2.0);
        }
        if Ui.is_pressed(World.find("tool-probe")) {
            if Game.tool == 3.0 { Game.probe = (Game.probe + 1.0) % 4.0; }
            choose_tool(3.0);
        }
        if Ui.is_pressed(World.find("toy-prev")) { choose_toy(Game.toy - 1.0); }
        if Ui.is_pressed(World.find("toy-next")) { choose_toy(Game.toy + 1.0); }
        for i in 0..4 {
            let action = i + 1.0;
            if Ui.is_pressed(World.find("act-" + action)) { act(action); }
        }
    }

    // The domino run's red button: the one thing in the room that presses
    // DROP EVERYTHING by itself.
    on ButtonPressed(x, y) {
        Camera.impact(1.0);
        print("Playground red button");
        if !Game.chaos { drop_everything(); }
    }

    fn act(action: number) {
        if Game.toy == 0.0 {
            if action == 1.0 { drop_everything(); }
            if action == 2.0 { this.balls_owed += 100.0; }
            if action == 3.0 { blast(this.view_center, 40.0, 26.0); }
            if action == 4.0 { clean_up(); }
            return;
        }
        ToyAction.emit(Game.toy, action);
    }

    fn choose_tool(index: number) {
        Game.tool = index;
        let_go();
        refresh_labels();
    }

    fn choose_toy(index: number) {
        var toy = index;
        if toy < 0.0 { toy = TOYS; }
        if toy > TOYS { toy = 0.0; }
        Game.toy = toy;
        refresh_labels();
        print("Playground toy " + toy_name(toy));
    }

    fn cycle_gravity() {
        Game.gravity = (Game.gravity + 1.0) % 5.0;
        Physics.set_gravity(gravity_pull(Game.gravity));
        refresh_labels();
        print("Playground gravity " + gravity_name(Game.gravity));
    }

    // The 3D annex is its own scene; this one waits, switched off, as it was.
    fn to_quarry() {
        let_go();
        print("Playground off to the quarry");
        // Coming back, the bars are laid out again a frame after this scene
        // is switched on; until then there is nothing to measure.
        this.frames = 0.0;
        Scene.go("quarry.scene");
    }

    fn toggle_debug() {
        Game.debug = !Game.debug;
        refresh_labels();
        print("Playground debug " + Game.debug);
    }

    fn drop_everything() {
        Game.chaos = !Game.chaos;
        let_go();
        DropEverything.emit(Game.chaos);
        if Game.chaos {
            for joint in World.with_tag("joint") {
                Physics.set_joint_enabled(joint, false);
            }
            Camera.impact(0.8);
            print("Playground DROP EVERYTHING");
        } else {
            for joint in World.with_tag("joint") {
                Physics.set_joint_enabled(joint, true);
            }
            print("Playground joints restored");
        }
        refresh_labels();
    }

    fn reset() {
        let_go();
        if Game.toy == 0.0 {
            clean_up();
            Game.gravity = 0.0;
            Physics.set_gravity(gravity_pull(0.0));
            if Game.chaos { drop_everything(); }
        } else {
            // With everything dropped, the room's parts lie wherever gravity
            // threw them, and some in this contraption's way. Setting it up
            // again puts the room back first, or the debris undoes it.
            if Game.chaos {
                drop_everything();
                ResetToy.emit(0.0);
            }
            clear_around(toy_view(Game.toy));
        }
        ResetToy.emit(Game.toy);
        refresh_labels();
        print("Playground reset " + toy_name(Game.toy));
    }

    // Takes away what was spawned in a contraption's view, so a reset puts it
    // back clear of the balls that rained into it.
    fn clear_around(view: Vec3) {
        let half = Vec2(view.z * 0.9, view.z * 0.5);
        var kept: List<Entity> = [];
        for thing in this.spawned {
            if World.exists(thing) {
                let at = thing.transform.world_position;
                if abs(at.x - view.x) < half.x && abs(at.y - view.y) < half.y {
                    World.despawn(thing);
                } else {
                    kept.push(thing);
                }
            }
        }
        this.spawned = kept;
        Game.spawned = kept.length;
    }

    fn clean_up() {
        for thing in this.spawned {
            if World.exists(thing) { World.despawn(thing); }
        }
        this.spawned.clear();
        this.balls_owed = 0.0;
        Game.spawned = 0.0;
    }

    // Pours owed balls in from the ceiling a few a step, so a hundred arrive
    // as a downpour rather than as one solid lump.
    fn rain() {
        var poured = 0.0;
        while this.balls_owed > 0.0 && poured < 3.0 {
            let x = Random.range(-28.0, 28.0);
            let y = Random.range(12.0, 13.5);
            let ball = make(0.0, Vec2(x, y));
            Physics.set_velocity(ball, Random.range(-2.0, 2.0), Random.range(-4.0, 0.0));
            this.balls_owed -= 1.0;
            poured += 1.0;
        }
    }

    // Spawns item 0 ball, 1 crate, 2 plank, 3 anvil or 4 bowling ball.
    fn make(kind: number, at: Vec2) -> Entity {
        var prefab = this.ball;
        if kind == 1.0 { prefab = this.crate_box; }
        if kind == 2.0 { prefab = this.plank; }
        if kind == 3.0 { prefab = this.anvil; }
        if kind == 4.0 { prefab = this.bowling; }
        let thing = World.spawn(prefab);
        thing.transform.position = Vec3(at.x, at.y, 0.3);
        this.spawned.push(thing);
        // The room holds a few hundred loose things; past that the oldest go.
        while this.spawned.length > 320.0 {
            let oldest = this.spawned.remove_at(0);
            if World.exists(oldest) { World.despawn(oldest); }
        }
        Game.spawned = this.spawned.length;
        return thing;
    }

    fn pointer(dt: number) {
        let at = pointer_world();
        let cursor = World.find("cursor");
        cursor.transform.position = Vec3(at.x, at.y, 2.0);
        World.set_active(cursor, Pointer.inside && !Pointer.over_ui);
        if Pointer.just_pressed("Left") && !Pointer.over_ui {
            if Game.tool == 0.0 { grab(at); }
            if Game.tool == 1.0 { blast(at, 3.2, 9.0); }
            if Game.tool == 2.0 { make(this.item, at); }
        }
        if this.hand != null {
            hold(at, dt);
            if !Pointer.is_down("Left") { let_go(); }
        }
    }

    // Ties a kinematic hand to whatever is under the pointer with a short
    // rope, and moves the hand with the pointer. Letting go keeps the body's
    // speed, which is how a throw works.
    fn grab(at: Vec2) {
        let found = Physics.overlap_circle(at, 0.12, movable_mask(), false, null);
        if found.length == 0.0 { return; }
        let target = found[0];
        let hand: Entity = World.spawn(this.hand_prefab);
        hand.transform.position = Vec3(at.x, at.y, 1.0);
        let centre = target.transform.world_position;
        let reach = max(0.05, Vec2(centre.x, centre.y).distance(at));
        Physics.connect_distance(hand, target, reach);
        this.hand = hand;
        this.held = target;
        Game.grabs += 1.0;
        print("Playground grabbed");
    }

    fn hold(at: Vec2, dt: number) {
        let hand = this.hand;
        if hand == null { return; }
        let now = hand.transform.world_position;
        let pull = (at - Vec2(now.x, now.y)) / max(dt, 0.001);
        Physics.set_velocity(hand, pull.x, pull.y);
        let held = this.held;
        let string = World.find("grab-string");
        if held != null && World.exists(held) {
            let other = held.transform.world_position;
            line(string, Vec2(now.x, now.y), Vec2(other.x, other.y), 0.06);
            World.set_active(string, true);
        }
    }

    fn let_go() {
        let hand = this.hand;
        if hand != null {
            if World.exists(hand) { World.despawn(hand); }
        }
        this.hand = null;
        this.held = null;
        World.set_active(World.find("grab-string"), false);
    }

    // Shoves everything movable inside a circle outwards, pushing each at a
    // point on its near side so it spins as well as flies.
    fn blast(at: Vec2, radius: number, strength: number) {
        for thing in Physics.overlap_circle(at, radius, movable_mask(), false, null) {
            let place = thing.transform.world_position;
            let away = Vec2(place.x, place.y) - at;
            let distance = away.length;
            var direction = away.normalized;
            if distance < 0.01 { direction = Vec2(0.0, 1.0); }
            let falloff = 1.0 - min(1.0, distance / radius) * 0.6;
            let push = direction * strength * falloff + Vec2(0.0, strength * 0.25);
            let point = Vec2(place.x, place.y) - direction * 0.15 + Vec2(-direction.y, direction.x) * 0.1;
            Physics.apply_impulse_at_point(thing, push, point);
        }
        let ring = World.find("blast-ring");
        BlastRing.on(ring).go(at, radius);
        Effects.burst_at(World.find("blast-sparks"), at.x, at.y);
        Camera.impact(min(1.0, 0.25 + strength / 40.0));
        Game.blasts += 1.0;
        print("Playground blast");
    }

    fn stats() {
        let things = World.with_tag("body").length + this.spawned.length;
        Ui.set_text(World.find("stats"), things + " bodies  /  " + World.with_tag("joint").length + " joints  /  gravity " + gravity_name(Game.gravity));
        // What the probe and the debug overlay have to say, under the title,
        // once every script has had its first frame.
        this.frames += 1.0;
        if this.frames < 2.0 { return; }
        var readout = Probe.on(World.find("Probe")).text;
        let debug = DebugOverlay.on(World.find("Debug overlay")).text;
        if debug != "" {
            if readout != "" { readout = readout + "\n"; }
            readout = readout + debug;
        }
        Ui.set_text(World.find("readout"), readout);
    }

    fn refresh_labels() {
        Ui.set_text(World.find("btn-gravity-label"), "GRAVITY: " + gravity_name(Game.gravity));
        var debug = "DEBUG OFF";
        if Game.debug { debug = "DEBUG ON"; }
        Ui.set_text(World.find("btn-debug-label"), debug);
        var drop = "DROP EVERYTHING";
        if Game.chaos { drop = "PUT IT BACK"; }
        Ui.set_text(World.find("btn-drop-label"), drop);
        Ui.set_text(World.find("toy-label"), toy_name(Game.toy));
        Ui.set_text(World.find("hint"), toy_hint(Game.toy));
        for i in 0..4 {
            let action = i + 1.0;
            Ui.set_text(World.find("act-" + action + "-label"), action_name(Game.toy, action));
        }
        var spawn = "SPAWN BALL";
        if this.item == 1.0 { spawn = "SPAWN CRATE"; }
        if this.item == 2.0 { spawn = "SPAWN PLANK"; }
        if this.item == 3.0 { spawn = "SPAWN ANVIL"; }
        if this.item == 4.0 { spawn = "SPAWN BOWLING"; }
        Ui.set_text(World.find("tool-spawn-label"), spawn);
        var probe = "PROBE";
        if Game.tool == 3.0 { probe = probe_name(Game.probe); }
        Ui.set_text(World.find("tool-probe-label"), probe);
        for i in 0..TOOLS {
            let chosen = World.find("tool-mark-" + i);
            World.set_active(chosen, Game.tool == i);
        }
    }
}

// Draws an entity as a bar from one point to another.
shared fn line(entity: Entity, start: Vec2, end: Vec2, width: number) {
    let delta = end - start;
    entity.transform.position.x = (start.x + end.x) * 0.5;
    entity.transform.position.y = (start.y + end.y) * 0.5;
    entity.transform.scale = Vec3(max(0.001, delta.length), width, 1.0);
    entity.transform.rotation_z = atan2(delta.y, delta.x);
}

// The ring a blast leaves, growing and fading.
script BlastRing {
    var age: number = 1.0;
    var radius: number = 1.0;
    fn go(at: Vec2, size: number) {
        this.age = 0.0;
        this.radius = size;
        this.transform.position = Vec3(at.x, at.y, 1.5);
    }
    fn update(dt: number) {
        this.age += dt;
        let t = min(1.0, this.age / 0.35);
        let size = this.radius * 2.0 * (0.3 + 0.7 * t);
        this.transform.scale = Vec3(size, size, 1.0);
        this.shape.stroke = Color(1.0, 0.8, 0.35, 1.0 - t);
        this.shape.fill = Color(1.0, 0.55, 0.2, 0.25 * (1.0 - t));
    }
}
```

### `assets/scripts/quarry.decay`

```rust
// The Voxel Quarry: the playground's 3D annex.
//
// The floor is a voxel world with a voxel collider. Crates are 3D rigid
// bodies resting on it; digging a block out from under one opens a real hole
// it falls into. A laser sweeps across firing a 3D ray straight down and says
// what it hits. K, or the button, goes back to the room.

script Quarry {
    @export let crate_prefab: Prefab;
    @export let sweep: number = 8.0;
    @export let laser_speed: number = 3.0;

    // Every block dug, and what it was, so RESET can fill the holes again.
    var dug: List<Vec3> = [];
    var was: List<String> = [];
    var dropped: List<Entity> = [];
    var scan: number = -8.0;
    var scan_direction: number = 1.0;
    var laser_on: bool = true;
    var ray: String = "";

    fn update(dt: number) {
        if Input.just_pressed("K") || Ui.is_pressed(World.find("q-back")) {
            print("Playground quarry back to the room");
            Scene.go("playground.scene");
            return;
        }
        if Input.just_pressed("Digit1") || Ui.is_pressed(World.find("q-act-1")) { drop_crates(); }
        if Input.just_pressed("Digit2") || Ui.is_pressed(World.find("q-act-2")) { dig_under(); }
        if Input.just_pressed("Digit3") || Ui.is_pressed(World.find("q-act-3")) { shove(); }
        if Input.just_pressed("Digit4") || Ui.is_pressed(World.find("q-act-4")) {
            this.laser_on = !this.laser_on;
            var words = "START LASER";
            if this.laser_on { words = "STOP LASER"; }
            Ui.set_text(World.find("q-act-4-label"), words);
        }
        if Input.just_pressed("R") || Ui.is_pressed(World.find("q-reset")) { reset(); }
        if Pointer.just_pressed("Left") && !Pointer.over_ui && Aim.hit {
            if dig(Aim.x, Aim.y, Aim.z) { print("Playground quarry dug " + Aim.x + " " + Aim.y + " " + Aim.z); }
        }
        laser(dt);
        Ui.set_text(World.find("q-stats"), World.with_tag("crate").length + " crates  /  " + this.dug.length + " blocks dug  /  gravity 16");
        Ui.set_text(World.find("q-readout"), this.ray);
    }

    // Takes one block out of the floor, remembering what it was. Bodies near
    // the hole are nudged awake so a resting crate notices the floor is gone.
    fn dig(column: number, row: number, level: number) -> bool {
        let ground = World.find("Quarry Floor");
        let block = Grid.block(ground, column, row, level);
        if block == "" { return false; }
        Grid.set_block(ground, column, row, level, "");
        this.dug.push(Vec3(column, row, level));
        this.was.push(block);
        let hole = Vec3(column + 0.5, level + 0.5, row + 0.5);
        for crate in World.with_tag("crate") {
            if crate.transform.world_position.distance(hole) < 3.0 {
                Physics3d.apply_impulse(crate, Vec3(0.0, 0.01, 0.0));
            }
        }
        return true;
    }

    // Finds the ground under every crate with a 3D ray and digs a pit there,
    // three by three and two deep, so each one drops into its own hole.
    fn dig_under() {
        let ground = World.find("Quarry Floor");
        var pits = 0.0;
        for crate in World.with_tag("crate") {
            let at = crate.transform.world_position;
            let hit = Physics3d.raycast(at, Vec3(0.0, -1.0, 0.0), 30.0, Physics3d.layer("solid"), false, crate);
            if hit != null {
                if hit.entity == ground {
                    let column = floor(at.x);
                    let row = floor(at.z);
                    let level = floor(hit.point.y - 0.5);
                    for dx in 0..3 {
                        for dz in 0..3 {
                            dig(column + dx - 1.0, row + dz - 1.0, level);
                            dig(column + dx - 1.0, row + dz - 1.0, level - 1.0);
                        }
                    }
                    pits += 1.0;
                }
            }
        }
        print("Playground quarry dug under " + pits + " crates");
    }

    fn drop_crates() {
        for i in 0..6 {
            let crate: Entity = World.spawn(this.crate_prefab);
            crate.transform.position = Vec3(Random.range(-5.0, 4.0), 16.0 + i * 1.3, Random.range(-4.0, 5.0));
            this.dropped.push(crate);
        }
        print("Playground quarry crates dropped");
    }

    fn shove() {
        for crate in World.with_tag("crate") {
            Physics3d.apply_impulse(crate, Vec3(Random.range(-3.0, 3.0), Random.range(6.0, 10.0), Random.range(-3.0, 3.0)));
            Physics3d.set_angular_velocity(crate, Vec3(Random.range(-4.0, 4.0), Random.range(-4.0, 4.0), Random.range(-4.0, 4.0)));
        }
        print("Playground quarry shoved");
    }

    fn reset() {
        let ground = World.find("Quarry Floor");
        var index = this.dug.length - 1.0;
        while index >= 0.0 {
            let cell = this.dug[index];
            Grid.set_block(ground, cell.x, cell.y, cell.z, this.was[index]);
            index -= 1.0;
        }
        this.dug.clear();
        this.was.clear();
        for crate in this.dropped {
            if World.exists(crate) { World.despawn(crate); }
        }
        this.dropped.clear();
        QuarryReset.emit();
        print("Playground quarry reset");
    }

    // A 3D ray fired straight down from above the quarry, swept back and
    // forth; the laser is drawn as long as the ray travelled.
    fn laser(dt: number) {
        let beam = World.find("q-laser");
        let spot = World.find("q-laser-hit");
        World.set_active(beam, this.laser_on);
        World.set_active(spot, this.laser_on);
        if !this.laser_on {
            this.ray = "3D RAY / off";
            return;
        }
        this.scan += this.scan_direction * this.laser_speed * dt;
        if abs(this.scan) > this.sweep {
            this.scan = clamp(this.scan, 0.0 - this.sweep, this.sweep);
            this.scan_direction = 0.0 - this.scan_direction;
        }
        let top = 20.0;
        let origin = Vec3(this.scan, top, 0.5);
        let hit = Physics3d.raycast(origin, Vec3(0.0, -1.0, 0.0), 40.0, Physics3d.layer("solid"), false, null);
        var reach = 40.0;
        if hit != null {
            reach = hit.distance;
            spot.transform.position = hit.point;
            let ground = World.find("Quarry Floor");
            if hit.entity == ground {
                let column = floor(hit.point.x);
                let row = floor(hit.point.z);
                let level = floor(hit.point.y - 0.5);
                this.ray = "3D RAY / " + Grid.block(ground, column, row, level) + " block " + column + ", " + level + ", " + row + " at " + hit.distance.fixed(2) + " m";
            } else {
                this.ray = "3D RAY / a crate at " + hit.distance.fixed(2) + " m, normal " + hit.normal.y.fixed(2) + " up";
            }
        } else {
            this.ray = "3D RAY / nothing below";
        }
        beam.transform.position = Vec3(this.scan, top - reach * 0.5, 0.5);
        beam.transform.scale = Vec3(0.06, max(0.01, reach), 0.06);
    }
}

// RESET in the quarry: the authored crates go home.
event QuarryReset();

script QuarryCrate {
    var home: Vec3 = Vec3(0.0, 0.0, 0.0);

    fn start() { this.home = this.transform.position; }

    on QuarryReset() {
        this.transform.position = this.home;
        this.transform.rotation_z = 0.0;
        Physics3d.set_velocity(this.entity, Vec3(0.0, 0.0, 0.0));
        Physics3d.set_angular_velocity(this.entity, Vec3(0.0, 0.0, 0.0));
    }
}
```

### `assets/scripts/toys.decay`

```rust
// The contraptions' controls. Each listens for its own ToyAction and its own
// ResetToy; the physics is all authored joints and bodies in the scene.

// A steel ball on a rigid arm. A hinge position motor winds it up; letting
// go switches the motor off so the arm swings free; cutting the rope disables
// the hinge, and the ball keeps whatever speed it had.
script WreckingBall {
    @export let wind_angle: number = -1.15;
    var winding: bool = false;

    fn start() { relax(); }

    on ToyAction(toy, action) {
        if toy != 1.0 { return; }
        let hinge = World.find("wreck-hinge");
        if action == 1.0 {
            this.winding = true;
            Physics.set_joint_enabled(hinge, true);
            Physics.set_hinge_position_motor(hinge, this.wind_angle, 7000.0, 900.0, 14000.0);
            print("Playground wrecking ball winding");
        }
        if action == 2.0 { relax(); print("Playground wrecking ball released"); }
        if action == 3.0 {
            relax();
            Physics.set_joint_enabled(hinge, false);
            print("Playground wrecking ball cut loose");
        }
        if action == 4.0 { ResetToy.emit(1.0); }
    }

    on ResetToy(toy) {
        if toy == 0.0 || toy == 1.0 { relax(); }
    }

    on DropEverything(on) { relax(); }

    fn relax() {
        this.winding = false;
        // Zero torque coasts rather than brakes: the arm swings free.
        Physics.set_hinge_motor(World.find("wreck-hinge"), 0.0, 0.0);
    }
}

// A trolley on a slider along the ceiling girder, driven by a position
// motor; a hook on a spring whose rest length is the winch; and a grip, a
// distance joint created at runtime between the hook and the cargo under it.
script Crane {
    @export let speed: number = 5.0;
    @export let high: number = 1.4;
    @export let low: number = 9.5;
    var cable: number = 1.4;
    var depth: number = 9.5;
    var lowering: bool = false;
    // Where the trolley is told to be, along the rail from its middle.
    var target: number = -4.0;
    var parked: bool = false;
    var gripping: bool = false;
    var held: Entity? = null;

    fn update(dt: number) {
        var drive = 0.0;
        if Game.toy == 2.0 {
            if Ui.is_held(World.find("act-1")) || Input.is_down("Digit1") || Input.is_down("ArrowLeft") { drive = -1.0; }
            if Ui.is_held(World.find("act-2")) || Input.is_down("Digit2") || Input.is_down("ArrowRight") { drive = 1.0; }
        }
        if drive != 0.0 || !this.parked {
            // A position motor chasing a target that runs ahead of it: the
            // trolley keeps an even speed under any load, and stops dead
            // where the target stops however the hook swings.
            this.target = clamp(this.target + drive * this.speed * dt, -11.5, 11.5);
            Physics.set_slider_position_motor(World.find("crane-slider"), this.target, 600.0, 90.0, 4000.0);
            this.parked = drive == 0.0;
        }
        var goal = this.high;
        if this.lowering { goal = this.depth; }
        if abs(goal - this.cable) > 0.01 {
            let step = 4.0 * dt;
            this.cable = this.cable + clamp(goal - this.cable, -step, step);
            Physics.set_spring(World.find("crane-cable"), this.cable, 90.0, 6.0);
        }
        draw();
    }

    on ToyAction(toy, action) {
        if toy != 2.0 { return; }
        if action == 3.0 {
            this.lowering = !this.lowering;
            if this.lowering { this.depth = sound(); }
            print("Playground crane hoist " + this.lowering);
        }
        if action == 4.0 {
            if this.gripping { release(); } else { grab(); }
        }
    }

    on ResetToy(toy) {
        if toy == 0.0 || toy == 2.0 {
            release();
            this.cable = 1.4;
            this.lowering = false;
            this.target = -4.0;
            this.parked = false;
            Physics.set_spring(World.find("crane-cable"), this.cable, 90.0, 6.0);
        }
    }

    on DropEverything(on) { release(); }

    // How far down to let the hook go: the hook's own footprint, swept
    // straight down, finds the first thing it would land on, so it stops
    // just above the load instead of shoving it off whatever it stands on.
    fn sound() -> number {
        let hook = World.find("crane-hook");
        let at = hook.transform.world_position;
        let mask = Physics.mask(["static", "props", "balls", "glass"]);
        let hit = Physics.cast_box(Vec2(at.x, at.y - 0.25), Vec2(0.5, 0.3), 0.0, Vec2(0.0, -1.0), 30.0, mask, false, hook);
        if hit == null { return this.low; }
        return clamp(this.cable + hit.distance - 0.08, this.high, this.low);
    }

    // The room's own cargo only: an authored joint names bodies in its own
    // scene, and loose things spawned at runtime are not in it.
    fn grab() {
        let hook = World.find("crane-hook");
        let at = hook.transform.world_position;
        for thing in Physics.overlap_box(Vec2(at.x, at.y - 0.7), Vec2(0.7, 0.6), 0.0, movable_mask(), false, hook) {
            if !this.gripping && World.has_tag(thing, "cargo") {
                Physics.create_distance_joint(World.find("crane-grip"), hook, thing, 1.0);
                this.gripping = true;
                this.held = thing;
                print("Playground crane grabbed");
            }
        }
        if !this.gripping { print("Playground crane found nothing"); }
    }

    fn release() {
        if this.gripping {
            Physics.remove_joint(World.find("crane-grip"));
            print("Playground crane dropped");
        }
        this.gripping = false;
        this.held = null;
    }

    fn draw() {
        let trolley = World.find("crane-trolley").transform.world_position;
        let hook = World.find("crane-hook").transform.world_position;
        line(World.find("crane-cable-line"), Vec2(trolley.x, trolley.y - 0.25), Vec2(hook.x, hook.y + 0.12), 0.06);
        let grip = World.find("crane-grip-line");
        let held = this.held;
        if held != null {
            if World.exists(held) {
                let at = held.transform.world_position;
                line(grip, Vec2(hook.x, hook.y), Vec2(at.x, at.y), 0.08);
                World.set_active(grip, true);
                return;
            }
        }
        World.set_active(grip, false);
    }
}

// A cannon on a hinge held by a position motor. Shells leave at a speed that
// covers more than a pane's thickness in one fixed step, so with continuous
// collision off most of them pass through the glass without touching it.
state Gallery {
    var hits: number = 0.0;
    var ghosts: number = 0.0;
    var fired: number = 0.0;
    var ccd: bool = true;
}

script Cannon {
    @export let shell: Prefab;
    @export let muzzle_speed: number = 90.0;
    var aim: number = 0.25;
    var cooldown: number = 0.0;

    fn update(dt: number) {
        this.cooldown = max(0.0, this.cooldown - dt);
        if Game.toy == 3.0 {
            var turn = 0.0;
            if Ui.is_held(World.find("act-1")) || Input.is_down("Digit1") || Input.is_down("ArrowUp") { turn = 1.0; }
            if Ui.is_held(World.find("act-2")) || Input.is_down("Digit2") || Input.is_down("ArrowDown") { turn = -1.0; }
            if turn != 0.0 {
                this.aim = clamp(this.aim + turn * 0.9 * dt, -0.1, 1.15);
                Physics.set_hinge_position_motor(World.find("cannon-hinge"), this.aim, 400.0, 60.0, 3000.0);
            }
            if Ui.is_held(World.find("act-3")) && this.cooldown <= 0.0 { fire(); }
            var ccd = "CCD ON";
            if !Gallery.ccd { ccd = "CCD OFF"; }
            Ui.set_text(World.find("hint"), ccd + "   /   " + Gallery.fired + " fired   " + Gallery.hits + " panes hit   " + Gallery.ghosts + " passed straight through");
        }
    }

    on ToyAction(toy, action) {
        if toy != 3.0 { return; }
        if action == 3.0 && this.cooldown <= 0.0 { fire(); }
        if action == 4.0 {
            Gallery.ccd = !Gallery.ccd;
            print("Playground cannon ccd " + Gallery.ccd);
        }
    }

    on ResetToy(toy) {
        if toy == 0.0 || toy == 3.0 {
            this.aim = 0.25;
            Gallery.hits = 0.0;
            Gallery.ghosts = 0.0;
            Gallery.fired = 0.0;
            Physics.set_hinge_position_motor(World.find("cannon-hinge"), this.aim, 400.0, 60.0, 3000.0);
        }
    }

    fn fire() {
        this.cooldown = 0.18;
        let barrel = World.find("cannon-barrel");
        let at = barrel.transform.world_position;
        let angle = barrel.transform.rotation_z;
        let along = Vec2(cos(angle), sin(angle));
        let muzzle = Vec2(at.x, at.y) + along * 1.45;
        let shell = World.spawn(this.shell);
        shell.transform.position = Vec3(muzzle.x, muzzle.y, 0.35);
        Physics.set_continuous_collision(shell, Gallery.ccd);
        let speed = along * this.muzzle_speed;
        Physics.set_velocity(shell, speed.x, speed.y);
        // Recoil: the barrel is shoved back at its muzzle, and the motor
        // brings it back to its aim.
        let kick = along * -1.6;
        Physics.apply_impulse_at_point(barrel, kick, muzzle);
        Effects.burst_at(World.find("muzzle-flash"), muzzle.x, muzzle.y);
        Camera.impact(0.15);
        Gallery.fired += 1.0;
        print("Playground cannon fired");
    }
}

// A shell: remembers whether it touched glass, and leaves after a while.
script Shell {
    var age: number = 0.0;
    var hit_glass: bool = false;
    fn update(dt: number) {
        this.age += dt;
        for other in Physics.collision_started() {
            if World.exists(other) && World.has_tag(other, "glass") { this.hit_glass = true; }
        }
        if this.age > 4.0 { World.despawn(this.entity); }
    }
}

// A pane of glass on a hinge. A shell that touches it knocks it off its
// hinge; a reset hangs it back up.
script GlassPane {
    @export let index: number = 0.0;
    var home: Vec3 = Vec3(0.0, 0.0, 0.0);
    var broken: bool = false;
    fn start() { this.home = this.transform.position; }
    fn update(dt: number) {
        for other in Physics.collision_started() {
            if !this.broken && World.exists(other) && World.has_tag(other, "shell") {
                this.broken = true;
                Gallery.hits += 1.0;
                Physics.set_joint_enabled(World.find("pane-hinge-" + this.index), false);
                let at = this.transform.world_position;
                Effects.burst_at(World.find("glass-shards"), at.x, at.y);
                this.shape.fill = Color(0.66, 0.9, 1.0, 0.2);
                print("Playground pane " + this.index + " shattered");
            }
        }
    }
    on ResetToy(toy) {
        if toy == 0.0 || toy == 3.0 {
            this.broken = false;
            this.transform.position = this.home;
            this.transform.rotation_z = 0.0;
            this.shape.fill = Color(0.38, 0.79, 1.0, 0.45);
            Physics.set_velocity(this.entity, 0.0, 0.0);
            Physics.set_angular_velocity(this.entity, 0.0);
        }
    }
}

// Behind the gallery: counts the shells that got here without touching glass.
// Every pane hangs across the gallery's whole height, so a shell at that
// height that reached here while a pane still hung must have crossed one.
script BackStop {
    @export let top: number = -2.9;
    @export let bottom: number = -11.5;
    fn update(dt: number) {
        for other in Physics.sensor_entered() {
            if World.exists(other) && World.has_tag(other, "shell") {
                let y = other.transform.world_position.y;
                let crossed = y < this.top && y > this.bottom && Gallery.hits < 4.0;
                if crossed && !Shell.on(other).hit_glass {
                    Gallery.ghosts += 1.0;
                    print("Playground shell passed through");
                }
                World.despawn(other);
            }
        }
    }
}

state Pit {
    var score: number = 0.0;
    var lifting: bool = true;
    var lifts: number = 0.0;
}

// The pinball wall and the lift that feeds it. The flippers are position
// motors between a rest angle and a raised one; the lift is a slider
// position motor that waits at the bottom, climbs, and tips its bucket.
script Pinball {
    @export let ball: Prefab;
    @export let wait_bottom: number = 2.5;
    @export let wait_top: number = 1.2;
    // 0 waiting below, 1 climbing, 2 pouring, 3 coming down.
    var phase: number = 0.0;
    var clock: number = 0.0;
    var left_up: bool = false;
    var right_up: bool = false;
    var balls: List<Entity> = [];

    fn update(dt: number) {
        flippers();
        lift(dt);
        if Game.toy == 4.0 {
            var running = "LIFT RUNNING";
            if !Pit.lifting { running = "LIFT STOPPED"; }
            Ui.set_text(World.find("hint"), "Score " + Pit.score + "   /   " + running + "   /   " + Pit.lifts + " loads carried   /   flip with Z and M");
        }
    }

    fn flippers() {
        let selected = Game.toy == 4.0;
        let left = Input.is_down("Z") || (selected && (Ui.is_held(World.find("act-1")) || Input.is_down("Digit1")));
        let right = Input.is_down("M") || (selected && (Ui.is_held(World.find("act-2")) || Input.is_down("Digit2")));
        if left != this.left_up {
            this.left_up = left;
            var angle = -0.45;
            if left { angle = 0.45; }
            Physics.set_hinge_position_motor(World.find("flipper-left-hinge"), angle, 2500.0, 80.0, 6000.0);
        }
        if right != this.right_up {
            this.right_up = right;
            var angle = 0.45;
            if right { angle = -0.45; }
            Physics.set_hinge_position_motor(World.find("flipper-right-hinge"), angle, 2500.0, 80.0, 6000.0);
        }
    }

    fn lift(dt: number) {
        if !Pit.lifting && this.phase == 0.0 { return; }
        this.clock += dt;
        let slider = World.find("lift-slider");
        let tilt = World.find("lift-tilt");
        if this.phase == 0.0 && this.clock > this.wait_bottom {
            this.phase = 1.0;
            this.clock = 0.0;
            // Lean back so the load rolls against the far wall, then climb.
            Physics.set_hinge_position_motor(tilt, -0.25, 1500.0, 120.0, 6000.0);
            Physics.set_slider_position_motor(slider, 12.6, 900.0, 140.0, 8000.0);
        }
        if this.phase == 1.0 && World.find("lift-car").transform.world_position.y > 11.3 {
            this.phase = 2.0;
            this.clock = 0.0;
            Physics.set_hinge_position_motor(tilt, 1.1, 1500.0, 120.0, 6000.0);
            Pit.lifts += 1.0;
            print("Playground lift pouring");
        }
        if this.phase == 2.0 && this.clock > this.wait_top {
            this.phase = 3.0;
            this.clock = 0.0;
            Physics.set_hinge_position_motor(tilt, 0.0, 1500.0, 120.0, 6000.0);
            Physics.set_slider_position_motor(slider, -12.4, 900.0, 140.0, 8000.0);
        }
        if this.phase == 3.0 && World.find("lift-car").transform.world_position.y < -13.0 {
            this.phase = 0.0;
            this.clock = 0.0;
        }
    }

    on ToyAction(toy, action) {
        if toy != 4.0 { return; }
        if action == 3.0 {
            Pit.lifting = !Pit.lifting;
            print("Playground lift " + Pit.lifting);
        }
        if action == 4.0 { multiball(); }
    }

    // Six balls straight into the chute at the top of the table.
    fn multiball() {
        for i in 0..6 {
            let ball = World.spawn(this.ball);
            ball.transform.position = Vec3(24.4 - i * 0.65, 11.6 + i * 0.2, 0.3);
            this.balls.push(ball);
        }
        while this.balls.length > 60.0 {
            let old = this.balls.remove_at(0);
            if World.exists(old) { World.despawn(old); }
        }
        print("Playground multiball");
    }

    on ResetToy(toy) {
        if toy == 0.0 || toy == 4.0 {
            for ball in this.balls {
                if World.exists(ball) { World.despawn(ball); }
            }
            this.balls.clear();
            Pit.score = 0.0;
            Pit.lifts = 0.0;
            this.phase = 0.0;
            this.clock = 0.0;
            Physics.set_slider_position_motor(World.find("lift-slider"), -12.4, 900.0, 140.0, 8000.0);
            Physics.set_hinge_position_motor(World.find("lift-tilt"), 0.0, 1500.0, 120.0, 6000.0);
        }
    }
}

// A bumper: whatever touches it is kicked away along the contact normal, and
// it lights up. The kick is an impulse, so a heavy ball leaves slower.
script Bumper {
    var glow: number = 0.0;
    fn update(dt: number) {
        this.glow = max(0.0, this.glow - dt * 4.0);
        for other in Physics.collision_started() {
            if World.exists(other) && (World.has_tag(other, "loose") || World.has_tag(other, "body")) {
                let here = this.transform.world_position;
                let there = other.transform.world_position;
                let away = Vec2(there.x - here.x, there.y - here.y).normalized;
                Physics.apply_impulse(other, away.x * 1.6, away.y * 1.6);
                this.glow = 1.0;
                Pit.score += 100.0;
                Effects.burst_at(World.find("bumper-sparks"), there.x, there.y);
            }
        }
        this.shape.fill = Color(1.0, 0.36 + this.glow * 0.6, 0.56 + this.glow * 0.4, 1.0);
        this.transform.scale = Vec3(1.4 + this.glow * 0.25, 1.4 + this.glow * 0.25, 1.0);
    }
}

// Ice, wood and rubber on one frictionless ramp. They do not collide with one
// another, so each has the ramp to itself; only their materials differ.
script Racer {
    @export let lane: number = 0.0;
    var home: Vec3 = Vec3(0.0, 0.0, 0.0);
    var turn: number = 0.0;
    fn start() {
        this.home = this.transform.position;
        this.turn = this.transform.rotation_z;
    }
    on ToyAction(toy, action) {
        if toy != 5.0 { return; }
        if action == 1.0 { go(2.5); }
        if action == 3.0 { go(7.0); }
    }
    on ResetToy(toy) {
        if toy == 0.0 || toy == 5.0 { go(0.0); }
    }
    // Back to the top of the ramp, then the same push down the slope for all.
    fn go(push: number) {
        this.transform.position = this.home;
        this.transform.rotation_z = this.turn;
        let along = Vec2(cos(this.turn), sin(this.turn));
        Physics.set_velocity(this.entity, along.x * push, along.y * push);
        Physics.set_angular_velocity(this.entity, 0.0);
    }
    // Read by the lab's readout. A field, because another script's function
    // is a message: it runs after the pass and gives nothing back.
    var travelled: number = 0.0;
    fn update(dt: number) {
        this.travelled = this.transform.position.x - this.home.x;
    }
}

// The same ball dropped on four pads; remembers how high it came back up.
script Bouncer {
    @export let pad: number = 0.0;
    var home: Vec3 = Vec3(0.0, 0.0, 0.0);
    var landed: bool = false;
    var bounces: number = 0.0;
    var peak: number = 0.0;
    // How high the bounce came back above where it rests on its pad.
    var rebound: number = 0.0;
    fn start() { this.home = this.transform.position; }
    fn update(dt: number) {
        // The first bounce only: later ones are lower and would hide it.
        for other in Physics.collision_started() {
            this.bounces += 1.0;
            if !this.landed {
                this.landed = true;
                this.peak = this.transform.position.y;
            }
        }
        if this.bounces == 1.0 && this.transform.position.y > this.peak {
            this.peak = this.transform.position.y;
        }
        let mark = World.find("bounce-mark-" + pad_name(this.pad));
        if this.landed {
            mark.transform.position.y = this.peak;
            this.rebound = this.peak + 13.5 - 0.35;
        }
    }
    on ToyAction(toy, action) {
        if toy == 5.0 && action == 2.0 { drop(); }
    }
    on ResetToy(toy) {
        if toy == 0.0 || toy == 5.0 { drop(); }
    }
    fn drop() {
        this.transform.position = this.home;
        Physics.set_velocity(this.entity, 0.0, 0.0);
        this.landed = false;
        this.bounces = 0.0;
        this.peak = this.home.y;
        this.rebound = 0.0;
    }
}

shared fn pad_name(index: number) -> String {
    if index == 1.0 { return "steel"; }
    if index == 2.0 { return "wood"; }
    if index == 3.0 { return "clay"; }
    return "rubber";
}

script MaterialLab {
    fn update(dt: number) {
        if Game.toy != 5.0 { return; }
        let ice = Racer.on(World.find("racer-ice")).travelled;
        let wood = Racer.on(World.find("racer-wood")).travelled;
        let rubber = Racer.on(World.find("racer-rubber")).travelled;
        var bounce = "";
        for i in 0..4 {
            let height = Bouncer.on(World.find("bouncer-" + pad_name(i))).rebound;
            bounce = bounce + "  " + pad_name(i) + " " + height.fixed(1);
        }
        Ui.set_text(World.find("hint"), "RACE ice " + ice.fixed(1) + "  wood " + wood.fixed(1) + "  rubber " + rubber.fixed(1) + "   /   BOUNCE" + bounce);
    }
    on ToyAction(toy, action) {
        if toy == 5.0 && action == 4.0 { ResetToy.emit(5.0); }
    }
}

// The seesaw's anvil hangs on a rope; dropping it cuts the rope. The
// trampoline's two springs can be made stiffer or softer.
script Seesaw {
    var stiffness: number = 160.0;
    fn update(dt: number) {
        let rope = World.find("anvil-rope-line");
        let hanging = Physics.joint_enabled(World.find("anvil-rope"));
        World.set_active(rope, hanging);
        if hanging {
            let hook = World.find("anvil-hook").transform.world_position;
            let anvil = World.find("anvil").transform.world_position;
            line(rope, Vec2(hook.x, hook.y), Vec2(anvil.x, anvil.y), 0.05);
        }
        if Game.toy == 6.0 {
            let ball = World.find("seesaw-ball").transform.world_position;
            Ui.set_text(World.find("hint"), "Ball height " + (ball.y + 14.0).fixed(1) + "   /   trampoline springs " + this.stiffness + " N/m");
        }
    }
    on ToyAction(toy, action) {
        if toy != 6.0 { return; }
        if action == 1.0 {
            Physics.set_joint_enabled(World.find("anvil-rope"), false);
            print("Playground anvil dropped");
        }
        if action == 2.0 {
            let ball = World.find("tramp-ball");
            ball.transform.position = Vec3(-7.0, -3.0, 0.25);
            Physics.set_velocity(ball, 0.0, 0.0);
            print("Playground trampoline ball");
        }
        if action == 3.0 { tune(this.stiffness * 1.5); }
        if action == 4.0 { tune(this.stiffness / 1.5); }
    }
    on ResetToy(toy) {
        if toy == 0.0 || toy == 6.0 { tune(160.0); }
    }
    fn tune(stiffness: number) {
        this.stiffness = clamp(round(stiffness), 20.0, 1200.0);
        Physics.set_spring(World.find("tramp-spring-left"), 2.2, this.stiffness, 0.4);
        Physics.set_spring(World.find("tramp-spring-right"), 2.2, this.stiffness, 0.4);
        print("Playground trampoline " + this.stiffness);
    }
}
```

### `assets/scripts/track.decay`

```rust
// The test track and the domino run.
//
// The robot is a Character 2D. Its script decides speed, jumps and gravity
// and asks the scene to move it; the controller climbs the steps, follows the
// slope, stands on the lift while the lift's own motion carries it, and lets
// it drop through one-way planks. Crates it walks into get shoved, and a kick
// is an impulse at the point of contact.

script Robot {
    @export let speed: number = 6.0;
    @export let acceleration: number = 60.0;
    @export let air_acceleration: number = 30.0;
    @export let jump_speed: number = 13.0;
    // How much harder than the room's gravity the robot falls: snappy jumps.
    @export let heft: number = 1.9;
    @export let shove: number = 0.35;
    @export let kick: number = 5.5;

    var home: Vec3 = Vec3(0.0, 0.0, 0.0);
    var horizontal: number = 0.0;
    var rise: number = 0.0;
    var grounded: bool = false;
    var on_one_way: bool = false;
    var drop_left: number = 0.0;
    var facing: number = 1.0;
    var boot_left: number = 0.0;
    var jump_asked: bool = false;
    var kick_asked: bool = false;
    var kicks: number = 0.0;

    fn start() { this.home = this.transform.position; }

    on ToyAction(toy, action) {
        if toy != 7.0 { return; }
        if action == 3.0 { this.jump_asked = true; }
        if action == 4.0 { this.kick_asked = true; }
    }

    on ResetToy(toy) {
        if toy == 0.0 || toy == 7.0 { go_home(); }
    }

    fn update(dt: number) {
        this.drop_left = max(0.0, this.drop_left - dt);
        this.boot_left = max(0.0, this.boot_left - dt);
        World.set_active(World.find("robot-boot"), this.boot_left > 0.0);
        sense();
        let p = this.transform.position;
        if abs(p.x) > 31.0 || abs(p.y) > 15.5 {
            go_home();
            return;
        }

        var run = 0.0;
        var jump = this.jump_asked;
        var drop = false;
        var kicking = this.kick_asked;
        this.jump_asked = false;
        this.kick_asked = false;
        if Game.toy == 7.0 {
            if Input.is_down("A") || Input.is_down("ArrowLeft") || Ui.is_held(World.find("act-1")) { run -= 1.0; }
            if Input.is_down("D") || Input.is_down("ArrowRight") || Ui.is_held(World.find("act-2")) { run += 1.0; }
            if Input.just_pressed("W") || Input.just_pressed("Space") || Input.just_pressed("ArrowUp") { jump = true; }
            if Input.just_pressed("S") || Input.just_pressed("ArrowDown") { drop = true; }
            if Input.just_pressed("F") { kicking = true; }
        }
        if run != 0.0 { face(run); }
        if kicking { boot(); }

        if drop && this.grounded && this.on_one_way {
            Physics.drop_through(this.entity, 0.3);
            this.drop_left = 0.3;
            this.grounded = false;
            this.rise = -2.0;
            print("Playground robot drops through");
        } else if jump && this.grounded {
            this.rise = this.jump_speed;
            this.grounded = false;
        }

        // The room's gravity, whichever way it points, at the robot's heft.
        let pull = Physics.gravity() * this.heft;
        var rate = this.air_acceleration;
        if this.grounded { rate = this.acceleration; }
        let target = run * this.speed;
        let step = rate * dt;
        this.horizontal = this.horizontal + clamp(target - this.horizontal, -step, step);
        if !this.grounded { this.horizontal += pull.x * dt; }
        this.rise = clamp(this.rise + pull.y * dt, -30.0, 30.0);
        if this.grounded && this.rise < 0.0 { this.rise = 0.0; }
        Physics.move_character(this.entity, Vec2(this.horizontal * dt, this.rise * dt), this.grounded && this.drop_left <= 0.0 && this.rise <= 0.0);
    }

    // What the last controller pass found: support, and what the robot walked
    // into. A loose thing it walks into is shoved, so crates move out of the
    // way instead of standing like walls.
    fn sense() {
        this.grounded = false;
        this.on_one_way = false;
        let motion = Physics.character_motion(this.entity);
        if motion == null { return; }
        if motion.grounded && this.drop_left <= 0.0 {
            this.grounded = true;
            let support = motion.ground;
            if support != null { this.on_one_way = World.has_tag(support.entity, "one-way"); }
        }
        for hit in motion.collisions {
            if hit.normal.y < -0.5 && this.rise > 0.0 { this.rise = 0.0; }
            if World.has_tag(hit.entity, "body") || World.has_tag(hit.entity, "loose") {
                Physics.apply_impulse_at_point(hit.entity, hit.normal * (0.0 - this.shove), hit.point);
            }
        }
    }

    fn face(direction: number) {
        this.facing = sign(direction);
        World.find("robot-visor").transform.position.x = 0.12 * this.facing;
        World.find("robot-boot").transform.position.x = 0.4 * this.facing;
    }

    // A boot out in front: everything loose it reaches gets an impulse at the
    // point where the boot meets it, so a crate kicked high also spins.
    fn boot() {
        this.boot_left = 0.18;
        let p = this.transform.position;
        let reach = Vec2(p.x + this.facing * 0.75, p.y - 0.2);
        var hits = 0.0;
        for thing in Physics.overlap_circle(reach, 0.75, Physics.mask(["props", "balls", "shells", "glass"]), false, this.entity) {
            if World.has_tag(thing, "body") || World.has_tag(thing, "loose") {
                let there = thing.transform.position;
                let point = Vec2(there.x - this.facing * 0.3, p.y - 0.2);
                Physics.apply_impulse_at_point(thing, Vec2(this.facing * this.kick, this.kick * 0.55), point);
                hits += 1.0;
            }
        }
        Effects.burst_at(World.find("muzzle-flash"), reach.x, reach.y);
        this.kicks += 1.0;
        print("Playground robot kick " + hits);
    }

    fn go_home() {
        this.transform.position = this.home;
        this.horizontal = 0.0;
        this.rise = 0.0;
        this.drop_left = 0.0;
        Physics.drop_through(this.entity, 0.0);
        Physics.move_character(this.entity, Vec2(0.0, 0.0), false);
    }
}

// A kinematic platform the robot calls by standing on it. It rises with its
// rider, waits at the top until the rider steps off, and will not come down on
// a robot standing underneath. The controller carries the rider from the
// platform's solved motion; nothing here moves the robot.
script TrackLift {
    @export let bottom: number = -7.6;
    @export let top: number = -2.15;
    @export let speed: number = 2.4;
    @export let half_width: number = 0.9;
    var waiting: number = 0.0;
    var rising: bool = false;

    on ResetToy(toy) {
        if toy == 0.0 || toy == 7.0 {
            this.transform.position.y = this.bottom;
            this.rising = false;
            Physics.set_velocity(this.entity, 0.0, 0.0);
        }
    }

    fn update(dt: number) {
        let here = this.transform.position;
        let robot = World.find("Robot").transform.position;
        // The robot stands about 0.75 above the platform's middle when on it.
        let over = abs(robot.x - here.x) < this.half_width + 0.3;
        let riding = over && robot.y > here.y + 0.68 && robot.y < here.y + 1.2;
        let beneath = over && robot.y < here.y + 0.5;
        this.waiting = max(0.0, this.waiting - dt);
        if !this.rising && riding && here.y <= this.bottom + 0.05 && this.waiting <= 0.0 {
            this.rising = true;
            print("Playground lift going up");
        }
        if this.rising && here.y >= this.top {
            this.rising = false;
            this.waiting = 1.5;
        }
        var velocity = 0.0;
        if this.rising {
            velocity = this.speed;
        } else if here.y > this.bottom && this.waiting <= 0.0 && !riding && !beneath {
            velocity = 0.0 - this.speed;
        }
        // Land exactly: a step that would overshoot an end is cut short.
        if velocity > 0.0 { velocity = min(velocity, (this.top - here.y) / dt); }
        if velocity < 0.0 { velocity = max(velocity, (this.bottom - here.y) / dt); }
        Physics.set_velocity(this.entity, 0.0, velocity);
    }
}

// The red button: a cap on a vertical slider, held up by a spring. Pushed
// far enough down, by a domino or anything else heavy, it sets off DROP
// EVERYTHING; it re-arms once the spring has it back up.
script RedButton {
    @export let toy: number = 8.0;
    @export let travel: number = 0.2;
    var home: Vec3 = Vec3(0.0, 0.0, 0.0);
    var armed: bool = true;
    var down: bool = false;
    var settle: number = 1.0;
    var presses: number = 0.0;

    fn start() { this.home = this.transform.position; }

    on ToyAction(toy, action) {
        if toy != 8.0 { return; }
        if action == 1.0 {
            let first = World.find("domino-0");
            let top = first.transform.position + Vec3(0.0, 0.7, 0.0);
            Physics.apply_impulse_at_point(first, Vec2(0.35, 0.0), Vec2(top.x, top.y));
            print("Playground domino pushed");
        }
        if action == 2.0 { ResetToy.emit(8.0); }
        if action == 3.0 {
            let at = this.transform.position;
            Physics.apply_impulse_at_point(this.entity, Vec2(0.0, -2.5), Vec2(at.x, at.y));
        }
        if action == 4.0 {
            this.armed = !this.armed;
            if this.armed { print("Playground red button armed"); } else { print("Playground red button disarmed"); }
        }
    }

    on ResetToy(toy) {
        if toy == 0.0 || toy == 8.0 {
            this.transform.position = this.home;
            Physics.set_velocity(this.entity, 0.0, 0.0);
            this.settle = 1.0;
            this.down = false;
        }
    }

    fn update(dt: number) {
        this.settle = max(0.0, this.settle - dt);
        let depth = this.home.y - this.transform.position.y;
        if !this.down && depth > this.travel && this.settle <= 0.0 {
            this.down = true;
            this.presses += 1.0;
            let at = this.transform.position;
            Effects.burst_at(World.find("confetti"), at.x, at.y + 0.4);
            if this.armed {
                ButtonPressed.emit(at.x, at.y);
            } else {
                print("Playground red button pressed while disarmed");
            }
        }
        if this.down && depth < this.travel * 0.4 { this.down = false; }
    }
}

// The red button went down while armed.
event ButtonPressed(x: number, y: number);
```

### `assets/ui/playground.weave`

```css
/* Physics Playground: the bars over the room. Decay writes the words; this
   decides how they look. The room itself is never covered: everything sits
   in a top strip and a bottom dock. */

:root {
    --ink: #e8f1fb;
    --muted: #7f98b3;
    --panel: #0b1522ee;
    --line: #29425c;
    --accent: #ffd166;
    --hot: #ff4f5e;
    --cool: #4cc9f0;
}

sindri.ui.text { color: var(--ink); }

.hud {
    width: 100vw;
    height: 100vh;
    padding: 14px 18px;
    direction: column;
    justify-content: space-between;
    align-items: center;
}

.top { width: 100%; height: 120px; direction: row; justify-content: space-between; align-items: start; }
.brand { width: 440px; height: 120px; direction: column; gap: 2px; justify-content: start; align-items: start; }
.readout { width: 440px; height: 44px; font-size: 11px; line-height: 15px; text-align: start; color: #9fe8ff; }
.mask { width: 170px; height: 28px; margin-top: 4px; border-color: #7df9ff; }
.mask .button-label { font-size: 11px; color: #7df9ff; }
.title { width: 360px; height: 28px; font-size: 22px; letter-spacing: 4px; text-align: start; color: var(--accent); }
.stats { width: 420px; height: 16px; font-size: 11px; letter-spacing: 1px; text-align: start; color: var(--muted); }
.globals { width: 760px; height: 40px; direction: row; gap: 8px; justify-content: end; align-items: center; }

.bottom { width: 100%; height: 118px; direction: column; gap: 8px; justify-content: end; align-items: center; }
.toy-row { width: 100%; height: 36px; direction: row; gap: 10px; justify-content: center; align-items: center; }
.toy-label { width: 300px; height: 24px; font-size: 18px; letter-spacing: 3px; text-align: center; color: var(--accent); }
.dock { width: 100%; height: 44px; direction: row; justify-content: space-between; align-items: center; }
.tools { width: 520px; height: 44px; direction: row; gap: 8px; justify-content: start; align-items: center; }
.actions { width: 600px; height: 44px; direction: row; gap: 8px; justify-content: end; align-items: center; }
.hint { width: 100%; height: 14px; font-size: 11px; color: var(--muted); text-align: center; }

.button {
    width: 120px;
    height: 38px;
    direction: row;
    justify-content: center;
    align-items: center;
    background: var(--panel);
    border-color: var(--line);
    border-width: 1px;
    border-radius: 10px;
}
.button:hover { background: #15283d; border-color: #4f7499; }
.button:active { background: #1f3a57; border-color: var(--accent); }
.button-label { width: 100%; height: 18px; font-size: 13px; letter-spacing: 1px; text-align: center; }

.danger { width: 190px; background: #3a0d16ee; border-color: var(--hot); border-width: 2px; }
.danger:hover { background: #5c1422; }
.danger .button-label { color: #ffd1d6; font-size: 14px; letter-spacing: 2px; }
.loud { width: 120px; border-color: var(--cool); }
.loud .button-label { color: var(--cool); }
#btn-gravity { width: 180px; }
#btn-debug { width: 120px; }
#btn-reset { width: 90px; }
#btn-quarry { width: 120px; }

.tool { width: 122px; height: 40px; direction: column; gap: 3px; }
.tool-mark { width: 60%; height: 3px; background: var(--accent); border-radius: 2px; }
.arrow { width: 44px; height: 34px; }
.arrow .button-label { font-size: 18px; color: var(--accent); }
.action { width: 140px; height: 40px; border-color: #3d6185; }
.action .button-label { color: #cfe3ff; }

/* Narrow screens: the dock stacks, labels shrink, the room keeps the middle. */
@media (orientation: portrait) {
    .hud { padding: 10px 8px; }
    .top { height: 140px; direction: column; gap: 6px; align-items: center; }
    .brand { width: 100%; height: 96px; align-items: center; }
    .title { width: 100%; font-size: 17px; letter-spacing: 2px; text-align: center; }
    .stats { width: 100%; font-size: 9px; text-align: center; }
    .readout { width: 100%; height: 28px; font-size: 9px; line-height: 13px; text-align: center; }
    .mask { width: 120px; height: 24px; }
    .globals { width: 100%; height: 34px; gap: 4px; justify-content: center; }
    .button { height: 32px; border-radius: 8px; }
    .button-label { font-size: 10px; letter-spacing: 0px; }
    .danger { width: 108px; }
    .danger .button-label { font-size: 10px; letter-spacing: 0px; }
    .loud { width: 62px; }
    #btn-gravity { width: 92px; }
    #btn-debug { width: 58px; }
    #btn-reset { width: 46px; }
    #btn-quarry { width: 62px; }
    .bottom { height: 150px; gap: 6px; }
    .toy-label { width: 220px; font-size: 14px; letter-spacing: 1px; }
    .dock { height: 84px; direction: column; gap: 6px; justify-content: end; }
    .tools { width: 100%; height: 38px; gap: 4px; justify-content: center; }
    .actions { width: 100%; height: 38px; gap: 4px; justify-content: center; }
    .tool { width: 86px; height: 36px; }
    .action { width: 86px; height: 36px; }
    .hint { font-size: 9px; }
}

@media (orientation: landscape) and (max-height: 460px) {
    .hud { padding: 6px 10px; }
    .top { height: 36px; }
    .title { font-size: 16px; height: 20px; }
    .readout { height: 0px; font-size: 1px; }
    .button { height: 30px; }
    .button-label { font-size: 11px; }
    .bottom { height: 84px; gap: 4px; }
    .toy-row { height: 28px; }
    .dock { height: 34px; }
    .tool { height: 32px; }
    .action { height: 32px; }
    .hint { height: 0px; font-size: 1px; }
}
```

### `sindri.toml`

```toml
format_version = 1

[project]
name = "Physics Playground"
main_scene = "assets/playground.scene"
scenes = ["assets/quarry.scene"]

[assets]
include = ["fonts/Fonts-OFL.txt", "ui/playground.weave"]
```

### Binary files in this project

`assets/fonts/ChakraPetch-Regular.ttf`, `assets/fonts/Fonts-OFL.txt`, `assets/textures/crate.png`, `assets/textures/dirt.png`, `assets/textures/grass-side.png`, `assets/textures/grass.png`, `assets/textures/laser.png`, `assets/textures/sand.png`, `assets/textures/spark.png`, `assets/textures/steel.png`, `assets/textures/stone.png`


---

## Example: Sprite Masks (`examples/sprite-masks`)

Every text file of the project follows, whole, under its path relative to the project folder. JSON files are shown with short arrays on one line; whitespace does not matter to Sindri.

### Sprite Masks

A feature example for sprite masks. Open `sindri.toml` in the editor or run the
exported project in a WebGPU browser. Pages publishes it at
`https://sindri.vardir.no/examples/sprite-masks/`.

A keyhole wanders over a night sky in a slow figure of eight:

- `keyhole` carries `sindri.sprite_mask` with the keyhole picture, named
  `keyhole`, and the `Wander` script in `assets/scripts/keyhole.decay` that
  moves it.
- `night` is a dark sprite masked `outside` the keyhole, so the sunny meadow
  under it shows only through the hole.
- `gem` is masked `inside` the keyhole, so it is only ever seen when the hole
  passes over it.

The art is drawn by `art/draw.py`. `game/tests/the_sprite_mask_demo_works.rs`
plays the scene and checks both masks. See the sprite mask
contract (in `06-2d.md`).

### `assets/scripts/keyhole.decay`

```rust
// Moves the keyhole over the night in a slow figure of eight. The mask does
// the rest: the night is masked outside it, so the meadow shows through, and
// the gem is masked inside it, so it is only ever seen through the hole.
script Wander {
    @export let width: number = 2.6;
    @export let height: number = 1.1;
    @export let seconds: number = 8.0;
    var clock: number = 0.0;

    fn update(dt: number) {
        this.clock = this.clock + dt;
        let phase = this.clock / this.seconds * 6.2831853;
        let at = this.transform.position;
        this.transform.position = Vec3(sin(phase) * this.width, sin(phase * 2.0) * this.height, at.z);
    }
}
```

### `assets/sprite-masks.scene`

```json
{
  "format_version": 10,
  "metadata": {
    "name": "Sprite Masks"
  },
  "entities": [
    {
      "id": "camera",
      "name": "camera",
      "transform_3d": {
        "position": [0, 0, 10],
        "rotation": [0, 0, 0, 1],
        "scale": [1, 1, 1]
      },
      "components": {
        "sindri.camera": {
          "projection": "orthographic",
          "vertical_size": 4,
          "near": 0.1,
          "far": 100,
          "fit": "height"
        }
      }
    },
    {
      "id": "meadow",
      "name": "meadow",
      "transform_3d": {
        "position": [0, 0, 0],
        "rotation": [0, 0, 0, 1],
        "scale": [7.2, 4.05, 1]
      },
      "components": {
        "sindri.sprite": {
          "texture": "textures/meadow.png",
          "layer": 0
        }
      }
    },
    {
      "id": "night",
      "name": "night",
      "transform_3d": {
        "position": [0, 0, 0.1],
        "rotation": [0, 0, 0, 1],
        "scale": [12, 6, 1]
      },
      "components": {
        "sindri.sprite": {
          "texture": "textures/white.png",
          "tint": [0.03, 0.04, 0.1, 0.97],
          "layer": 1,
          "mask": "outside",
          "mask_name": "keyhole"
        }
      }
    },
    {
      "id": "gem",
      "name": "gem",
      "transform_3d": {
        "position": [1.3, -0.95, 0.2],
        "rotation": [0, 0, 0, 1],
        "scale": [0.7, 0.7, 1]
      },
      "components": {
        "sindri.sprite": {
          "texture": "textures/gem.png",
          "layer": 2,
          "mask": "inside",
          "mask_name": "keyhole"
        }
      }
    },
    {
      "id": "keyhole",
      "name": "keyhole",
      "transform_3d": {
        "position": [0, 0, 0.3],
        "rotation": [0, 0, 0, 1],
        "scale": [1.5, 1.5, 1]
      },
      "components": {
        "sindri.sprite_mask": {
          "texture": "textures/keyhole.png",
          "name": "keyhole"
        },
        "sindri.script": {
          "source": "scripts/keyhole.decay",
          "script": "Wander"
        }
      }
    }
  ]
}
```

### `sindri.toml`

```toml
format_version = 1
[project]
name = "Sprite Masks"
main_scene = "assets/sprite-masks.scene"
```

### Binary files in this project

`art/draw.py`, `assets/textures/gem.png`, `assets/textures/keyhole.png`, `assets/textures/meadow.png`, `assets/textures/white.png`


---

## Example: Sprite Shaders (`examples/sprite-shaders`)

Every text file of the project follows, whole, under its path relative to the project folder. JSON files are shown with short arrays on one line; whitespace does not matter to Sindri.

### Sprite Shaders

A feature example for project sprite shaders. Open `sindri.toml` in the editor
or run the exported project in a WebGPU browser. Pages publishes it at
`https://sindri.vardir.no/examples/sprite-shaders/`.

Five copies of one badge draw left to right with no shader and then through
four shaders in `assets/shaders/`:

- `outline.wgsl` rims the opaque shape with a pulsing glow. Its width in
  texels is the first shader parameter, authored in the scene.
- `dissolve.wgsl` burns the sprite away through blocky noise, with a glowing
  edge. The `Burn` script sweeps the first parameter from 0 to 1 and back.
- `flash.wgsl` whitens the sprite. The `HitFlash` script sets the first
  parameter to 1 on a hit, every 1.2 seconds or when Space is pressed, and
  fades it out.
- `wave.wgsl` sways the picture row by row. Amplitude and wave count are the
  first two parameters; the motion comes from the frame's time.

Each shader is one function, `fn sprite_shader(input: SpriteShaderInput) ->
vec4<f32>`, which the engine wraps in the sprite pipeline. Edit one while the
editor has the scene open and saving reloads it; a mistake is reported in the
console with the file and line, and the sprite draws with the built-in look
until it is fixed.

`assets/scripts/shader-demo.decay` holds both scripts. Shader parameters are
written as decimals in the scene (`0.0`, not `0`): a script writes a value back
in the number kind the scene stored, so an integer would round every write.

`game/tests/the_sprite_shader_demo_works.rs` compiles every shader the scene
names and plays the scripts. See the sprite shader contract (in `06-2d.md`).

### `assets/scripts/shader-demo.decay`

```rust
// Drives each sprite's shader through its four parameters. The shaders do the
// drawing; the script only says how far a burn has got or how bright a flash
// is, which is the split a game keeps too.
script Burn {
    @export let seconds: number = 3.0;
    var clock: number = 0.0;

    fn update(dt: number) {
        this.clock = this.clock + dt;
        // Burns away, then grows back, over and over.
        let phase = this.clock / this.seconds;
        this.sprite.shader_params.x = 0.5 - 0.5 * cos(phase * 6.2831853);
    }
}

script HitFlash {
    @export let every: number = 1.2;
    var since: number = 0.0;

    fn update(dt: number) {
        this.since = this.since + dt;
        if this.since > this.every || Input.just_pressed("Space") {
            this.since = 0.0;
        }
        // Full white on the hit, faded out over a quarter of a second.
        this.sprite.shader_params.x = clamp(1.0 - this.since * 4.0, 0.0, 1.0);
    }
}
```

### `assets/shaders/dissolve.wgsl`

```wgsl
// Burns the sprite away through blocky noise.
//
// params.x is how far the burn has got, 0 untouched to 1 gone. Pixels just
// ahead of the burn glow orange.
fn dissolve_noise(cell: vec2<f32>) -> f32 {
    return fract(sin(dot(cell, vec2<f32>(12.9898, 78.233))) * 43758.5453);
}

fn sprite_shader(input: SpriteShaderInput) -> vec4<f32> {
    let noise = dissolve_noise(floor(input.local_uv * 16.0));
    let burn = input.params.x * 1.1;
    if noise < burn - 0.1 {
        return vec4<f32>(0.0);
    }
    if noise < burn {
        return vec4<f32>(1.0, 0.45, 0.1, input.color.a);
    }
    return input.color;
}
```

### `assets/shaders/flash.wgsl`

```wgsl
// Whitens the sprite, the usual way to show a hit.
//
// params.x is how white, 0 to 1; a script sets it on a hit and lets it fade.
fn sprite_shader(input: SpriteShaderInput) -> vec4<f32> {
    let white = vec3<f32>(1.0, 1.0, 1.0);
    return vec4<f32>(mix(input.color.rgb, white, input.params.x), input.color.a);
}
```

### `assets/shaders/outline.wgsl`

```wgsl
// A glowing rim around whatever the texture leaves opaque.
//
// params.x is the rim's width in texels; the rim pulses with time. Pixels the
// sprite already covers draw as they are, so only the transparent margin
// around the picture changes.
fn sprite_shader(input: SpriteShaderInput) -> vec4<f32> {
    if input.color.a > 0.5 {
        return input.color;
    }
    let reach = sprite_texel_size() * max(input.params.x, 1.0);
    var near = 0.0;
    near = max(near, sample_sprite(input.uv + vec2<f32>(reach.x, 0.0)).a);
    near = max(near, sample_sprite(input.uv - vec2<f32>(reach.x, 0.0)).a);
    near = max(near, sample_sprite(input.uv + vec2<f32>(0.0, reach.y)).a);
    near = max(near, sample_sprite(input.uv - vec2<f32>(0.0, reach.y)).a);
    let pulse = 0.65 + 0.35 * sin(input.time * 4.0);
    return vec4<f32>(1.0, 0.82, 0.25, near * pulse);
}
```

### `assets/shaders/wave.wgsl`

```wgsl
// Sways the picture sideways, row by row, like a flag or heat haze.
//
// params.x is how far each row moves, as a share of the sprite's width, and
// params.y how many waves fit down it. Samples stay inside the sprite's own
// part of the texture, so this works on one frame of a sheet too.
fn sprite_shader(input: SpriteShaderInput) -> vec4<f32> {
    let shift = sin(input.local_uv.y * input.params.y * 6.2831853 + input.time * 3.0)
        * input.params.x;
    let across = input.local_uv.x + shift;
    if across < 0.0 || across > 1.0 {
        return vec4<f32>(0.0);
    }
    let uv = input.uv_rect.xy + vec2<f32>(across, input.local_uv.y) * input.uv_rect.zw;
    // `sample_sprite` reads the texture untinted, which is fine for a sprite
    // drawn in its own colors.
    return sample_sprite(uv);
}
```

### `assets/sprite-shaders.scene`

```json
{
  "format_version": 10,
  "metadata": {
    "name": "Sprite Shaders"
  },
  "entities": [
    {
      "id": "camera",
      "name": "camera",
      "transform_3d": {
        "position": [0, 0, 10],
        "scale": [1, 1, 1]
      },
      "components": {
        "sindri.camera": {
          "projection": "orthographic",
          "vertical_size": 4,
          "near": 0.1,
          "far": 100,
          "fit": "height"
        }
      }
    },
    {
      "id": "plain",
      "name": "plain",
      "transform_3d": {
        "position": [-4, 0, 0],
        "scale": [1.6, 1.6, 1]
      },
      "components": {
        "sindri.sprite": {
          "texture": "textures/badge.png",
          "tint": [1.0, 1.0, 1.0, 1.0],
          "layer": 0,
          "shader": "",
          "shader_params": [0.0, 0.0, 0.0, 0.0]
        }
      }
    },
    {
      "id": "outline",
      "name": "outline",
      "transform_3d": {
        "position": [-2, 0, 0],
        "scale": [1.6, 1.6, 1]
      },
      "components": {
        "sindri.sprite": {
          "texture": "textures/badge.png",
          "tint": [1.0, 1.0, 1.0, 1.0],
          "layer": 0,
          "shader": "shaders/outline.wgsl",
          "shader_params": [3.0, 0.0, 0.0, 0.0]
        }
      }
    },
    {
      "id": "dissolve",
      "name": "dissolve",
      "transform_3d": {
        "position": [0, 0, 0],
        "scale": [1.6, 1.6, 1]
      },
      "components": {
        "sindri.sprite": {
          "texture": "textures/badge.png",
          "tint": [1.0, 1.0, 1.0, 1.0],
          "layer": 0,
          "shader": "shaders/dissolve.wgsl",
          "shader_params": [0.0, 0.0, 0.0, 0.0]
        },
        "sindri.script": {
          "source": "scripts/shader-demo.decay",
          "script": "Burn"
        }
      }
    },
    {
      "id": "flash",
      "name": "flash",
      "transform_3d": {
        "position": [2, 0, 0],
        "scale": [1.6, 1.6, 1]
      },
      "components": {
        "sindri.sprite": {
          "texture": "textures/badge.png",
          "tint": [1.0, 1.0, 1.0, 1.0],
          "layer": 0,
          "shader": "shaders/flash.wgsl",
          "shader_params": [0.0, 0.0, 0.0, 0.0]
        },
        "sindri.script": {
          "source": "scripts/shader-demo.decay",
          "script": "HitFlash"
        }
      }
    },
    {
      "id": "wave",
      "name": "wave",
      "transform_3d": {
        "position": [4, 0, 0],
        "scale": [1.6, 1.6, 1]
      },
      "components": {
        "sindri.sprite": {
          "texture": "textures/badge.png",
          "tint": [1.0, 1.0, 1.0, 1.0],
          "layer": 0,
          "shader": "shaders/wave.wgsl",
          "shader_params": [0.06, 2.0, 0.0, 0.0]
        }
      }
    }
  ]
}
```

### `sindri.toml`

```toml
format_version = 1
[project]
name = "Sprite Shaders"
main_scene = "assets/sprite-shaders.scene"
```

### Binary files in this project

`assets/textures/badge.png`

