Writing a .scene
Name the models you want, lay them out by structure, and give them something to do.
A .scene script composes library models and built-in generators into a living diorama. You name what you want, the compiler places it by its real footprint, and life lines keep it moving. The quickest start is the skeleton:
bun run cel scene new <slug> --template <t> [--seed N] [--size N] [--mood day|dusk|night|dark] [--title "…"]
It writes a file that checks clean as written: one area or room, six people who wander it, a light when the mood is night or dark, a close view, and a commented example of every verb sized to the template. Scene scripts live in packages/engine/src/scene/scripts/<slug>.scene.
Header, templates and moods
scene <name> "<Title>" template=<t> seed=<n> [size=<radius m>] [mood=day|dusk|night|dark]
Units are metres; +x is right, +z is toward the default camera, y is up; face 0 looks at +z. The ground is a disc of radius size round the origin; on retro, interior and dungeon it is a square.
| Template | Size | Ground | Notes |
|---|---|---|---|
town | 36 | grass disc, trees ring the edge | streets, houses, a square |
forest | 22 | a clearing in a thick wood | |
beach | 30 | sand; the sea covers the back third | boats float on the sea |
arena | 24 | sand floor in grass, stones and trees | |
plaza | 24 | a paved square in a park ring | |
tabletop | 10 | a round toy base you orbit, no fog | |
retro | 30 | square grass walled in by lollipop trees | an old handheld-game town |
carnival | 34 | a dirt circle; mood night | needs light lines |
mountain | 40 | a valley floor ringed by snowy peaks | |
interior | 24 | a square floor plate, cut away | rooms, halls, storeys |
dungeon | 30 | a dark stone plate; mood dark | lit only by light lines |
mood replaces the template's light, sky and fog: day, dusk, night or dark.
Assets
use <alias> = <ref> [seed=<n> | seeds=<a>..<b>] [scale=<k> | height=<m> | width=<m>] [float]
refis a library slug (the latest version),slug@v(pinned), or a built-in:nature/<kind>[:<form>],prop/<kind>[:<form>],building/<kind>[:<form>],person/<archetype>orperson/any.seeds=1..6: each placement draws one of those variants. Each seed is built once and shared.height=andwidth=scale the model to that size, whatever it was built at.floatlets it stand in water (lilies, boats, ducks) and skip floor checks.bun run cel cataloguelists every built-in with its sizes and forms: naturetree bush hedge rock flower tuft; props frombenchandlamptobusstopandvehicle; buildingscottage townhouse shop cafe civic tower; peoplekid chibi average lanky stocky round athletic elder.
Ground
area <name> circle <x> <z> r=<r> | area <name> rect <x0> <z0> <x1> <z1> | area <name> ring <x> <z> r=<r0>..<r1> plaza <name> <x> <z> r=<r> [paved|dirt|sand|grass|flowers] ground <area> <grass|flowers|sand|dirt|water|paved|tallgrass|stone|snow> street <name> <x z> <x z> … [width=4] path <name> <x z> <x z> … [width=2] [dirt|paved|sand|water|…|none] [loop]
Every ground surface is textured procedurally: grass tufts, dirt speckles, paving joints, plank seams. You never ship an image file.
Layout
The compiler places things by their real footprints. Never compute many coordinates by hand.
lots <street> <list> [side=left|right|both] [gap=1.5] [setback=1] [from=0..1] [to=0..1] place <alias> at <x> <z> [face <deg>|face <x> <z>] row <list> from <x z> to <x z> [gap=<m>] [count=<n>] [face …] grid <list> at <x z> <cols>x<rows> [gap=1] ring <list> at <x z> r=<r> [count=<n>] [face in|out|<deg>] scatter <list> in <area> [count=<n>] [spacing=1] cluster <list> at <x z> [r=3] [count=<n>] [spacing=0.3] along <path> <list> [every=6] [side=left|right|both] [offset=0.6]
<list> is alias, alias*3 or a weighted mix house*3,shop. Any layout line can end with seed=<n>, as <group> (a name life lines can target) and lift=<m>. A scene holds at most 1,500 placements.
Life
do <target> <state> [speed=<k>] do <target> sit at <thing> do <target> stand at <thing> ride <target> on <thing> cycle <target> <state>[,<state>] every <a>..<b>s [for <s>s] wander <target> in <area> [speed=0.8] [pause=1..4s] [moving=<state>] wander <target> on <thing> [speed=0.8] [pause=1..4s] [moving=<state>] follow <target> <path> [speed=1] [pingpong] [moving=<state>] face <target> <x> <z> | face <target> camera | face <target> <other target>
Seats, saddles, stand spots and decks come from a model's spot lines or a built-in prop's seats. Each rider takes the nearest free spot and stays attached as the host animates: teacup riders spin with the cup, carousel riders rise and fall with the horse.
speed= on do scales that state's clock, from -10 to 10: do wheel spin speed=2, do cups spin speed=-1. A mover plays the first state it has from walk run hop move waddle trot scurry crawl fly swim; a crab whose gait is called scuttle needs moving=scuttle.
Lights, camera and views
light at <x> <z> [y=2.4] [color=<swatch|#hex>] [radius=6] [power=1] [flicker] light on <alias|group> [y=<m above its top>] [color=…] [radius=…] [power=…] [flicker] camera orbit <distance> height <h> [at <x> <z>] [spin=<deg/s>] [pitch=<deg>] [fov=<deg>] view <name> at <x> <z> [y=1.6] dist=<m> height=<m> [yaw=<deg>] [hide above <n>]
At most 16 lights after light on expands. overview is the default view and its name is reserved. hide above <n> on a view shows storeys 0 to n only: a cutaway of the ground floor under the upper storeys. Fog scales with the camera's distance and lens, so a far view stays as clear as the overview.
Interiors
level <n> [y=<floor top m>] room <name> rect <x0> <z0> <x1> <z1> [floor=plain|checker|tiles|planks|flagstone|carpet] [wall=plain|panel|stone|brick|rail|none] hall <name> <x> <z> <x> <z> … width=<m> [room options] door <x> <z> [width=1.1] [height=2.2] window <x> <z> [width=1.4] [sill=0.9] [head=2.3] stairs <x> <z> to <level> face <deg> [width=1.2] against <room> <alias> side=n|s|e|w [gap=0.05] [from=<m>] [to=<m>] [count=<n>]
Rooms are areas, so scatter … in <room> and wander … in <room> work. Rooms that share an edge share one wall. A door snaps to the nearest wall within half a metre; keep 1.2 m clear on both sides.
Checks and budgets
bun run cel scene check <file> lays the scene out against the real footprints of the latest library versions. Errors name the line and a code (unknown-asset, overlap, in-wall, blocks-path, blocks-door, in-water, too-many-lights), and placement errors end with the nearest legal spot.
| Measure | Where to read it | Healthy |
|---|---|---|
| Errors | cel scene check | 0 |
| Placements | cel scene check | a few hundred; 1,500 is the cap |
| Draw calls | cel shot scene stats | 100 to 500 for a showcase scene |
| Dynamic placements | cel shot scene stats | as few as the scene needs |
| Lights | cel shot scene stats | 16 or fewer |
| fps | cel shot scene stats | 55 to 60 in the headless shot |
Tricks from the showcase scenes
- Bridges over paths. Give the bridge model deck spots and place it over the path or water; walkers cross on its deck.
- Standing things on things.
lift=raises a placement onto a ledge, an outcrop or a planter. - Groups move together. Three copies of one
followloop started a little apart keep a party together; two at equal speed and a fixed gap make a chase. - `wander` needs room. In an area that is mostly built over, use
followloops instead. - Patch models beat crowds of tiny live ones. One grass patch instead of six animated tufts took a town from 183 dynamic placements to 92.
- Upper floors hide lower back rooms from above: add a view with
hide above <n>.
Every scene on this site shows its full script: open one and press Script in the dock.