How it's made · celworld

How it's made

celworld is a procedural, cel-shaded world engine. Claude writes every model as a small script. A seed grows a person, a tree, a machine or a whole town from it. Nothing here is a mesh file or a texture.

165 models · 178 versions · 6 scenes

Every model is a small script

This is Duck dipper, a machine from Fold-Up Factory. Claude wrote it by hand in .cel, celworld's asset language: 76 lines, 5.0 KB. Shapes, colours, joints and animation states, coarse to fine. Your browser compiles the script into the live mesh you can turn.

duck_dipper.cel · v176 lines
1asset duck_dipper "Duck dipper" complexity=4
2// the toy factory's duck machine, front +z toward the belt: a chunky sky-blue cabinet on a white plinth, a big
3// white paint vat on top brimming with yellow paint (drips down its sides, a happy face on its front, bubbles
4// popping on the surface), and a little crane on a striped post at the back whose boom swings a rubber duck over
5// the vat. A dark slot in the cabinet front feeds a cherry-railed chute (part `chute`, toys leave near
6// (0, .75, .95)). Idle: the vat hums, the paint bubbles, the duck sways, the side cog turns, the beacon pulses.
7// work (1.6 s): the boom dips the duck into the paint, swishes it about and lifts it out in a yellow splash
8let bodyC = sky
9let trimC = white
10let paintC = sunflower
11let popC = cherry
12let metalC = silver
13let beakC = tangerine
14let slotC = glassDark*.6
15
16// the cabinet: a white plinth band under sky-blue panels, a duck badge, three buttons and a dark toy slot
17box base at=(0, 0, -.1) size=(1.8, .8, 1.5) round=.14 bev=2 bands=[.12:bodyC .78:trimC .85:bodyC] color=trimC
18box hood at=(0, .44, .615) size=(.86, .34, .1) round=.06 color=popC
19box mouth at=(0, .49, .64) size=(.68, .24, .08) round=.05 color=slotC
20slab badge at=(.6, .46, .665) pts=[-.13,-.06 .09,-.06 .12,-.01 .07,.05 .1,.07 .16,.08 .1,.11 .08,.15 .02,.15 0,.1 .02,.06 -.1,.06 -.17,.11 -.16,.03] t=.02 round=.006 color=paintC
21ball btn x3 at=(-.58, .5, .655) row=(.14, 0, 0) r=(.05, .05, .03) color=sel(i, popC, paintC, lime) segs=8 rings=4
22
23// the chute: a white slide with cherry rails, out of the slot and down toward the belt
24box chute at=(0, .64, .8) tilt=10 size=(.56, .05, .44) round=.02 color=trimC
25box rail on=chute at=(.26, .02, 0) mirror size=(.05, .09, .44) round=.02 color=popC
26rod strut at=(0, .3, .66) to=(0, .32, .26) r=.03 sides=8 color=metalC
27
28// the paint vat: a white tub, yellow where the paint slops over, drips down its sides, a cherry rim and a face
29lathe vat at=(.05, .8, -.05) h=.52 r=.5 prof=[0:.84 .12:.95 .5:1 .88:1.02 1:1.04] open=top bands=[.74:paintC] color=trimC segs=20 rings=7 joint
30cyl paint on=vat at=(0, .44, 0) h=.03 r=.505 color=paintC segs=22
31torus rim on=vat at=(0, .52, 0) R=.52 r=.045 color=popC segs=20 sides=6
32ball drip x4 on=vat ring ring0=45 at=(.5, .36, 0) r=(.05, .1, .07) color=paintC segs=8 rings=5
33face look on=vat eyes=round mouth=grin spacing=25 eyeY=0 mouthY=-28 mouthW=.5 size=.27 detail=.5
34ball bubble x3 on=paint ring ring0=20 at=(.26, .03, 0) r=.055 color=paintC*1.1 segs=6 rings=4 joint
35
36// the crane: a striped post, a cherry turret aimed over the vat, a sky boom with a counterweight
37cyl post at=(-.62, .8, -.62) h=1.16 r=.085 bands=[.5:popC .58:metalC] color=metalC segs=12
38ball turret on=post at=(0, 1.2, 0) yaw=45 r=(.15, .12, .15) color=popC segs=14 rings=8 joint
39box boom on=turret at=(0, -.07, .44) size=(.14, .13, .9) round=.05 color=bodyC
40box weight on=turret at=(0, -.11, -.2) size=(.26, .22, .22) round=.07 color=trimC
41rod cable on=boom at=(0, 0, .4) to=(0, -.26, 0) r=.018 sides=6 color=iron joint
42ball claw on=cable at=end r=.05 color=metalC segs=10 rings=6
43
44// the rubber duck on the hook, turned to face the belt
45ball duck on=cable at=end+(0, -.13, 0) yaw=-45 r=(.12, .1, .14) color=paintC segs=14 rings=9 joint
46ball dhead on=duck at=(0, .1, .1) r=.08 color=paintC segs=10 rings=7
47cone beak on=dhead at=(0, -.01, .065) rot=(90, 0, 0) h=.07 r=.035 r2=.012 color=beakC segs=8
48ball deye on=dhead at=(.04, .025, .06) mirror r=.016 color=inkDark segs=6 rings=4
49cone dtail on=duck at=(0, .02, -.12) rot=(-60, 0, 0) h=.07 r=.05 color=paintC segs=8
50ball wing on=duck at=(.1, .01, -.01) mirror r=(.03, .06, .09) color=paintC*.92 segs=8 rings=5
51
52// the beacon and the side cog
53cyl lampbase at=(.7, .8, .35) h=.08 r=.08 color=metalC segs=12
54ball lamp on=lampbase at=(0, .14, 0) r=.09 color=glow ink=glow segs=10 rings=7 joint
55cyl cog at=(.89, .42, -.2) rot=(0, 0, -90) h=.06 r=.2 color=metalC segs=16 joint
56box tooth x3 on=cog ring at=(0, -.012+.004*i, 0) size=(.5, .075, .07) round=.015 bev=1 color=metalC
57ball hub on=cog at=(0, .06, 0) r=(.07, .03, .07) color=popC segs=10 rings=5
58
59// cel FX: a yellow splash while it works
60fx work: splash on=paint at=top speed=1.4 spread=40 n=8 hz=1.3 size=.05 color=paintC color2=lemon
61
62// idle: the vat hums, bubbles swell and pop, the crane and duck sway, the cog turns, the beacon pulses
63anim vat s sin amp=.01 hz=3
64anim bubble s keys=[0:-.7 .55:.2 .62:-.99 1:-.7] hz=.8 stagger=.37
65anim turret ry sin amp=5 hz=.22
66anim cable rx sin amp=5 hz=.6
67anim duck ry sin amp=12 hz=.45
68anim cog ry spin hz=.3
69anim lamp s keys=[0:0 .5:.22 1:0] hz=.9
70
71// work (1.6 s): the boom dips the duck under the paint (the cable stays plumb), a swish, then up and out
72anim work: turret rx keys=[0:0 .12:-5 .38:26 .5:26 .56:22 .62:26 .82:-6 .92:2 1:0] hz=.625 once
73anim work: cable rx keys=[0:0 .12:5 .38:-26 .5:-26 .56:-22 .62:-26 .82:6 .92:-2 1:0] hz=.625 once
74anim work: duck rz keys=[0:0 .45:0 .5:14 .56:-14 .62:10 .68:0 1:0] hz=.625 once
75anim work: vat py keys=[0:0 .4:0 .44:-.02 .5:.01 .56:0 1:0] hz=.625 once
76anim work: lamp s keys=[0:0 .1:.5 .2:0 .3:.5 .4:0 .82:.5 .92:0 1:0] hz=.625 once
77
live · drag to turn it
seed

