@osmo.inc/cli-linux-arm64 0.1.23 → 0.1.25

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 (72) hide show
  1. package/dist/studio.js +5058 -930
  2. package/package.json +1 -1
  3. package/release.json +32 -32
  4. package/runtime/apps/native/tools/compile.mjs +22 -4
  5. package/runtime/packages/render-protocol/src/film-catalog.json +2543 -0
  6. package/runtime/packages/render-protocol/src/film-resources.mjs +91 -0
  7. package/runtime/packages/render-protocol/src/film-store.node.mjs +220 -0
  8. package/runtime/packages/scene/scripts/sync-film.mjs +8 -0
  9. package/runtime/packages/scene/scripts/validate-strict.mjs +14 -4
  10. package/runtime/packages/scene/scripts/verify-export-resolution.mjs +67 -0
  11. package/runtime/packages/scene/scripts/verify-film.mjs +66 -0
  12. package/runtime/packages/scene/src/effect-schemas.d.mts +246 -0
  13. package/runtime/packages/scene/src/effect-schemas.mjs +136 -0
  14. package/runtime/packages/scene/src/effects.ts +23 -6
  15. package/runtime/packages/scene/src/elements.ts +15 -3
  16. package/runtime/packages/scene/src/evaluate.ts +88 -12
  17. package/runtime/packages/scene/src/film-catalog.generated.ts +3 -0
  18. package/runtime/packages/scene/src/film.ts +39 -0
  19. package/runtime/packages/scene/src/index.ts +6 -2
  20. package/runtime/packages/scene/src/knob-schema.ts +5 -2
  21. package/runtime/packages/scene/src/metadata.d.mts +2 -1
  22. package/runtime/packages/scene/src/metadata.mjs +4 -4
  23. package/runtime/packages/scene/src/motion-blur.ts +30 -0
  24. package/src/starter/scene.tsx +1 -1
  25. package/studio/src/app.css +1 -1
  26. package/studio/src/app.js +197 -179
  27. package/studio/src/engine_bg.wasm +0 -0
  28. package/studio/src/preview-worker.js +33 -0
  29. package/studio/src/stretch-worklet-Z4ADHTIR.mjs +1 -0
  30. package/third-party/braw/BlackmagicRawAPI.h +1137 -0
  31. package/third-party/braw/README.md +7 -0
  32. package/third-party/cargo/block-buffer@0.10.4/Cargo.toml +32 -0
  33. package/third-party/cargo/block-buffer@0.10.4/LICENSE-APACHE +201 -0
  34. package/third-party/cargo/block-buffer@0.10.4/LICENSE-MIT +25 -0
  35. package/third-party/cargo/cc@1.4.5/Cargo.toml +89 -0
  36. package/third-party/cargo/cc@1.4.5/LICENSE-APACHE +201 -0
  37. package/third-party/cargo/cc@1.4.5/LICENSE-MIT +25 -0
  38. package/third-party/cargo/cpufeatures@0.2.17/Cargo.toml +71 -0
  39. package/third-party/cargo/cpufeatures@0.2.17/LICENSE-APACHE +201 -0
  40. package/third-party/cargo/cpufeatures@0.2.17/LICENSE-MIT +25 -0
  41. package/third-party/cargo/crypto-common@0.1.7/Cargo.toml +62 -0
  42. package/third-party/cargo/crypto-common@0.1.7/LICENSE-APACHE +201 -0
  43. package/third-party/cargo/crypto-common@0.1.7/LICENSE-MIT +25 -0
  44. package/third-party/cargo/digest@0.10.7/Cargo.toml +70 -0
  45. package/third-party/cargo/digest@0.10.7/LICENSE-APACHE +201 -0
  46. package/third-party/cargo/digest@0.10.7/LICENSE-MIT +25 -0
  47. package/third-party/cargo/find-msvc-tools@0.1.12/Cargo.toml +49 -0
  48. package/third-party/cargo/find-msvc-tools@0.1.12/LICENSE-APACHE +201 -0
  49. package/third-party/cargo/find-msvc-tools@0.1.12/LICENSE-MIT +25 -0
  50. package/third-party/cargo/generic-array@0.14.7/Cargo.toml +75 -0
  51. package/third-party/cargo/generic-array@0.14.7/LICENSE +21 -0
  52. package/third-party/cargo/sha2@0.10.9/Cargo.toml +90 -0
  53. package/third-party/cargo/sha2@0.10.9/LICENSE-APACHE +201 -0
  54. package/third-party/cargo/sha2@0.10.9/LICENSE-MIT +27 -0
  55. package/third-party/cargo/shlex@2.0.1/Cargo.toml +51 -0
  56. package/third-party/cargo/shlex@2.0.1/LICENSE-APACHE +13 -0
  57. package/third-party/cargo/shlex@2.0.1/LICENSE-MIT +21 -0
  58. package/third-party/cargo/typenum@1.20.1/Cargo.toml +75 -0
  59. package/third-party/cargo/typenum@1.20.1/LICENSE +1 -0
  60. package/third-party/cargo/typenum@1.20.1/LICENSE-APACHE +201 -0
  61. package/third-party/cargo/typenum@1.20.1/LICENSE-MIT +21 -0
  62. package/third-party/film/camera-COLOUR-LICENSE +11 -0
  63. package/third-party/film/camera-CONFIG-LICENSE +26 -0
  64. package/third-party/film/camera-LICENSE +26 -0
  65. package/third-party/film/camera-metadata.json +344 -0
  66. package/third-party/film/provenance.json +7 -0
  67. package/third-party/inventory.json +162 -0
  68. package/third-party/npm/signalsmith-stretch@1.3.2/package.json +26 -0
  69. package/third-party/npm/signalsmith-stretch@1.3.2/signalsmith-stretch-1.3.2-LICENSE.txt +21 -0
  70. package/vendor/osmo-lite +0 -0
  71. package/vendor/osmo-scene-0.1.0.tgz +0 -0
  72. package/vendor/platform.json +1 -1
