@neta-art/cohub 5.10.0 → 7.0.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.
- package/README.md +47 -50
- package/dist/board/animation.d.ts +157 -67
- package/dist/board/animation.js +161 -306
- package/dist/board/core/connections.js +4 -4
- package/dist/board/core/export-plan.js +1 -1
- package/dist/board/core/shape-types.js +0 -1
- package/dist/board/geometry.js +4 -4
- package/dist/board/index.d.ts +8 -7
- package/dist/board/index.js +8 -9
- package/dist/board/mutation.d.ts +2 -17
- package/dist/board/mutation.js +2 -93
- package/dist/board/render/renderers/text-card-renderer.js +1 -1
- package/dist/board/semantic-document.d.ts +30 -0
- package/dist/board/semantic-document.js +271 -0
- package/dist/board/semantic-mutation.d.ts +10 -0
- package/dist/board/semantic-mutation.js +203 -0
- package/dist/chunks/http.d.ts +27 -95
- package/dist/chunks/http.js +628 -1610
- package/dist/chunks/transport.js +1 -1
- package/dist/chunks/websocket.d.ts +3462 -533
- package/dist/chunks/websocket.js +1 -1
- package/dist/http.d.ts +3 -3
- package/dist/index.d.ts +158 -299
- package/dist/index.js +1207 -499
- package/dist/protocol/dist/board-authoring.d.ts +1305 -0
- package/dist/protocol/dist/board-authoring.js +291 -0
- package/dist/protocol/dist/board-capability-registry.d.ts +2 -0
- package/dist/protocol/dist/board-capability-registry.js +74 -0
- package/dist/protocol/dist/board-codec.d.ts +2 -0
- package/dist/protocol/dist/board-codec.js +19 -0
- package/dist/protocol/dist/board-composition.d.ts +383 -0
- package/dist/protocol/dist/board-composition.js +310 -0
- package/dist/protocol/dist/board-connection.d.ts +16 -16
- package/dist/protocol/dist/board-connection.js +16 -16
- package/dist/protocol/dist/board-constants.js +0 -25
- package/dist/protocol/dist/board-document.d.ts +34 -3
- package/dist/protocol/dist/board-document.js +3 -1
- package/dist/protocol/dist/board-effect.d.ts +94 -0
- package/dist/protocol/dist/board-effect.js +53 -0
- package/dist/protocol/dist/board-json.js +16 -0
- package/dist/protocol/dist/board-node.d.ts +1 -12
- package/dist/protocol/dist/board-node.js +1 -81
- package/dist/protocol/dist/board.d.ts +42 -245
- package/dist/protocol/dist/board.js +56 -117
- package/dist/protocol/dist/index.d.ts +9 -4
- package/dist/types.d.ts +4 -1
- package/docs/work-runtime-guide.md +19 -3
- package/package.json +2 -2
- package/dist/board/codec.d.ts +0 -27
- package/dist/board/codec.js +0 -277
- package/dist/board/nodes.d.ts +0 -113
- package/dist/board/nodes.js +0 -154
- package/dist/protocol/dist/board-content.js +0 -26
- package/dist/protocol/dist/identifiers.js +0 -10
- package/dist/protocol/dist/index.js +0 -14
- package/dist/protocol/dist/provenance.js +0 -15
- package/dist/protocol/dist/public-identifiers.js +0 -38
- package/dist/protocol/dist/realtime/board-awareness.js +0 -119
- package/dist/protocol/dist/ui-command.js +0 -2
- package/dist/protocol/dist/work-promotion-stats.js +0 -11
- package/dist/protocol/dist/work-surface.js +0 -2
- package/dist/protocol/dist/work-view-stats.js +0 -3
package/dist/board/animation.js
CHANGED
|
@@ -1,282 +1,147 @@
|
|
|
1
1
|
import { BOARD_BUILTIN_CAPABILITIES, DEFAULT_BOARD_RENDER_LIMITS } from "../protocol/dist/board-constants.js";
|
|
2
|
-
import {
|
|
3
|
-
import "../protocol/dist/
|
|
2
|
+
import { BOARD_ANIMATION_CHANNELS, BoardCompositionSchema, BoardProceduralClipSchema, BoardTrackSchema } from "../protocol/dist/board-composition.js";
|
|
3
|
+
import { estimateBuiltinBoardClipCost, validateBuiltinBoardClip } from "../protocol/dist/board-capability-registry.js";
|
|
4
4
|
//#region src/board/animation.ts
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
vertices: 0,
|
|
8
|
-
dynamicVertices: 0,
|
|
9
|
-
drawCalls: 0,
|
|
10
|
-
filterPasses: 0,
|
|
11
|
-
renderTexturePixels: 0,
|
|
12
|
-
textureBytes: 0,
|
|
13
|
-
bufferBytes: 0,
|
|
14
|
-
simulationSteps: 0
|
|
15
|
-
};
|
|
16
|
-
const DEFAULT_BOARD_LIMITS = DEFAULT_BOARD_RENDER_LIMITS;
|
|
17
|
-
const BUILTIN_CAPABILITIES = BOARD_BUILTIN_CAPABILITIES;
|
|
18
|
-
function numericParam(params, key, fallback) {
|
|
19
|
-
const value = params[key];
|
|
20
|
-
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
5
|
+
function track(input) {
|
|
6
|
+
return BoardTrackSchema.parse(input);
|
|
21
7
|
}
|
|
22
|
-
function
|
|
23
|
-
return {
|
|
24
|
-
...
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
path: "params.bounds",
|
|
41
|
-
coordinateSpace: "world"
|
|
42
|
-
});
|
|
43
|
-
else {
|
|
44
|
-
const value = bounds;
|
|
45
|
-
if (![
|
|
46
|
-
value.x,
|
|
47
|
-
value.y,
|
|
48
|
-
value.width,
|
|
49
|
-
value.height
|
|
50
|
-
].every((item) => typeof item === "number" && Number.isFinite(item)) || value.width <= 0 || value.height <= 0) diagnostics.push({
|
|
51
|
-
severity: "error",
|
|
52
|
-
code: "INVALID_PARTICLE_BOUNDS",
|
|
53
|
-
message: "particle bounds must have a positive finite size",
|
|
54
|
-
path: "params.bounds",
|
|
55
|
-
coordinateSpace: "world"
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
if (capability.id === "motion.path") {
|
|
60
|
-
const points = params.points;
|
|
61
|
-
if (!Array.isArray(points) || points.length < 2 || points.length > 1e4) diagnostics.push({
|
|
62
|
-
severity: "error",
|
|
63
|
-
code: "INVALID_MOTION_PATH",
|
|
64
|
-
message: "motion path must contain 2 to 10000 world-offset points",
|
|
65
|
-
path: "params.points",
|
|
66
|
-
coordinateSpace: "world-offset"
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
if (capability.id === "camera.focus") {
|
|
70
|
-
const parsed = BoardCameraFocusParamsSchema.safeParse(params);
|
|
71
|
-
if (!parsed.success) diagnostics.push({
|
|
72
|
-
severity: "error",
|
|
73
|
-
code: "INVALID_CAMERA_FOCUS",
|
|
74
|
-
message: parsed.error.issues[0]?.message ?? "invalid camera focus",
|
|
75
|
-
path: "params",
|
|
76
|
-
coordinateSpace: "world"
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
return diagnostics;
|
|
8
|
+
function proceduralClip(input) {
|
|
9
|
+
return BoardProceduralClipSchema.parse({
|
|
10
|
+
...input,
|
|
11
|
+
kindVersion: input.kindVersion ?? 1
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
function composition(input) {
|
|
15
|
+
return BoardCompositionSchema.parse(input);
|
|
16
|
+
}
|
|
17
|
+
function compileComposition(input) {
|
|
18
|
+
return composition({
|
|
19
|
+
id: input.id,
|
|
20
|
+
name: input.name,
|
|
21
|
+
timeline: {
|
|
22
|
+
duration: input.duration,
|
|
23
|
+
tracks: (input.tracks ?? []).map(track),
|
|
24
|
+
clips: (input.clips ?? []).map(proceduralClip),
|
|
25
|
+
markers: input.markers ?? []
|
|
80
26
|
},
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
return [
|
|
86
|
-
value.x,
|
|
87
|
-
value.y,
|
|
88
|
-
value.width,
|
|
89
|
-
value.height
|
|
90
|
-
].every((item) => typeof item === "number" && Number.isFinite(item)) ? value : null;
|
|
27
|
+
playback: input.playback ?? {
|
|
28
|
+
loop: false,
|
|
29
|
+
endBehavior: "hold",
|
|
30
|
+
reducedMotion: { mode: "base" }
|
|
91
31
|
},
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
const particles = Math.max(0, Math.floor(numericParam(params, "count", 0)));
|
|
96
|
-
return {
|
|
97
|
-
particles,
|
|
98
|
-
vertices: particles * 4,
|
|
99
|
-
dynamicVertices: particles * 4,
|
|
100
|
-
drawCalls: 1,
|
|
101
|
-
bufferBytes: particles * 48,
|
|
102
|
-
simulationSteps: particles
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
case "effects.trail": return {
|
|
106
|
-
vertices: 32,
|
|
107
|
-
dynamicVertices: 32,
|
|
108
|
-
drawCalls: 1,
|
|
109
|
-
bufferBytes: 1024,
|
|
110
|
-
simulationSteps: 16
|
|
111
|
-
};
|
|
112
|
-
case "effects.impact":
|
|
113
|
-
case "effects.flash": return {
|
|
114
|
-
vertices: 64,
|
|
115
|
-
drawCalls: 1
|
|
116
|
-
};
|
|
117
|
-
case "effects.color": return {
|
|
118
|
-
drawCalls: 1,
|
|
119
|
-
filterPasses: 1
|
|
120
|
-
};
|
|
121
|
-
case "draw.reveal":
|
|
122
|
-
case "draw.handwrite": return {
|
|
123
|
-
drawCalls: 1,
|
|
124
|
-
dynamicVertices: 1
|
|
125
|
-
};
|
|
126
|
-
default: return {};
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
};
|
|
32
|
+
metadata: input.metadata ?? {},
|
|
33
|
+
revision: 0
|
|
34
|
+
});
|
|
130
35
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
36
|
+
const clamp01 = (value) => Math.max(0, Math.min(1, value));
|
|
37
|
+
function sampleEasing(easing, value) {
|
|
38
|
+
const t = clamp01(value);
|
|
39
|
+
switch (easing) {
|
|
40
|
+
case "ease-in-quad": return t * t;
|
|
41
|
+
case "ease-out-quad": return 1 - (1 - t) ** 2;
|
|
42
|
+
case "ease-in-out-quad": return t < .5 ? 2 * t * t : 1 - (-2 * t + 2) ** 2 / 2;
|
|
43
|
+
case "ease-in-cubic": return t ** 3;
|
|
44
|
+
case "ease-out-cubic": return 1 - (1 - t) ** 3;
|
|
45
|
+
case "ease-in-out-cubic": return t < .5 ? 4 * t ** 3 : 1 - (-2 * t + 2) ** 3 / 2;
|
|
46
|
+
case "ease-out-quart": return 1 - (1 - t) ** 4;
|
|
47
|
+
case "ease-out-expo": return t === 1 ? 1 : 1 - 2 ** (-10 * t);
|
|
48
|
+
default: return t;
|
|
136
49
|
}
|
|
137
|
-
return (hash >>> 0).toString(36).padStart(7, "0");
|
|
138
|
-
}
|
|
139
|
-
function stableId(prefix, seed, path) {
|
|
140
|
-
return `${prefix}_${stableHash(`${seed}:${path}`)}`;
|
|
141
|
-
}
|
|
142
|
-
function assertDuration(value, field) {
|
|
143
|
-
if (!Number.isFinite(value) || value < 0) throw new TypeError(`${field} must be a finite non-negative number`);
|
|
144
50
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
};
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
return {
|
|
154
|
-
type: "parallel",
|
|
155
|
-
children
|
|
156
|
-
};
|
|
157
|
-
},
|
|
158
|
-
sequence(...children) {
|
|
159
|
-
return {
|
|
160
|
-
type: "sequence",
|
|
161
|
-
children
|
|
162
|
-
};
|
|
163
|
-
},
|
|
164
|
-
stagger(each, ...children) {
|
|
165
|
-
assertDuration(each, "stagger each");
|
|
166
|
-
return {
|
|
167
|
-
type: "stagger",
|
|
168
|
-
each,
|
|
169
|
-
children
|
|
170
|
-
};
|
|
171
|
-
},
|
|
172
|
-
delay(duration, child) {
|
|
173
|
-
assertDuration(duration, "delay duration");
|
|
174
|
-
return {
|
|
175
|
-
type: "delay",
|
|
176
|
-
duration,
|
|
177
|
-
child
|
|
178
|
-
};
|
|
179
|
-
},
|
|
180
|
-
repeat(count, child) {
|
|
181
|
-
if (!Number.isSafeInteger(count) || count < 1 || count > 1e3) throw new TypeError("repeat count must be an integer between 1 and 1000");
|
|
182
|
-
return {
|
|
183
|
-
type: "repeat",
|
|
184
|
-
count,
|
|
185
|
-
child
|
|
186
|
-
};
|
|
51
|
+
function interpolate(left, right, progress) {
|
|
52
|
+
if (typeof left === "number" && typeof right === "number") return left + (right - left) * progress;
|
|
53
|
+
if (left && right && typeof left === "object" && typeof right === "object" && !Array.isArray(left) && !Array.isArray(right)) {
|
|
54
|
+
const a = left;
|
|
55
|
+
const b = right;
|
|
56
|
+
const result = {};
|
|
57
|
+
for (const key of /* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(b)])) result[key] = interpolate(a[key], b[key], progress);
|
|
58
|
+
return result;
|
|
187
59
|
}
|
|
188
|
-
|
|
189
|
-
function clip(input) {
|
|
190
|
-
assertDuration(input.duration, "clip duration");
|
|
191
|
-
if (input.duration === 0) throw new TypeError("clip duration must be greater than zero");
|
|
192
|
-
return timeline.clip({
|
|
193
|
-
id: input.id,
|
|
194
|
-
kind: input.kind,
|
|
195
|
-
kindVersion: input.kindVersion ?? 1,
|
|
196
|
-
target: input.target,
|
|
197
|
-
duration: input.duration,
|
|
198
|
-
layer: input.layer ?? "content",
|
|
199
|
-
fill: input.fill ?? "none",
|
|
200
|
-
easing: input.easing ?? "linear",
|
|
201
|
-
params: input.params ?? {},
|
|
202
|
-
keyframes: input.keyframes ?? [],
|
|
203
|
-
assetRefs: input.assetRefs ?? [],
|
|
204
|
-
seed: input.seed,
|
|
205
|
-
metadata: input.metadata ?? {}
|
|
206
|
-
});
|
|
60
|
+
return progress < 1 ? left : right;
|
|
207
61
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
62
|
+
/** Deterministically sample one validated Track without touching scene state. */
|
|
63
|
+
function sampleTrack(trackValue, time) {
|
|
64
|
+
const frames = trackValue.keyframes;
|
|
65
|
+
const first = frames[0];
|
|
66
|
+
const last = frames.at(-1);
|
|
67
|
+
if (!first || !last) return null;
|
|
68
|
+
if (time < first.time) {
|
|
69
|
+
if (trackValue.fill !== "backwards" && trackValue.fill !== "both") return null;
|
|
211
70
|
return {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
id: input.clip.id ?? stableId("clip", sequenceSeed, path),
|
|
216
|
-
start: offset,
|
|
217
|
-
seed: clipSeed
|
|
218
|
-
}]
|
|
71
|
+
target: trackValue.target,
|
|
72
|
+
channel: trackValue.channel,
|
|
73
|
+
value: first.value
|
|
219
74
|
};
|
|
220
75
|
}
|
|
221
|
-
if (
|
|
222
|
-
|
|
76
|
+
if (time > last.time) {
|
|
77
|
+
if (trackValue.fill !== "forwards" && trackValue.fill !== "both") return null;
|
|
223
78
|
return {
|
|
224
|
-
|
|
225
|
-
|
|
79
|
+
target: trackValue.target,
|
|
80
|
+
channel: trackValue.channel,
|
|
81
|
+
value: last.value
|
|
226
82
|
};
|
|
227
83
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
clips.push(...nested.clips);
|
|
235
|
-
cursor += nested.duration;
|
|
236
|
-
total += nested.duration;
|
|
237
|
-
}
|
|
238
|
-
return {
|
|
239
|
-
duration: total,
|
|
240
|
-
clips
|
|
241
|
-
};
|
|
242
|
-
}
|
|
243
|
-
const clips = [];
|
|
244
|
-
let duration = 0;
|
|
245
|
-
for (const [index, child] of input.children.entries()) {
|
|
246
|
-
const childOffset = input.type === "sequence" ? duration : input.type === "stagger" ? input.each * index : 0;
|
|
247
|
-
const nested = flatten(child, offset + childOffset, sequenceSeed, `${path}.${index}`);
|
|
248
|
-
clips.push(...nested.clips);
|
|
249
|
-
duration = input.type === "sequence" ? duration + nested.duration : Math.max(duration, childOffset + nested.duration);
|
|
84
|
+
let low = 0;
|
|
85
|
+
let high = frames.length;
|
|
86
|
+
while (low < high) {
|
|
87
|
+
const middle = low + high >>> 1;
|
|
88
|
+
if ((frames[middle]?.time ?? Number.POSITIVE_INFINITY) <= time) low = middle + 1;
|
|
89
|
+
else high = middle;
|
|
250
90
|
}
|
|
91
|
+
const left = frames[Math.max(0, low - 1)] ?? first;
|
|
92
|
+
const right = frames[Math.min(frames.length - 1, low)] ?? last;
|
|
93
|
+
if (left === right || trackValue.interpolation === "step") return {
|
|
94
|
+
target: trackValue.target,
|
|
95
|
+
channel: trackValue.channel,
|
|
96
|
+
value: left.value
|
|
97
|
+
};
|
|
98
|
+
const span = Math.max(Number.EPSILON, right.time - left.time);
|
|
99
|
+
const progress = sampleEasing(right.easing ?? "linear", (time - left.time) / span);
|
|
251
100
|
return {
|
|
252
|
-
|
|
253
|
-
|
|
101
|
+
target: trackValue.target,
|
|
102
|
+
channel: trackValue.channel,
|
|
103
|
+
value: interpolate(left.value, right.value, progress)
|
|
254
104
|
};
|
|
255
105
|
}
|
|
256
|
-
function
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
106
|
+
function sampleCompositionTracks(value, time) {
|
|
107
|
+
const position = Math.max(0, Math.min(value.timeline.duration, time));
|
|
108
|
+
return value.timeline.tracks.flatMap((item) => {
|
|
109
|
+
const sampled = sampleTrack(item, position);
|
|
110
|
+
return sampled ? [sampled] : [];
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
const ZERO_COST = {
|
|
114
|
+
particles: 0,
|
|
115
|
+
vertices: 0,
|
|
116
|
+
dynamicVertices: 0,
|
|
117
|
+
drawCalls: 0,
|
|
118
|
+
filterPasses: 0,
|
|
119
|
+
renderTexturePixels: 0,
|
|
120
|
+
textureBytes: 0,
|
|
121
|
+
bufferBytes: 0,
|
|
122
|
+
simulationSteps: 0
|
|
123
|
+
};
|
|
124
|
+
const DEFAULT_BOARD_LIMITS = DEFAULT_BOARD_RENDER_LIMITS;
|
|
125
|
+
function builtinDefinition(capability) {
|
|
261
126
|
return {
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
duration: flattened.duration,
|
|
266
|
-
seed: input.seed,
|
|
267
|
-
restPose: input.restPose ?? {},
|
|
268
|
-
metadata: input.metadata ?? {}
|
|
127
|
+
...capability,
|
|
128
|
+
validate() {
|
|
129
|
+
return [];
|
|
269
130
|
},
|
|
270
|
-
|
|
271
|
-
|
|
131
|
+
estimateCost(params) {
|
|
132
|
+
return estimateBuiltinBoardClipCost({
|
|
133
|
+
kind: capability.id,
|
|
134
|
+
params
|
|
135
|
+
});
|
|
136
|
+
}
|
|
272
137
|
};
|
|
273
138
|
}
|
|
274
|
-
function addCost(target, source) {
|
|
275
|
-
for (const key of Object.keys(target)) target[key] += source[key] ?? 0;
|
|
276
|
-
}
|
|
277
139
|
var BoardExtensionRegistry = class {
|
|
278
140
|
#extensions = /* @__PURE__ */ new Map();
|
|
279
141
|
#presets = /* @__PURE__ */ new Map();
|
|
142
|
+
constructor() {
|
|
143
|
+
for (const capability of BOARD_BUILTIN_CAPABILITIES) this.register(builtinDefinition(capability));
|
|
144
|
+
}
|
|
280
145
|
register(definition) {
|
|
281
146
|
const key = `${definition.kind}:${definition.id}@${definition.version}`;
|
|
282
147
|
const target = definition.kind === "preset" ? this.#presets : this.#extensions;
|
|
@@ -285,9 +150,15 @@ var BoardExtensionRegistry = class {
|
|
|
285
150
|
return this;
|
|
286
151
|
}
|
|
287
152
|
capabilities() {
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
153
|
+
return [...this.#extensions.values(), ...this.#presets.values()].map((definition) => ({
|
|
154
|
+
kind: definition.kind,
|
|
155
|
+
id: definition.id,
|
|
156
|
+
version: definition.version,
|
|
157
|
+
...definition.digest ? { digest: definition.digest } : {},
|
|
158
|
+
...definition.renderers ? { renderers: definition.renderers } : {},
|
|
159
|
+
...definition.fallbackId ? { fallbackId: definition.fallbackId } : {},
|
|
160
|
+
...definition.schema ? { schema: definition.schema } : {}
|
|
161
|
+
}));
|
|
291
162
|
}
|
|
292
163
|
compilePreset(id, version, params) {
|
|
293
164
|
const preset = this.#presets.get(`preset:${id}@${version}`);
|
|
@@ -295,88 +166,72 @@ var BoardExtensionRegistry = class {
|
|
|
295
166
|
return preset.compile(params);
|
|
296
167
|
}
|
|
297
168
|
validate(input) {
|
|
169
|
+
const parsed = BoardCompositionSchema.safeParse(input.composition);
|
|
170
|
+
if (!parsed.success) return {
|
|
171
|
+
valid: false,
|
|
172
|
+
diagnostics: parsed.error.issues.map((issue) => ({
|
|
173
|
+
severity: "error",
|
|
174
|
+
code: "INVALID_COMPOSITION",
|
|
175
|
+
message: issue.message,
|
|
176
|
+
path: issue.path.join(".")
|
|
177
|
+
})),
|
|
178
|
+
peakCost: { ...ZERO_COST }
|
|
179
|
+
};
|
|
298
180
|
const diagnostics = [];
|
|
181
|
+
const composition = parsed.data;
|
|
182
|
+
const cost = { ...ZERO_COST };
|
|
299
183
|
const events = [];
|
|
300
|
-
const
|
|
301
|
-
const persistentCost = { ...ZERO_COST };
|
|
302
|
-
for (const [index, effect] of (input.effects ?? []).entries()) {
|
|
303
|
-
const definition = this.#extensions.get(`effect:${effect.kind}@${effect.kindVersion}`);
|
|
304
|
-
if (!definition) {
|
|
305
|
-
diagnostics.push({
|
|
306
|
-
severity: "warning",
|
|
307
|
-
code: "UNKNOWN_EFFECT",
|
|
308
|
-
message: `No renderer is registered for ${effect.kind}@${effect.kindVersion}`,
|
|
309
|
-
path: `effects.${index}`
|
|
310
|
-
});
|
|
311
|
-
continue;
|
|
312
|
-
}
|
|
313
|
-
diagnostics.push(...definition.validate?.(effect.params) ?? []);
|
|
314
|
-
addCost(persistentCost, definition.estimateCost(effect.params, profile));
|
|
315
|
-
}
|
|
316
|
-
for (const [index, clip] of input.clips.entries()) {
|
|
184
|
+
for (const clip of composition.timeline.clips) {
|
|
317
185
|
const definition = this.#extensions.get(`clip:${clip.kind}@${clip.kindVersion}`);
|
|
318
186
|
if (!definition) {
|
|
319
187
|
diagnostics.push({
|
|
320
188
|
severity: "warning",
|
|
321
189
|
code: "UNKNOWN_CLIP",
|
|
322
|
-
message: `No renderer is registered for ${clip.kind}@${clip.kindVersion}
|
|
323
|
-
path: `clips.${index}`
|
|
190
|
+
message: `No renderer is registered for ${clip.kind}@${clip.kindVersion}`
|
|
324
191
|
});
|
|
325
192
|
continue;
|
|
326
193
|
}
|
|
194
|
+
diagnostics.push(...validateBuiltinBoardClip(clip, `composition.timeline.clips.${clip.id}`));
|
|
327
195
|
diagnostics.push(...definition.validate?.(clip.params) ?? []);
|
|
328
|
-
const
|
|
329
|
-
|
|
196
|
+
const estimate = definition.estimateCost(clip.params, input.profile ?? "high");
|
|
197
|
+
const clipCost = { ...ZERO_COST };
|
|
198
|
+
for (const key of Object.keys(clipCost)) clipCost[key] = estimate[key] ?? 0;
|
|
330
199
|
events.push({
|
|
331
200
|
at: clip.start,
|
|
332
201
|
direction: 1,
|
|
333
|
-
cost
|
|
334
|
-
}
|
|
202
|
+
cost: clipCost
|
|
203
|
+
});
|
|
204
|
+
events.push({
|
|
335
205
|
at: clip.start + clip.duration,
|
|
336
206
|
direction: -1,
|
|
337
|
-
cost
|
|
338
|
-
});
|
|
339
|
-
}
|
|
340
|
-
const cameraFocusClips = input.clips.map((clip, index) => ({
|
|
341
|
-
clip,
|
|
342
|
-
index
|
|
343
|
-
})).filter(({ clip }) => clip.kind === "camera.focus").sort((left, right) => left.clip.start - right.clip.start);
|
|
344
|
-
for (let index = 1; index < cameraFocusClips.length; index += 1) {
|
|
345
|
-
const previous = cameraFocusClips[index - 1];
|
|
346
|
-
const currentFocus = cameraFocusClips[index];
|
|
347
|
-
if (previous && currentFocus && currentFocus.clip.start < previous.clip.start + previous.clip.duration) diagnostics.push({
|
|
348
|
-
severity: "error",
|
|
349
|
-
code: "OVERLAPPING_CAMERA_FOCUS",
|
|
350
|
-
message: "camera.focus clips must not overlap",
|
|
351
|
-
path: `clips.${currentFocus.index}.start`
|
|
207
|
+
cost: clipCost
|
|
352
208
|
});
|
|
353
209
|
}
|
|
354
210
|
events.sort((left, right) => left.at - right.at || left.direction - right.direction);
|
|
355
|
-
const
|
|
356
|
-
const
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
peak[key] = Math.max(peak[key], current[key]);
|
|
211
|
+
const active = { ...ZERO_COST };
|
|
212
|
+
for (const event of events) for (const key of Object.keys(active)) {
|
|
213
|
+
active[key] += event.cost[key] * event.direction;
|
|
214
|
+
cost[key] = Math.max(cost[key], active[key]);
|
|
360
215
|
}
|
|
216
|
+
for (const effect of input.effects ?? []) if (effect.kind === "effects.pulse" || effect.kind === "effects.float") cost.drawCalls += 1;
|
|
361
217
|
const limits = input.limits ?? DEFAULT_BOARD_LIMITS;
|
|
362
|
-
for (const key of Object.keys(
|
|
218
|
+
for (const key of Object.keys(cost)) if (cost[key] > limits[key]) diagnostics.push({
|
|
363
219
|
severity: "warning",
|
|
364
220
|
code: "RENDER_BUDGET_EXCEEDED",
|
|
365
|
-
message: `${key} peaks at ${
|
|
221
|
+
message: `${key} peaks at ${cost[key]}, above ${limits[key]}`,
|
|
366
222
|
path: key,
|
|
367
223
|
adaptation: { quality: "lower" }
|
|
368
224
|
});
|
|
369
225
|
return {
|
|
370
226
|
valid: !diagnostics.some((item) => item.severity === "error"),
|
|
371
227
|
diagnostics,
|
|
372
|
-
peakCost:
|
|
228
|
+
peakCost: cost
|
|
373
229
|
};
|
|
374
230
|
}
|
|
375
231
|
};
|
|
376
|
-
function createBoardExtensionRegistry(
|
|
377
|
-
|
|
378
|
-
if (input.builtins !== false) for (const capability of BUILTIN_CAPABILITIES) registry.register(builtinDefinition(capability));
|
|
379
|
-
return registry;
|
|
232
|
+
function createBoardExtensionRegistry() {
|
|
233
|
+
return new BoardExtensionRegistry();
|
|
380
234
|
}
|
|
235
|
+
const BOARD_CHANNELS = BOARD_ANIMATION_CHANNELS;
|
|
381
236
|
//#endregion
|
|
382
|
-
export { BoardExtensionRegistry, DEFAULT_BOARD_LIMITS,
|
|
237
|
+
export { BOARD_CHANNELS, BoardExtensionRegistry, DEFAULT_BOARD_LIMITS, compileComposition, composition, createBoardExtensionRegistry, proceduralClip, sampleCompositionTracks, sampleEasing, sampleTrack, track };
|
|
@@ -238,11 +238,11 @@ const CURVE_SEGMENTS = 20;
|
|
|
238
238
|
* fetched.
|
|
239
239
|
*/
|
|
240
240
|
function resolveConnection(connection, getFrame, options = {}) {
|
|
241
|
-
const sourceFrame = getFrame(connection.source.
|
|
242
|
-
const targetFrame = getFrame(connection.target.
|
|
241
|
+
const sourceFrame = getFrame(connection.source.itemId);
|
|
242
|
+
const targetFrame = getFrame(connection.target.itemId);
|
|
243
243
|
if (!sourceFrame || !targetFrame) return null;
|
|
244
244
|
const gap = options.gap ?? 4;
|
|
245
|
-
if (connection.source.
|
|
245
|
+
if (connection.source.itemId === connection.target.itemId) {
|
|
246
246
|
const path = selfLoopPath(sourceFrame, gap);
|
|
247
247
|
const first = path[0];
|
|
248
248
|
const last = path[path.length - 1];
|
|
@@ -398,7 +398,7 @@ function createConnectionIndex(connections) {
|
|
|
398
398
|
const byId = /* @__PURE__ */ new Map();
|
|
399
399
|
for (const connection of connections) {
|
|
400
400
|
byId.set(connection.id, connection);
|
|
401
|
-
for (const nodeId of /* @__PURE__ */ new Set([connection.source.
|
|
401
|
+
for (const nodeId of /* @__PURE__ */ new Set([connection.source.itemId, connection.target.itemId])) {
|
|
402
402
|
const list = byNode.get(nodeId);
|
|
403
403
|
if (list) list.push(connection.id);
|
|
404
404
|
else byNode.set(nodeId, [connection.id]);
|
|
@@ -55,7 +55,7 @@ function exportConnectionBounds(connection, getFrame) {
|
|
|
55
55
|
function connectionsWithin(document, items) {
|
|
56
56
|
if (document.connections.length === 0) return [];
|
|
57
57
|
const present = new Set(items.map((item) => item.id));
|
|
58
|
-
return document.connections.filter((connection) => present.has(connection.source.
|
|
58
|
+
return document.connections.filter((connection) => present.has(connection.source.itemId) && present.has(connection.target.itemId));
|
|
59
59
|
}
|
|
60
60
|
function resolveRegion(document, region, getFrame) {
|
|
61
61
|
switch (region.kind) {
|
package/dist/board/geometry.js
CHANGED
|
@@ -538,19 +538,19 @@ function cameraForRect(content, surface, options = {
|
|
|
538
538
|
}
|
|
539
539
|
function rectForCameraFocus(focus, getFrame) {
|
|
540
540
|
if (focus.type === "rect") return focus.rect;
|
|
541
|
-
if (focus.type === "
|
|
542
|
-
const frame = getFrame(focus.
|
|
541
|
+
if (focus.type === "item") {
|
|
542
|
+
const frame = getFrame(focus.itemId);
|
|
543
543
|
return frame ? itemBounds(frame) : null;
|
|
544
544
|
}
|
|
545
545
|
if (focus.type === "frame") {
|
|
546
546
|
const frame = getFrame(focus.frameId);
|
|
547
547
|
return frame ? itemBounds(frame) : null;
|
|
548
548
|
}
|
|
549
|
-
const frames = focus.
|
|
549
|
+
const frames = focus.itemIds.flatMap((id) => {
|
|
550
550
|
const frame = getFrame(id);
|
|
551
551
|
return frame ? [frame] : [];
|
|
552
552
|
});
|
|
553
|
-
return frames.length === focus.
|
|
553
|
+
return frames.length === focus.itemIds.length ? selectionBounds(frames) : null;
|
|
554
554
|
}
|
|
555
555
|
function cameraForFocus(params, getFrame, surface) {
|
|
556
556
|
const rect = rectForCameraFocus(params.focus, getFrame);
|