reframe-video 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/LICENSE +24 -0
  2. package/README.md +77 -0
  3. package/assets/fonts/inter-400.woff2 +0 -0
  4. package/assets/fonts/inter-700.woff2 +0 -0
  5. package/assets/fonts/inter-800.woff2 +0 -0
  6. package/assets/sfx/LICENSE.md +12 -0
  7. package/assets/sfx/click_002.ogg +0 -0
  8. package/assets/sfx/click_003.ogg +0 -0
  9. package/assets/sfx/click_004.ogg +0 -0
  10. package/assets/sfx/confirmation_001.ogg +0 -0
  11. package/assets/sfx/keypress-001.wav +0 -0
  12. package/assets/sfx/keypress-004.wav +0 -0
  13. package/assets/sfx/keypress-007.wav +0 -0
  14. package/assets/sfx/keypress-010.wav +0 -0
  15. package/assets/sfx/keypress-014.wav +0 -0
  16. package/dist/analyze.js +344 -0
  17. package/dist/bin.js +1677 -0
  18. package/dist/browserEntry.js +532 -0
  19. package/dist/cli.js +1205 -0
  20. package/dist/index.d.ts +1 -0
  21. package/dist/index.js +889 -0
  22. package/dist/renderer-canvas.js +89 -0
  23. package/dist/types/audio.d.ts +53 -0
  24. package/dist/types/behaviors.d.ts +7 -0
  25. package/dist/types/compile.d.ts +38 -0
  26. package/dist/types/compose.d.ts +64 -0
  27. package/dist/types/dsl.d.ts +66 -0
  28. package/dist/types/evaluate.d.ts +59 -0
  29. package/dist/types/index.d.ts +9 -0
  30. package/dist/types/interpolate.d.ts +12 -0
  31. package/dist/types/ir.d.ts +213 -0
  32. package/dist/types/validate.d.ts +12 -0
  33. package/guides/edsl-guide.md +202 -0
  34. package/guides/regen-contract.md +18 -0
  35. package/package.json +55 -0
  36. package/preview/index.html +60 -0
  37. package/preview/src/main.ts +162 -0
  38. package/preview/src/panel.ts +347 -0
  39. package/preview/src/store.ts +220 -0
  40. package/preview/src/virtual.d.ts +4 -0
  41. package/preview/vite.config.ts +52 -0
