cinecode

The storyboard: film.json

A film is a folder films/<slug>/ with one storyboard file, film.json. It says who is in the film, where it takes place, and what happens shot by shot. The engine works out everything else: where the actors walk, how the camera frames them, how long each shot lasts, the captions, the sound effects and the music cues.

This page lists every field the engine reads. cinecode check validates the file against the same rules (the validator is engine/film/validate.mjs; the compiler that turns it into a timed plan is engine/film/compile.mjs).

Contents: top level · format and titles · voices and cast · sets · score · scenes · places · shots · camera · framing part of a subject · beats · timing rules · say · do · depth · sfx · fx · music · title · mark · wait · captions · extensions · checking

A complete small example is templates/film.json (what cinecode new gives you). A large one is films/seventy-six-summers/film.json.

Top level

field required meaning
title no (warning if missing) the film's title: opening card, contact sheets, the video's metadata
logline no one sentence for people reading the file; the engine does not use it
format no { width, height, fps, captions }, see below
titles no opening and end cards, see below
score no the film's key and melodic motif, see score
voices for any line voice id → { voice, tone }
cast for any actor cast id → { rig, name, voice, params, scale }
sets yes (at least one) set id → { kit, params }
scenes yes (at least one) the film, in order
seed no seed for the score (cinecode music, mix, animatic, render); default 1

Fields the engine doesn't know at the top level, scene or shot level are ignored; unknown fields on a beat are reported as warnings (see checking).

Format and titles

"format": { "width": 1920, "height": 1080, "fps": 24, "captions": "dialogue" },
"titles": {
  "open": { "title": "The Lantern Moth", "subtitle": "a cinecode film", "dur": 4 },
  "end":  { "lines": ["The End"], "dur": 4 }
}
field default rules
format.width, format.height 1920, 1080 even integers, 16–8192
format.fps 24 integer, 1–120
format.captions "dialogue" which captions are burned into the picture: dialogue (cast lines only), all (also narration and sound captions), none. The .srt file and the soft subtitles in the MP4 always carry every caption.
titles.open none a parchment title card before the first scene: title (default: the film's title), subtitle, dur (seconds, default 5). It dissolves into the first shot over its last second.
titles.end none an end card after the last scene: lines (default ["The End"]), dur (default 5). It dissolves in from the last shot and fades to black over its last 1.2 s.

Without an opening card the first scene fades in from black (0.8 s); without an end card the last scene fades out (1.2 s). The first music cue starts under the opening card and the last one runs on under the end card (its final chord dies about 3 s before the end unless the cue has "hold": true: see music). The cards count in the film's length (plan's total includes them, and the first shot starts after the opening card).

How long a card is readable: the opening card fades in from black over 0.8 s, its title inks in from 0.5 s to 1.4 s (the rule under it by 2.2 s, a subtitle by 2.7 s) and it dissolves into the first shot over its last 1 s, so the title is at full strength for dur − 2.4 s. The end card dissolves in over 1 s, its first line inks in from 0.4 s to 1.3 s (a second line by 1.8 s) and it fades to black over its last 1.2 s: full strength for dur − 2.5 s. A 2 s card never reaches full strength; 3 s is the shortest that reads (a short film), 4 s is comfortable.

Voices and cast

"voices": {
  "narrator": { "voice": "Sulafat", "tone": "warm, gentle, unhurried" },
  "pip":      { "voice": "Leda",    "tone": "bright, eager" }
},
"cast": {
  "pip": { "rig": "human", "name": "Pip", "voice": "pip",
           "params": { "age": "child", "hairStyle": "braids", "hair": "#3b2a20" } },
  "fox": { "rig": "fox", "name": "Old Fox", "voice": "fox", "scale": 1.2 }
}

voices.<id>: voice is one of the 30 prebuilt Gemini voices (required), tone is the voice's standing direction, joined with each line's own tone. See sound.md for the list and for how to write tones. The id narrator is the narrator; every other voice id is referred to by a cast member's voice.