Pick a state to play it. The grid button grows seeds 1 to 9 from the same script: every seed is a different, equally good variant.

Seed, spec, geometry, instance

Everything in celworld runs one way. A seed becomes a spec, the spec becomes geometry, and the geometry becomes a live instance. Only the last two steps touch the GPU, so a spec can be computed anywhere and checked by a test.

  1. Seed
    32-bit number
    Same seed, same thing, forever.
  2. Spec
    versioned pure data
    Every choice is drawn from tables. No three.js.
  3. Geometry
    one mesh per ink class
    Lathes, balls, limbs and boxes merged with vertex colours.
  4. Instance
    shared toon materials
    Ink outlines, walk cycles, animation states.

Variety is data

A generator never special-cases a look in mesh code. Forms, ranges and palettes live in tables, and the seed draws from them. Here is the built-in tree generator. Change the seed and you get sixteen new trees and a new spec for the first one.

seedfirst tree: seed 3439929727
{
  "kind": "tree",
  "v": 0,
  "seed": 3439929727,
  "form": "cloud",
  "height": 4.34,
  "trunk": {
    "h": 1.94,
    "r0": 0.27,
    "lean": [0.01, 0.01],
    "color": "#8d5f3c"
  },
  "branches": 3,
  "puffs": 8,
  "firstPuff": {
    "c": [1.35, 2.93, -0.34],
    "rx": 1,
    "ry": 0.85,
    "color": "#40b15b"
  },
  "tiers": 0,
  "dots": 0
}
browser 50157f7fserver 50157f7fsame spec
The tree generator
16 trees · seed 7All generators