@@ -0,0 +1,246 @@
1
+ /** @param {{id:string,collection:string,label:string}[]} FILM_STOCKS @param {readonly string[]} FILM_CAMERAS @param {boolean} [grouped] */
2
+ export function filmKnobs(FILM_STOCKS: {
3
+ id: string;
4
+ collection: string;
5
+ label: string;
6
+ }[], FILM_CAMERAS: readonly string[], grouped?: boolean): {
7
+ stock: {
8
+ type: "select";
9
+ label: string;
10
+ default: string;
11
+ options: {
12
+ [k: string]: string;
13
+ };
14
+ };
15
+ cameraProfile: {
16
+ type: "select";
17
+ label: string;
18
+ default: string;
19
+ options: {
20
+ [k: string]: string;
21
+ };
22
+ };
23
+ strength: {
24
+ type: "number";
25
+ label: string;
26
+ default: number;
27
+ min: number;
28
+ max: number;
29
+ step: number;
30
+ };
31
+ exposure: {
32
+ group?: string | undefined;
33
+ type: "number";
34
+ label: string;
35
+ default: number;
36
+ min: number;
37
+ max: number;
38
+ step: number;
39
+ };
40
+ warmth: {
41
+ group?: string | undefined;
42
+ type: "number";
43
+ label: string;
44
+ default: number;
45
+ min: number;
46
+ max: number;
47
+ step: number;
48
+ };
49
+ tint: {
50
+ group?: string | undefined;
51
+ type: "number";
52
+ label: string;
53
+ default: number;
54
+ min: number;
55
+ max: number;
56
+ step: number;
57
+ };
58
+ contrast: {
59
+ group?: string | undefined;
60
+ type: "number";
61
+ label: string;
62
+ default: number;
63
+ min: number;
64
+ max: number;
65
+ step: number;
66
+ };
67
+ saturation: {
68
+ group?: string | undefined;
69
+ type: "number";
70
+ label: string;
71
+ default: number;
72
+ min: number;
73
+ max: number;
74
+ step: number;
75
+ };
76
+ lift: {
77
+ group?: string | undefined;
78
+ type: "number";
79
+ label: string;
80
+ default: number;
81
+ min: number;
82
+ max: number;
83
+ step: number;
84
+ };
85
+ gamma: {
86
+ group?: string | undefined;
87
+ type: "number";
88
+ label: string;
89
+ default: number;
90
+ min: number;
91
+ max: number;
92
+ step: number;
93
+ };
94
+ print: {
95
+ group?: string | undefined;
96
+ type: "number";
97
+ label: string;
98
+ default: number;
99
+ min: number;
100
+ max: number;
101
+ step: number;
102
+ };
103
+ grain: {
104
+ group?: string | undefined;
105
+ type: "number";
106
+ label: string;
107
+ default: number;
108
+ min: number;
109
+ max: number;
110
+ step: number;
111
+ };
112
+ grainSaturation: {
113
+ group?: string | undefined;
114
+ type: "number";
115
+ label: string;
116
+ default: number;
117
+ min: number;
118
+ max: number;
119
+ step: number;
120
+ };
121
+ grainSoftness: {
122
+ group?: string | undefined;
123
+ type: "number";
124
+ label: string;
125
+ default: number;
126
+ min: number;
127
+ max: number;
128
+ step: number;
129
+ };
130
+ grainRoughness: {
131
+ group?: string | undefined;
132
+ type: "number";
133
+ label: string;
134
+ default: number;
135
+ min: number;
136
+ max: number;
137
+ step: number;
138
+ };
139
+ halation: {
140
+ group?: string | undefined;
141
+ type: "number";
142
+ label: string;
143
+ default: number;
144
+ min: number;
145
+ max: number;
146
+ step: number;
147
+ };
148
+ halationRadius: {
149
+ group?: string | undefined;
150
+ type: "number";
151
+ label: string;
152
+ default: number;
153
+ min: number;
154
+ max: number;
155
+ step: number;
156
+ };
157
+ looksGrain: {
158
+ group?: string | undefined;
159
+ type: "number";
160
+ label: string;
161
+ default: number;
162
+ min: number;
163
+ max: number;
164
+ step: number;
165
+ };
166
+ looksHalation: {
167
+ group?: string | undefined;
168
+ type: "number";
169
+ label: string;
170
+ default: number;
171
+ min: number;
172
+ max: number;
173
+ step: number;
174
+ };
175
+ gauge: {
176
+ label: string;
177
+ type: "select";
178
+ default: string;
179
+ options: {
180
+ "35mm": string;
181
+ "16mm": string;
182
+ "8mm": string;
183
+ };
184
+ group?: string | undefined;
185
+ };
186
+ seed: {
187
+ group?: string | undefined;
188
+ integer: boolean;
189
+ type: "number";
190
+ label: string;
191
+ default: number;
192
+ min: number;
193
+ max: number;
194
+ step: number;
195
+ };
196
+ textureFps: {
197
+ group?: string | undefined;
198
+ type: "number";
199
+ label: string;
200
+ default: number;
201
+ min: number;
202
+ max: number;
203
+ step: number;
204
+ };
205
+ };
206
+ export namespace BLUR_KNOBS {
207
+ namespace radius {
208
+ export let type: "number";
209
+ export let label: string;
210
+ let _default: number;
211
+ export { _default as default };
212
+ export let min: number;
213
+ export let max: number;
214
+ export let step: number;
215
+ }
216
+ }
217
+ export namespace GRAIN_KNOBS {
218
+ namespace amount {
219
+ let type_1: "number";
220
+ export { type_1 as type };
221
+ let label_1: string;
222
+ export { label_1 as label };
223
+ let _default_1: number;
224
+ export { _default_1 as default };
225
+ let min_1: number;
226
+ export { min_1 as min };
227
+ let max_1: number;
228
+ export { max_1 as max };
229
+ let step_1: number;
230
+ export { step_1 as step };
231
+ }
232
+ }
233
+ export namespace FILM_GROUPS {
234
+ namespace color {
235
+ let label_2: string;
236
+ export { label_2 as label };
237
+ }
238
+ namespace grain {
239
+ let label_3: string;
240
+ export { label_3 as label };
241
+ }
242
+ namespace halation {
243
+ let label_4: string;
244
+ export { label_4 as label };
245
+ }
246
+ }
@@ -0,0 +1,136 @@
1
+ // Pure schema data shared by the authoring runtime and the source editor.
2
+ export const BLUR_KNOBS = {
3
+ radius: {
4
+ type: /** @type {const} */ ("number"),
5
+ label: "Radius",
6
+ default: 8,
7
+ min: 0,
8
+ max: 64,
9
+ step: 0.5,
10
+ },
11
+ };
12
+ export const GRAIN_KNOBS = {
13
+ amount: {
14
+ type: /** @type {const} */ ("number"),
15
+ label: "Amount",
16
+ default: 0.04,
17
+ min: 0,
18
+ max: 1,
19
+ step: 0.01,
20
+ },
21
+ };
22
+ export const FILM_GROUPS = {
23
+ color: { label: "Color" },
24
+ grain: { label: "Grain" },
25
+ halation: { label: "Halation" },
26
+ };
27
+ /** @param {string} label @param {number} value @param {number} min @param {number} max @param {number} [step] */
28
+ const number = (label, value, min, max, step = 0.01) => ({
29
+ type: /** @type {const} */ ("number"),
30
+ label,
31
+ default: value,
32
+ min,
33
+ max,
34
+ step,
35
+ });
36
+ /** @param {{id:string,collection:string,label:string}[]} FILM_STOCKS @param {readonly string[]} FILM_CAMERAS @param {boolean} [grouped] */
37
+ export function filmKnobs(FILM_STOCKS, FILM_CAMERAS, grouped = false) {
38
+ const stockOptions = Object.fromEntries(
39
+ FILM_STOCKS.map((s) => [s.id, `${s.collection} / ${s.label}`]),
40
+ );
41
+ return {
42
+ stock: {
43
+ type: /** @type {const} */ ("select"),
44
+ label: "Film stock",
45
+ default: "stock/Vision3/Default/500T.fbfilmneg",
46
+ options: stockOptions,
47
+ },
48
+ cameraProfile: {
49
+ type: /** @type {const} */ ("select"),
50
+ label: "Input profile",
51
+ default: FILM_CAMERAS[0],
52
+ options: Object.fromEntries(FILM_CAMERAS.map((n) => [n, n])),
53
+ },
54
+ strength: number("Strength", 1, 0, 1),
55
+ exposure: {
56
+ ...number("Exposure", 0, -16, 16, 0.05),
57
+ ...(grouped ? { group: "color" } : {}),
58
+ },
59
+ warmth: {
60
+ ...number("Warmth", 0, -100, 100, 1),
61
+ ...(grouped ? { group: "color" } : {}),
62
+ },
63
+ tint: {
64
+ ...number("Tint", 0, -100, 100, 1),
65
+ ...(grouped ? { group: "color" } : {}),
66
+ },
67
+ contrast: {
68
+ ...number("Contrast", 1, 0, 4),
69
+ ...(grouped ? { group: "color" } : {}),
70
+ },
71
+ saturation: {
72
+ ...number("Saturation", 1, 0, 4),
73
+ ...(grouped ? { group: "color" } : {}),
74
+ },
75
+ lift: {
76
+ ...number("Lift", 0, -1, 1),
77
+ ...(grouped ? { group: "color" } : {}),
78
+ },
79
+ gamma: {
80
+ ...number("Gamma", 1, 0.1, 4),
81
+ ...(grouped ? { group: "color" } : {}),
82
+ },
83
+ print: {
84
+ ...number("Print gain", 1, 0, 4),
85
+ ...(grouped ? { group: "color" } : {}),
86
+ },
87
+ grain: {
88
+ ...number("Grain", 1, 0, 3),
89
+ ...(grouped ? { group: "grain" } : {}),
90
+ },
91
+ grainSaturation: {
92
+ ...number("Grain saturation", 1, 0, 2),
93
+ ...(grouped ? { group: "grain" } : {}),
94
+ },
95
+ grainSoftness: {
96
+ ...number("Grain softness", 0, -1, 1),
97
+ ...(grouped ? { group: "grain" } : {}),
98
+ },
99
+ grainRoughness: {
100
+ ...number("Grain roughness", 0, -1, 1),
101
+ ...(grouped ? { group: "grain" } : {}),
102
+ },
103
+ halation: {
104
+ ...number("Halation", 1, 0, 1),
105
+ ...(grouped ? { group: "halation" } : {}),
106
+ },
107
+ halationRadius: {
108
+ ...number("Halation radius", 1, 0, 2),
109
+ ...(grouped ? { group: "halation" } : {}),
110
+ },
111
+ looksGrain: {
112
+ ...number("Look grain adjustment", 0, -1, 1),
113
+ ...(grouped ? { group: "grain" } : {}),
114
+ },
115
+ looksHalation: {
116
+ ...number("Look halation adjustment", 0, -1, 1),
117
+ ...(grouped ? { group: "halation" } : {}),
118
+ },
119
+ gauge: {
120
+ ...(grouped ? { group: "grain" } : {}),
121
+ label: "Film gauge",
122
+ type: /** @type {const} */ ("select"),
123
+ default: "35mm",
124
+ options: { "35mm": "35 mm", "16mm": "16 mm", "8mm": "8 mm" },
125
+ },
126
+ seed: {
127
+ ...number("Grain seed", 0, 0, 16777215, 1),
128
+ integer: true,
129
+ ...(grouped ? { group: "grain" } : {}),
130
+ },
131
+ textureFps: {
132
+ ...number("Grain frame rate", 24, 1, 240, 1),
133
+ ...(grouped ? { group: "grain" } : {}),
134
+ },
135
+ };
136
+ }
@@ -1,3 +1,4 @@
1
+ import {BLUR_KNOBS, GRAIN_KNOBS} from "./effect-schemas.mjs";
1
2
  import { isKnobRef, resolveKnob, type KnobRef } from "./knobs.js";
