المصادقة
الـ AI (المتحكم الكامل): أرسل الهيدر X-Engine-Key: <ENGINE_API_KEY> أو Authorization: Bearer ….
المحرك (المتصفح على الهاتف): يستخدم ?token=<viewerToken> الخاص بالمشهد فقط.
حلقة التحكم (Loop)
1. POST https://cinema-ai-engine.pages.dev/api/scenes -> {scene:{id, viewerToken, viewerUrl}}
2. (human) opens viewerUrl on phone -> engine online
3. POST /api/scenes/:id/commands [ {op:...}, ... ] (HANDS)
optional ?wait=10 to block until executed
4. GET /api/scenes/:id/state (EYES: structured)
5. GET /api/scenes/:id/frames/latest (EYES: JPEG image)
or ?format=json for base64 data URL
6. POST /api/scenes/:id/director {brief:"..."} (built-in LLM director)
7. GET /api/scenes/:id/renders -> videoUrl (final video)
نقاط النهاية (Endpoints)
GET /api/health
GET /api/manifest command catalog (JSON)
GET /llms.txt full AI guide (text)
POST /api/scenes {name, description}
GET /api/scenes
GET /api/scenes/:id
PATCH /api/scenes/:id
DELETE /api/scenes/:id
POST /api/scenes/:id/commands command | [commands] | {commands:[...]} ?wait=sec
GET /api/scenes/:id/commands ?since=seq&status=pending|done|error
DELETE /api/scenes/:id/commands cancel all pending
GET /api/scenes/:id/state engine state (objects, camera, fps, onScreen...)
GET /api/scenes/:id/frames list frames
GET /api/scenes/:id/frames/latest JPEG (or ?format=json)
GET /api/scenes/:id/frames/:fid
GET /api/scenes/:id/renders
GET /api/scenes/:id/renders/:rid
GET /api/scenes/:id/renders/:rid/video WebM/MP4 bytes
DELETE /api/scenes/:id/renders/:rid
GET /api/scenes/:id/events engine log
POST /api/scenes/:id/director {brief, model?, dryRun?, useVision?}
GET /api/presets ready-made films
POST /api/scenes/:id/preset {name, render?, fps?, width?, height?}
GET /api/scenes/:id/export film JSON
POST /api/scenes/:id/import {film}
POST /api/scenes/:id/duplicate
GET /api/stats
POST /api/maintenance/cleanup
POST /api/scenes/:id/checkpoints {name?} save film so far (rewind point)
GET /api/scenes/:id/checkpoints
POST /api/scenes/:id/checkpoints/:cid/restore rewind (cancels pending, reset+replay)
DELETE /api/scenes/:id/checkpoints/:cid
GET /api/macros · GET/PUT/DELETE /api/macros/:name {description, params:{var:default}, commands:[... {{var}} ...]}
POST /api/scenes/:id/macros/:name {vars:{...}, snapshot?:bool} run a macro
POST /api/scenes/:id/auto?wait=15 {target?, shot?, look?, targetMean?} auto-director pass (expose+snapshot+meter)
GET /api/scenes/:id/state?view=summary|ids|diff compact state views (save tokens)
POST /api/scenes/:id/pair {ttl?} 6-digit pairing code + qrUrl (human opens /pair on the phone and types it)
GET /api/qr?text=... QR code SVG
GET /api/scenes/:id/stream?key=|token= SSE live stream: events, acks, frames (EventSource)
POST /api/scenes/:id/lock {owner, ttl?, force?} · GET · DELETE ?owner= collaboration lock for several AIs
POST /api/story/plan {prompt} one sentence -> plan + screenplay (no LLM)
POST /api/scenes/:id/story {prompt, render?} one sentence -> full film (runs + records + critiques)
POST /api/scenes/:id/polish {targetScore?, rounds?, target?} critique->fix loop until score reached
GET /api/films · POST /api/films · POST /api/scenes/:id/films (save current) · GET/DELETE /api/films/:fid · POST /api/scenes/:id/films/:fid/play
POST /api/screenplay/compile {text} dry-run: screenplay text -> commands (no LLM)
POST /api/scenes/:id/screenplay {text, strict?, snapshot?} compile + queue a whole film
GET /api/screenplay/grammar · /api/screenplay/example
POST /api/assets (raw body+?name= | multipart file | json {url}) → /api/assets/:id (GLB/images/audio on R2)
GET /api/assets · DELETE /api/assets/:id
GET /api/scenes/:id/devices multi-viewer: which phone is PRIMARY
GET /control/:id live control panel (human, needs key)
-- engine side (viewer token) --
POST /api/engine/:id/sync {since, state, acks, events} -> {commands}
POST /api/engine/:id/frame {data(base64), label, width, height, sceneTime}
POST /api/engine/:id/render/begin
POST /api/engine/:id/render/:rid/chunk?idx=n (binary body)
POST /api/engine/:id/render/:rid/complete
POST /api/engine/:id/render/:rid/fail
كتالوج الأوامر (79)
scene
scene.reset
Remove all objects, lights, particles, animations and reset camera/effects to defaults.
{
"keepCamera": "bool (optional)"
}
{
"op": "scene.reset"
}
scene.config
Configure global scene settings.
{
"background": "CSS color string, e.g. \"#ff8800\" or \"skyblue\" | \"transparent\"",
"fog": "{ color, near, far } | { color, density } (exp2) | null",
"exposure": "number (0.1-4) tone-mapping exposure",
"toneMapping": "none | linear | reinhard | cineon | aces | agx | neutral",
"shadows": "bool",
"shadowMapSize": "512|1024|2048",
"pixelRatio": "number 0.5-2 (lower = faster on phone)",
"environment": "null | \"studio\" | \"sunset\" | \"night\" | \"dawn\" | \"warehouse\" | \"forest\" | \"city\" (procedural HDR-like env lighting)",
"gravity": "number (for particles)"
}
{
"op": "scene.config",
"background": "#05070f",
"fog": {
"color": "#05070f",
"near": 20,
"far": 120
},
"exposure": 1.1,
"toneMapping": "aces",
"environment": "night"
}
scene.snapshot
Capture the current frame (the AI's EYES). Uploads a JPEG to the server; read it via GET /api/scenes/:id/frames/latest.
{
"label": "string",
"quality": "number 0.3-0.95",
"width": "max width px (default 960)",
"delay": "seconds to wait before capturing (1-2 after tweens/titles)"
}
{
"op": "scene.snapshot",
"label": "check-lighting",
"quality": 0.8,
"delay": 1
}
scene.autoSnapshot
Automatically capture a frame every N seconds (0 disables). Lets the AI watch continuously.
{
"interval": "seconds (0 = off)",
"quality": "number",
"width": "px"
}
{
"op": "scene.autoSnapshot",
"interval": 2,
"quality": 0.6,
"width": 640
}
scene.setTime
Seek the master timeline to time t (seconds).
{
"t": "seconds"
}
{
"op": "scene.setTime",
"t": 3.5
}
scene.play
Start master timeline.
{}
{
"op": "scene.play"
}
scene.pause
Pause master timeline.
{}
{
"op": "scene.pause"
}
scene.timeline
Set timeline length/loop/speed. Animations use timeline time.
{
"duration": "seconds",
"loop": "bool",
"speed": "number (1 = realtime)"
}
{
"op": "scene.timeline",
"duration": 12,
"loop": false,
"speed": 1
}
scene.inspect
AI EYES: one contact-sheet image of the scene/object from N angles (turnaround + top). Camera restored. Result -> GET frames/latest.
{
"target": "object id | [x,y,z]",
"angles": "4|6|8",
"radius": "distance",
"height": "camera height offset",
"width": "total px (default 1024)"
}
{
"op": "scene.inspect",
"target": "hero",
"angles": 6,
"radius": 8
}
scene.storyboard
AI EYES: frames at several timeline times in one image (see the whole film). Timeline restored.
{
"times": "array of seconds (default 6 evenly spaced)",
"width": "total px",
"cols": "columns"
}
{
"op": "scene.storyboard",
"times": [
0,
2,
4,
6,
8,
10
]
}
scene.measure
Bounding boxes, pairwise distances/overlaps and camera visibility (JSON, no image). Use to check collisions and framing.
{
"ids": "array of ids (default all)"
}
{
"op": "scene.measure",
"ids": [
"hero",
"car"
]
}
scene.quality
Performance tier: low | medium | high | ultra.
{
"tier": "low | medium | high | ultra"
}
{
"op": "scene.quality",
"tier": "medium"
}
environment
env.sky
Add a procedural sky dome with a sun. Presets tune colors and sun angle.
{
"preset": "day | sunset | night | dawn | space | overcast | alien",
"sunPosition": "[x,y,z] number array (direction)",
"turbidity": "number",
"rayleigh": "number",
"stars": "bool (add star field)",
"starCount": "number"
}
{
"op": "env.sky",
"preset": "sunset",
"stars": false
}
env.ground
Add a ground plane (optionally with grid / procedural terrain / reflective water).
{
"type": "plane | grid | terrain | water | checker | none",
"size": "number (default 200)",
"color": "CSS color string, e.g. \"#ff8800\" or \"skyblue\"",
"color2": "CSS color string, e.g. \"#ff8800\" or \"skyblue\" (checker second color)",
"height": "terrain max height",
"roughness": "number",
"metalness": "number",
"reflective": "bool (mirror-like floor)"
}
{
"op": "env.ground",
"type": "terrain",
"size": 300,
"color": "#3b4a2e",
"height": 14
}
weather.set
One-call weather. type: rain | snow | lightning | wind | clear. Lightning = random flashes + thunder light. Wind sways trees.
{
"type": "rain | snow | lightning | wind | clear",
"intensity": "0-1",
"count": "particles override"
}
{
"op": "weather.set",
"type": "rain",
"intensity": 0.8
}
objects
object.add
Add a 3D object. Types: box, sphere, cylinder, cone, torus, torusKnot, plane, capsule, icosahedron, octahedron, dodecahedron, ring, text (billboard sprite), text3d (extruded 3D letters), group (empty container), model (GLTF/GLB url), tree, rock, building, car, human (procedural low-poly props; human supports walk:true), lamp (street lamp with light), crystal (glowing gem).
{
"id": "string unique id (auto if omitted)",
"type": "see desc",
"position": "[x,y,z] number array",
"rotation": "[x,y,z] number array (degrees)",
"scale": "[x,y,z] number array | number",
"size": "[x,y,z] number array | number (geometry dimensions)",
"segments": "number (geometry detail)",
"material": "{ color, emissive, emissiveIntensity, metalness(0-1), roughness(0-1), opacity(0-1), transparent(bool), wireframe(bool), flatShading(bool), side(\"front\"|\"back\"|\"double\"), texture(url), envMapIntensity, type(\"standard\"|\"physical\"|\"basic\"|\"toon\"|\"normal\") , clearcoat, transmission, ior }",
"castShadow": "bool",
"receiveShadow": "bool",
"parent": "id of parent object/group",
"text": "string (type=text)",
"fontSize": "number (type=text)",
"url": "GLB/GLTF url (type=model)",
"playClip": "animation clip name or \"*\" (type=model)",
"visible": "bool",
"spin": "{ axis:[x,y,z], speed: deg/sec } continuous rotation",
"float": "{ amplitude, speed } bobbing motion",
"walk": "bool (type=human) animate legs/arms while moving",
"windowColor": "CSS color string, e.g. \"#ff8800\" or \"skyblue\" (type=building)",
"clothColor": "CSS color string, e.g. \"#ff8800\" or \"skyblue\" (type=human)",
"depth": "number (type=text3d extrusion)",
"tag": "string label for grouping"
}
{
"op": "object.add",
"id": "hero",
"type": "sphere",
"position": [
0,
1.5,
0
],
"size": 1.5,
"material": {
"color": "#ffcc00",
"metalness": 0.8,
"roughness": 0.2,
"emissive": "#ff6600",
"emissiveIntensity": 0.6
},
"castShadow": true
}
object.update
Update any property of an existing object (same params as object.add; partial). Use `duration` to tween the change.
{
"id": "string",
"duration": "seconds (optional tween)",
"easing": "linear | easeIn | easeOut | easeInOut | easeInOutCubic | easeOutBack | easeOutElastic | easeInExpo | easeOutExpo",
"...": "any object.add params"
}
{
"op": "object.update",
"id": "hero",
"position": [
3,
2,
-1
],
"material": {
"color": "#00ffcc"
},
"duration": 2,
"easing": "easeInOut"
}
object.remove
Remove object (and children).
{
"id": "string | array of ids | \"tag:<tag>\""
}
{
"op": "object.remove",
"id": "hero"
}
object.animate
Keyframe animation for an object on the master timeline. Each keyframe has time t and any of position/rotation/scale/opacity/color/emissiveIntensity.
{
"id": "string object id",
"keyframes": "[{ t, position?, rotation?, scale?, opacity?, color?, emissiveIntensity? }]",
"easing": "linear | easeIn | easeOut | easeInOut | easeInOutCubic | easeOutBack | easeOutElastic | easeInExpo | easeOutExpo",
"loop": "bool | \"pingpong\"",
"relative": "bool (t relative to now)"
}
{
"op": "object.animate",
"id": "hero",
"keyframes": [
{
"t": 0,
"position": [
0,
1,
0
],
"scale": 1
},
{
"t": 2,
"position": [
0,
4,
0
],
"scale": 1.5
},
{
"t": 4,
"position": [
5,
1,
-3
],
"scale": 1
}
],
"easing": "easeInOutCubic"
}
object.lookAt
Rotate object to face a point or another object.
{
"id": "string",
"target": "[x,y,z] number array | object id"
}
{
"op": "object.lookAt",
"id": "car1",
"target": "hero"
}
object.path
Move object along a smooth curve through points, over duration, optionally facing forward.
{
"id": "string",
"points": "array of [x,y,z] number array",
"duration": "seconds",
"startAt": "timeline t (default now)",
"loop": "bool",
"faceForward": "bool",
"easing": "linear | easeIn | easeOut | easeInOut | easeInOutCubic | easeOutBack | easeOutElastic | easeInExpo | easeOutExpo",
"closed": "bool"
}
{
"op": "object.path",
"id": "drone",
"points": [
[
0,
5,
0
],
[
10,
6,
-5
],
[
0,
8,
-12
],
[
-10,
5,
-5
]
],
"duration": 8,
"loop": true,
"faceForward": true,
"closed": true
}
object.clone
Duplicate object N times with offsets (crowds, forests, cities).
{
"id": "source id",
"count": "number",
"offset": "[x,y,z] number array step",
"jitter": "number random spread",
"prefix": "id prefix",
"grid": "[cols, rows] arrange in grid",
"circle": "{ radius } arrange in circle"
}
{
"op": "object.clone",
"id": "tree",
"count": 40,
"jitter": 40,
"prefix": "forest"
}
object.drop
Gravity drop with bounce onto ground (y=0) or floor.
{
"id": "string | array",
"height": "start height",
"bounce": "0-1",
"floor": "y"
}
{
"op": "object.drop",
"id": "crate",
"height": 8,
"bounce": 0.4
}
object.explode
Break an object into flying fragments + flash + particles + shake, then remove it.
{
"id": "string",
"force": "number",
"fragments": "number (default 24)"
}
{
"op": "object.explode",
"id": "crate",
"force": 8
}
object.attach
Parent an object (or the camera with id "camera") to another object with an offset so it follows it.
{
"id": "object id | \"camera\"",
"to": "target object id | null (detach)",
"offset": "[x,y,z] number array"
}
{
"op": "object.attach",
"id": "lamp1",
"to": "car",
"offset": [
0,
2,
0
]
}
object.trail
Glowing motion trail behind a moving object.
{
"id": "string",
"color": "CSS color string, e.g. \"#ff8800\" or \"skyblue\"",
"length": "points (default 60)",
"width": "number",
"enabled": "bool"
}
{
"op": "object.trail",
"id": "ship",
"color": "#7df9ff",
"length": 80
}
object.label
Floating text label above an object (billboard). Empty text removes it.
{
"id": "string",
"text": "string",
"color": "CSS color string, e.g. \"#ff8800\" or \"skyblue\"",
"size": "number"
}
{
"op": "object.label",
"id": "hero",
"text": "HERO",
"color": "#ffd700"
}
lights
light.add
Add a light. Types: ambient, hemisphere, directional (sun), point, spot, rect (area).
{
"id": "string",
"type": "see desc",
"color": "CSS color string, e.g. \"#ff8800\" or \"skyblue\"",
"groundColor": "CSS color string, e.g. \"#ff8800\" or \"skyblue\" (hemisphere)",
"intensity": "number. Ranges: ambient/hemisphere 0.2-1.5, directional 0.5-4, point 1-15, spot 2-25 (>40 blows out to white)",
"position": "[x,y,z] number array",
"target": "[x,y,z] number array | object id (directional/spot)",
"distance": "number",
"decay": "number",
"angle": "degrees (spot cone)",
"penumbra": "0-1",
"castShadow": "bool",
"width": "rect width",
"height": "rect height",
"helper": "bool (debug gizmo)",
"volumetric": "bool (spot only) visible light cone / god rays",
"shadowSize": "directional shadow area half-size (default 30)",
"flicker": "{ amount, speed } candle/fire flicker",
"pulse": "{ min, max, speed }"
}
{
"op": "light.add",
"id": "key",
"type": "spot",
"color": "#ffffff",
"intensity": 10,
"position": [
5,
8,
5
],
"target": "hero",
"angle": 30,
"penumbra": 0.4,
"castShadow": true
}
light.update
Update light params (partial; `duration` tweens).
{
"id": "string",
"duration": "seconds",
"...": "light.add params"
}
{
"op": "light.update",
"id": "key",
"intensity": 0,
"duration": 1.5
}
light.remove
Remove light.
{
"id": "string"
}
{
"op": "light.remove",
"id": "key"
}
camera
camera.set
Set camera instantly (or tween with duration).
{
"position": "[x,y,z] number array",
"lookAt": "[x,y,z] number array | object id",
"fov": "degrees",
"roll": "degrees",
"near": "number",
"far": "number",
"duration": "seconds",
"easing": "linear | easeIn | easeOut | easeInOut | easeInOutCubic | easeOutBack | easeOutElastic | easeInExpo | easeOutExpo"
}
{
"op": "camera.set",
"position": [
8,
4,
10
],
"lookAt": "hero",
"fov": 45,
"duration": 2
}
camera.animate
Camera keyframes on the master timeline (dolly, crane, pan, zoom, push-in...).
{
"keyframes": "[{ t, position?, lookAt?, fov?, roll? }]",
"easing": "linear | easeIn | easeOut | easeInOut | easeInOutCubic | easeOutBack | easeOutElastic | easeInExpo | easeOutExpo",
"relative": "bool"
}
{
"op": "camera.animate",
"keyframes": [
{
"t": 0,
"position": [
0,
2,
20
],
"lookAt": [
0,
1,
0
],
"fov": 60
},
{
"t": 5,
"position": [
4,
3,
6
],
"lookAt": "hero",
"fov": 35
}
],
"easing": "easeInOutCubic"
}
camera.orbit
Continuously orbit around a target. speed in deg/sec. Set speed 0 or enabled:false to stop.
{
"target": "[x,y,z] number array | object id",
"radius": "number",
"height": "number",
"speed": "deg/sec",
"startAngle": "deg",
"enabled": "bool"
}
{
"op": "camera.orbit",
"target": "hero",
"radius": 12,
"height": 4,
"speed": 15
}
camera.follow
Follow an object with an offset and smoothing (chase cam).
{
"target": "object id",
"offset": "[x,y,z] number array",
"lerp": "0-1 smoothing",
"lookAhead": "number",
"enabled": "bool"
}
{
"op": "camera.follow",
"target": "car1",
"offset": [
0,
3,
-8
],
"lerp": 0.08
}
camera.shake
Handheld / impact camera shake.
{
"intensity": "number (0.05 subtle, 1 violent)",
"duration": "seconds (0 = continuous until intensity 0)",
"frequency": "number"
}
{
"op": "camera.shake",
"intensity": 0.4,
"duration": 1.2
}
camera.path
Fly camera along smooth spline through points.
{
"points": "array of [x,y,z] number array",
"lookAt": "[x,y,z] number array | object id | \"forward\"",
"duration": "seconds",
"startAt": "t",
"easing": "linear | easeIn | easeOut | easeInOut | easeInOutCubic | easeOutBack | easeOutElastic | easeInExpo | easeOutExpo",
"loop": "bool"
}
{
"op": "camera.path",
"points": [
[
20,
5,
20
],
[
0,
8,
25
],
[
-20,
5,
20
],
[
-10,
3,
0
]
],
"lookAt": "hero",
"duration": 10
}
camera.dolly
Move camera along its view axis (negative = push in). Optional fov for dolly-zoom (Vertigo).
{
"distance": "meters (+ back, - forward)",
"duration": "seconds",
"fov": "target fov",
"easing": "linear | easeIn | easeOut | easeInOut | easeInOutCubic | easeOutBack | easeOutElastic | easeInExpo | easeOutExpo"
}
{
"op": "camera.dolly",
"distance": -6,
"duration": 4,
"fov": 70
}
camera.preset
Classic framing relative to a target: wide, medium, closeup, lowAngle, highAngle, overShoulder, birdsEye, dutch.
{
"target": "object id",
"shot": "see desc",
"duration": "seconds tween",
"side": "left | right"
}
{
"op": "camera.preset",
"target": "hero",
"shot": "lowAngle",
"duration": 2
}
camera.frame
EXACT framing by geometry: put the target at a screen anchor (screenX/screenY 0-1, thirds = 0.333/0.667) filling a fraction of frame height, from a chosen azimuth/elevation. Use this to fix "subject too small / off thirds" precisely.
{
"target": "object id",
"fill": "0.05-0.95 fraction of frame height (default 0.45)",
"screenX": "0-1 (default 0.5)",
"screenY": "0-1 (default 0.5)",
"azimuth": "deg around target, 0 = front (default 35)",
"elevation": "deg (default 12)",
"fov": "deg",
"roll": "deg",
"headroom": "extra space above in radii",
"duration": "seconds tween",
"easing": "linear | easeIn | easeOut | easeInOut | easeInOutCubic | easeOutBack | easeOutElastic | easeInExpo | easeOutExpo"
}
{
"op": "camera.frame",
"target": "hero",
"fill": 0.5,
"screenX": 0.667,
"screenY": 0.4,
"azimuth": 40,
"elevation": 10,
"duration": 1.5
}
effects
fx.set
Post-processing look. All optional; duration tweens.
{
"bloom": "{ enabled, strength(0-3), radius(0-1), threshold(0-1) }",
"vignette": "{ enabled, darkness(0-2), offset(0-2) }",
"grain": "{ enabled, amount(0-1) }",
"chromatic": "{ enabled, amount(0-0.02) }",
"colorGrade": "{ saturation, contrast, brightness, temperature(-1..1), tint(color), tintAmount }",
"letterbox": "{ enabled, ratio (2.39 cinematic) }",
"dof": "{ enabled, focus (distance m | object id), aperture (0.0005-0.05), maxblur (0.005-0.02) } depth of field",
"pixelate": "{ enabled, size }",
"scanlines": "{ enabled, amount }",
"duration": "seconds"
}
{
"op": "fx.set",
"bloom": {
"enabled": true,
"strength": 1.2,
"threshold": 0.6
},
"vignette": {
"enabled": true,
"darkness": 1.1
},
"letterbox": {
"enabled": true,
"ratio": 2.39
},
"colorGrade": {
"saturation": 1.1,
"contrast": 1.1,
"temperature": 0.2
}
}
fx.fade
Fade screen from/to a color (fade in from black, fade out to white...).
{
"from": "0-1 opacity",
"to": "0-1",
"duration": "seconds",
"color": "CSS color string, e.g. \"#ff8800\" or \"skyblue\""
}
{
"op": "fx.fade",
"from": 1,
"to": 0,
"duration": 2,
"color": "#000000"
}
fx.flash
Quick screen flash (explosion, lightning).
{
"color": "CSS color string, e.g. \"#ff8800\" or \"skyblue\"",
"duration": "seconds",
"intensity": "0-1"
}
{
"op": "fx.flash",
"color": "#ffffff",
"duration": 0.25
}
fx.slowmo
Timeline speed ramp (bullet-time). speed<1 slow, >1 fast.
{
"speed": "number",
"duration": "seconds to ramp",
"hold": "seconds to hold then return to 1"
}
{
"op": "fx.slowmo",
"speed": 0.2,
"duration": 0.5,
"hold": 2
}
fx.look
Apply a complete color look in one call: teal_orange | noir | vintage | cyberpunk | dream | horror | documentary | none.
{
"name": "see desc",
"duration": "seconds tween"
}
{
"op": "fx.look",
"name": "teal_orange",
"duration": 1
}
music.beatFx
Beat-synced effects: pulse a light, pump an object scale, flash, camera nudge on every N beats.
{
"light": "light id",
"object": "object id",
"flash": "bool",
"shake": "intensity",
"every": "beats (default 1)",
"amount": "0-1 (default 0.35)",
"color": "CSS color string, e.g. \"#ff8800\" or \"skyblue\"",
"enabled": "false to stop"
}
{
"op": "music.beatFx",
"light": "neonA",
"object": "hero",
"every": 2
}
particles
particles.add
GPU point particle system. Presets: fire, smoke, sparks, rain, snow, dust, stars, embers, magic, explosion, fog, confetti, fireflies.
{
"id": "string",
"preset": "see desc",
"position": "[x,y,z] number array",
"count": "number (200-5000; keep low on phone)",
"color": "CSS color string, e.g. \"#ff8800\" or \"skyblue\"",
"color2": "CSS color string, e.g. \"#ff8800\" or \"skyblue\" (end color)",
"size": "number",
"speed": "number",
"spread": "number | [x,y,z] number array",
"lifetime": "seconds",
"gravity": "number",
"emit": "bool (false = burst once)",
"parent": "object id (attach to object)",
"area": "[x,y,z] number array (rain/snow volume)"
}
{
"op": "particles.add",
"id": "fire1",
"preset": "fire",
"position": [
0,
0,
0
],
"count": 800,
"size": 0.6
}
particles.update
Update particle system params.
{
"id": "string",
"...": "particles.add params"
}
{
"op": "particles.update",
"id": "fire1",
"emit": false
}
particles.remove
Remove particle system.
{
"id": "string"
}
{
"op": "particles.remove",
"id": "fire1"
}
overlay
title.show
Cinematic title card / subtitle overlay (2D). Supports Arabic and RTL.
{
"id": "string (default \"main\")",
"text": "string",
"subtitle": "string",
"position": "center | top | bottom | lowerThird",
"duration": "seconds (0 = until hidden)",
"style": "cinematic | minimal | bold | typewriter | neon | subtitle",
"color": "CSS color string, e.g. \"#ff8800\" or \"skyblue\"",
"size": "number (vw units, default 6)",
"font": "CSS font-family",
"fadeIn": "seconds",
"fadeOut": "seconds",
"letterSpacing": "em",
"align": "left | center | right",
"rtl": "bool"
}
{
"op": "title.show",
"text": "CHAPTER ONE",
"subtitle": "The Awakening",
"position": "center",
"duration": 4,
"style": "cinematic"
}
title.hide
Hide a title overlay.
{
"id": "string"
}
{
"op": "title.hide",
"id": "main"
}
hud.set
Engine HUD options (debug info for the human viewer; AI sees stats via /state anyway).
{
"stats": "bool",
"watermark": "string | null",
"safeArea": "bool"
}
{
"op": "hud.set",
"stats": false,
"watermark": null
}
audio
audio.play
Play an audio URL (music/sfx). Mobile may require the viewer to tap once first; engine reports blocked state.
{
"id": "string",
"url": "string",
"volume": "0-1",
"loop": "bool",
"fadeIn": "seconds",
"at": "timeline t"
}
{
"op": "audio.play",
"id": "music",
"url": "https://example.com/track.mp3",
"volume": 0.6,
"loop": true
}
audio.stop
Stop audio.
{
"id": "string",
"fadeOut": "seconds"
}
{
"op": "audio.stop",
"id": "music",
"fadeOut": 2
}
speech.say
Text-to-speech narration in the phone browser (supports Arabic). Also shows optional subtitle. Requires the viewer to have tapped once (mobile autoplay).
{
"text": "string",
"lang": "ar-EG | ar-SA | en-US | ...",
"rate": "0.5-2",
"pitch": "0-2",
"volume": "0-1",
"subtitle": "bool show text as subtitle",
"voice": "voice name substring"
}
{
"op": "speech.say",
"text": "في البداية كان الظلام",
"lang": "ar-EG",
"subtitle": true
}
speech.stop
Stop narration.
{}
{
"op": "speech.stop"
}
music.play
Generative film score synthesized live in the browser (no files): chords, bass, arps, pads, drums matched to a mood. Recorded into the video. Moods: horror, melancholy, action, dream, romance, epic, commercial, documentary, cyberpunk, calm, neutral. Needs one tap on the phone (autoplay policy) — emits music_blocked event otherwise.
{
"mood": "see desc",
"bpm": "override tempo",
"scale": "minor|major|dorian|phrygian|pentMinor|lydian|wholeTone",
"root": "MIDI note (default per mood)",
"intensity": "0-1 (default 0.7)",
"volume": "0-1 (default 0.5)",
"drums": "bool",
"arp": "bool",
"fadeIn": "seconds",
"seed": "number (reproducible)"
}
{
"op": "music.play",
"mood": "epic",
"intensity": 0.8
}
music.stop
Fade out and stop the score.
{
"fade": "seconds (default 1.5)"
}
{
"op": "music.stop",
"fade": 2
}
music.set
Live adjust volume / intensity (e.g. build-up before a climax).
{
"volume": "0-1",
"intensity": "0-1"
}
{
"op": "music.set",
"intensity": 1
}
render
render.start
Record the canvas into a WebM/MP4 video for `duration` seconds and upload it. Timeline is rewound to `from` and played. Result at GET /api/scenes/:id/renders.
{
"renderId": "string (auto)",
"duration": "seconds",
"from": "timeline start t (default 0)",
"fps": "24|30|60",
"width": "px",
"height": "px",
"bitrate": "bits/sec (default 6e6)",
"mime": "video/webm | video/mp4 (auto best)"
}
{
"op": "render.start",
"duration": 10,
"from": 0,
"fps": 30,
"width": 1280,
"height": 720
}
render.cancel
Cancel active recording.
{}
{
"op": "render.cancel"
}
sequencing
sequence
Run a list of commands at scheduled timeline times (a full shot list in one call). Each step: { at: seconds, cmd: <any command> }. Steps with no `at` run immediately.
{
"steps": "[{ at, cmd }]",
"autoplay": "bool (start timeline)",
"resetTimeline": "bool"
}
{
"op": "sequence",
"resetTimeline": true,
"autoplay": true,
"steps": [
{
"at": 0,
"cmd": {
"op": "fx.fade",
"from": 1,
"to": 0,
"duration": 2
}
},
{
"at": 1,
"cmd": {
"op": "title.show",
"text": "GENESIS",
"duration": 3
}
},
{
"at": 5,
"cmd": {
"op": "camera.shake",
"intensity": 0.5,
"duration": 1
}
},
{
"at": 6,
"cmd": {
"op": "fx.flash",
"color": "#ffffff",
"duration": 0.3
}
}
]
}
batch
Execute many commands immediately in order (one round-trip).
{
"commands": "array of commands"
}
{
"op": "batch",
"commands": [
{
"op": "scene.reset"
},
{
"op": "env.sky",
"preset": "night",
"stars": true
}
]
}
editing
shot.save
Save the current camera (position/lookAt/fov/roll) as a named shot for later cuts.
{
"name": "string",
"position": "[x,y,z] number array (optional override)",
"lookAt": "[x,y,z] number array | id",
"fov": "deg",
"roll": "deg"
}
{
"op": "shot.save",
"name": "wide"
}
shot.cut
Hard cut (or dissolve) to a saved shot. Use in sequence steps to edit like a film.
{
"name": "saved shot name",
"dissolve": "seconds (0 = hard cut)",
"color": "dissolve color (default black)"
}
{
"op": "shot.cut",
"name": "closeup",
"dissolve": 0.4
}
shot.list
List saved shots.
{}
{
"op": "shot.list"
}
eyes
scene.exposure
LIGHT METER (no image). Returns mean luminance (0-1), clipped highlight/shadow ratios, 16-bin histogram, 3x3 zone brightness map, current exposure, a verdict (overexposed | underexposed | hot highlights | dark (moody) | ok) and suggestedExposureMultiplier. Measures the 3D render only (ignores fades/titles/letterbox). Call after lighting changes to avoid blown-out frames.
{
"target": "desired mean luminance (default 0.42)"
}
{
"op": "scene.exposure"
}
scene.autoExpose
Automatically adjust tone-mapping exposure so mean luminance approaches target. Returns from/to exposure and final verdict.
{
"target": "0.1-0.8 (default 0.42)",
"iterations": "1-8 (default 4)",
"delay": "seconds to wait first (let fades/tweens finish)",
"minExposure": "default 0.3",
"maxExposure": "default 2.2"
}
{
"op": "scene.autoExpose",
"target": 0.4,
"delay": 2
}
scene.find
Query objects in the scene. Filter by type, tag, id prefix / regex, distance to a point or object, on-screen visibility. Sorted by distance. Use to locate things before acting on them.
{
"type": "object type",
"tag": "string",
"prefix": "id prefix",
"match": "regex on id",
"near": "[x,y,z] number array | object id",
"radius": "number (with near)",
"onScreen": "bool",
"sort": "distance | distanceToCamera",
"limit": "default 50"
}
{
"op": "scene.find",
"near": "hero",
"radius": 5,
"onScreen": true
}
scene.compare
A/B check: captures BEFORE, runs the inner commands, captures AFTER, uploads a side-by-side sheet and returns exposure of both plus per-command results.
{
"commands": "[command objects]",
"delay": "seconds to wait before AFTER capture",
"width": "sheet width px (default 900)",
"label": "string"
}
{
"op": "scene.compare",
"commands": [
{
"op": "fx.look",
"name": "noir"
}
],
"label": "noir-test"
}
scene.timelapse
Capture N frames spaced by interval seconds while the scene keeps playing, into ONE sheet (see motion/animation over real time). Then GET /frames/latest.
{
"count": "2-16 (default 6)",
"interval": "seconds (default 1)",
"width": "px",
"label": "string"
}
{
"op": "scene.timelapse",
"count": 6,
"interval": 0.5
}
scene.critique
FILM-SCHOOL SCORECARD (JSON, no image). Scores the current frame 0-100 (grade A-F): exposure, contrast, subject on-screen/size/rule-of-thirds/headroom, clutter, edge distraction. Returns issues[] and fixes[] = ready-to-send commands. Subject = param target or the object tagged "hero".
{
"target": "object id (optional)"
}
{
"op": "scene.critique",
"target": "hero"
}
music.analyze
Live audio energy (low/mid/high, 0-1), bar, beatPhase, bpm — use to time cuts on the beat.
{}
{
"op": "music.analyze"
}
scene.history
Motion memory: sampled world positions [t,x,y,z] of objects for the last N seconds (10 Hz), plus distance travelled, avgSpeed, moving flag. Use to verify that an animation/path really happened.
{
"id": "object id (default: all, max 20)",
"seconds": "0.5-30 (default 10)",
"maxSamples": "default 40"
}
{
"op": "scene.history",
"id": "hero",
"seconds": 5
}
engine
guardian.start
SELF-DIRECTING LOOP inside the engine: every N seconds it meters exposure (auto-fix), keeps the hero in frame (reframe when it leaves), and scores the frame. Runs without any API traffic. Emits "guardian" events. Stops on scene.reset.
{
"every": "seconds (default 3)",
"target": "object id (default: tag hero)",
"exposure": "bool (default true)",
"keepInFrame": "bool (default true)",
"targetMean": "default 0.38",
"quiet": "bool: no events"
}
{
"op": "guardian.start",
"every": 2,
"target": "hero"
}
guardian.stop
Stop the self-directing loop.
{}
{
"op": "guardian.stop"
}
guardian.status
Guardian status: runs, last score, recent actions.
{}
{
"op": "guardian.status"
}
engine.selfTest
Device health report: GPU name, WebGL2, measured fps + tier (high/medium/low), counts (objects/lights/particles/triangles/drawCalls), memory, audioUnlocked, speechVoices, canRecord, recommendedBudget for this phone. Call once at the start to plan scene complexity.
{
"seconds": "fps sampling window (default 1.5)"
}
{
"op": "engine.selfTest"
}
engine.reload
Reload the engine page (recover from errors).
{}
{
"op": "engine.reload"
}
engine.ping
Ask engine to immediately push a fresh state report.
{}
{
"op": "engine.ping"
}