cast.<id> (the id narrator is reserved):

field meaning
rig required: a rig kind (hare, human, …; see cast.md) or a rig from custom.mjs
name display name (default: the id)
voice a key of voices. A speaker may also use a voice with the same id as itself (voices.fox for cast.fox).
params the rig's look: colours, sizes, the human's age, hair, clothes… (cast.md)
scale size multiplier, 0.1–10 (default 1): a big old tortoise is 1.35, a baby one 0.4

Sets

"sets": { "meadow": { "kit": "meadow" }, "home": { "kit": "village", "params": { "seed": 2 } } }

A set is an instance of a kit (a painted location; see sets.md). kit is required; params is passed to the kit (the built-in kits read seed, which changes the random details of the painting; space also reads milky, nebula and tint). Two sets may use the same kit.

Score

"score": { "key": "G", "motif": "D5:1 B4:0.5 D5:0.5 E5:2 G5:3" }

key is a key name ("C", "Eb", "F#m", "A minor"); motif is the film's tune as note:beats tokens (G4:1, Bb3:0.5; beats > 0). With a score every cue is in that key and its melodies grow out of the motif; a music cue with "motif": n states the motif's first n notes. See sound.md.

Scenes

{
  "id": "s01", "set": "meadow", "time": "dawn",
  "music": { "mood": "wonder", "intensity": 0.4 }, "ambience": "auto",
  "actors": {
    "pip": { "at": "pond_edge", "face": "right", "pose": "sit" },
    "fox": { "offstage": "right" }
  },
  "shots": [ … ]
}
field meaning
id required, unique in the film
set required: a key of sets
time time of day: dawn, morning, noon, afternoon, golden, sunset, twilight, night, or a change across the scene { "from": "golden", "to": "twilight" } (the change happens over the middle 80% of the scene). A kit that is not under Earth's sky has times of its own instead (the space kit: space): only those are valid on it. A name the kit doesn't have is a check error (it is never lit like morning). Default: the kit's default time.
grade a story grade on top of the time of day: memory (sepia storybook plate with a torn page edge), dream (soft, bloomed), none
transition how the scene begins: cut, fade (0.35 s through black on each side), dissolve (0.8 s, but never more than 40% of the shot it opens or of the shot it leaves: a 1.2 s shot dissolves in over 0.48 s; there is no field to change it). Default: dissolve when the set or the time changes from the previous scene, else cut. In a dissolve the last scene shows through the new one's first shot, through anything drawn over it too: a scene that opens on a full-frame effect (the eyepiece beat's black field) wants "transition": "cut".
music the scene's music cue: a mood name, "none" (silence; the score fades out over 0.3 s), or { "mood", "intensity", "motif", "motifVoice", "hold", "downbeat", "tempo" }. Leave it out to continue the previous scene's mood and intensity. See music.
ambience "auto" (default: the set's day or night bed by the time of day), "none", or an ambience name (meadow-day, meadow-night, forest-day, forest-night, stream, village-day, village-night, wind-hill)
fx built-in effects for the whole scene: ["fireflies", "mist"] (leaves, rain, fireflies, motes, mist)
overlays drawings for the whole scene: built-in effect names as above, built-in overlays (skydot, sunglow: effects.md), or overlays from custom.mjs: ["comet", { "name": "far-hill", "params": { "frac": 0.04 } }]. { "name", "params", "shots": ["2.1", "2.4"] } draws only in those shots of the scene (a non-empty list of its shot ids; a built-in field covers each run of consecutive listed shots, and a rain fades only at the scene's start and end). See effects.md.
rim a rim light on every visible actor, drawn after the grade: { "dir", "color", "strength", "start", "rise", "width" }, see below
actors who is in the scene at its start, see below
shots required, at least one

rim: dir is the side the light comes from (left, right, top, upper-left (default), upper-right), color a CSS colour (default #fff1d6), strength 0..1 (default 0.8), width in screen pixels (default 3, at least 2 is drawn), start a mark name or seconds from the scene start (default 0), rise seconds to ease in (default 0).

Scene actors. Each entry is one of:

There is no way to start an actor invisible: a stand-in faded out by a fade beat at the start of a shot is still drawn in that shot's first frame, and the opening card dissolves into the first shot's first frame.

Anyone not listed can still come in with enter. Actor state carries from shot to shot within a scene (positions, facing, poses, expressions), so continuity is automatic; each scene starts again from its own actors.

Places

Everywhere a beat or an actor needs a place (at, to, from, camera on), it takes one of:

place meaning
"stump_front" an anchor of the scene's set (sets.md lists them; cinecode set-sheet draws them)
"x:1500" that x on the set's main lane (or on the slope of a ridge set); give a y for anywhere else
"x:1500,y:1060" an exact ground point in set coordinates (y grows downward; bigger y = nearer the camera). In a camera's on, toOn or focus it is the point itself, in the air too
"near:fox" beside that actor, on the side the mover comes from, facing it. The gap allows for the mover's front (a flier arriving to hover: its beating wings), the other actor's longer end (head or tail, as it may turn to face the mover) and about a fifth of their two heights (two mice: about 10 world px between them)
"left-of:fox", "right-of:fox" beside that actor on that side, with the same gap
"behind:fox", "front-of:fox" a little further from / nearer to the camera than that actor
"on:pebble" riding that actor (only for hop and place; see riding)
"left", "right" off that side of the frame (only enter from and exit to)

Ground places are moved to the nearest walkable spot that doesn't overlap a solid prop or another standing actor. Moves follow the set's walkable ground around solid props and water (stepping stones become hops). The one exception is a place a flier hovers over: it is taken exactly as written (over water, a stepping stone, anywhere nobody could stand), only kept inside the set's area.

Shots

{ "id": "1.2", "camera": { "size": "full", "on": ["pip"], "move": "track" },
  "beats": [ … ], "hold": 0.6 }
field meaning
id required, unique in the film (cinecode sheet and lint refer to shots by id). Any string works; the screenplay's <scene>.<shot> (2.3) keeps the two in step.
camera see camera
beats what happens, see beats. A shot with no beats and no dur lasts only its hold (a warning).
hold seconds added after the last beat ends (default 0.5)
dur the shot's exact length in seconds; overrides beats + hold. Beats that run past it carry on into the next shot of the scene.
time this shot's own time of day (a name or { "from", "to" }, blended across the shot), e.g. a time-lapse
grade, gradeTo this shot's story grade, and a grade to blend to across the shot ("none" = no story grade): "grade": "memory", "gradeTo": "none"
music a music cue starting at the shot's first frame (same forms as the scene's music)
fadeIn, fadeOut seconds of fade from / to black at the shot's start / end
lint "lint": { "allow": ["subject-size"], "note": "why" }: the shot's lint warnings of those rules are intended. Lint lists them as kept, with the note and no fix tried. Errors can't be kept, and check rejects a malformed block.
note free text for people; ignored

Shot boundaries land on frames. A shot is at least one frame long.

Camera

"camera": { "size": "medium", "on": ["pip", "fox"], "move": "push-in", "angle": "low" }
field values default
size establishing (the whole view), wide (subject ≈ 18% of the frame height), full (whole body ≈ 60%), medium (waist up ≈ 75%), close (head and shoulders ≈ 70%), extreme-close (the face) wide with subjects, else establishing
on actor ids, anchor names and/or points of the set (x:… or x:…,y:…): the union of their regions is framed. "x:1500" is framed like a figure standing on the lane there (an anchor's box), "x:1500,y:700" as the point itself, in the air too, at the size things have on the lane below it. Points must lie inside the set the actors the shot's beats name, else everyone on stage
move static, push-in, pull-out, pan, track, tilt-up, tilt-down, handheld static
angle eye, low (subjects sit lower, under more sky), high (higher, over more ground) eye
zoom multiplier on the size's zoom: 0.8 a little wider, 1.3 tighter. With a subject that has a face the frame still keeps every such head in it and above the captions (it cuts the body, never the head). When no subject has a face (a planet, a prop) a zoom above 1 crops into the size's framing: framing part of a subject 1
focus [fx, fy] (a point of the subjects' box: [0, 0] its top left, [1, 1] its bottom right), "head" (their heads' mean) or a world point "x:…,y:…": that point at the centre of the frame, at the size's zoom × zoom: framing part of a subject none
offset [x, y]: frame fractions when both are ≤ 2 in size ([0.16, 0] moves the subject right of centre), else world pixels none
z, zTo the camera's depth at the shot's start and at its move's end, world px (positive: toward the stage, a dolly in; negative: pulled back), each under 3/4 of the set's lens (1728 on the built-in kits): depth 0
dof depth of field: true, false or a blur radius. Everything behind the subjects is blurred; every subject in on and in toOn (anchors too) stays sharp for the whole shot. on for close and extreme-close; elsewhere automatic as the set is enlarged

Moves:

Moves are eased over the whole shot unless confined (below). Framing keeps lead room in the direction a single subject faces (a subject without a face, a planet or a prop, faces nowhere and gets none: it is centred across the frame), keeps headroom, keeps the heads of subjects with a face above the caption band, and is clamped to the set's view so the frame never shows unpainted edges.

What gets framed is each subject's poses over the shot, not the peaks of its gestures and jumps. A static or handheld close shot follows a gesture that holds the head in one place for 0.5 s or more and would take it out of the frame (the mouse's gasp sits her up, a deer grazes): the frame eases out just before, holds the head with its ears, and eases back after; the rest of the shot keeps its composition. A jump's apex, a quick nod, an extreme-close shot and the other moves are not followed, so there a gesture that lifts the head can still leave the frame: use the pose of the same name ({ "do": "pose", "who": "tilly", "pose": "gasp", "dur": 0.3 }), which is framed, or a looser size. An anchor in on is framed as a box about the size of a standing hare (120 × 110 world px × the scale there), so a small actor framed with an anchor comes out small: staging warns when an actor in on gets far under its own size because an anchor or a much bigger actor sets the framing (checking).

§9 additions for push-in / pull-out and windowed moves:

field meaning
to the move ends at this size's framing: "size": "full", "move": "push-in", "to": "close"
toOn …of these subjects (default: on): a push-in from a two-shot to a close-up of one of them
zoomTo the move ends at k × the starting zoom, about the subjects (ignored when to is given)
moveAt, moveDur the move happens only between these seconds of the shot (eased); the camera holds before and after. Works with any move.

Framing part of a subject

A shot size frames a region of each subject (the body, waist up, head and shoulders, the face) whole. To frame a part of something (the edge of Saturn's rings, its cloud bands, a tree's crown) there are three ways, and none needs a hidden stand-in:

Lint takes a focused shot (and a zoom above 1 on faceless subjects, and a dolly) as a deliberate crop: it doesn't warn that the subject is too big or gone off frame, nor that the crop leaves a sliver of the subject (lint). A hidden stand-in is still the way to give the camera a subject that moves through the air (effects.md).

Beats

A beat is one object with exactly one of these keys:

key what it is takes time
say a spoken line the voice's length
do an action (walk, gesture, look, …) depends on the action
sfx a sound effect 0 (or its dur)
fx a visual effect (dust, splash, sparkle, rain, …) 0
music a music cue or a sting 0
title an in-picture title its dur (default 3)
mark a named instant 0
wait a pause its value

Modifiers any beat may carry:

field meaning
with "prev": start together with the previous beat group (see below); or the id of an earlier beat of this shot: start with that beat
after seconds of delay added to the start
at pin the start to this many seconds from the shot's start (for look, place, fx and sfx, at may instead be a place)
id a name for with to refer to
touch, contact an actor id or list: this beat brings these actors into contact on purpose (lint then reports an overlap as a warning, not an error)
note, comment free text; ignored

Timing rules

Beats run in sequence: each starts when the previous one ends. Precisely:

  1. A beat with neither with nor at starts when the previous group ends. A group is a beat plus every beat started "with": "prev" after it; it ends when the last of them ends.
  2. "with": "prev" starts at the start of the current group (its first beat), and makes the group last until the longer of the two ends. "with": "<id>" starts with that beat.
  3. "at": 2.0 starts the beat 2.0 s after the shot starts, wherever it is in the list. A pinned beat starts a new group: the beat after it follows it, not the beats before it.
  4. "after": 0.4 delays any of the above by 0.4 s.
  5. The shot lasts until the latest beat ends plus hold (default 0.5 s), rounded to a frame, or exactly dur when given.
  6. A custom or sfx beat with "hold": false plays or draws its whole dur but ends, for the timeline, where it starts: the next beat, its with group and the shot's length don't wait for it (the rule fx, mark and music beats always follow). A long custom beat laid "with": "prev" over a line otherwise holds the next line back until it ends.

An example, with the times the compiler gives (the fox's wave lasts 1.6 s, the human's nod 0.9 s):

beat starts ends
{ "do": "gesture", "who": "fox", "name": "wave" } 0.0 1.6
{ "do": "look", "who": "pip", "to": "fox", "with": "prev", "after": 0.5 } 0.5 0.8
{ "do": "gesture", "who": "pip", "name": "nod" } 1.6 2.5
{ "wait": 1, "with": "prev" } 1.6 2.6
{ "sfx": "owl-hoot", "at": 0.2 } 0.2 0.2
{ "do": "turn", "who": "fox", "face": "around" } 0.2 0.45

The shot ends at 2.6 + 0.5 = 3.1 s (frame 74 at 24 fps). Note the last beat: it follows the pinned sfx, not the wait.

Before the voices exist a line lasts an estimate of words / 2.6 + 0.3 seconds; once they are generated, plan, sheet, lint and the renders use the real lengths, so shots can change length after cinecode voice. cinecode plan marks estimated lines with ~.

say

{ "say": "pip", "text": "Then wake up! Race you to the pond!", "tone": "excited", "expr": "happy" }
field meaning
say "narrator" or a cast id with a voice
text required: the caption, and what is spoken unless tts is given
tone the line's delivery, joined after the voice's tone: one to three adjectives (sound.md)
expr the speaker's expression from the line's start: a mood (happy, sad, smug, worried, …) is held until a beat changes it (listening doesn't reset it); a reaction (surprised, laugh, laughing, scared) lasts the line plus 0.4 s, or until the cut if that comes first, then gives way to the expression the actor had before
vo true: a voice-over; the speaker may be off screen or not in the scene
tts §9: what the voice actually says when it differs from the caption: { "text": "[laughs]", "tts": "Hahaha!" }
trimEnd §9: seconds cut off the end of the spoken audio (a line interrupted mid-word); timing and lip sync follow the cut

A line whose text starts with [ is non-verbal ([laughs], [gasps]): captioned in italics, and voice QA does not hold it to the words. The speaker's mouth follows the voice (lip sync from the audio's envelope); the other actors on stage glance at the speaker; a speaker who hasn't been told where to look talks to the nearest actor in front of them. A speaker in the scene who is not vo should be at least 60% on screen (lint offscreen-speaker). Each line is voiced once and cached; changing its words, its tone or its voice makes it a new line (sound.md).

do: actions

Every action except custom needs who, an actor present in the scene (in its actors, or brought in by an earlier enter).

action fields takes
walk, run to (a place), speed, gait, pose, face, wade, depth, z the route's length at the rig's pace
hop to (a place or on:<actor>), speed, pose, face, z as walk, hopping (rigs without a hop gait repeat their jump, or walk)
fly to, speed, pose, face, height (hovering), z; only rigs that fly flight time
enter from (left / right or a place), to (a place; default about a quarter of the way into the frame), run, speed, gait, pose, fly (true: a flier flies in from above), height, z as walk
exit to (left / right, or a place), run, speed, gait, z as walk
place to (a place or on:<actor>), face, pose, height, z 0: the actor is simply there, visible
pose pose, dur, expr dur (default 1)
gesture name (a gesture of the rig) the gesture's length
look to: an actor, an anchor, a place, left, right, up, sky, down, camera or none; tilt (−1..1, the head's pitch) 0.3
turn face: left, right or around 0.25
emote expr 0
sit, stand, lie, sleep 0.6
wake pose (default: the pose before sleeping, or stand) 0.75
fade to (opacity 0..1), dur (default 1) dur
set params (rig param changes from now on), pose (instant), expr; z with dur: a change of depth over that time 0 (with a z: its dur)
blink 0.2
custom name (a built-in effect or one in custom.mjs custom), dur (default 1), scope, hold, any other fields dur ("hold": false: 0)

Details:

Riding

§9: an actor can ride another (a squirrel on a tortoise's shell). Start mounted with a scene actor { "on": "pebble" }, or get on with { "do": "hop", "who": "fidget", "to": "on:pebble" } (or place there). A rider moves with its carrier, is drawn just in front of it, casts no shadow, and keeps its own facing, pose, gestures and lines. A rider can't walk, run or exit; it gets off with an ordinary hop to a place. A carrier that exits takes its riders with it. Riders can't carry riders.

Wading

§9: { "do": "walk", "who": "pebble", "to": "x:1700", "wade": true } lets a walk or run cross water in a straight line. In the water the actor sinks as it goes deeper (0.6 px per px travelled into the water) down to depth rig units (default 1.1 × the rig's height: fully under), and rises again on the far side. Riders sink with their carrier. If a solid prop is in the way, the dry route is taken instead.

The body is not cut flat at the waterline: wherever it breaks the surface (the trunk, each leg, a tail) it curves down into the water along its own near arc, with a dark meniscus lip, a shivering highlight, a darker band just under the surface and flecks of foam, and rings spread round the whole body. Below the surface it shows faintly through the water. A body fully under shows only faint rings, with nothing dry below the surface.

Hovering

A flier (a rig with a fly gait: butterfly, songbird, owl; not the beetle) can stay in the air. Give a fly, enter or place beat "height": N and the flight ends N units above its place, at the set's scale (the units of rig heights: a songbird is 24 tall, an owl 62); above the anchor point for a perch or surface anchor. The flier holds there, keeps flapping with a gentle bob (about 4% of its height), and can talk and gesture:

{ "do": "enter", "who": "wren", "from": "right", "to": "x:1250,y:1080", "height": 60 }
{ "do": "fly", "who": "wren", "to": "near:tilly", "height": 40 }

check reports as errors: height on a rig that can't fly, on a beat other than fly, enter or place, 0 or less, with an on: target, or on a scene actor without at; a pose given with height; and sit, lie, sleep, pose (or a set with a pose) while the actor hovers. Land it first: a fly or walk to a place without height.

Depth

Things can be in front of and behind each other, not only up and down the ground: a moon behind a planet, debris the camera flies through. z is world px off the stage, the plane the set stages on (its ground, props and anchors): positive is farther from the lens, negative nearer. A thing at depth z is drawn at p = (lens − c) / (lens + z − c) of its size about the frame centre, c being the camera's depth; the lens is the kit's (2304 world px on the built-in kits), so a camera at depth 0 sees z 2400 at about half size and z −900 at about 1.6×.

sfx

{ "sfx": "owl-hoot", "gain": 0.8, "with": "prev", "after": 0.3 }
{ "sfx": "hum", "caption": "[hums]", "params": { "notes": "G4:1 E4:0.5 G4:0.5 A4:2", "tempo": 100, "voice": "child" } }
field meaning
sfx a built-in effect name or alias, or a sound from custom.mjs sounds (sound.md)
gain level multiplier (≥ 0, default 1)
pan −1..1; default: from where on / who / at is on screen
on, who an actor or anchor the sound comes from (pans it; quieter when far off screen)
params the generator's parameters (size, weight, notes, …)
dur length for continuous effects; the beat then takes dur seconds. Put the length in params instead ("params": { "dur": 30 }) and the beat takes no time while the sound plays on, across cuts: a bed of rain or wind under a whole scene
seed a fixed seed (default: from the name, time and shot)
hold false: a beat dur plays but takes no time (the next beat and the shot don't wait); default true
caption a sound caption such as "[croak]", shown for max(1.2 s, dur) but counted as heard only for dur (give a long sound a dur if its caption should cross a cut; see captions); burned in only with captions: "all"

fx

{ "fx": "splash", "at": "pond_edge" }
{ "fx": "sparkle", "on": "pip", "size": 1.5, "color": "255,236,170" }
{ "fx": "rain", "dur": 6 }
field meaning
fx dust, splash, sparkle (one-shots: 1.3, 1.2 and 1.6 s), or leaves, rain, fireflies, motes, mist (fields). dust takes its colour from the set's painted ground where it rises, in the scene's light (below).
on, who, at where: an actor or an anchor (or x:…)
dur how long a field lasts (default: to the end of the scene); fields fade in and out over 1.2 s
size size multiplier (one-shots): it scales a sparkle's spread and stars, but a sparkle is always 14 stars, so for a single glint on a small prop write a custom beat
n density multiplier for fields
dir direction of travel for dust (1 or −1)
color sparkle colour as "r,g,b"

An fx beat takes no time. For a whole scene, use the scene's fx or overlays.

Dust (an fx beat, and the dust a run or a landing on dirt kicks up by itself) takes its colour once, as it is kicked up, from the set's painted ground on the contact's own surface (the path, not the grass or water beside it) in the scene's light, and keeps it for its 1.3 s. Actors, shadows and props over the spot never tint it. By day it is a little lighter and greyer than the ground, on grass a pale green-grey, by night a dim haze in the night's light.

Fields (fireflies, leaves, motes, mist, rain) fill the whole frame: at, on and size don't place or scale them (only mist takes its height from at). Their density is per world area, so how many you see depends on the shot: fireflies puts 3–6 × n in every 420 × 420 world px (from 220 px above the horizon down), each lit about half the time. An establishing shot shows dozens; a shot of a mouse, a few hundred world px across, shows a few or none even at n: 6. For a swarm around small characters, raise n a lot or draw your own overlay (effects.md).

music

{ "music": "tension", "intensity": 0.6 }
{ "music": { "mood": "triumph", "intensity": 0.8, "motif": 5, "motifVoice": "tutti", "downbeat": true } }
{ "music": "sting", "name": "surprise" }
{ "music": "none" }

A music beat starts a new cue at that moment (until the next cue or the end of the scene), plays a sting, or silences the score. Forms:

§9 extras on a cue: motif (state the first n notes of the score's motif), motifVoice (bassoon, flute, cello, horn, celesta, violin, strings, music-box, tutti), hold (true: the last stated note sustains to the cue's end), downbeat (true: bar 1 lands exactly on the beat, cutting the previous cue), tempo (20–300 bpm). See sound.md. Two habits: give a mood change that starts on a cut "downbeat": true (without it the new cue can open with a second or more of near-silence), and give the film's last cue "hold": true if the end card is short (its final chord lands about 3 s before the end and dies).

title

{ "title": "SEVENTY-SIX SUMMERS", "sub": "a film in code", "dur": 2.7, "place": "upper" }

Big italic lettering in the picture for dur seconds (default 3). sub (or subtitle) adds a smaller line; a \n in it starts another line under it (a credit, say). place: "auto" (default: the calmest band of the picture, centred over the subjects), top, upper, center (or middle), lower, bottom, or a fraction 0..1 of the frame height for the title line.

mark

{ "mark": "bloom", "with": "prev" }

§9: a named instant (takes no time). Mark names are unique in the film. A scene's rim.start may name a mark, and custom drawing code reads them as api.marks.bloom (film time in seconds), so drawing can be keyed to a moment in the dialogue whose time is only known once the voices exist.

wait

{ "wait": 1.5 }

A pause of that many seconds.

Captions

Every spoken line gets a caption from its text, shown from the line's start until 0.25 s after it ends (at least 1 s, and never overlapping the next line). Long lines are split into chunks of at most two lines, timed by length. Narration is set in italics in a soft band. What is burned in depends on format.captions; the .srt and the MP4's soft subtitles carry every caption.

Burned-in captions are always centred; only their band changes, the bottom or the top of the frame:

Extensions at a glance

These were added in DESIGN.md §9 (round 2) and §10–11 (round 6) and are documented in place above:

where field
say tts, trimEnd; non-verbal lines [laughs]
beat { "mark": name }
hop / place, scene actors riding: "to": "on:<actor>", { "on": "<actor>" }
walk / run wading: "wade": true, "depth"
fly / enter / place, scene actors hovering: "height"; enter "fly": true
look "to": "up" / "sky" / "down", "tilt"
camera to, toOn, zoomTo, moveAt, moveDur
shot time, grade, gradeTo
scene overlays, rim
sfx names checked against the built-in list and custom.mjs sounds
music stings plunk, stab, swell; motif, motifVoice, hold, downbeat, tempo
top level score
camera focus; zoom cropping subjects with no face; points in on / toOn; z, zTo
scene actors, moves, place, set z
custom beat scope, hold
sfx hold
scene overlays shots; time names a kit declares (space)
shot lint

Checking a storyboard

node bin/cinecode.mjs check films/my-film

check loads custom.mjs, builds every set and validates the whole file, listing every problem with its path:

cinecode check: film.json has 2 problems:
  - scenes[0].shots[0].beats[1].who: "pip" is not in this scene yet (add it to the scene's actors, or bring it in with an "enter" beat first)
  - scenes[0].shots[1].camera.size: must be one of establishing, wide, full, medium, close, extreme-close (got "mid")
check failed after 0.2 s

Invalid JSON is reported with its line and column (film.json: invalid JSON at line 4, column 2: Expected ',' or '}' after property value). Some things are only warnings and don't stop a render: an unknown field on a beat, an expression a rig doesn't have on a say, a speaker not in the scene without vo, a missing custom beat, a z on an action that can't change depth, and a film's rig, kit or effect named like a built-in one (it replaces it; check also marks the file in its list: "replaces the built-in …"). Neither check nor plan prints warnings, and staging adds its own: a blocked route, a perch for a rig that can't perch, a two-shot staged too far apart, an actor in on shown far under the size it would get alone because something else in on sets the framing (under 45% of it when an anchor does, under a sixth when a much bigger actor does; not in wide shots), a custom beat without a scope drawn on into a later shot or ending partway through a dissolve, and an actor standing off the stage on a painted set. The Claude Code skill's script prints both kinds:

node .claude/skills/cinecode/scripts/warnings.mjs films/my-film
check  scenes[0].shots[2].beats[0].speeed: unknown field "speeed" for a "look" beat; ignored
check  scenes[0].shots[5].beats[2].name: custom.mjs has no "glints" (have: no custom.mjs); the beat only takes time
plan   3.3: the medium shot of pebble, frog shows frog at 11% of the size it would get alone; pebble sets the framing (give frog a shot without pebble, or leave frog out of "on" if the shot is about pebble)

"no warnings" says little about framing. Problems that only show once the film is staged (an actor standing in a tree, a speaker out of frame, a subject too small, a head cut by the frame edge, the moon behind a head, a caption over a face) are found by cinecode lint, and the rest only by looking at the contact sheets.

This page on GitHub