2
3
  import { isMotion, sampleMotion, type Motion, type MotionValue, type TrackDefinition } from "./motion.js";
3
4
  import { parseColor } from "./color.js";
@@ -28,13 +29,20 @@ export interface EffectDefinition<K extends Knobs = Knobs> {
28
29
  * Padding is reserved once for the whole effect. Library API, at most 4 passes. */
29
30
  passes?: readonly Readonly<Record<string, UniformValue>>[];
30
31
  }
32
+ /** Shared effect state, independent of its named knobs. */
33
+ export interface EffectOptions {
34
+ readonly enabled?: boolean;
35
+ }
31
36
  export interface EffectInstance {
37
+ readonly enabled: boolean;
32
38
  readonly [EFFECT]: true;
33
39
  readonly definition: EffectDefinition;
34
40
  readonly sourceFile?: string;
35
41
  readonly bindings: Readonly<Record<string, MotionValue | Motion | KnobRef>>;
42
+ /** Built-in processors resolve named controls after knob/motion evaluation. */
43
+ readonly evaluateBuiltin?: (values: Readonly<Record<string, MotionValue>>) => EvaluatedEffect;
36
44
  }
37
- export type EffectFactory<K extends Knobs> = ((values?: EffectBindings<K>) => EffectInstance) & {
45
+ export type EffectFactory<K extends Knobs> = ((values?: EffectBindings<K>, options?: EffectOptions) => EffectInstance) & {
38
46
  readonly definition: EffectDefinition<K>;
39
47
  };
40
48
  export interface EvaluatedEffect {
@@ -43,6 +51,9 @@ export interface EvaluatedEffect {
43
51
  code?: string;
44
52
  sourceFile?: string;
45
53
  uniforms: Readonly<Record<string, UniformValue>>;
54
+ /** Pinned catalog asset key for the native multi-pass film processor. */
55
+ film?: string;
56
+ uniformData?: number[];
46
57
  }
47
58
  function fail(message: string): never { throw new Error(`Effect: ${message}`); }
48
59
  function record(value: unknown): value is Record<string, any> {
@@ -86,7 +97,10 @@ export function defineEffect<const K extends Knobs>(definition: EffectDefinition
86
97
  fail(`${name}: more than 24 choices needs a uniform mapping`);
87
98
  // Copy metadata: modifying the caller's objects cannot change a loaded effect.
88
99
  const frozen = freeze({ ...JSON.parse(JSON.stringify({ ...definition, uniforms: undefined })), ...(definition.uniforms ? { uniforms: definition.uniforms } : {}), ...(typeof definition.padding === "function" ? { padding: definition.padding } : {}) }) as EffectDefinition<K>;
89
- const factory = (values: EffectBindings<K> = {}): EffectInstance => {
100
+ const factory = (values: EffectBindings<K> = {}, options: EffectOptions = {}): EffectInstance => {
101
+ if (!record(options)) fail("expected effect options");
102
+ keys(options, ["enabled"], "options");
103
+ if (options.enabled !== undefined && typeof options.enabled !== "boolean") fail("enabled must be a boolean");
90
104
  if (!record(values)) fail("expected named knob values");
91
105
  const bindings: Record<string, MotionValue | Motion | KnobRef> = Object.create(null);
92
106
  for (const [name, value] of Object.entries(values)) {
@@ -97,7 +111,7 @@ export function defineEffect<const K extends Knobs>(definition: EffectDefinition
97
111
  } else validateValue(frozen.knobs[name], value, name);
98
112
  bindings[name] = Array.isArray(value) ? Object.freeze([...value]) : value as MotionValue | Motion;
99
113
  }
100
- return Object.freeze({ [EFFECT]: true as const, definition: frozen as EffectDefinition, bindings: Object.freeze(bindings), ...(sourceFile ? { sourceFile } : {}) });
114
+ return Object.freeze({ [EFFECT]: true as const, enabled: options.enabled ?? true, definition: frozen as EffectDefinition, bindings: Object.freeze(bindings), ...(sourceFile ? { sourceFile } : {}) });
101
115
  };
102
116
  return Object.freeze(Object.assign(factory, { definition: frozen }));
103
117
  }
@@ -124,13 +138,16 @@ export function describeEffect(instance: EffectInstance, time = 0, resolveBindin
124
138
  visible(knob.visibleWhen) && (!knob.group || visibleGroups[knob.group])
125
139
  ]));
126
140
  const padding = normalizePadding(typeof definition.padding === "function" ? definition.padding(values as KnobValues<Knobs>) : definition.padding);
127
- return freeze({ padding, src: definition.src, knobs: definition.knobs, groups, values, tracks, visibleGroups, visibleKnobs });
141
+ return freeze({ enabled: instance.enabled, padding, src: definition.src, knobs: definition.knobs, groups, values, tracks, visibleGroups, visibleKnobs });
128
142
  }
129
143
  export function evaluateEffects(effects: unknown, time: number, sourceFile?: string, resolveBinding?: (value: unknown) => unknown): EvaluatedEffect[] {
130
144
  if (effects === undefined) return [];
131
145
  if (!Array.isArray(effects) || effects.length > 32) fail("effects must be an array of at most 32 instances");
132
146
  const evaluated = effects.flatMap((instance: EffectInstance) => {
147
+ if (!isEffect(instance)) fail("effects must contain effect instances");
148
+ if (!instance.enabled) return [];
133
149
  const { values, padding } = describeEffect(instance, time, resolveBinding);
150
+ if (instance.evaluateBuiltin) return [instance.evaluateBuiltin(values)];
134
151
  const def = instance.definition;
135
152
  let uniforms: Readonly<Record<string, UniformValue>>;
136
153
  if (def.uniforms) uniforms = def.uniforms(values as KnobValues<Knobs>);
@@ -178,7 +195,7 @@ fn eng_input(uv: vec2f) -> vec4f {
178
195
  */
179
196
  export const blur = defineEffect({
180
197
  padding: ({ radius }) => Math.max(0, radius) * 2,
181
- knobs: { radius: { type: "number", default: 8, min: 0, max: 64, step: 0.5 } },
198
+ knobs: BLUR_KNOBS,
182
199
  // Separable Gaussian, sigma = radius, reaching two sigmas: a horizontal run,
183
200
  // then a vertical one. 33 closely spaced taps each stay smooth at large radii,
184
201
  // where a single 2D grid of the same cost left visible steps.
@@ -202,7 +219,7 @@ fn effect(uv: vec2f, time: f32) -> vec4f {
202
219
  */
203
220
  export const grain = defineEffect({
204
221
  padding: 0,
205
- knobs: { amount: { type: "number", default: 0.04, min: 0, max: 1, step: 0.01 } },
222
+ knobs: GRAIN_KNOBS,
206
223
  code: `struct Uniforms { amount: f32 }
207
224
  fn effect(uv: vec2f, time: f32) -> vec4f {
208
225
  let c = eng_input(uv);
@@ -24,6 +24,7 @@ export type InputProps<P> = {
24
24
  : K extends
25
25
  | "id"
26
26
  | "label"
27
+ | "originalSrc"
27
28
  | "hidden"
28
29
  | "solo"
29
30
  | "track"
@@ -53,7 +54,7 @@ export interface FrameContext {
53
54
  export interface ClipProps {
54
55
  /** Stable source identity, unique within its animation file. */
55
56
  id?: string;
56
- /** Literal root timeline lane, 0–99. Required at the Scene root; forbidden on nested layers. Does not change paint order. */
57
+ /** Literal root timeline lane, 0–99. Required at the Scene root; forbidden on nested layers. With Scene stacking="tracks", lower numbers paint in front. */
57
58
  track?: number;
58
59
  /** Start in the parent clock, in seconds. Motion uses time relative to this start. Default: 0. */
59
60
  start?: number;
@@ -81,7 +82,12 @@ export interface TransformProps extends ClipProps {
81
82
  opacity?: number;
82
83
  }
83
84
  export interface SceneStyleProps {
84
- /** Optional lane metadata keyed by track number (0–99). Labels are static, one word maximum. Does not change paint order. */
85
+ /** Integrate animation subframes over an exposure. Video footage and its own properties hold the center frame; ancestor animation still applies. Models are unsupported. Default: off; true uses 180°, centered, 16 samples. */
86
+ motionBlur?: boolean | import("./motion-blur.js").MotionBlurSettings;
87
+ /** Track-first compositing: track 0 is frontmost; groups are local stacking scopes.
88
+ * Omitted or "source" preserves legacy source-order compositing. New projects use "tracks". */
89
+ stacking?: "tracks" | "source";
90
+ /** Optional lane metadata keyed by track number (0–99). Labels are static, one word maximum. Stacking is controlled by Scene.stacking. */
85
91
  tracks?: Readonly<Record<number, TrackMetadata>>;
86
92
  /** Filter the complete viewport, including 3D surfaces; array order matters. */
87
93
  effects?: readonly EffectInstance[];
@@ -104,6 +110,8 @@ export type SceneProps<K extends Knobs = Knobs> = SceneStyleProps & (
104
110
  );
105
111
  /** Timeline lane metadata; independent of the clips occupying the lane. */
106
112
  export interface TrackMetadata {
113
+ /** Sample this track's animation across the scene shutter. Default: true; requires Scene.motionBlur. */
114
+ motionBlur?: boolean;
107
115
  /** Optional static display name, one word maximum, e.g. "Titles". */
108
116
  label?: string;
109
117
  /** Hide this lane's visuals; audio continues unless muted. Default: false. */
@@ -284,7 +292,11 @@ export interface MediaTiming extends ClipProps {
284
292
  /** Gain from 0–1; animatable in JSX. Use 0 to mute video audio. Default: 1. */
285
293
  volume?: number;
286
294
  }
287
- export interface VideoProps extends Omit<ImageProps, "src">, MediaTiming { src: AssetPath<"video"> }
295
+ export interface VideoProps extends Omit<ImageProps, "src">, MediaTiming {
296
+ src: AssetPath<"video">;
297
+ /** Local full-quality source used only by CLI rendering with --original. Otherwise uses src. */
298
+ originalSrc?: string;
299
+ }
288
300
  export interface AudioProps extends MediaTiming {
289
301
  /** Local audio file path, relative to this TSX file. */
290
302
  src: AssetPath<"audio">;