Scenes are scripts too

A .scene file names library models and built-in generators, lays them out and gives them something to do. Moonlight Carnival places 159 things from 17 library models with 149 lines.

moonlight_carnival.scene · excerpt
1scene moonlight_carnival "Moonlight Carnival" template=carnival seed=21 size=34 mood=night
2
3use gate = carnival_arch
4use toss = ring_toss_stall seeds=1..3
5use folk = person/any seeds=1..48
6
7// the front: the gate over the midway, the ticket booth beside it
8place gate at 0 28.4
9
10// the midway: two rows of game stalls facing in, festoon spans overhead
11along stallrow toss,duck,tester,candy,stall,popcorn,balloons,icecream,stall,toss every=4.2 side=both offset=0.7
12
13// people: the crowd, kids running the midway and circling the carousel
14scatter folk in fair count=40 spacing=0.4 as crowd
15
16// life
17wander crowd in fair speed=0.9 pause=1..5s
18follow runners midrun speed=1.6 pingpong
19
20cycle launchers launch every 8..12s for 3s
carnival template149 lines
Moonlight Carnival, the scene

Templates

A template sets the ground, the sky and the mood: town, carnival, beach, mountain, interior, dungeon.

Layout by footprint

Streets with lots, rows, rings and scatters place things by their real size, so nothing overlaps.

Life loops

Wander, follow and cycle lines keep people walking, rides turning and fireworks going off.

Agents build it with a CLI

No one draws these models. A Claude session follows a written guide and drives the cel command line: check, save, shot, stamp. It looks at its own screenshots and keeps going until the model is right. The output below is this library's, for Duck dipper.

  1. 1
    cel check
    Compile three seeds, measure them against the budget, list what must be fixed.
  2. 2
    cel save
    Store a new version in Postgres, tied to the agent run.
  3. 3
    cel shot
    Render it headless and look at the picture. Fix and repeat.
  4. 4
    cel stamp
    Log the session as a run: its tokens and time, split across what it saved.
$ bun run cel check duck_dipper.cel
duck_dipper.cel: duck_dipper c4 · seeds 1,2,3
shapes 43 / 70 (61%)
tris 5446 / 6000 (91%)
draw calls 4 / 4 (100%)
states work
0 must-fix · 0 advice
$ bun run cel save duck_dipper.cel --agent cmuyylfkq001701p6vwajlouj --new
duck_dipper v1
$ bun run cel shot model duck_dipper --out duck_dipper.png
duck_dipper.png (1024×768)
tris 5446 · shapes 43 · drawCalls 4
$ bun run cel agent stamp cmuyylfkq001701p6vwajlouj
Fold-Up Factory art: wave B · claude-opus-5-5
model duck_dipper v1 5139 chars
model car_press v1 4713 chars
model plush_stitcher v1 5468 chars
model block_assembler v1 5808 chars

The guide

The whole workflow, the two languages and the engine laws are written down in one guide in the repo (docs/build-on-celworld.md), with a Claude skill that condenses it. Any session can pick it up and make a model, a scene or a game.

Fold-Up Factory

The worked example of a game on the engine: a packing puzzle invented by a 7-year-old. Its machines, toys and cartons were all made this way.

Engine numbers

Static models merge into batches, so a whole town draws in a few hundred calls. Run the showcase scene and these numbers come from your own GPU, measured while it plays.

Moonlight Carnival
Moonlight Carnival
Draw calls
—
run it to measure
Frames per second
—
run it to measure
Triangles
—
run it to measure
Instances
—
run it to measure

Ink fins

The outline is the back-face hull trick, which loses its line wherever a hard crease has flat normals. Every kit adds thin ink fins along those creases, so boxes and roofs keep a clean edge. Budgets count only the drawn triangles, not the fins. Toggle ink on the model above to see the difference.

Golden seeds

Nothing in the engine calls Math.random(). Every draw goes through a seeded generator, sub-seeded per section, so a saved seed keeps producing the same thing. Golden tests freeze a whole town's layout to the millimetre. The browser and server digests above are the same check, live.