@@ -0,0 +1,89 @@
1
+ // ../renderer-canvas/src/index.ts
2
+ import { evaluate } from "@reframe/core";
3
+ function renderFrame(ctx, compiled, t) {
4
+ const { size, background } = compiled.ir;
5
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
6
+ ctx.clearRect(0, 0, size.width, size.height);
7
+ if (background) {
8
+ ctx.fillStyle = background;
9
+ ctx.fillRect(0, 0, size.width, size.height);
10
+ }
11
+ drawDisplayList(ctx, evaluate(compiled, t));
12
+ }
13
+ function drawDisplayList(ctx, ops) {
14
+ for (const op of ops) {
15
+ ctx.save();
16
+ ctx.setTransform(...op.transform);
17
+ ctx.globalAlpha = Math.max(0, Math.min(1, op.opacity));
18
+ switch (op.type) {
19
+ case "rect": {
20
+ ctx.beginPath();
21
+ if (op.radius && op.radius > 0) {
22
+ ctx.roundRect(op.offsetX, op.offsetY, op.width, op.height, op.radius);
23
+ } else {
24
+ ctx.rect(op.offsetX, op.offsetY, op.width, op.height);
25
+ }
26
+ if (op.fill) {
27
+ ctx.fillStyle = op.fill;
28
+ ctx.fill();
29
+ }
30
+ if (op.stroke) {
31
+ ctx.strokeStyle = op.stroke;
32
+ ctx.lineWidth = op.strokeWidth ?? 1;
33
+ ctx.stroke();
34
+ }
35
+ break;
36
+ }
37
+ case "ellipse": {
38
+ ctx.beginPath();
39
+ ctx.ellipse(
40
+ op.offsetX + op.width / 2,
41
+ op.offsetY + op.height / 2,
42
+ Math.abs(op.width / 2),
43
+ Math.abs(op.height / 2),
44
+ 0,
45
+ 0,
46
+ Math.PI * 2
47
+ );
48
+ if (op.fill) {
49
+ ctx.fillStyle = op.fill;
50
+ ctx.fill();
51
+ }
52
+ if (op.stroke) {
53
+ ctx.strokeStyle = op.stroke;
54
+ ctx.lineWidth = op.strokeWidth ?? 1;
55
+ ctx.stroke();
56
+ }
57
+ break;
58
+ }
59
+ case "line": {
60
+ ctx.beginPath();
61
+ ctx.moveTo(op.x1, op.y1);
62
+ ctx.lineTo(op.x2, op.y2);
63
+ ctx.strokeStyle = op.stroke;
64
+ ctx.lineWidth = op.strokeWidth;
65
+ ctx.lineCap = "round";
66
+ ctx.stroke();
67
+ break;
68
+ }
69
+ case "text": {
70
+ ctx.font = `${op.fontWeight} ${op.fontSize}px ${quoteFamily(op.fontFamily)}`;
71
+ if (op.letterSpacing) ctx.letterSpacing = `${op.letterSpacing}px`;
72
+ ctx.textAlign = op.align;
73
+ ctx.textBaseline = op.baseline;
74
+ ctx.fillStyle = op.fill;
75
+ ctx.fillText(op.content, 0, 0);
76
+ if (op.letterSpacing) ctx.letterSpacing = "0px";
77
+ break;
78
+ }
79
+ }
80
+ ctx.restore();
81
+ }
82
+ }
83
+ function quoteFamily(family) {
84
+ return family.includes(" ") && !family.includes('"') ? `"${family}"` : family;
85
+ }
86
+ export {
87
+ drawDisplayList,
88
+ renderFrame
89
+ };
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Audio cue resolution: turn label-anchored cues into an absolute-time plan.
3
+ * Pure data in, pure data out — the renderer only executes the plan, so the
4
+ * plan itself is golden-testable and reusable (e.g. preview cue markers).
5
+ *
6
+ * Anchoring to labels is the point: retime a step via an overlay, or let an
7
+ * AI regenerate the scene, and the sound design follows.
8
+ */
9
+ import type { CompiledScene } from "./compile.js";
10
+ import type { SfxName } from "./ir.js";
11
+ /** Nominal cue lengths (s) for duck-window math; file cues use a default. */
12
+ export declare const SFX_DURATION: Record<SfxName, number>;
13
+ export interface ResolvedCue {
14
+ t: number;
15
+ gain: number;
16
+ duration: number;
17
+ source: {
18
+ kind: "sfx";
19
+ name: SfxName;
20
+ params: Record<string, number>;
21
+ } | {
22
+ kind: "file";
23
+ path: string;
24
+ };
25
+ }
26
+ export interface AudioPlan {
27
+ duration: number;
28
+ bgm: {
29
+ source: {
30
+ kind: "file";
31
+ path: string;
32
+ } | {
33
+ kind: "synth";
34
+ name: "ambient-pad";
35
+ };
36
+ gain: number;
37
+ fadeIn: number;
38
+ fadeOut: number;
39
+ duck: {
40
+ depth: number;
41
+ attack: number;
42
+ release: number;
43
+ } | null;
44
+ } | null;
45
+ cues: ResolvedCue[];
46
+ /** Merged [t0, t1] cue windows the bed should duck under. */
47
+ duckWindows: {
48
+ t0: number;
49
+ t1: number;
50
+ }[];
51
+ warnings: string[];
52
+ }
53
+ export declare function resolveAudioPlan(compiled: CompiledScene): AudioPlan | null;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Named parametric behaviors (Fran-style continuous-time functions).
3
+ * Composed additively on top of the timeline value. All deterministic:
4
+ * randomness comes from an explicit seed, never Math.random.
5
+ */
6
+ import type { BehaviorIR } from "./ir.js";
7
+ export declare function sampleBehavior(b: BehaviorIR["behavior"], t: number): number;
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Compile the authored structure (states + timeline tree) into a flat list of
3
+ * property segments with absolute times and baked `from` values.
4
+ *
5
+ * The tree is the canonical IR; segments are a derived runtime artifact.
6
+ * Baking `from` at compile time is what makes `to("state")` synthesize
7
+ * "current value -> target" transitions deterministically (Gemini-style
8
+ * start/end synthesis): everything is data, so "current value" is computable
9
+ * without running anything.
10
+ */
11
+ import type { NodeIR, PropValue, SceneIR, Ease } from "./ir.js";
12
+ export interface PropertySegment {
13
+ target: string;
14
+ prop: string;
15
+ t0: number;
16
+ t1: number;
17
+ from: PropValue;
18
+ to: PropValue;
19
+ ease?: Ease;
20
+ }
21
+ export interface LabelSpan {
22
+ t0: number;
23
+ t1: number;
24
+ }
25
+ export interface CompiledScene {
26
+ ir: SceneIR;
27
+ duration: number;
28
+ /** Keyed by `${target}.${prop}`, sorted by t0. */
29
+ segments: Map<string, PropertySegment[]>;
30
+ /** Base props merged with the initial state, keyed by `${target}.${prop}`. */
31
+ initialValues: Map<string, PropValue>;
32
+ nodeById: Map<string, NodeIR>;
33
+ /** Declaration order — defines stagger order. */
34
+ nodeOrder: string[];
35
+ /** Absolute [start, end] of every labeled timeline step. */
36
+ labelTimes: Map<string, LabelSpan>;
37
+ }
38
+ export declare function compileScene(ir: SceneIR): CompiledScene;
@@ -0,0 +1,64 @@
1
+ /**
2
+ * USD-style non-destructive layer composition: human edits live in overlay
3
+ * documents that re-apply on top of a regenerated base scene.
4
+ *
5
+ * Contract: composeScene never throws because the base drifted (a renamed or
6
+ * retyped node) — those edits are skipped and reported as orphans, loudly.
7
+ * Defects in the overlay itself (duplicate ids, invalid values) surface as
8
+ * validation errors on the composed result. Silent failure is the one
9
+ * behavior this module must never have.
10
+ */
11
+ import type { BehaviorIR, Ease, NodeIR, PropValue, SceneIR } from "./ir.js";
12
+ export interface OverlayDoc {
13
+ reframeOverlay: 1;
14
+ /** Shown in reports; falls back to "overlay-<index>". */
15
+ name?: string;
16
+ /** Scene id this overlay was authored against — mismatch is a warning. */
17
+ target?: string;
18
+ scene?: {
19
+ background?: string;
20
+ duration?: number;
21
+ fps?: number;
22
+ };
23
+ /** nodeId -> prop -> value; null deletes the prop key (USD "block"). */
24
+ nodes?: Record<string, Record<string, PropValue | null>>;
25
+ /** stateName -> nodeId -> prop -> value/null. */
26
+ states?: Record<string, Record<string, Record<string, PropValue | null>>>;
27
+ behaviors?: {
28
+ /** Upsert keyed by (target, prop): replaces a matching behavior or appends. */
29
+ set?: BehaviorIR[];
30
+ remove?: {
31
+ target: string;
32
+ prop: string;
33
+ }[];
34
+ };
35
+ /** Complete nodes appended at the scene root, owned by this overlay. */
36
+ addNodes?: NodeIR[];
37
+ /**
38
+ * Parameter patches on labeled timeline steps. Patchable per kind:
39
+ * to -> duration/ease/stagger, tween -> duration/ease, wait -> duration.
40
+ */
41
+ timeline?: Record<string, {
42
+ duration?: number;
43
+ ease?: Ease;
44
+ stagger?: number;
45
+ }>;
46
+ }
47
+ export interface ComposeReport {
48
+ applied: {
49
+ layer: string;
50
+ address: string;
51
+ action: "set" | "unset" | "add-node" | "behavior-set" | "behavior-remove";
52
+ }[];
53
+ orphans: {
54
+ layer: string;
55
+ address: string;
56
+ reason: string;
57
+ }[];
58
+ warnings: string[];
59
+ }
60
+ export declare function composeScene(base: SceneIR, ...overlays: OverlayDoc[]): {
61
+ ir: SceneIR;
62
+ report: ComposeReport;
63
+ };
64
+ export declare function formatComposeReport(report: ComposeReport): string;
@@ -0,0 +1,66 @@
1
+ /**
2
+ * The eDSL surface: thin factories that build plain IR objects.
3
+ * `scene()` validates and returns the IR — the return value is the document.
4
+ */
5
+ import type { AudioIR, BehaviorIR, Ease, EllipseProps, GroupProps, LineProps, NodeIR, PropValue, RectProps, SceneIR, Size, StateOverride, TextProps, TimelineIR } from "./ir.js";
6
+ export interface SceneInput {
7
+ id: string;
8
+ size: Size;
9
+ fps?: number;
10
+ duration?: number;
11
+ background?: string;
12
+ nodes: NodeIR[];
13
+ states?: Record<string, StateOverride>;
14
+ initial?: string;
15
+ timeline?: TimelineIR;
16
+ behaviors?: BehaviorIR[];
17
+ audio?: AudioIR;
18
+ meta?: Record<string, unknown>;
19
+ }
20
+ export declare function scene(input: SceneInput): SceneIR;
21
+ export declare function rect(props: {
22
+ id: string;
23
+ } & RectProps): NodeIR;
24
+ export declare function ellipse(props: {
25
+ id: string;
26
+ } & EllipseProps): NodeIR;
27
+ export declare function line(props: {
28
+ id: string;
29
+ } & LineProps): NodeIR;
30
+ export declare function text(props: {
31
+ id: string;
32
+ } & TextProps): NodeIR;
33
+ export declare function group(props: {
34
+ id: string;
35
+ } & GroupProps, children: NodeIR[]): NodeIR;
36
+ export declare function seq(...children: TimelineIR[]): TimelineIR;
37
+ export declare function par(...children: TimelineIR[]): TimelineIR;
38
+ export declare function stagger(interval: number, ...children: TimelineIR[]): TimelineIR;
39
+ export declare function to(state: string, opts?: {
40
+ duration?: number;
41
+ ease?: Ease;
42
+ stagger?: number;
43
+ filter?: string[];
44
+ label?: string;
45
+ }): TimelineIR;
46
+ export declare function tween(target: string, props: Record<string, PropValue>, opts?: {
47
+ duration?: number;
48
+ ease?: Ease;
49
+ label?: string;
50
+ }): TimelineIR;
51
+ export declare function wait(duration: number, label?: string): TimelineIR;
52
+ export interface BehaviorWindow {
53
+ from?: number;
54
+ until?: number;
55
+ ramp?: number;
56
+ }
57
+ export declare function oscillate(target: string, prop: string, params: {
58
+ amplitude: number;
59
+ frequency: number;
60
+ phase?: number;
61
+ }, window?: BehaviorWindow): BehaviorIR;
62
+ export declare function wiggle(target: string, prop: string, params: {
63
+ amplitude: number;
64
+ frequency: number;
65
+ seed: number;
66
+ }, window?: BehaviorWindow): BehaviorIR;
@@ -0,0 +1,59 @@
1
+ /**
2
+ * evaluate(compiled, t) -> DisplayList. The pure function at the heart of the
3
+ * determinism contract: same compiled scene + same t = same display list,
4
+ * always. Renderers only draw; they never compute animation.
5
+ */
6
+ import type { CompiledScene } from "./compile.js";
7
+ /** Canvas-style 2D affine matrix [a, b, c, d, e, f]. */
8
+ export type Mat2D = [number, number, number, number, number, number];
9
+ export type TextAlign = "left" | "center" | "right";
10
+ export type TextBaseline = "top" | "middle" | "bottom";
11
+ interface OpBase {
12
+ /** Source node id — lets editors map ops back to the scene graph. */
13
+ id: string;
14
+ /** Maps local coords (origin = anchor point) to scene coords. */
15
+ transform: Mat2D;
16
+ /** Cumulative opacity, parent-multiplied. */
17
+ opacity: number;
18
+ }
19
+ export type DisplayOp = (OpBase & {
20
+ type: "rect";
21
+ width: number;
22
+ height: number;
23
+ offsetX: number;
24
+ offsetY: number;
25
+ fill?: string;
26
+ stroke?: string;
27
+ strokeWidth?: number;
28
+ radius?: number;
29
+ }) | (OpBase & {
30
+ type: "ellipse";
31
+ width: number;
32
+ height: number;
33
+ offsetX: number;
34
+ offsetY: number;
35
+ fill?: string;
36
+ stroke?: string;
37
+ strokeWidth?: number;
38
+ }) | (OpBase & {
39
+ type: "line";
40
+ x1: number;
41
+ y1: number;
42
+ x2: number;
43
+ y2: number;
44
+ stroke: string;
45
+ strokeWidth: number;
46
+ }) | (OpBase & {
47
+ type: "text";
48
+ content: string;
49
+ fontFamily: string;
50
+ fontSize: number;
51
+ fontWeight: number;
52
+ fill: string;
53
+ letterSpacing: number;
54
+ align: TextAlign;
55
+ baseline: TextBaseline;
56
+ });
57
+ export type DisplayList = DisplayOp[];
58
+ export declare function evaluate(compiled: CompiledScene, t: number): DisplayList;
59
+ export {};
@@ -0,0 +1,9 @@
1
+ export * from "./ir.js";
2
+ export * from "./dsl.js";
3
+ export { validateScene, SceneValidationError, PROPS_BY_TYPE } from "./validate.js";
4
+ export { composeScene, formatComposeReport, type OverlayDoc, type ComposeReport, } from "./compose.js";
5
+ export { compileScene, type CompiledScene, type PropertySegment, type LabelSpan } from "./compile.js";
6
+ export { resolveAudioPlan, SFX_DURATION, type AudioPlan, type ResolvedCue, } from "./audio.js";
7
+ export { evaluate, type DisplayList, type DisplayOp, type Mat2D, type TextAlign, type TextBaseline, } from "./evaluate.js";
8
+ export { resolveEase, lerpValue, isColor, EASE_NAMES } from "./interpolate.js";
9
+ export { sampleBehavior } from "./behaviors.js";
@@ -0,0 +1,12 @@
1
+ import type { Ease, PropValue } from "./ir.js";
2
+ type EaseFn = (u: number) => number;
3
+ export declare const EASE_NAMES: import("./ir.js").EaseName[];
4
+ export declare function resolveEase(ease: Ease | undefined): EaseFn;
5
+ export declare function isColor(v: PropValue): v is string;
6
+ /**
7
+ * Interpolate two prop values at progress u (already eased).
8
+ * number↔number lerps, color↔color lerps in RGB, anything else switches
9
+ * discretely at the start of the segment.
10
+ */
11
+ export declare function lerpValue(from: PropValue, to: PropValue, u: number): PropValue;
12
+ export {};
@@ -0,0 +1,213 @@
1
+ /**
2
+ * reframe IR — the serialized scene graph.
3
+ *
4
+ * Invariant: every value in the IR is plain JSON data. No functions, ever.
5
+ * Easing is a name or bezier params, dynamic motion is a named behavior with
6
+ * params. `JSON.stringify(scene)` IS the serialization format.
7
+ *
8
+ * Semantics: a scene is evaluated as a pure function of continuous time
9
+ * `evaluate(scene, tSeconds) -> DisplayList`. `fps` is a render hint only.
10
+ */
11
+ export type EaseName = "linear" | "easeInQuad" | "easeOutQuad" | "easeInOutQuad" | "easeInCubic" | "easeOutCubic" | "easeInOutCubic" | "easeInQuart" | "easeOutQuart" | "easeInOutQuart" | "easeInExpo" | "easeOutExpo" | "easeInOutExpo";
12
+ export type Ease = EaseName | {
13
+ cubicBezier: [number, number, number, number];
14
+ };
15
+ export type Anchor = "top-left" | "top-center" | "top-right" | "center-left" | "center" | "center-right" | "bottom-left" | "bottom-center" | "bottom-right";
16
+ export interface Size {
17
+ width: number;
18
+ height: number;
19
+ }
20
+ /** Props shared by every node. All numeric props are animatable. */
21
+ export interface BaseProps {
22
+ x: number;
23
+ y: number;
24
+ opacity?: number;
25
+ rotation?: number;
26
+ scale?: number;
27
+ anchor?: Anchor;
28
+ }
29
+ export interface RectProps extends BaseProps {
30
+ width: number;
31
+ height: number;
32
+ fill?: string;
33
+ stroke?: string;
34
+ strokeWidth?: number;
35
+ radius?: number;
36
+ }
37
+ export interface EllipseProps extends BaseProps {
38
+ width: number;
39
+ height: number;
40
+ fill?: string;
41
+ stroke?: string;
42
+ strokeWidth?: number;
43
+ }
44
+ export interface LineProps {
45
+ x1: number;
46
+ y1: number;
47
+ x2: number;
48
+ y2: number;
49
+ stroke: string;
50
+ strokeWidth?: number;
51
+ opacity?: number;
52
+ /** 0..1 — how much of the line is drawn (for draw-on effects). */
53
+ progress?: number;
54
+ }
55
+ export interface TextProps extends BaseProps {
56
+ /** Numbers interpolate (count-up) and render via toFixed(contentDecimals). */
57
+ content: string | number;
58
+ /** Decimal places when content is numeric (default 0). */
59
+ contentDecimals?: number;
60
+ fontFamily: string;
61
+ fontSize: number;
62
+ fontWeight?: number;
63
+ fill?: string;
64
+ letterSpacing?: number;
65
+ }
66
+ export interface GroupProps extends BaseProps {
67
+ }
68
+ export type NodeIR = {
69
+ type: "rect";
70
+ id: string;
71
+ props: RectProps;
72
+ } | {
73
+ type: "ellipse";
74
+ id: string;
75
+ props: EllipseProps;
76
+ } | {
77
+ type: "line";
78
+ id: string;
79
+ props: LineProps;
80
+ } | {
81
+ type: "text";
82
+ id: string;
83
+ props: TextProps;
84
+ } | {
85
+ type: "group";
86
+ id: string;
87
+ props: GroupProps;
88
+ children: NodeIR[];
89
+ };
90
+ export type PropValue = number | string;
91
+ /**
92
+ * A state is a sparse override: only the props it mentions differ from base.
93
+ * This shape is isomorphic to a USD-style override layer — future
94
+ * human-edit layers compose with the same merge.
95
+ */
96
+ export type StateOverride = Record<string, Record<string, PropValue>>;
97
+ export type TimelineIR = {
98
+ kind: "seq";
99
+ children: TimelineIR[];
100
+ } | {
101
+ kind: "par";
102
+ children: TimelineIR[];
103
+ } | {
104
+ kind: "stagger";
105
+ interval: number;
106
+ children: TimelineIR[];
107
+ } | {
108
+ kind: "to";
109
+ state: string;
110
+ duration?: number;
111
+ ease?: Ease;
112
+ /** Per-node offset (seconds), in node declaration order. */
113
+ stagger?: number;
114
+ /** Restrict the transition to these node ids. */
115
+ filter?: string[];
116
+ /** Stable address for overlay timeline patches; must be unique. */
117
+ label?: string;
118
+ } | {
119
+ kind: "tween";
120
+ target: string;
121
+ props: Record<string, PropValue>;
122
+ duration?: number;
123
+ ease?: Ease;
124
+ label?: string;
125
+ } | {
126
+ kind: "wait";
127
+ duration: number;
128
+ label?: string;
129
+ };
130
+ export interface BehaviorIR {
131
+ target: string;
132
+ prop: string;
133
+ /** Active window in seconds; omit for the whole scene. */
134
+ from?: number;
135
+ until?: number;
136
+ /** Linear fade length (s) at each window boundary, avoiding pops. Default 0.2. */
137
+ ramp?: number;
138
+ /** Composed additively on top of the timeline value. */
139
+ behavior: {
140
+ kind: "named";
141
+ name: "oscillate";
142
+ params: {
143
+ amplitude: number;
144
+ frequency: number;
145
+ phase?: number;
146
+ };
147
+ } | {
148
+ kind: "named";
149
+ name: "wiggle";
150
+ params: {
151
+ amplitude: number;
152
+ frequency: number;
153
+ seed: number;
154
+ };
155
+ };
156
+ }
157
+ export type SfxName = "whoosh" | "pop" | "tick" | "rise" | "shimmer" | "thud";
158
+ export interface AudioCueIR {
159
+ /** Anchor: a timeline label (the step's start) or absolute seconds. */
160
+ at: string | number;
161
+ /** Seconds relative to the anchor (negative allowed; result clamps to 0). */
162
+ offset?: number;
163
+ /** Procedural SFX name — exactly one of sfx | file. */
164
+ sfx?: SfxName;
165
+ /** Audio file path (absolute, scene-relative, or assets/sfx-relative). */
166
+ file?: string;
167
+ /** Linear gain, default 1. */
168
+ gain?: number;
169
+ /** Synth parameter overrides (seed, duration, …) — numbers only. */
170
+ params?: Record<string, number>;
171
+ }
172
+ export interface AudioIR {
173
+ bgm?: {
174
+ file?: string;
175
+ /** License-free synthesized bed. */
176
+ synth?: "ambient-pad";
177
+ gain?: number;
178
+ fadeIn?: number;
179
+ fadeOut?: number;
180
+ /** Dip the bed under cues. false disables. */
181
+ duck?: {
182
+ depth?: number;
183
+ attack?: number;
184
+ release?: number;
185
+ } | false;
186
+ };
187
+ cues?: AudioCueIR[];
188
+ }
189
+ export interface SceneIR {
190
+ version: 1;
191
+ id: string;
192
+ size: Size;
193
+ /** Render hint only — semantics are continuous-time. */
194
+ fps?: number;
195
+ /** Inferred from the timeline when omitted. */
196
+ duration?: number;
197
+ background?: string;
198
+ nodes: NodeIR[];
199
+ states?: Record<string, StateOverride>;
200
+ /** State applied at t=0. */
201
+ initial?: string;
202
+ timeline?: TimelineIR;
203
+ behaviors?: BehaviorIR[];
204
+ /** Label-anchored sound design — cues survive retiming and regeneration. */
205
+ audio?: AudioIR;
206
+ /** Reserved for v2 (Madeus-style temporal constraints). */
207
+ constraints?: unknown[];
208
+ /** Editor-only data (Theatre.js state.json pattern). */
209
+ meta?: Record<string, unknown>;
210
+ }
211
+ export declare const DEFAULT_TO_DURATION = 0.5;
212
+ export declare const DEFAULT_TWEEN_DURATION = 0.5;
213
+ export declare const DEFAULT_FPS = 30;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Scene validation with actionable error messages — these errors are the
3
+ * feedback loop for LLM-generated scenes, so they name the exact location
4
+ * and suggest what valid input looks like.
5
+ */
6
+ import type { NodeIR, SceneIR } from "./ir.js";
7
+ export declare const PROPS_BY_TYPE: Record<NodeIR["type"], string[]>;
8
+ export declare class SceneValidationError extends Error {
9
+ problems: string[];
10
+ constructor(problems: string[]);
11
+ }
12
+ export declare function validateScene(ir: SceneIR): void;