Validation and errors
This content is for v0.4. Switch to the latest version for up-to-date documentation.
A project is checked in layers, from its JSON shape to what a frame needs when it draws. This page explains each layer, where to run it, and what its messages look like. After reading it you can catch a bad document before it reaches a render, and read the error when one does.
Layer 1: shape
Section titled “Layer 1: shape”The JSON Schema (schema/project.v1.schema.json) describes shape only: JSON types, required fields, enumerated and tagged values, and the rejection of unknown fields. A document it rejects is never a valid project. One it accepts may still be invalid.
Use it in your editor or your own tooling. The engine does not need it: it checks shape itself when it parses a document.
Layer 2: the model’s invariants
Section titled “Layer 2: the model’s invariants”A well-shaped document must also make sense. The engine’s model checks ranges, limits, references between items, keyframe order and transition coverage, without opening any media. The same checks run when a document loads (loadProject, Project::from_json) and when you ask for them:
import { validateProject, validateProjectJson } from "alpha2/project";
const problems = validateProject(project); // [] when valid; never throwsif (problems.length > 0) console.error(problems.join("\n"));validateProject mirrors the engine’s checks in TypeScript, so an editor can reject a document before it loads one. validateProjectJson(text) also reports what only the text shows: size over the limit, repeated fields, integers spelled with a fraction or exponent (1920.0), and nesting deeper than 127 levels. ProjectBuilder.build() from alpha2/builder runs validateProject and throws on a problem.
@alpha2-sh/node runs validateProject before it starts a host. An invalid project is refused with an Alpha2ProjectValidationError whose problems list what is wrong.
import { Alpha2ProjectValidationError, exportProject } from "@alpha2-sh/node";
try { await exportProject(request);} catch (error) { if (error instanceof Alpha2ProjectValidationError) console.error(error.problems); else throw error;}import alpha2
problems = alpha2.validate_project(project) # [] when validValidation runs the engine’s own validator in-process, with the engine’s messages.
let project = alpha2::Project::from_json(&json)?; // parses and validatesproject.validate()?; // checks a project you built or changed in codeProject::validate returns the first invalid invariant it finds.
Reading the messages
Section titled “Reading the messages”Messages name the item and, for shape problems, the path to it. A few examples, from validateProject:
| Problem | Message |
|---|---|
| A field the schema doesn’t have | project.timeline.tracks[1].clips[0] has unknown field "duration" |
| A wrong type | project.settings.width must be integer |
| A range that ends before it starts | project.timeline.tracks[0].clips[0].timeline_range.end must not precede its start |
| A clip that names a missing source | clip forest-clip refers to unknown source beach |
| Clips that overlap without a transition | clips coast-clip and forest-clip overlap without a transition |
| Keyframes out of order | clip coast-clip has invalid visual properties |
The engine’s own messages, from loadProject, alpha2-host and Python, say the same things in its words, with the position in the JSON text for a shape problem:
invalid project JSON: unknown field `duration`, expected one of `id`, `source_id`, `timeline_range`, ... at line 1 column 1276invalid project: clips coast-clip and forest-clip overlap without a covering transition on track photosLayer 3: when a frame renders
Section titled “Layer 3: when a frame renders”Some things can only be checked by the renderer that draws them, so loading a project does not catch them:
- Effects. The document doesn’t know which effects exist; the renderer’s registry does. A frame that draws an unregistered effect, or an effect parameter outside its definition’s range, fails with
EFFECT_ERROR. On the CPU renderer, a runtime WGSL effect fails the same way. - Fonts. A project that shows text or captions with no fonts registered fails with
MISSING_INPUT: “the project shows text, but no fonts were registered”. - Media.
renderAtreports the sources it had no frame for (missingSources,missingFrames) instead of failing; an export asks you for them with awaitingticket. Natively,alpha2-host conformandclient.conformcheck a project’s media without rendering, and an export whose media isn’t ready fails withAlpha2ConformErrorin Node.js.
A valid project can still hold likely mistakes. The linter reports them without blocking anything:
import init, * as wasm from "alpha2/wasm";import { lintProject } from "alpha2/project";
await init();const issues = lintProject(wasm, project); // throws, as loadProject does, if the project is invalidIn Python, alpha2.lint_project(project). Each issue has a kind:
kind |
Means |
|---|---|
unreached_keyframe |
A keyframe no time its owner is shown at depends on, usually one in the wrong time base. |
hidden_clip |
A clip covered for its whole time by an opaque full-frame clip above it. |
short_transition |
A transition no frame shows: shorter than a frame, or one frame long and off the grid. |
off_frame_boundary |
An edge between frames, whose change shows only at the next frame. |
unused_source |
A source no clip or reachable media node reads. |
hdr_source |
An HDR source in a project whose color pipeline refuses HDR. |
empty_subpath, paint_without_geometry |
Shapes that draw nothing. |
For example, a shapes generator keyed past the end of the clip that shows it reports:
{ "kind": "unreached_keyframe", "owner": { "type": "source", "source": "dot" }, "property": "items[0].center", "time": 480000 }Limits
Section titled “Limits”The engine enforces fixed limits on every document: 256 tracks, 4,096 clips per track, 100,000 clips in all, a 64 MiB JSON document, a canvas of at most 16,384 pixels a side and 67,108,864 pixels in all, and more. A document over a limit is refused when it loads, with an error that names the limit. The numbers ship as TypeScript constants in alpha2/project and as Python constants, so you can check a document before you send it. Limits lists every one.
