Error codes
All fifteen typed script failures in one table: what each code means, what triggers it and how to put it right.
Every script failure is TYPED: one of the 15 codes below, carried on the script's status with a message (and, for cross-script pulls, the chain of who was evaluating whom). Errors never crash the editor or the render - the failing script's outputs fall back to their base values for that frame, the frame still draws, and nothing latches: the next frame evaluates afresh.
Language
Raised before a single frame runs - the source itself is unusable.
syntax_error
The source did not parse.
no_output
The script exports no frame function / returned undefined for its one output.
Runtime and budgets
Raised while a frame evaluates. The frame still renders with base values; nothing crashes and nothing latches - the next frame evaluates afresh.
runtime_error
The script threw at runtime.
interrupted
The script exhausted its poll budget.
Worked example: A runaway loop, caught
The scene. One orange square resting on a dark 640 by 360 stage.
- The comp Drift: 640 by 360 at 25 fps, 100 frames (4 seconds).
- Square: a solid layer, at [320, 180], 60 by 60, filled f2a833.
The script. Add a project script named Runaway and paste:
export const outputs = ["layer_1:transform.opacity"];
export function frame() {
let n = 0;
while (true) {
n += 1;
}
}
Line by line:
- Lines 5-7: The loop never ends - and nothing freezes. The engine counts interpreter polls and stops the script when the budget (200 polls per script per frame) is spent: the status becomes budget with the typed interrupted code, the frame still renders with base values, and the editor stays responsive. The IDE presents it as a fixable budget issue, pointing at the loop. Regular expressions that backtrack forever and runaway recursion are caught by the same net.
What you see. The editor keeps running: the script's status shows a budget trip with the interrupted code, the square keeps its base look, and nothing anywhere freezes.
stack_overflow
The JS call stack guard fired.
out_of_memory
The realm hit its memory limit.
Outputs and types
Raised by writes - the outputs contract and the drive set's boundary.
type_mismatch
A value could not be coerced to the property kind (or the kind is not drivable yet).
Worked example: A value the property cannot take
The scene. One orange square resting on a dark 640 by 360 stage.
- The comp Drift: 640 by 360 at 25 fps, 100 frames (4 seconds).
- Square: a solid layer, at [320, 180], 60 by 60, filled f2a833.
The script. Add a project script named Colour push and paste:
export const outputs = ["layer_1:content.fill_colour"];
export function frame() {
set("layer_1:content.fill_colour", 42);
}
Line by line:
- Line 4: Every ANIMATABLE property is writable - fills, strokes, effects, masks, text, the lot - but each takes its own wire shape: a colour is [r, g, b, a] in 0..1 or a "#rrggbb" string, a point is [x, y], a stop list is an array of stops. A bare number cannot become a colour, so the write refuses with the typed type_mismatch and the message names the shape it wanted. The same code fires for a value out of range (an opacity above 100, a trim fraction above 1), for a STATIC key (a path layer's geometry, a stroke's cap, an effect's mode - those change through commands: "the property is static"), and for a value outside a key's rails - an audio level above +12 dB refuses ("Audio level stays within -96..+12 dB.") rather than clamping.
What you see. The fill stays put: the script errors with type_mismatch and its status names the shape the colour wanted - a typed refusal, never a silent no-op.
undeclared_output
set() targeted a property outside the script's outputs.
Worked example: Writing outside the declaration
The scene. One orange square resting on a dark 640 by 360 stage.
- The comp Drift: 640 by 360 at 25 fps, 100 frames (4 seconds).
- Square: a solid layer, at [320, 180], 60 by 60, filled f2a833.
The script. Add a project script named Overreach and paste:
export const outputs = ["layer_1:transform.opacity"];
export function frame() {
set("layer_1:transform.rotation", 45);
}
Line by line:
- Line 1: The declaration says: this script may drive the Square's opacity, nothing else.
- Line 4: The set() targets rotation - a property the script never declared. The write refuses with the typed undeclared_output, the whole frame's outputs fall back to their base values, and the script's status chip carries the error. Add the id to outputs (the quick fix offers to) and the same line succeeds.
What you see. Nothing moves: the script errors with undeclared_output, its status chip explains which target was outside the declaration, and every property keeps its base value.
References and scheduling
Raised by reads - missing ids and circular pulls between scripts.
reference_missing
An id named no property, layer or control in scope.
Worked example: An id that names nothing
The scene. One orange square resting on a dark 640 by 360 stage.
- The comp Drift: 640 by 360 at 25 fps, 100 frames (4 seconds).
- Square: a solid layer, at [320, 180], 60 by 60, filled f2a833.
The script. Add a project script named Ghost read and paste:
export const outputs = ["layer_1:transform.opacity"];
export function frame() {
return layer("layer_9").opacity.value;
}
Line by line:
- Line 4: No ninth layer exists, so layer() raises the typed reference_missing - never undefined, never a silent NaN. The IDE shows the dangling id in the error tint the moment the layer disappears, and the status chip names the missing id. Deleting a referenced layer is the ONE way to break a reference; renaming never is.
What you see. The square keeps its base look while the script's status reports reference_missing, naming the id that resolves to nothing.
cycle
A script read a property another script was still computing.
Worked example: Two scripts reading each other
The scene. A teal leader square keyframed to cross the stage left to right, with an orange follower resting below its start point.
- The comp Leaders: 640 by 360 at 25 fps, 100 frames (4 seconds).
- Leader: a solid layer, at [80, 140], 40 by 40, filled 2ec4b6.
- Keyframe transform.position at frame 0: [80, 140] (linear).
- Keyframe transform.position at frame 96: [560, 140] (linear).
- Follower: a solid layer, at [80, 240], 40 by 40, filled f2a833.
A second script, A reads B, is added first:
export const outputs = ["layer_1:transform.opacity"];
export function frame() {
return prop("Follower:transform.opacity").value;
}
Then the script under test, B reads A:
export const outputs = ["layer_2:transform.opacity"];
export function frame() {
return prop("Leader:transform.opacity").value;
}
Line by line:
- Line 4: Script A drives the Leader's opacity by reading the Follower's; this script drives the Follower's by reading the Leader's. Evaluation is demand-driven - reading a property another script drives runs that script first - so this read re-enters a script already on the stack. That is the typed cycle: the read falls back to the pre-expression value, the reading script errors for the frame, and the chain in its status shows the loop. Break the loop by feeding one side from something else.
What you see. Both squares keep their base opacity while one script's status reports the typed cycle, its chain spelling out who was reading whom.
State
Raised by the state fold's rules.
state_unserialisable
A state store held a non-plain-data value (state PR).
state_too_large
A state store exceeded its size cap (state PR).
memo_time_access
A memo body read time/frame (state PR).
Scheduling and policy
The frame boundary and the capability gate.
async_unsettled
A returned promise never settled inside the frame.
disabled_by_engine
A capability was withheld.