scene3d.create
Schema
Input = { description: string, // objects, mood, colours, motion path: string, // destination .html, relative to the workspace title?: string, procedural?: boolean, // default false — skip the model, use the deterministic generator directly}
Output = { path: string, size: number, mime: 'text/html', source: 'model' | 'procedural', costUsd: number, note?: string,}How it stays safe
The model is asked to author only the scene’s body — a buildScene(THREE, scene, camera, renderer) function returning an update(t, dt) callback, plain JavaScript, no markdown fences. That
script is never eval’d against live data; validateSceneScript() parses it with new Function(...)
(syntax-only) and rejects anything referencing fetch, XMLHttpRequest, WebSocket,
localStorage, document.cookie, or eval — a scene can move objects and animate, never call home
or touch storage. On a validation failure, the model gets one repair round-trip with the exact error;
if it still fails, or no OpenRouter key is configured, a procedural scene is generated instead —
deterministically seeded from the description’s text, picking one of five looks (orbit, waves,
forest, city, or an abstract field of icosahedra/torus knots) so the result is never empty or
broken.
The HTML shell
The final file is fully self-contained: an import map pinning three@0.170.0 from
cdn.jsdelivr.net, a small HUD showing the title/description, mouse-parallax camera drift, and a
render loop that keeps going even if update() throws mid-frame (caught per-frame, not fatal). This
is exactly what the 3D scene generator wraps, and what a
custom widget can also point at.
Example
curl -s -X POST http://localhost:4000/api/studio/skills/invoke \ -H 'content-type: application/json' -b cookies.txt \ -d '{"name":"scene3d.create","input":{"description":"A low-poly island at dusk with fireflies","path":"scene.html"}}'{ "ok": true, "output": { "path": "scene.html", "size": 6412, "mime": "text/html", "source": "model", "costUsd": 0.0006 } }