@vizij/runtime-react 0.0.14 → 0.2.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/dist/index.cjs ADDED
@@ -0,0 +1,4077 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ EMOTION_POSE_KEYS: () => EMOTION_POSE_KEYS,
24
+ EXPRESSIVE_EMOTION_POSE_KEYS: () => EXPRESSIVE_EMOTION_POSE_KEYS,
25
+ POSE_WEIGHT_INPUT_PATH_PREFIX: () => POSE_WEIGHT_INPUT_PATH_PREFIX,
26
+ VISEME_POSE_KEYS: () => VISEME_POSE_KEYS,
27
+ VizijRuntimeFace: () => VizijRuntimeFace,
28
+ VizijRuntimeProvider: () => VizijRuntimeProvider,
29
+ buildPoseWeightInputPathSegment: () => buildPoseWeightInputPathSegment,
30
+ buildPoseWeightPathMap: () => buildPoseWeightPathMap,
31
+ buildPoseWeightRelativePath: () => buildPoseWeightRelativePath,
32
+ buildRigInputPath: () => buildRigInputPath,
33
+ buildSemanticPoseWeightPathMap: () => buildSemanticPoseWeightPathMap,
34
+ filterPosesBySemanticKind: () => filterPosesBySemanticKind,
35
+ getPoseSemanticKey: () => getPoseSemanticKey,
36
+ mapNormalizedControlValue: () => mapNormalizedControlValue,
37
+ mapUnitControlValue: () => mapUnitControlValue,
38
+ normalizePoseSemanticKey: () => normalizePoseSemanticKey,
39
+ resolveFaceControls: () => resolveFaceControls,
40
+ resolvePoseMembership: () => resolvePoseMembership,
41
+ resolvePoseSemantics: () => resolvePoseSemantics,
42
+ resolveRuntimeUpdatePlan: () => resolveRuntimeUpdatePlan,
43
+ useOptionalVizijRuntime: () => useOptionalVizijRuntime,
44
+ useRigInput: () => useRigInput,
45
+ useVizijOutputs: () => useVizijOutputs,
46
+ useVizijRuntime: () => useVizijRuntime
47
+ });
48
+ module.exports = __toCommonJS(index_exports);
49
+
50
+ // src/VizijRuntimeProvider.tsx
51
+ var import_react2 = require("react");
52
+ var import_render = require("@vizij/render");
53
+ var import_orchestrator_react = require("@vizij/orchestrator-react");
54
+ var import_node_graph_authoring = require("@vizij/node-graph-authoring");
55
+ var import_value_json2 = require("@vizij/value-json");
56
+ var import_utils = require("@vizij/utils");
57
+
58
+ // src/context.ts
59
+ var import_react = require("react");
60
+ var VizijRuntimeContext = (0, import_react.createContext)(null);
61
+
62
+ // src/updatePolicy.ts
63
+ function applyRuntimeGraphBundle(base, bundle) {
64
+ const next = {
65
+ ...base
66
+ };
67
+ const hasRigOverride = Object.prototype.hasOwnProperty.call(bundle, "rig");
68
+ const hasPoseOverride = Object.prototype.hasOwnProperty.call(bundle, "pose");
69
+ const hasAnimationsOverride = Object.prototype.hasOwnProperty.call(
70
+ bundle,
71
+ "animations"
72
+ );
73
+ const hasProgramsOverride = Object.prototype.hasOwnProperty.call(
74
+ bundle,
75
+ "programs"
76
+ );
77
+ if (hasRigOverride) {
78
+ if (bundle.rig) {
79
+ next.rig = bundle.rig;
80
+ } else {
81
+ delete next.rig;
82
+ }
83
+ }
84
+ if (hasPoseOverride) {
85
+ if (bundle.pose) {
86
+ next.pose = bundle.pose;
87
+ } else {
88
+ delete next.pose;
89
+ }
90
+ }
91
+ if (hasAnimationsOverride) {
92
+ next.animations = Array.isArray(bundle.animations) ? bundle.animations : void 0;
93
+ }
94
+ if (hasProgramsOverride) {
95
+ next.programs = Array.isArray(bundle.programs) ? bundle.programs : void 0;
96
+ }
97
+ return next;
98
+ }
99
+ function normalizeSpecPayload(value) {
100
+ if (!value) {
101
+ return "";
102
+ }
103
+ try {
104
+ return JSON.stringify(value);
105
+ } catch {
106
+ return String(value);
107
+ }
108
+ }
109
+ function glbSignature(glb) {
110
+ if (glb.kind === "url") {
111
+ return `url:${glb.src}`;
112
+ }
113
+ if (glb.kind === "blob") {
114
+ return `blob:${glb.blob?.size ?? 0}`;
115
+ }
116
+ return `world:${normalizeSpecPayload(glb.world)}`;
117
+ }
118
+ function graphSignature(graph) {
119
+ if (!graph) {
120
+ return "";
121
+ }
122
+ const id = graph.id ?? "";
123
+ return `${id}:${normalizeSpecPayload(graph.spec ?? graph.ir ?? null)}`;
124
+ }
125
+ function poseSignature(pose) {
126
+ if (!pose) {
127
+ return "";
128
+ }
129
+ const graph = pose.graph;
130
+ const config = pose.config;
131
+ const graphPart = graph ? graphSignature({ id: graph.id, spec: graph.spec }) : "";
132
+ const configPart = config ? normalizeSpecPayload(config) : "";
133
+ return `${graphPart}:${configPart}`;
134
+ }
135
+ function animationsSignature(animations) {
136
+ if (!Array.isArray(animations) || animations.length === 0) {
137
+ return "";
138
+ }
139
+ return animations.map((animation) => {
140
+ const id = animation.id ?? "";
141
+ const clipSignature = normalizeSpecPayload(animation.clip ?? null);
142
+ const setupSignature = normalizeSpecPayload(animation.setup ?? null);
143
+ const weightSignature = animation.weight == null ? "" : String(animation.weight);
144
+ return `${id}:${clipSignature}:${setupSignature}:${weightSignature}`;
145
+ }).sort().join("|");
146
+ }
147
+ function programsSignature(programs) {
148
+ if (!Array.isArray(programs) || programs.length === 0) {
149
+ return "";
150
+ }
151
+ return programs.map((program) => {
152
+ const id = program.id ?? "";
153
+ const label = program.label ?? "";
154
+ const graphId = program.graph?.id ?? "";
155
+ const graphPayload = normalizeSpecPayload(
156
+ program.graph?.spec ?? program.graph?.ir ?? null
157
+ );
158
+ const resetValues = normalizeSpecPayload(program.resetValues ?? null);
159
+ return `${id}:${label}:${graphId}:${graphPayload}:${resetValues}`;
160
+ }).sort().join("|");
161
+ }
162
+ function resolveRuntimeUpdatePlan(previous, next, tier) {
163
+ if (!previous) {
164
+ return { reloadAssets: true, reregisterGraphs: false };
165
+ }
166
+ const glbChanged = glbSignature(previous.glb) !== glbSignature(next.glb);
167
+ const rigChanged = graphSignature(previous.rig) !== graphSignature(next.rig);
168
+ const poseChanged = poseSignature(previous.pose) !== poseSignature(next.pose);
169
+ const rigReferenceChanged = previous.rig?.id !== next.rig?.id || previous.rig?.spec !== next.rig?.spec || previous.rig?.ir !== next.rig?.ir;
170
+ const poseReferenceChanged = previous.pose?.graph?.id !== next.pose?.graph?.id || previous.pose?.graph?.spec !== next.pose?.graph?.spec || previous.pose?.config !== next.pose?.config;
171
+ const graphsChanged = rigChanged || poseChanged || rigReferenceChanged || poseReferenceChanged;
172
+ const animationsChanged = animationsSignature(previous.animations) !== animationsSignature(next.animations);
173
+ const programsChanged = programsSignature(previous.programs) !== programsSignature(next.programs);
174
+ const controllersChanged = graphsChanged || animationsChanged || programsChanged;
175
+ if (tier === "assets") {
176
+ return { reloadAssets: true, reregisterGraphs: false };
177
+ }
178
+ if (tier === "graphs") {
179
+ if (glbChanged) {
180
+ return { reloadAssets: true, reregisterGraphs: false };
181
+ }
182
+ return { reloadAssets: false, reregisterGraphs: controllersChanged };
183
+ }
184
+ if (glbChanged) {
185
+ return { reloadAssets: true, reregisterGraphs: false };
186
+ }
187
+ if (controllersChanged) {
188
+ return { reloadAssets: false, reregisterGraphs: true };
189
+ }
190
+ return { reloadAssets: false, reregisterGraphs: false };
191
+ }
192
+
193
+ // src/utils/graph.ts
194
+ function getNodes(spec) {
195
+ if (!spec || typeof spec !== "object") {
196
+ return [];
197
+ }
198
+ const maybeNodes = spec.nodes;
199
+ if (!Array.isArray(maybeNodes)) {
200
+ return [];
201
+ }
202
+ return maybeNodes;
203
+ }
204
+ function collectOutputPaths(spec) {
205
+ const nodes = getNodes(spec);
206
+ const outputs = /* @__PURE__ */ new Set();
207
+ nodes.forEach((node) => {
208
+ if (typeof node !== "object" || !node) {
209
+ return;
210
+ }
211
+ if (String(node.type ?? "").toLowerCase() !== "output") {
212
+ return;
213
+ }
214
+ const path = node.params?.path;
215
+ if (typeof path === "string" && path.trim()) {
216
+ outputs.add(path.trim());
217
+ }
218
+ });
219
+ return Array.from(outputs);
220
+ }
221
+ function collectInputPaths(spec) {
222
+ const nodes = getNodes(spec);
223
+ const inputs = /* @__PURE__ */ new Set();
224
+ nodes.forEach((node) => {
225
+ if (String(node.type ?? "").toLowerCase() !== "input") {
226
+ return;
227
+ }
228
+ const path = node.params?.path;
229
+ if (typeof path === "string" && path.trim()) {
230
+ inputs.add(path.trim());
231
+ }
232
+ });
233
+ return Array.from(inputs);
234
+ }
235
+ function collectInputPathMap(spec) {
236
+ const map = {};
237
+ const addVariant = (key, path, force = false) => {
238
+ if (!key || !force && map[key]) {
239
+ return;
240
+ }
241
+ map[key] = path;
242
+ };
243
+ const nodes = getNodes(spec);
244
+ nodes.forEach((node) => {
245
+ if (String(node.type ?? "").toLowerCase() !== "input") {
246
+ return;
247
+ }
248
+ const path = node.params?.path;
249
+ if (typeof path !== "string" || !path.trim()) {
250
+ return;
251
+ }
252
+ const id = String(node.id ?? "");
253
+ const key = id.startsWith("input_") ? id.slice("input_".length) : id || path.trim();
254
+ const trimmedPath = path.trim();
255
+ addVariant(key, trimmedPath);
256
+ if (key.startsWith("direct_")) {
257
+ addVariant(key.slice("direct_".length), trimmedPath, true);
258
+ }
259
+ if (key.startsWith("pose_control_")) {
260
+ addVariant(key.slice("pose_control_".length), trimmedPath);
261
+ }
262
+ });
263
+ return map;
264
+ }
265
+
266
+ // src/utils/posePaths.ts
267
+ var POSE_WEIGHT_INPUT_PATH_PREFIX = "/poses/";
268
+ var VISEME_POSE_KEYS = [
269
+ "a",
270
+ "at",
271
+ "b",
272
+ "e",
273
+ "e_2",
274
+ "f",
275
+ "i",
276
+ "k",
277
+ "m",
278
+ "o",
279
+ "o_2",
280
+ "p",
281
+ "r",
282
+ "s",
283
+ "t",
284
+ "t_2",
285
+ "u"
286
+ ];
287
+ var EXPRESSIVE_EMOTION_POSE_KEYS = [
288
+ "concerned",
289
+ "happy",
290
+ "sad",
291
+ "sleepy",
292
+ "surprise"
293
+ ];
294
+ var EMOTION_POSE_KEYS = [
295
+ "concerned",
296
+ "happy",
297
+ "neutral",
298
+ "sad",
299
+ "sleepy",
300
+ "surprise",
301
+ "angry"
302
+ ];
303
+ var VISEME_GROUP_NEEDLES = ["viseme", "phoneme", "lip", "mouth"];
304
+ var EMOTION_GROUP_NEEDLES = ["emotion", "expression", "mood", "affect"];
305
+ var VISEME_POSE_KEY_SET = new Set(VISEME_POSE_KEYS);
306
+ var EMOTION_POSE_KEY_SET = new Set(EMOTION_POSE_KEYS);
307
+ var POSE_KEY_ALIASES = {
308
+ concern: "concerned",
309
+ surprised: "surprise"
310
+ };
311
+ function buildRigInputPath(faceId, path) {
312
+ let trimmed = path.startsWith("/") ? path.slice(1) : path;
313
+ if (!trimmed) {
314
+ return `rig/${faceId}`;
315
+ }
316
+ while (trimmed.startsWith("rig/")) {
317
+ const segments = trimmed.split("/");
318
+ if (segments.length >= 3) {
319
+ const existingFaceId = segments[1];
320
+ const remainder = segments.slice(2).join("/");
321
+ if (existingFaceId === faceId) {
322
+ return trimmed;
323
+ }
324
+ trimmed = remainder || "";
325
+ } else {
326
+ trimmed = segments.slice(1).join("/");
327
+ }
328
+ }
329
+ const suffix = trimmed ? `/${trimmed}` : "";
330
+ return `rig/${faceId}${suffix}`;
331
+ }
332
+ function normalizePoseWeightPathSegment(value) {
333
+ const trimmed = value?.trim() ?? "";
334
+ if (!trimmed) {
335
+ return "pose";
336
+ }
337
+ const normalized = trimmed.replace(/[^a-zA-Z0-9_-]+/g, "_").replace(/^_+|_+$/g, "");
338
+ return normalized || "pose";
339
+ }
340
+ function buildPoseWeightInputPathSegment(poseId) {
341
+ return normalizePoseWeightPathSegment(poseId);
342
+ }
343
+ function buildPoseWeightRelativePath(poseId) {
344
+ return `${POSE_WEIGHT_INPUT_PATH_PREFIX}${buildPoseWeightInputPathSegment(
345
+ poseId
346
+ )}.weight`;
347
+ }
348
+ function buildPoseWeightPathMap(poses, faceId) {
349
+ const faceSegment = faceId?.trim() || "face";
350
+ const map = /* @__PURE__ */ new Map();
351
+ poses.forEach((pose) => {
352
+ map.set(
353
+ pose.id,
354
+ buildRigInputPath(faceSegment, buildPoseWeightRelativePath(pose.id))
355
+ );
356
+ });
357
+ return map;
358
+ }
359
+ function normalizePoseSemanticKey(value) {
360
+ const trimmed = value?.trim() ?? "";
361
+ if (!trimmed) {
362
+ return null;
363
+ }
364
+ const normalized = trimmed.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").replace(/_+/g, "_");
365
+ if (!normalized) {
366
+ return null;
367
+ }
368
+ return POSE_KEY_ALIASES[normalized] ?? normalized;
369
+ }
370
+ function derivePoseSemanticKeyFromId(poseId) {
371
+ const trimmed = poseId?.trim() ?? "";
372
+ if (!trimmed) {
373
+ return null;
374
+ }
375
+ const stripped = trimmed.replace(/^pose_d_/i, "").replace(/^pose_/i, "").replace(/^d_/i, "").replace(/_d$/i, "");
376
+ return normalizePoseSemanticKey(stripped);
377
+ }
378
+ function getPoseSemanticKey(pose) {
379
+ return normalizePoseSemanticKey(pose.name) ?? derivePoseSemanticKeyFromId(pose.id) ?? null;
380
+ }
381
+ function normalizePoseGroupPath(value) {
382
+ const trimmed = value?.trim() ?? "";
383
+ if (!trimmed) {
384
+ return null;
385
+ }
386
+ return trimmed.replace(/^\/+|\/+$/g, "").replace(/\/+/g, "/");
387
+ }
388
+ function sanitizePoseGroupId(value, fallback) {
389
+ const normalized = (value ?? "").trim().replace(/[^a-zA-Z0-9_/-]+/g, "_").replace(/^\/+|\/+$/g, "").replace(/\/+/g, "_");
390
+ if (!normalized) {
391
+ return fallback.replace(/\//g, "_");
392
+ }
393
+ return normalized;
394
+ }
395
+ function buildPoseGroupLookup(groups) {
396
+ const byId = /* @__PURE__ */ new Map();
397
+ const byPath = /* @__PURE__ */ new Map();
398
+ const orderById = /* @__PURE__ */ new Map();
399
+ (groups ?? []).forEach((group, index) => {
400
+ const path = normalizePoseGroupPath(group.path ?? group.name ?? group.id);
401
+ if (!path) {
402
+ return;
403
+ }
404
+ const id = sanitizePoseGroupId(group.id, path);
405
+ const humanizedName = typeof group.name === "string" && group.name.trim().length > 0 ? group.name.trim() : path.split("/").filter(Boolean).pop()?.split(/[_-]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ") ?? path;
406
+ const normalized = {
407
+ ...group,
408
+ id,
409
+ path,
410
+ name: humanizedName
411
+ };
412
+ byId.set(id, normalized);
413
+ if (!orderById.has(id)) {
414
+ orderById.set(id, index);
415
+ }
416
+ if (!byPath.has(path)) {
417
+ byPath.set(path, normalized);
418
+ }
419
+ });
420
+ return { byId, byPath, orderById };
421
+ }
422
+ function orderPoseMembershipIds(groupIds, groups) {
423
+ const { orderById } = buildPoseGroupLookup(groups);
424
+ const unique = Array.from(
425
+ new Set(
426
+ Array.from(groupIds).map((groupId) => groupId.trim()).filter((groupId) => groupId.length > 0)
427
+ )
428
+ );
429
+ unique.sort((left, right) => {
430
+ const leftIndex = orderById.get(left);
431
+ const rightIndex = orderById.get(right);
432
+ if (leftIndex !== void 0 && rightIndex !== void 0) {
433
+ return leftIndex - rightIndex;
434
+ }
435
+ if (leftIndex !== void 0) {
436
+ return -1;
437
+ }
438
+ if (rightIndex !== void 0) {
439
+ return 1;
440
+ }
441
+ const leftPath = normalizePoseGroupPath(left) ?? left;
442
+ const rightPath = normalizePoseGroupPath(right) ?? right;
443
+ const byPath = leftPath.localeCompare(rightPath);
444
+ if (byPath !== 0) {
445
+ return byPath;
446
+ }
447
+ return left.localeCompare(right);
448
+ });
449
+ return unique;
450
+ }
451
+ function valueHasNeedle(value, needles) {
452
+ const normalized = normalizePoseSemanticKey(value);
453
+ if (!normalized) {
454
+ return false;
455
+ }
456
+ return needles.some((needle) => normalized.includes(needle));
457
+ }
458
+ function resolvePoseMembership(pose, groups) {
459
+ const { byId, byPath } = buildPoseGroupLookup(groups);
460
+ const resolvedGroupIds = [];
461
+ const pathById = /* @__PURE__ */ new Map();
462
+ const addMembership = (groupId, path) => {
463
+ if (!groupId) {
464
+ return;
465
+ }
466
+ if (!resolvedGroupIds.includes(groupId)) {
467
+ resolvedGroupIds.push(groupId);
468
+ }
469
+ if (!path) {
470
+ return;
471
+ }
472
+ const existingPath = pathById.get(groupId);
473
+ const normalizedGroupIdPath = normalizePoseGroupPath(groupId);
474
+ const normalizedExistingPath = normalizePoseGroupPath(existingPath) ?? existingPath ?? null;
475
+ const normalizedIncomingPath = normalizePoseGroupPath(path) ?? path ?? null;
476
+ const shouldPromotePath = normalizedExistingPath === null || normalizedGroupIdPath !== null && normalizedExistingPath === normalizedGroupIdPath && normalizedIncomingPath !== normalizedGroupIdPath;
477
+ if (shouldPromotePath) {
478
+ pathById.set(groupId, path);
479
+ }
480
+ };
481
+ const addByPath = (rawPath) => {
482
+ const normalizedPath = normalizePoseGroupPath(rawPath);
483
+ if (!normalizedPath) {
484
+ return;
485
+ }
486
+ const existing = byPath.get(normalizedPath);
487
+ if (existing) {
488
+ addMembership(existing.id, existing.path);
489
+ return;
490
+ }
491
+ addMembership(sanitizePoseGroupId(null, normalizedPath), normalizedPath);
492
+ };
493
+ const addById = (rawId) => {
494
+ const trimmed = rawId?.trim() ?? "";
495
+ if (!trimmed) {
496
+ return;
497
+ }
498
+ const normalizedPath = normalizePoseGroupPath(trimmed);
499
+ const normalizedId = sanitizePoseGroupId(trimmed, trimmed);
500
+ const matchedById = byId.get(trimmed) ?? byId.get(normalizedId);
501
+ if (matchedById) {
502
+ addMembership(matchedById.id, matchedById.path);
503
+ return;
504
+ }
505
+ if (normalizedPath) {
506
+ const matchedByPath = byPath.get(normalizedPath);
507
+ if (matchedByPath) {
508
+ addMembership(matchedByPath.id, matchedByPath.path);
509
+ return;
510
+ }
511
+ }
512
+ addMembership(
513
+ normalizedId,
514
+ normalizedPath && normalizedPath.length > 0 ? normalizedPath : null
515
+ );
516
+ };
517
+ pose.groupIds?.forEach((groupId) => addById(groupId));
518
+ addById(pose.groupId);
519
+ addByPath(pose.group);
520
+ const orderedGroupIds = orderPoseMembershipIds(resolvedGroupIds, groups);
521
+ const primaryGroupId = orderedGroupIds[0] ?? null;
522
+ const primaryGroupPath = primaryGroupId ? byId.get(primaryGroupId)?.path ?? pathById.get(primaryGroupId) ?? null : null;
523
+ const groupPathsById = {};
524
+ orderedGroupIds.forEach((groupId) => {
525
+ const path = byId.get(groupId)?.path ?? pathById.get(groupId) ?? null;
526
+ if (path) {
527
+ groupPathsById[groupId] = path;
528
+ }
529
+ });
530
+ return {
531
+ groupIds: orderedGroupIds,
532
+ primaryGroupId,
533
+ primaryGroupPath,
534
+ groupPathsById
535
+ };
536
+ }
537
+ function poseMatchesGroupKind(pose, groups, needles) {
538
+ const membership = resolvePoseMembership(pose, groups);
539
+ if (valueHasNeedle(pose.group, needles) || valueHasNeedle(pose.groupId, needles)) {
540
+ return true;
541
+ }
542
+ if (pose.groupIds?.some((groupId) => valueHasNeedle(groupId, needles))) {
543
+ return true;
544
+ }
545
+ return membership.groupIds.some((groupId) => {
546
+ const path = membership.groupPathsById[groupId] ?? null;
547
+ const group = (groups ?? []).find((entry) => entry.id === groupId) ?? null;
548
+ return valueHasNeedle(groupId, needles) || valueHasNeedle(path, needles) || valueHasNeedle(group?.name, needles);
549
+ });
550
+ }
551
+ function resolvePoseSemantics(pose, groups) {
552
+ const membership = resolvePoseMembership(pose, groups);
553
+ const key = getPoseSemanticKey(pose);
554
+ const looksLikeVisemeGroup = poseMatchesGroupKind(
555
+ pose,
556
+ groups,
557
+ VISEME_GROUP_NEEDLES
558
+ );
559
+ const looksLikeEmotionGroup = poseMatchesGroupKind(
560
+ pose,
561
+ groups,
562
+ EMOTION_GROUP_NEEDLES
563
+ );
564
+ let kind = "other";
565
+ if (looksLikeVisemeGroup || key && VISEME_POSE_KEY_SET.has(key)) {
566
+ kind = "viseme";
567
+ } else if (looksLikeEmotionGroup || key && EMOTION_POSE_KEY_SET.has(key)) {
568
+ kind = "emotion";
569
+ }
570
+ return { key, kind, membership };
571
+ }
572
+ function filterPosesBySemanticKind(poses, groups, kind) {
573
+ return poses.filter(
574
+ (pose) => resolvePoseSemantics(pose, groups).kind === kind
575
+ );
576
+ }
577
+ function buildSemanticPoseWeightPathMap(poses, groups, faceId, kind) {
578
+ const map = /* @__PURE__ */ new Map();
579
+ const pathMap = buildPoseWeightPathMap(poses, faceId);
580
+ poses.forEach((pose) => {
581
+ const semantics = resolvePoseSemantics(pose, groups);
582
+ const path = pathMap.get(pose.id);
583
+ if (semantics.kind !== kind || !semantics.key || !path || map.has(semantics.key)) {
584
+ return;
585
+ }
586
+ map.set(semantics.key, path);
587
+ });
588
+ return map;
589
+ }
590
+
591
+ // src/utils/poseRuntime.ts
592
+ function shouldUseLegacyPoseWeightFallback(hasPoseGraph) {
593
+ return !hasPoseGraph;
594
+ }
595
+ function resolvePoseControlInputPath({
596
+ inputId,
597
+ basePath,
598
+ rigInputPathMap,
599
+ hasNativePoseControlInput
600
+ }) {
601
+ if (!inputId.trim()) {
602
+ return void 0;
603
+ }
604
+ return rigInputPathMap[inputId] ?? rigInputPathMap[`pose_control_${inputId}`] ?? rigInputPathMap[`direct_${inputId}`] ?? (hasNativePoseControlInput ? basePath : void 0);
605
+ }
606
+
607
+ // src/utils/clipPlayback.ts
608
+ var EPSILON = 1e-6;
609
+ function toFiniteNumber(value, fallback) {
610
+ const parsed = Number(value);
611
+ return Number.isFinite(parsed) ? parsed : fallback;
612
+ }
613
+ function normaliseInterpolation(interpolation) {
614
+ const mode = typeof interpolation === "string" ? interpolation.trim().toLowerCase() : "linear";
615
+ if (mode === "step") {
616
+ return "step";
617
+ }
618
+ if (mode === "cubic" || mode === "cubicspline") {
619
+ return "cubic";
620
+ }
621
+ return "linear";
622
+ }
623
+ function asNumericKeyframe(keyframe) {
624
+ if (!keyframe || typeof keyframe !== "object") {
625
+ return null;
626
+ }
627
+ const time = Number(keyframe.time);
628
+ const value = Number(keyframe.value);
629
+ if (!Number.isFinite(time) || !Number.isFinite(value)) {
630
+ return null;
631
+ }
632
+ const inTangentRaw = keyframe.inTangent;
633
+ const outTangentRaw = keyframe.outTangent;
634
+ const inTangent = inTangentRaw == null || Number.isFinite(Number(inTangentRaw)) ? inTangentRaw : void 0;
635
+ const outTangent = outTangentRaw == null || Number.isFinite(Number(outTangentRaw)) ? outTangentRaw : void 0;
636
+ return {
637
+ time,
638
+ value,
639
+ inTangent,
640
+ outTangent
641
+ };
642
+ }
643
+ function getNumericKeyframes(track) {
644
+ const keyframes = Array.isArray(track.keyframes) ? track.keyframes : [];
645
+ const numeric = keyframes.map((keyframe) => asNumericKeyframe(keyframe)).filter((keyframe) => Boolean(keyframe));
646
+ if (numeric.length <= 1) {
647
+ return numeric;
648
+ }
649
+ return [...numeric].sort((a, b) => a.time - b.time);
650
+ }
651
+ function resolveTangent(value, fallback) {
652
+ const parsed = Number(value);
653
+ if (Number.isFinite(parsed)) {
654
+ return parsed;
655
+ }
656
+ return fallback;
657
+ }
658
+ function sampleHermite(startValue, endValue, outTangent, inTangent, factor, duration) {
659
+ const t = factor;
660
+ const t2 = t * t;
661
+ const t3 = t2 * t;
662
+ const h00 = 2 * t3 - 3 * t2 + 1;
663
+ const h10 = t3 - 2 * t2 + t;
664
+ const h01 = -2 * t3 + 3 * t2;
665
+ const h11 = t3 - t2;
666
+ return h00 * startValue + h10 * outTangent * duration + h01 * endValue + h11 * inTangent * duration;
667
+ }
668
+ function resolveClipDurationSeconds(clip, fallbackDurationSeconds = 0) {
669
+ const fallback = Math.max(0, toFiniteNumber(fallbackDurationSeconds, 0));
670
+ if (!clip || typeof clip !== "object") {
671
+ return fallback;
672
+ }
673
+ const clipDuration = Number(clip.duration);
674
+ if (Number.isFinite(clipDuration) && clipDuration > 0) {
675
+ return clipDuration;
676
+ }
677
+ const tracks = Array.isArray(clip.tracks) ? clip.tracks : [];
678
+ let maxTime = 0;
679
+ tracks.forEach((track) => {
680
+ const keyframes = getNumericKeyframes(track);
681
+ const last = keyframes[keyframes.length - 1];
682
+ if (last && last.time > maxTime) {
683
+ maxTime = last.time;
684
+ }
685
+ });
686
+ return maxTime > 0 ? maxTime : fallback;
687
+ }
688
+ function clampAnimationTime(time, duration) {
689
+ if (!Number.isFinite(duration) || duration <= 0) {
690
+ return 0;
691
+ }
692
+ if (!Number.isFinite(time)) {
693
+ return 0;
694
+ }
695
+ if (time <= 0) {
696
+ return 0;
697
+ }
698
+ if (time >= duration) {
699
+ return duration;
700
+ }
701
+ return time;
702
+ }
703
+ function advanceClipTime(state, dt) {
704
+ const duration = Math.max(0, toFiniteNumber(state.duration, 0));
705
+ const speed = Number.isFinite(state.speed) && state.speed > 0 ? state.speed : 1;
706
+ const currentTime = clampAnimationTime(state.time, duration);
707
+ const delta = Number.isFinite(dt) && dt > 0 ? Math.max(0, dt) * speed : 0;
708
+ if (!state.playing || delta <= 0) {
709
+ return { time: currentTime, completed: false };
710
+ }
711
+ if (duration <= 0) {
712
+ return { time: 0, completed: true };
713
+ }
714
+ const nextTime = currentTime + delta;
715
+ if (state.loop) {
716
+ if (nextTime < duration) {
717
+ return { time: nextTime, completed: false };
718
+ }
719
+ const wrapped = (nextTime % duration + duration) % duration;
720
+ return { time: wrapped, completed: false };
721
+ }
722
+ if (nextTime >= duration - EPSILON) {
723
+ return { time: duration, completed: true };
724
+ }
725
+ return { time: nextTime, completed: false };
726
+ }
727
+ function sampleTrackAtTime(track, timeSeconds) {
728
+ const keyframes = getNumericKeyframes(track);
729
+ if (keyframes.length === 0) {
730
+ return 0;
731
+ }
732
+ if (keyframes.length === 1) {
733
+ return keyframes[0].value;
734
+ }
735
+ const mode = normaliseInterpolation(track.interpolation);
736
+ const time = Number.isFinite(timeSeconds) ? timeSeconds : 0;
737
+ const first = keyframes[0];
738
+ if (time <= first.time + EPSILON) {
739
+ return first.value;
740
+ }
741
+ const last = keyframes[keyframes.length - 1];
742
+ if (time >= last.time - EPSILON) {
743
+ return last.value;
744
+ }
745
+ for (let i = 0; i < keyframes.length - 1; i += 1) {
746
+ const current = keyframes[i];
747
+ const next = keyframes[i + 1];
748
+ const start = current.time;
749
+ const end = next.time;
750
+ const duration = end - start;
751
+ if (duration <= EPSILON) {
752
+ if (time <= end + EPSILON) {
753
+ return next.value;
754
+ }
755
+ continue;
756
+ }
757
+ if (Math.abs(time - end) <= EPSILON) {
758
+ return next.value;
759
+ }
760
+ if (time < end) {
761
+ const factor = (time - start) / duration;
762
+ if (mode === "step") {
763
+ return current.value;
764
+ }
765
+ if (mode === "cubic") {
766
+ const slope = (next.value - current.value) / duration;
767
+ const outTangent = resolveTangent(current.outTangent, slope);
768
+ const inTangent = resolveTangent(next.inTangent, slope);
769
+ return sampleHermite(
770
+ current.value,
771
+ next.value,
772
+ outTangent,
773
+ inTangent,
774
+ factor,
775
+ duration
776
+ );
777
+ }
778
+ return current.value + (next.value - current.value) * factor;
779
+ }
780
+ }
781
+ return last.value;
782
+ }
783
+
784
+ // src/utils/animationBridge.ts
785
+ function resolveAnimationBridgeOutputPaths(channel, faceId, rigInputMap) {
786
+ const normalizedChannel = channel.trim().replace(/^\/+/, "");
787
+ if (!normalizedChannel) {
788
+ return [];
789
+ }
790
+ const outputPaths = /* @__PURE__ */ new Set([normalizedChannel]);
791
+ if (normalizedChannel.startsWith("animation/")) {
792
+ return Array.from(outputPaths).sort(
793
+ (left, right) => left.localeCompare(right)
794
+ );
795
+ }
796
+ if (rigInputMap && Object.keys(rigInputMap).length > 0) {
797
+ const candidateKeys = /* @__PURE__ */ new Set([normalizedChannel]);
798
+ const rigChannelMatch2 = /^rig\/[^/]+\/(.+)$/.exec(normalizedChannel);
799
+ if (rigChannelMatch2?.[1]) {
800
+ candidateKeys.add(rigChannelMatch2[1]);
801
+ }
802
+ candidateKeys.forEach((key) => {
803
+ const mapped = rigInputMap[key];
804
+ const normalized = mapped?.trim().replace(/^\/+/, "");
805
+ if (normalized) {
806
+ outputPaths.add(normalized);
807
+ }
808
+ });
809
+ const suffix = normalizedChannel.includes("/") ? normalizedChannel : `/${normalizedChannel}`;
810
+ Object.values(rigInputMap).forEach((mappedPath) => {
811
+ const normalized = mappedPath?.trim().replace(/^\/+/, "");
812
+ if (!normalized) {
813
+ return;
814
+ }
815
+ if (normalized === normalizedChannel || normalized.endsWith(suffix)) {
816
+ outputPaths.add(normalized);
817
+ }
818
+ });
819
+ }
820
+ if (!faceId) {
821
+ return Array.from(outputPaths).sort(
822
+ (left, right) => left.localeCompare(right)
823
+ );
824
+ }
825
+ const rigChannelMatch = /^rig\/[^/]+\/(.+)$/.exec(normalizedChannel);
826
+ if (rigChannelMatch?.[1]) {
827
+ outputPaths.add(`rig/${faceId}/${rigChannelMatch[1]}`);
828
+ } else if (!normalizedChannel.startsWith("rig/")) {
829
+ outputPaths.add(`rig/${faceId}/${normalizedChannel}`);
830
+ }
831
+ return Array.from(outputPaths).sort(
832
+ (left, right) => left.localeCompare(right)
833
+ );
834
+ }
835
+ function collectAnimationClipOutputPaths(clip, faceId, rigInputMap) {
836
+ const outputPaths = /* @__PURE__ */ new Set();
837
+ const tracks = Array.isArray(clip.tracks) ? clip.tracks : [];
838
+ tracks.forEach((track) => {
839
+ const channel = typeof track.channel === "string" ? track.channel.trim() : "";
840
+ if (!channel) {
841
+ return;
842
+ }
843
+ resolveAnimationBridgeOutputPaths(channel, faceId, rigInputMap).forEach(
844
+ (path) => {
845
+ outputPaths.add(path);
846
+ }
847
+ );
848
+ });
849
+ return Array.from(outputPaths).sort(
850
+ (left, right) => left.localeCompare(right)
851
+ );
852
+ }
853
+ function sampleAnimationClipOutputValues(clip, timeSeconds, weight = 1, faceId, rigInputMap) {
854
+ const appliedWeight = Number.isFinite(weight) && weight >= 0 ? Number(weight) : 1;
855
+ const outputValues = /* @__PURE__ */ new Map();
856
+ const tracks = Array.isArray(clip.tracks) ? clip.tracks : [];
857
+ tracks.forEach((track) => {
858
+ const channel = typeof track.channel === "string" ? track.channel.trim() : "";
859
+ if (!channel) {
860
+ return;
861
+ }
862
+ const sampledValue = sampleTrackAtTime(
863
+ track,
864
+ timeSeconds
865
+ );
866
+ const weightedValue = sampledValue * appliedWeight;
867
+ resolveAnimationBridgeOutputPaths(channel, faceId, rigInputMap).forEach(
868
+ (path) => {
869
+ outputValues.set(path, (outputValues.get(path) ?? 0) + weightedValue);
870
+ }
871
+ );
872
+ });
873
+ return outputValues;
874
+ }
875
+ function diffAnimationAggregateValues(previousAggregate, nextAggregate, epsilon = 1e-6) {
876
+ const operations = [];
877
+ const changedPaths = /* @__PURE__ */ new Set();
878
+ previousAggregate.forEach((previousValue, path) => {
879
+ const nextValue = nextAggregate.get(path);
880
+ if (nextValue === void 0 || Math.abs(nextValue - previousValue) > epsilon) {
881
+ changedPaths.add(path);
882
+ }
883
+ });
884
+ nextAggregate.forEach((nextValue, path) => {
885
+ const previousValue = previousAggregate.get(path);
886
+ if (previousValue === void 0 || Math.abs(nextValue - previousValue) > epsilon) {
887
+ changedPaths.add(path);
888
+ }
889
+ });
890
+ changedPaths.forEach((path) => {
891
+ const nextValue = nextAggregate.get(path);
892
+ if (nextValue === void 0) {
893
+ operations.push({ kind: "clear", path });
894
+ return;
895
+ }
896
+ operations.push({ kind: "set", path, value: nextValue });
897
+ });
898
+ return operations;
899
+ }
900
+
901
+ // src/utils/valueConversion.ts
902
+ var import_value_json = require("@vizij/value-json");
903
+ function numericArrayToRaw(arr) {
904
+ const normalised = arr.map((entry) => Number(entry ?? 0));
905
+ switch (normalised.length) {
906
+ case 2:
907
+ return {
908
+ x: normalised[0],
909
+ y: normalised[1],
910
+ r: normalised[0],
911
+ g: normalised[1]
912
+ };
913
+ case 3:
914
+ return {
915
+ x: normalised[0],
916
+ y: normalised[1],
917
+ z: normalised[2],
918
+ r: normalised[0],
919
+ g: normalised[1],
920
+ b: normalised[2]
921
+ };
922
+ case 4:
923
+ return {
924
+ x: normalised[0],
925
+ y: normalised[1],
926
+ z: normalised[2],
927
+ w: normalised[3],
928
+ r: normalised[0],
929
+ g: normalised[1],
930
+ b: normalised[2],
931
+ a: normalised[3]
932
+ };
933
+ default:
934
+ return normalised;
935
+ }
936
+ }
937
+ function valueJSONToRaw(value) {
938
+ if (value == null) {
939
+ return void 0;
940
+ }
941
+ if (typeof value === "number" || typeof value === "string" || typeof value === "boolean") {
942
+ return value;
943
+ }
944
+ if (Array.isArray(value)) {
945
+ return value.map((entry) => valueJSONToRaw(entry));
946
+ }
947
+ if (typeof value === "object" && !("type" in value)) {
948
+ const entries = Object.entries(value).map(([key, entry]) => [
949
+ key,
950
+ valueJSONToRaw(entry)
951
+ ]);
952
+ return Object.fromEntries(entries);
953
+ }
954
+ if (!(0, import_value_json.isNormalizedValue)(value)) {
955
+ return void 0;
956
+ }
957
+ switch (value.type) {
958
+ case "float": {
959
+ const num = (0, import_value_json.valueAsNumber)(value);
960
+ return typeof num === "number" ? num : void 0;
961
+ }
962
+ case "bool": {
963
+ const boolVal = (0, import_value_json.valueAsBool)(value);
964
+ return typeof boolVal === "boolean" ? boolVal : void 0;
965
+ }
966
+ case "text": {
967
+ const text = (0, import_value_json.valueAsText)(value);
968
+ return typeof text === "string" ? text : void 0;
969
+ }
970
+ case "vec2":
971
+ case "vec3":
972
+ case "vec4":
973
+ case "quat":
974
+ case "vector": {
975
+ const vec = (0, import_value_json.valueAsVector)(value);
976
+ return vec ? numericArrayToRaw(vec) : void 0;
977
+ }
978
+ case "colorrgba": {
979
+ const color = (0, import_value_json.valueAsColorRgba)(value);
980
+ if (!color) {
981
+ return void 0;
982
+ }
983
+ const [r = 0, g = 0, b = 0, a = 1] = color;
984
+ return { r, g, b, a };
985
+ }
986
+ case "transform": {
987
+ const transform = (0, import_value_json.valueAsTransform)(value);
988
+ if (!transform) {
989
+ return void 0;
990
+ }
991
+ return {
992
+ translation: numericArrayToRaw(transform.translation),
993
+ rotation: numericArrayToRaw(transform.rotation),
994
+ scale: numericArrayToRaw(transform.scale)
995
+ };
996
+ }
997
+ case "record": {
998
+ const entries = Object.entries(value.data ?? {}).map(([key, entry]) => [
999
+ key,
1000
+ valueJSONToRaw(entry)
1001
+ ]);
1002
+ return Object.fromEntries(entries);
1003
+ }
1004
+ case "enum": {
1005
+ const [tag, inner] = value.data;
1006
+ return {
1007
+ tag,
1008
+ value: valueJSONToRaw(inner)
1009
+ };
1010
+ }
1011
+ default:
1012
+ return void 0;
1013
+ }
1014
+ }
1015
+
1016
+ // src/VizijRuntimeProvider.tsx
1017
+ var import_jsx_runtime = require("react/jsx-runtime");
1018
+ var ACTIVE_GRACE_MS = 250;
1019
+ var VISIBLE_IDLE_FPS = 30;
1020
+ var HIDDEN_IDLE_FPS = 1;
1021
+ var DEFAULT_MERGE = {
1022
+ outputs: "add",
1023
+ intermediate: "add"
1024
+ };
1025
+ var DEFAULT_DURATION = 0.35;
1026
+ var POSE_CONTROL_BRIDGE_EPSILON = 1e-6;
1027
+ var DEV_MODE = (() => {
1028
+ const nodeEnv = globalThis.process?.env?.NODE_ENV;
1029
+ return typeof nodeEnv === "string" && nodeEnv === "development";
1030
+ })();
1031
+ function isRuntimeDebugEnabled() {
1032
+ if (DEV_MODE) {
1033
+ return true;
1034
+ }
1035
+ return Boolean(
1036
+ globalThis.__VIZIJ_RUNTIME_DEBUG__
1037
+ );
1038
+ }
1039
+ var EASINGS = {
1040
+ linear: (t) => t,
1041
+ easeIn: (t) => t * t,
1042
+ easeOut: (t) => 1 - (1 - t) * (1 - t),
1043
+ easeInOut: (t) => t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2
1044
+ };
1045
+ function resolveEasing(easing) {
1046
+ if (typeof easing === "function") {
1047
+ return easing;
1048
+ }
1049
+ if (typeof easing === "string" && easing in EASINGS) {
1050
+ return EASINGS[easing];
1051
+ }
1052
+ return EASINGS.linear;
1053
+ }
1054
+ function findRootId(world) {
1055
+ let fallback = null;
1056
+ for (const entry of Object.values(world)) {
1057
+ if (!entry || typeof entry !== "object" || entry.type !== "group") {
1058
+ continue;
1059
+ }
1060
+ if (entry.rootBounds && entry.id) {
1061
+ return entry.id;
1062
+ }
1063
+ if (!fallback && entry.id) {
1064
+ fallback = entry.id;
1065
+ }
1066
+ }
1067
+ return fallback;
1068
+ }
1069
+ function normalisePath(path) {
1070
+ if (!path) {
1071
+ return path;
1072
+ }
1073
+ return path.startsWith("debug/") ? path.slice("debug/".length) : path;
1074
+ }
1075
+ function normaliseBundleKind(kind) {
1076
+ return typeof kind === "string" ? kind.toLowerCase() : "";
1077
+ }
1078
+ function addConstraintVariant(map, key, constraint) {
1079
+ if (!key) return;
1080
+ if (!map[key]) {
1081
+ map[key] = constraint;
1082
+ }
1083
+ }
1084
+ function stripRigFacePrefix(path) {
1085
+ const trimmed = path.startsWith("/") ? path.slice(1) : path;
1086
+ const match = /^rig\/[^/]+\/(.+)$/.exec(trimmed);
1087
+ if (match && match[1]) {
1088
+ return match[1];
1089
+ }
1090
+ if (trimmed.startsWith("rig/")) {
1091
+ return trimmed.slice("rig/".length);
1092
+ }
1093
+ return trimmed;
1094
+ }
1095
+ function extractInputConstraints(spec, extraInputs, namespace) {
1096
+ if (!spec || typeof spec !== "object") {
1097
+ return {};
1098
+ }
1099
+ const inputs = [];
1100
+ if (Array.isArray(extraInputs)) {
1101
+ inputs.push(...extraInputs);
1102
+ }
1103
+ const entries = spec.metadata?.vizij?.inputs;
1104
+ if (Array.isArray(entries)) {
1105
+ entries.forEach((entry) => {
1106
+ if (entry && typeof entry === "object") {
1107
+ inputs.push(entry);
1108
+ }
1109
+ });
1110
+ }
1111
+ if (inputs.length === 0) {
1112
+ return {};
1113
+ }
1114
+ const map = {};
1115
+ inputs.forEach((entry) => {
1116
+ const path = entry.path;
1117
+ if (typeof path !== "string") return;
1118
+ const namespaced = namespaceTypedPath(path, namespace);
1119
+ const stripped = stripRigFacePrefix(path);
1120
+ const strippedNamespaced = stripped ? namespaceTypedPath(stripped, namespace) : stripped;
1121
+ const min = entry.range?.min;
1122
+ const max = entry.range?.max;
1123
+ const defaultValue = entry.defaultValue;
1124
+ const constraint = {
1125
+ ...Number.isFinite(Number(min)) ? { min: Number(min) } : {},
1126
+ ...Number.isFinite(Number(max)) ? { max: Number(max) } : {},
1127
+ ...Number.isFinite(Number(defaultValue)) ? { defaultValue: Number(defaultValue) } : {}
1128
+ };
1129
+ addConstraintVariant(map, namespaced, constraint);
1130
+ addConstraintVariant(map, path, constraint);
1131
+ if (stripped) {
1132
+ addConstraintVariant(map, stripped, constraint);
1133
+ }
1134
+ if (strippedNamespaced) {
1135
+ addConstraintVariant(map, strippedNamespaced, constraint);
1136
+ }
1137
+ });
1138
+ return map;
1139
+ }
1140
+ function namespaceTypedPath(path, namespace) {
1141
+ const trimmed = typeof path === "string" ? path.trim() : "";
1142
+ if (!trimmed) {
1143
+ return trimmed;
1144
+ }
1145
+ const prefix = `${namespace}/`;
1146
+ if (trimmed.startsWith(prefix)) {
1147
+ return trimmed;
1148
+ }
1149
+ if (trimmed.startsWith("debug/")) {
1150
+ const rest = trimmed.slice("debug/".length);
1151
+ const namespacedRest = namespaceTypedPath(rest, namespace);
1152
+ return namespacedRest.startsWith("debug/") ? namespacedRest : `debug/${namespacedRest}`;
1153
+ }
1154
+ return `${prefix}${trimmed}`;
1155
+ }
1156
+ function stripNamespace(path, namespace) {
1157
+ const prefix = `${namespace}/`;
1158
+ if (path.startsWith(prefix)) {
1159
+ return path.slice(prefix.length);
1160
+ }
1161
+ const debugPrefix = `debug/${prefix}`;
1162
+ if (path.startsWith(debugPrefix)) {
1163
+ return path.slice(debugPrefix.length);
1164
+ }
1165
+ if (path.startsWith("debug/")) {
1166
+ return path.slice("debug/".length);
1167
+ }
1168
+ return path;
1169
+ }
1170
+ function namespaceControllerId(id, namespace, kind = "graph") {
1171
+ if (!id) {
1172
+ return void 0;
1173
+ }
1174
+ const trimmed = id.trim();
1175
+ if (!trimmed) {
1176
+ return void 0;
1177
+ }
1178
+ const prefix = `${namespace}/${kind}/`;
1179
+ if (trimmed.startsWith(prefix)) {
1180
+ return trimmed;
1181
+ }
1182
+ return `${prefix}${trimmed}`;
1183
+ }
1184
+ function namespaceSubscriptions(subs, namespace) {
1185
+ if (!subs) {
1186
+ return void 0;
1187
+ }
1188
+ const inputs = Array.isArray(subs.inputs) ? subs.inputs.map((path) => namespaceTypedPath(path, namespace)) : void 0;
1189
+ const outputs = Array.isArray(subs.outputs) ? subs.outputs.map((path) => namespaceTypedPath(path, namespace)) : void 0;
1190
+ if (!inputs && !outputs) {
1191
+ return subs;
1192
+ }
1193
+ return {
1194
+ ...subs,
1195
+ ...inputs ? { inputs } : {},
1196
+ ...outputs ? { outputs } : {}
1197
+ };
1198
+ }
1199
+ function namespaceGraphSpec(spec, namespace) {
1200
+ if (!spec || typeof spec !== "object") {
1201
+ return spec;
1202
+ }
1203
+ const nodes = spec.nodes;
1204
+ if (!Array.isArray(nodes)) {
1205
+ return spec;
1206
+ }
1207
+ let changed = false;
1208
+ const nextNodes = nodes.map((node) => {
1209
+ if (!node || typeof node !== "object") {
1210
+ return node;
1211
+ }
1212
+ const path = node.params?.path;
1213
+ if (typeof path !== "string") {
1214
+ return node;
1215
+ }
1216
+ const namespacedPath = namespaceTypedPath(path, namespace);
1217
+ if (namespacedPath === path) {
1218
+ return node;
1219
+ }
1220
+ changed = true;
1221
+ return {
1222
+ ...node,
1223
+ params: {
1224
+ ...node.params ?? {},
1225
+ path: namespacedPath
1226
+ }
1227
+ };
1228
+ });
1229
+ if (!changed) {
1230
+ return spec;
1231
+ }
1232
+ return {
1233
+ ...spec,
1234
+ nodes: nextNodes
1235
+ };
1236
+ }
1237
+ function stripNulls(value) {
1238
+ if (value === null) {
1239
+ return void 0;
1240
+ }
1241
+ if (Array.isArray(value)) {
1242
+ const next2 = value.map((entry) => stripNulls(entry)).filter((entry) => entry !== void 0 && entry !== null);
1243
+ return next2;
1244
+ }
1245
+ if (typeof value !== "object" || value === void 0) {
1246
+ return value;
1247
+ }
1248
+ const next = {};
1249
+ Object.entries(value).forEach(([key, entry]) => {
1250
+ if (entry === null) {
1251
+ return;
1252
+ }
1253
+ const cleaned = stripNulls(entry);
1254
+ if (cleaned === void 0) {
1255
+ return;
1256
+ }
1257
+ next[key] = cleaned;
1258
+ });
1259
+ return next;
1260
+ }
1261
+ var now = () => typeof performance !== "undefined" ? performance.now() : Date.now();
1262
+ function pickBundleGraph(bundle, preferredKinds) {
1263
+ if (!bundle?.graphs || bundle.graphs.length === 0) {
1264
+ return null;
1265
+ }
1266
+ const preferred = preferredKinds.map((kind) => kind.toLowerCase());
1267
+ for (const entry of bundle.graphs) {
1268
+ if (!entry || typeof entry !== "object") {
1269
+ continue;
1270
+ }
1271
+ const kind = normaliseBundleKind(entry.kind);
1272
+ if (preferred.includes(kind)) {
1273
+ return entry;
1274
+ }
1275
+ }
1276
+ if (bundle.graphs.length === 1) {
1277
+ return bundle.graphs[0] ?? null;
1278
+ }
1279
+ return null;
1280
+ }
1281
+ function extractIrGraph(payload) {
1282
+ if (!payload || typeof payload !== "object") {
1283
+ return void 0;
1284
+ }
1285
+ return payload;
1286
+ }
1287
+ function convertBundleGraph(entry) {
1288
+ if (!entry || !entry.id) {
1289
+ return null;
1290
+ }
1291
+ const rawSpec = entry.spec;
1292
+ const inputMetadata = extractVizijInputMetadata(
1293
+ rawSpec
1294
+ );
1295
+ const spec = rawSpec ? stripVizijMetadata(rawSpec) : void 0;
1296
+ const ir = extractIrGraph(entry.ir);
1297
+ if (!spec && !ir) {
1298
+ return null;
1299
+ }
1300
+ return {
1301
+ id: entry.id,
1302
+ spec,
1303
+ ir: ir ?? null,
1304
+ inputMetadata
1305
+ };
1306
+ }
1307
+ function resolveGraphSpec(asset, context) {
1308
+ if (asset.spec) {
1309
+ return stripVizijMetadata(asset.spec);
1310
+ }
1311
+ if (asset.ir) {
1312
+ try {
1313
+ const compiled = (0, import_node_graph_authoring.compileIrGraph)(asset.ir, { preferLegacySpec: false });
1314
+ if (compiled.issues && compiled.issues.length > 0) {
1315
+ console.warn(
1316
+ `[vizij-runtime] IR compile for graph "${context}" reported issues`,
1317
+ compiled.issues
1318
+ );
1319
+ }
1320
+ return stripVizijMetadata(compiled.spec);
1321
+ } catch (error) {
1322
+ console.warn(
1323
+ `[vizij-runtime] Failed to compile IR graph "${context}"`,
1324
+ error
1325
+ );
1326
+ }
1327
+ }
1328
+ return null;
1329
+ }
1330
+ function stripVizijMetadata(spec) {
1331
+ if (!spec || typeof spec !== "object") {
1332
+ return spec;
1333
+ }
1334
+ const cloned = {
1335
+ ...spec,
1336
+ nodes: spec.nodes ? spec.nodes.map((node) => ({ ...node })) : spec.nodes,
1337
+ edges: spec.edges ? spec.edges.map((edge) => ({ ...edge })) : spec.edges
1338
+ };
1339
+ if (cloned.metadata && typeof cloned.metadata === "object") {
1340
+ const metadata = { ...cloned.metadata };
1341
+ if ("vizij" in metadata) {
1342
+ delete metadata.vizij;
1343
+ }
1344
+ if (Object.keys(metadata).length === 0) {
1345
+ delete cloned.metadata;
1346
+ } else {
1347
+ cloned.metadata = metadata;
1348
+ }
1349
+ }
1350
+ return cloned;
1351
+ }
1352
+ function extractVizijInputMetadata(spec) {
1353
+ if (!spec || typeof spec !== "object") {
1354
+ return [];
1355
+ }
1356
+ const inputs = spec.metadata?.vizij?.inputs;
1357
+ if (!Array.isArray(inputs)) {
1358
+ return [];
1359
+ }
1360
+ return inputs.map((entry) => {
1361
+ if (!entry || typeof entry !== "object") {
1362
+ return null;
1363
+ }
1364
+ return entry;
1365
+ }).filter(Boolean);
1366
+ }
1367
+ function convertBundleAnimations(entries) {
1368
+ if (!Array.isArray(entries) || entries.length === 0) {
1369
+ return [];
1370
+ }
1371
+ return entries.filter(
1372
+ (entry) => Boolean(entry && typeof entry.id === "string" && entry.clip)
1373
+ ).map((entry) => ({
1374
+ id: entry.id,
1375
+ clip: entry.clip
1376
+ }));
1377
+ }
1378
+ function resolveChannelId(track) {
1379
+ if (typeof track.componentId !== "string" || track.componentId.length === 0) {
1380
+ return null;
1381
+ }
1382
+ if (typeof track.component === "string" && track.component.length > 0) {
1383
+ return `${track.componentId}:${track.component}`;
1384
+ }
1385
+ const rawIndex = track.componentIndex != null ? Number(track.componentIndex) : void 0;
1386
+ const valueSize = track.valueSize != null ? Number(track.valueSize) : void 0;
1387
+ if (Number.isInteger(rawIndex) && Number.isFinite(rawIndex) && rawIndex >= 0 && Number.isFinite(valueSize) && valueSize > 1) {
1388
+ return `${track.componentId}:${rawIndex}`;
1389
+ }
1390
+ return track.componentId;
1391
+ }
1392
+ function convertExtractedAnimations(clips) {
1393
+ if (!Array.isArray(clips) || clips.length === 0) {
1394
+ return [];
1395
+ }
1396
+ const assets = [];
1397
+ clips.forEach((clip) => {
1398
+ const clipTracks = Array.isArray(clip.tracks) ? clip.tracks : [];
1399
+ if (clipTracks.length === 0) {
1400
+ return;
1401
+ }
1402
+ const convertedTracks = [];
1403
+ clipTracks.forEach((track) => {
1404
+ const channelId = resolveChannelId(track);
1405
+ if (!channelId) {
1406
+ return;
1407
+ }
1408
+ const rawTimes = Array.isArray(track.times) ? track.times : [];
1409
+ const rawValues = Array.isArray(track.values) ? track.values : [];
1410
+ if (rawTimes.length === 0 || rawValues.length === 0) {
1411
+ return;
1412
+ }
1413
+ const times = [];
1414
+ for (const entry of rawTimes) {
1415
+ const time = Number(entry);
1416
+ if (!Number.isFinite(time)) {
1417
+ return;
1418
+ }
1419
+ times.push(time);
1420
+ }
1421
+ const values = [];
1422
+ for (const entry of rawValues) {
1423
+ const value = Number(entry);
1424
+ if (!Number.isFinite(value)) {
1425
+ return;
1426
+ }
1427
+ values.push(value);
1428
+ }
1429
+ const parsedValueSize = track.valueSize != null ? Number(track.valueSize) : NaN;
1430
+ const valueSize = Number.isFinite(parsedValueSize) && parsedValueSize > 0 ? parsedValueSize : 1;
1431
+ const interpolationRaw = typeof track.interpolation === "string" ? track.interpolation.trim().toLowerCase() : "linear";
1432
+ const interpolation = interpolationRaw === "step" ? "step" : interpolationRaw === "cubic" || interpolationRaw === "cubicspline" ? "cubic" : "linear";
1433
+ const isCubic = interpolation === "cubic";
1434
+ const hasTripletTangents = isCubic && values.length === times.length * valueSize * 3;
1435
+ const hasFlatValues = values.length === times.length * valueSize;
1436
+ if (!hasTripletTangents && !hasFlatValues) {
1437
+ return;
1438
+ }
1439
+ const rawIndex = track.componentIndex != null ? Number(track.componentIndex) : 0;
1440
+ const componentIndex = Number.isInteger(rawIndex) && rawIndex >= 0 ? Math.min(rawIndex, valueSize - 1) : 0;
1441
+ const keyframes = [];
1442
+ times.forEach((time, index) => {
1443
+ const flatBase = index * valueSize + componentIndex;
1444
+ const valueBase = hasTripletTangents ? index * valueSize * 3 + valueSize + componentIndex : flatBase;
1445
+ const value = values[valueBase];
1446
+ if (!Number.isFinite(value)) {
1447
+ return;
1448
+ }
1449
+ const keyframe = {
1450
+ time,
1451
+ value
1452
+ };
1453
+ if (hasTripletTangents) {
1454
+ const inBase = index * valueSize * 3 + componentIndex;
1455
+ const outBase = index * valueSize * 3 + valueSize * 2 + componentIndex;
1456
+ const inTangent = values[inBase];
1457
+ const outTangent = values[outBase];
1458
+ if (Number.isFinite(inTangent)) {
1459
+ keyframe.inTangent = inTangent;
1460
+ }
1461
+ if (Number.isFinite(outTangent)) {
1462
+ keyframe.outTangent = outTangent;
1463
+ }
1464
+ }
1465
+ keyframes.push(keyframe);
1466
+ });
1467
+ if (keyframes.length === 0) {
1468
+ return;
1469
+ }
1470
+ convertedTracks.push({
1471
+ channel: channelId,
1472
+ keyframes,
1473
+ interpolation
1474
+ });
1475
+ });
1476
+ if (convertedTracks.length === 0) {
1477
+ return;
1478
+ }
1479
+ const durationFromTracks = convertedTracks.reduce((maxTime, track) => {
1480
+ const keyframes = Array.isArray(track.keyframes) ? track.keyframes : [];
1481
+ if (keyframes.length === 0) {
1482
+ return maxTime;
1483
+ }
1484
+ const lastKeyframe = keyframes[keyframes.length - 1];
1485
+ const time = Number(lastKeyframe?.time ?? 0);
1486
+ if (!Number.isFinite(time)) {
1487
+ return maxTime;
1488
+ }
1489
+ return time > maxTime ? time : maxTime;
1490
+ }, 0);
1491
+ const duration = typeof clip.duration === "number" && Number.isFinite(clip.duration) ? clip.duration : durationFromTracks;
1492
+ const clipId = typeof clip.id === "string" && clip.id.length > 0 ? clip.id : typeof clip.name === "string" && clip.name.length > 0 ? clip.name : `gltf-animation-${assets.length}`;
1493
+ const metadata = clip.metadata && typeof clip.metadata === "object" && !Array.isArray(clip.metadata) ? clip.metadata : void 0;
1494
+ assets.push({
1495
+ id: clipId,
1496
+ clip: {
1497
+ id: clipId,
1498
+ name: typeof clip.name === "string" ? clip.name : clipId,
1499
+ duration,
1500
+ tracks: convertedTracks,
1501
+ metadata
1502
+ }
1503
+ });
1504
+ });
1505
+ return assets;
1506
+ }
1507
+ function pickExtractedAnimations(asset) {
1508
+ if (!asset || typeof asset !== "object") {
1509
+ return void 0;
1510
+ }
1511
+ const animations = asset.animations;
1512
+ if (!Array.isArray(animations)) {
1513
+ return void 0;
1514
+ }
1515
+ return animations;
1516
+ }
1517
+ function mergeAnimationLists(explicit, fromBundle) {
1518
+ if (!explicit?.length && fromBundle.length === 0) {
1519
+ return void 0;
1520
+ }
1521
+ if (!explicit?.length) {
1522
+ return fromBundle.length > 0 ? fromBundle : void 0;
1523
+ }
1524
+ if (fromBundle.length === 0) {
1525
+ return explicit;
1526
+ }
1527
+ const seen = new Set(explicit.map((anim) => anim.id));
1528
+ let changed = false;
1529
+ const merged = [...explicit];
1530
+ for (const anim of fromBundle) {
1531
+ if (!anim.id || seen.has(anim.id)) {
1532
+ continue;
1533
+ }
1534
+ merged.push(anim);
1535
+ seen.add(anim.id);
1536
+ changed = true;
1537
+ }
1538
+ return changed ? merged : explicit;
1539
+ }
1540
+ function extractProgramResetValues(value) {
1541
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
1542
+ return void 0;
1543
+ }
1544
+ const entries = Object.entries(value).filter(
1545
+ ([, rawValue]) => Number.isFinite(Number(rawValue))
1546
+ );
1547
+ if (entries.length === 0) {
1548
+ return void 0;
1549
+ }
1550
+ return Object.fromEntries(
1551
+ entries.map(([path, rawValue]) => [path, Number(rawValue)])
1552
+ );
1553
+ }
1554
+ function convertBundlePrograms(entries) {
1555
+ if (!Array.isArray(entries) || entries.length === 0) {
1556
+ return [];
1557
+ }
1558
+ return entries.filter((entry) => normaliseBundleKind(entry?.kind) === "motiongraph").map((entry) => {
1559
+ const graph = convertBundleGraph(entry);
1560
+ if (!graph) {
1561
+ return null;
1562
+ }
1563
+ const metadata = entry.metadata && typeof entry.metadata === "object" && !Array.isArray(entry.metadata) ? entry.metadata : void 0;
1564
+ return {
1565
+ id: entry.id,
1566
+ label: typeof entry.label === "string" ? entry.label : void 0,
1567
+ graph,
1568
+ metadata,
1569
+ resetValues: extractProgramResetValues(metadata?.resetValues)
1570
+ };
1571
+ }).filter(Boolean);
1572
+ }
1573
+ function mergeProgramLists(explicit, fromBundle) {
1574
+ if (!explicit?.length && fromBundle.length === 0) {
1575
+ return void 0;
1576
+ }
1577
+ if (!explicit?.length) {
1578
+ return fromBundle.length > 0 ? fromBundle : void 0;
1579
+ }
1580
+ if (fromBundle.length === 0) {
1581
+ return explicit;
1582
+ }
1583
+ const seen = new Set(explicit.map((program) => program.id));
1584
+ let changed = false;
1585
+ const merged = [...explicit];
1586
+ for (const program of fromBundle) {
1587
+ if (!program.id || seen.has(program.id)) {
1588
+ continue;
1589
+ }
1590
+ merged.push(program);
1591
+ seen.add(program.id);
1592
+ changed = true;
1593
+ }
1594
+ return changed ? merged : explicit;
1595
+ }
1596
+ function mergeAssetBundle(base, extracted, extractedAnimations) {
1597
+ const resolvedBundle = base.bundle ?? extracted ?? null;
1598
+ const rigFromBundle = convertBundleGraph(
1599
+ pickBundleGraph(resolvedBundle, ["rig"])
1600
+ );
1601
+ const resolvedRig = base.rig ?? rigFromBundle ?? void 0;
1602
+ const basePose = base.pose;
1603
+ const hasBasePoseGraphOverride = Boolean(
1604
+ basePose && Object.prototype.hasOwnProperty.call(basePose, "graph")
1605
+ );
1606
+ const hasBasePoseConfigOverride = Boolean(
1607
+ basePose && Object.prototype.hasOwnProperty.call(basePose, "config")
1608
+ );
1609
+ const poseStageFilter = basePose?.stageNeutralFilter;
1610
+ const poseGraphFromBundle = hasBasePoseGraphOverride ? null : convertBundleGraph(
1611
+ pickBundleGraph(resolvedBundle, ["pose-driver", "pose"])
1612
+ );
1613
+ const resolvedPoseGraph = hasBasePoseGraphOverride ? basePose?.graph : basePose?.graph ?? poseGraphFromBundle ?? void 0;
1614
+ const resolvedPoseConfig = hasBasePoseConfigOverride ? basePose?.config : basePose?.config ?? resolvedBundle?.poses?.config ?? void 0;
1615
+ let resolvedPose = basePose;
1616
+ if (basePose) {
1617
+ const nextPose = { ...basePose };
1618
+ let changed = false;
1619
+ if (resolvedPoseGraph && basePose.graph !== resolvedPoseGraph) {
1620
+ nextPose.graph = resolvedPoseGraph;
1621
+ changed = true;
1622
+ }
1623
+ if (resolvedPoseConfig && basePose.config !== resolvedPoseConfig) {
1624
+ nextPose.config = resolvedPoseConfig;
1625
+ changed = true;
1626
+ }
1627
+ if (!resolvedPoseGraph && !basePose.graph) {
1628
+ }
1629
+ if (!resolvedPoseConfig && !basePose.config) {
1630
+ }
1631
+ resolvedPose = changed ? nextPose : basePose;
1632
+ } else if (resolvedPoseGraph || resolvedPoseConfig || typeof poseStageFilter === "function") {
1633
+ resolvedPose = {
1634
+ ...resolvedPoseGraph ? { graph: resolvedPoseGraph } : {},
1635
+ ...resolvedPoseConfig ? { config: resolvedPoseConfig } : {},
1636
+ ...typeof poseStageFilter === "function" ? { stageNeutralFilter: poseStageFilter } : {}
1637
+ };
1638
+ }
1639
+ const animationsFromBundle = convertBundleAnimations(
1640
+ resolvedBundle?.animations
1641
+ );
1642
+ let resolvedAnimations = mergeAnimationLists(
1643
+ base.animations,
1644
+ animationsFromBundle
1645
+ );
1646
+ const animationsFromAsset = extractedAnimations && extractedAnimations.length > 0 ? extractedAnimations : [];
1647
+ if (animationsFromAsset.length > 0) {
1648
+ resolvedAnimations = mergeAnimationLists(
1649
+ resolvedAnimations,
1650
+ animationsFromAsset
1651
+ );
1652
+ }
1653
+ const programsFromBundle = mergeProgramLists(
1654
+ base.programs,
1655
+ convertBundlePrograms(resolvedBundle?.graphs)
1656
+ );
1657
+ const merged = {
1658
+ ...base
1659
+ };
1660
+ if (resolvedRig) {
1661
+ merged.rig = resolvedRig;
1662
+ } else {
1663
+ merged.rig = void 0;
1664
+ }
1665
+ merged.pose = resolvedPose;
1666
+ merged.animations = resolvedAnimations;
1667
+ merged.programs = programsFromBundle;
1668
+ merged.bundle = resolvedBundle;
1669
+ return merged;
1670
+ }
1671
+ function normalizeStoredAnimationInterpolation(interpolation) {
1672
+ const mode = typeof interpolation === "string" ? interpolation.trim().toLowerCase() : "linear";
1673
+ if (mode === "step") {
1674
+ return "step";
1675
+ }
1676
+ if (mode === "cubic" || mode === "cubicspline") {
1677
+ return "cubic";
1678
+ }
1679
+ return "linear";
1680
+ }
1681
+ function buildStoredAnimationTransitions(mode) {
1682
+ if (mode === "cubic") {
1683
+ return void 0;
1684
+ }
1685
+ if (mode === "step") {
1686
+ return {
1687
+ in: { x: 1, y: 1 },
1688
+ out: { x: 1, y: 0 }
1689
+ };
1690
+ }
1691
+ return {
1692
+ in: { x: 1, y: 1 },
1693
+ out: { x: 0, y: 0 }
1694
+ };
1695
+ }
1696
+ function toStoredAnimationClip(fallbackId, clip) {
1697
+ const clipId = typeof clip.id === "string" && clip.id.trim().length > 0 ? clip.id.trim() : fallbackId;
1698
+ const clipName = typeof clip.name === "string" && clip.name.trim().length > 0 ? clip.name.trim() : clipId;
1699
+ const durationSeconds = resolveClipDurationSeconds(clip, 0);
1700
+ const durationMs = Math.max(1, Math.round(durationSeconds * 1e3));
1701
+ const tracks = (Array.isArray(clip.tracks) ? clip.tracks : []).map((rawTrack, trackIndex) => {
1702
+ const channel = typeof rawTrack.channel === "string" ? rawTrack.channel.trim() : "";
1703
+ if (!channel) {
1704
+ return null;
1705
+ }
1706
+ const keyframes = (Array.isArray(rawTrack.keyframes) ? rawTrack.keyframes : []).map((keyframe) => {
1707
+ const time = Number(keyframe.time);
1708
+ const value = Number(keyframe.value);
1709
+ const keyframeId = keyframe["id"];
1710
+ const keyframeInterpolation = keyframe["interpolation"];
1711
+ if (!Number.isFinite(time) || !Number.isFinite(value)) {
1712
+ return null;
1713
+ }
1714
+ return {
1715
+ id: typeof keyframeId === "string" && keyframeId.trim().length > 0 ? keyframeId.trim() : `${clipId}:track-${trackIndex.toString().padStart(4, "0")}:point-${time.toFixed(6)}`,
1716
+ time,
1717
+ value,
1718
+ mode: normalizeStoredAnimationInterpolation(
1719
+ keyframeInterpolation ?? rawTrack.interpolation
1720
+ )
1721
+ };
1722
+ }).filter(Boolean);
1723
+ if (keyframes.length === 0) {
1724
+ return null;
1725
+ }
1726
+ keyframes.sort((left, right) => {
1727
+ if (left.time !== right.time) {
1728
+ return left.time - right.time;
1729
+ }
1730
+ return left.id.localeCompare(right.id);
1731
+ });
1732
+ const rawTrackId = rawTrack["id"];
1733
+ const rawTrackName = rawTrack["name"];
1734
+ const trackId = typeof rawTrackId === "string" && rawTrackId.trim().length > 0 ? rawTrackId.trim() : `${clipId}:track-${trackIndex.toString().padStart(4, "0")}`;
1735
+ const trackName = typeof rawTrackName === "string" && rawTrackName.trim().length > 0 ? rawTrackName.trim() : channel.replace(/^\/+/, "") || trackId;
1736
+ const denominator = durationSeconds > 0 ? durationSeconds : 1;
1737
+ return {
1738
+ id: trackId,
1739
+ name: trackName,
1740
+ animatableId: channel,
1741
+ points: keyframes.map((keyframe) => {
1742
+ const stamp = Math.max(0, Math.min(1, keyframe.time / denominator));
1743
+ const transitions = buildStoredAnimationTransitions(keyframe.mode);
1744
+ return {
1745
+ id: keyframe.id,
1746
+ stamp,
1747
+ value: keyframe.value,
1748
+ ...transitions ? { transitions } : {}
1749
+ };
1750
+ })
1751
+ };
1752
+ }).filter(Boolean);
1753
+ return {
1754
+ id: clipId,
1755
+ name: clipName,
1756
+ duration: durationMs,
1757
+ groups: {},
1758
+ tracks
1759
+ };
1760
+ }
1761
+ function VizijRuntimeProvider({
1762
+ assetBundle,
1763
+ children,
1764
+ namespace: namespaceProp,
1765
+ faceId: faceIdProp,
1766
+ updateTier = "auto",
1767
+ autoCreate = true,
1768
+ createOptions,
1769
+ autostart = false,
1770
+ driveOrchestrator = true,
1771
+ mergeStrategy,
1772
+ onRegisterControllers,
1773
+ onStatusChange,
1774
+ transformOutputWrite,
1775
+ orchestratorScope = "auto"
1776
+ }) {
1777
+ const storeRef = (0, import_react2.useRef)(null);
1778
+ if (!storeRef.current) {
1779
+ storeRef.current = (0, import_render.createVizijStore)();
1780
+ }
1781
+ const parentOrchestrator = (0, import_react2.useContext)(import_orchestrator_react.OrchestratorContext);
1782
+ const hasParentOrchestrator = Boolean(parentOrchestrator);
1783
+ const shouldProvideOrchestrator = orchestratorScope === "isolated" || !hasParentOrchestrator && orchestratorScope !== "shared";
1784
+ if (orchestratorScope === "shared" && !hasParentOrchestrator) {
1785
+ console.warn(
1786
+ '[vizij-runtime] orchestratorScope="shared" requires an OrchestratorProvider higher in the tree; falling back to an isolated provider.'
1787
+ );
1788
+ }
1789
+ const runtimeTree = /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_render.VizijContext.Provider, { value: storeRef.current, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1790
+ VizijRuntimeProviderInner,
1791
+ {
1792
+ assetBundle,
1793
+ namespace: namespaceProp,
1794
+ faceId: faceIdProp,
1795
+ updateTier,
1796
+ autoCreate,
1797
+ autostart,
1798
+ driveOrchestrator,
1799
+ createOptions,
1800
+ mergeStrategy,
1801
+ onRegisterControllers,
1802
+ onStatusChange,
1803
+ transformOutputWrite,
1804
+ store: storeRef.current,
1805
+ children
1806
+ }
1807
+ ) });
1808
+ if (!shouldProvideOrchestrator) {
1809
+ return runtimeTree;
1810
+ }
1811
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1812
+ import_orchestrator_react.OrchestratorProvider,
1813
+ {
1814
+ autoCreate,
1815
+ createOptions,
1816
+ autostart: false,
1817
+ children: runtimeTree
1818
+ }
1819
+ );
1820
+ }
1821
+ function VizijRuntimeProviderInner({
1822
+ assetBundle: initialAssetBundle,
1823
+ namespace: namespaceProp,
1824
+ faceId: faceIdProp,
1825
+ updateTier,
1826
+ mergeStrategy,
1827
+ onRegisterControllers,
1828
+ onStatusChange,
1829
+ transformOutputWrite,
1830
+ store,
1831
+ children,
1832
+ autoCreate,
1833
+ autostart,
1834
+ createOptions,
1835
+ driveOrchestrator
1836
+ }) {
1837
+ const [assetBundleOverride, setAssetBundleOverride] = (0, import_react2.useState)(null);
1838
+ const [graphUpdateToken, setGraphUpdateToken] = (0, import_react2.useState)(0);
1839
+ const effectiveAssetBundle = assetBundleOverride ?? initialAssetBundle;
1840
+ const latestEffectiveAssetBundleRef = (0, import_react2.useRef)(effectiveAssetBundle);
1841
+ const [extractedBundle, setExtractedBundle] = (0, import_react2.useState)(() => {
1842
+ if (effectiveAssetBundle.bundle) {
1843
+ return effectiveAssetBundle.bundle;
1844
+ }
1845
+ if (effectiveAssetBundle.glb.kind === "world" && effectiveAssetBundle.glb.bundle) {
1846
+ return effectiveAssetBundle.glb.bundle;
1847
+ }
1848
+ return null;
1849
+ });
1850
+ const [extractedAnimations, setExtractedAnimations] = (0, import_react2.useState)([]);
1851
+ const previousBundleRef = (0, import_react2.useRef)(null);
1852
+ const suppressNextBundlePlanRef = (0, import_react2.useRef)(false);
1853
+ const pendingPlanRef = (0, import_react2.useRef)(null);
1854
+ const updateTierRef = (0, import_react2.useRef)(updateTier);
1855
+ (0, import_react2.useEffect)(() => {
1856
+ if (effectiveAssetBundle.bundle) {
1857
+ setExtractedBundle(effectiveAssetBundle.bundle);
1858
+ return;
1859
+ }
1860
+ if (effectiveAssetBundle.glb.kind === "world") {
1861
+ setExtractedBundle(effectiveAssetBundle.glb.bundle ?? null);
1862
+ } else {
1863
+ setExtractedBundle(null);
1864
+ }
1865
+ }, [effectiveAssetBundle]);
1866
+ (0, import_react2.useEffect)(() => {
1867
+ updateTierRef.current = updateTier;
1868
+ }, [updateTier]);
1869
+ const assetBundle = (0, import_react2.useMemo)(
1870
+ () => mergeAssetBundle(
1871
+ effectiveAssetBundle,
1872
+ extractedBundle,
1873
+ extractedAnimations
1874
+ ),
1875
+ [effectiveAssetBundle, extractedBundle, extractedAnimations]
1876
+ );
1877
+ (0, import_react2.useEffect)(() => {
1878
+ latestEffectiveAssetBundleRef.current = effectiveAssetBundle;
1879
+ }, [effectiveAssetBundle]);
1880
+ (0, import_react2.useEffect)(() => {
1881
+ if (suppressNextBundlePlanRef.current) {
1882
+ suppressNextBundlePlanRef.current = false;
1883
+ previousBundleRef.current = effectiveAssetBundle;
1884
+ return;
1885
+ }
1886
+ const plan = resolveRuntimeUpdatePlan(
1887
+ previousBundleRef.current,
1888
+ effectiveAssetBundle,
1889
+ updateTierRef.current
1890
+ );
1891
+ pendingPlanRef.current = plan;
1892
+ previousBundleRef.current = effectiveAssetBundle;
1893
+ if (plan.reregisterGraphs) {
1894
+ setGraphUpdateToken((prev) => prev + 1);
1895
+ }
1896
+ }, [effectiveAssetBundle]);
1897
+ const {
1898
+ ready,
1899
+ createOrchestrator,
1900
+ registerGraph,
1901
+ registerMergedGraph,
1902
+ registerAnimation,
1903
+ removeGraph,
1904
+ removeAnimation,
1905
+ removeInput,
1906
+ listControllers,
1907
+ setInput: orchestratorSetInput,
1908
+ getPathSnapshot,
1909
+ step: stepRuntime
1910
+ } = (0, import_orchestrator_react.useOrchestrator)();
1911
+ const frame = (0, import_orchestrator_react.useOrchFrame)();
1912
+ const namespace = namespaceProp ?? assetBundle.namespace ?? "default";
1913
+ const faceId = faceIdProp ?? assetBundle.faceId ?? assetBundle.pose?.config?.faceId ?? assetBundle.pose?.config?.faceId ?? void 0;
1914
+ const [status, setStatus] = (0, import_react2.useState)({
1915
+ loading: true,
1916
+ ready: false,
1917
+ error: null,
1918
+ errors: [],
1919
+ namespace,
1920
+ faceId,
1921
+ rootId: null,
1922
+ outputPaths: [],
1923
+ stepHz: void 0,
1924
+ controllers: { graphs: [], anims: [] }
1925
+ });
1926
+ const errorsRef = (0, import_react2.useRef)([]);
1927
+ const outputPathsRef = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
1928
+ const baseOutputPathsRef = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
1929
+ const namespacedOutputPathsRef = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
1930
+ const namespaceRef = (0, import_react2.useRef)(namespace);
1931
+ const driveOrchestratorRef = (0, import_react2.useRef)(driveOrchestrator);
1932
+ const rigInputMapRef = (0, import_react2.useRef)({});
1933
+ const rigPoseControlInputIdsRef = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
1934
+ const registeredGraphsRef = (0, import_react2.useRef)([]);
1935
+ const registeredAnimationsRef = (0, import_react2.useRef)([]);
1936
+ const mergedGraphRef = (0, import_react2.useRef)(null);
1937
+ const poseControlBridgeValuesRef = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
1938
+ const poseWeightFallbackMap = (0, import_react2.useMemo)(() => {
1939
+ const map = /* @__PURE__ */ new Map();
1940
+ const poseConfig = assetBundle.pose?.config;
1941
+ if (!poseConfig) {
1942
+ return map;
1943
+ }
1944
+ const posePaths = buildPoseWeightPathMap(
1945
+ poseConfig.poses ?? [],
1946
+ poseConfig.faceId ?? faceId ?? "face"
1947
+ );
1948
+ (poseConfig.poses ?? []).forEach((pose) => {
1949
+ const posePath = posePaths.get(pose.id);
1950
+ if (!posePath) {
1951
+ return;
1952
+ }
1953
+ const values = Object.fromEntries(
1954
+ Object.entries(pose.values ?? {}).filter(
1955
+ ([, value]) => Number.isFinite(value)
1956
+ )
1957
+ );
1958
+ map.set(posePath, values);
1959
+ });
1960
+ return map;
1961
+ }, [assetBundle.pose?.config, faceId]);
1962
+ const useLegacyPoseWeightFallback = (0, import_react2.useMemo)(
1963
+ () => shouldUseLegacyPoseWeightFallback(Boolean(assetBundle.pose?.graph)),
1964
+ [assetBundle.pose?.graph]
1965
+ );
1966
+ const resolvedProgramAssets = (0, import_react2.useMemo)(
1967
+ () => assetBundle.programs && assetBundle.programs.length > 0 ? assetBundle.programs : convertBundlePrograms(assetBundle.bundle?.graphs),
1968
+ [assetBundle.bundle?.graphs, assetBundle.programs]
1969
+ );
1970
+ const [inputConstraints, setInputConstraints] = (0, import_react2.useState)({});
1971
+ const inputConstraintsRef = (0, import_react2.useRef)({});
1972
+ const avgStepDtRef = (0, import_react2.useRef)(null);
1973
+ const animationTweensRef = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
1974
+ const clipPlaybackRef = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
1975
+ const programPlaybackRef = (0, import_react2.useRef)(
1976
+ /* @__PURE__ */ new Map()
1977
+ );
1978
+ const programControllerIdsRef = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
1979
+ const clipOutputValuesRef = (0, import_react2.useRef)(
1980
+ /* @__PURE__ */ new Map()
1981
+ );
1982
+ const clipAggregateValuesRef = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
1983
+ const animationSystemActiveRef = (0, import_react2.useRef)(true);
1984
+ const stagedInputsRef = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
1985
+ const autostartRef = (0, import_react2.useRef)(autostart);
1986
+ const lastActivityTimeRef = (0, import_react2.useRef)(now());
1987
+ const [loopMode, setLoopMode] = (0, import_react2.useState)("stopped");
1988
+ const loopModeRef = (0, import_react2.useRef)("stopped");
1989
+ (0, import_react2.useEffect)(() => {
1990
+ loopModeRef.current = loopMode;
1991
+ }, [loopMode]);
1992
+ const runtimeMountedRef = (0, import_react2.useRef)(true);
1993
+ (0, import_react2.useEffect)(() => {
1994
+ return () => {
1995
+ runtimeMountedRef.current = false;
1996
+ };
1997
+ }, []);
1998
+ (0, import_react2.useEffect)(() => {
1999
+ const rigAsset = assetBundle.rig;
2000
+ if (!rigAsset) {
2001
+ inputConstraintsRef.current = {};
2002
+ setInputConstraints({});
2003
+ return;
2004
+ }
2005
+ const rigSpec = resolveGraphSpec(
2006
+ rigAsset,
2007
+ `${rigAsset.id ?? "rig"} graph (constraints)`
2008
+ );
2009
+ const constraints = extractInputConstraints(
2010
+ rigSpec,
2011
+ rigAsset.inputMetadata,
2012
+ namespace
2013
+ );
2014
+ inputConstraintsRef.current = constraints;
2015
+ setInputConstraints(constraints);
2016
+ }, [assetBundle.rig, namespace]);
2017
+ const requestLoopMode = (0, import_react2.useCallback)((mode) => {
2018
+ if (!runtimeMountedRef.current) {
2019
+ return;
2020
+ }
2021
+ setLoopMode((prev) => prev === mode ? prev : mode);
2022
+ }, []);
2023
+ const hasActiveAnimations = (0, import_react2.useCallback)(() => {
2024
+ if (animationTweensRef.current.size > 0) {
2025
+ return true;
2026
+ }
2027
+ if (!animationSystemActiveRef.current) {
2028
+ for (const state of programPlaybackRef.current.values()) {
2029
+ if (state.state === "playing") {
2030
+ return true;
2031
+ }
2032
+ }
2033
+ return false;
2034
+ }
2035
+ for (const state of clipPlaybackRef.current.values()) {
2036
+ if (state.playing) {
2037
+ return true;
2038
+ }
2039
+ }
2040
+ for (const state of programPlaybackRef.current.values()) {
2041
+ if (state.state === "playing") {
2042
+ return true;
2043
+ }
2044
+ }
2045
+ return false;
2046
+ }, []);
2047
+ const computeDesiredLoopMode = (0, import_react2.useCallback)(() => {
2048
+ const hasAnimations = hasActiveAnimations();
2049
+ const recentlyActive = now() - lastActivityTimeRef.current <= ACTIVE_GRACE_MS;
2050
+ if (autostartRef.current && (hasAnimations || recentlyActive)) {
2051
+ return "active";
2052
+ }
2053
+ if (autostartRef.current) {
2054
+ return "idle-visible";
2055
+ }
2056
+ return "idle-hidden";
2057
+ }, [hasActiveAnimations]);
2058
+ const updateLoopMode = (0, import_react2.useCallback)(() => {
2059
+ requestLoopMode(computeDesiredLoopMode());
2060
+ }, [computeDesiredLoopMode, requestLoopMode]);
2061
+ const markActivity = (0, import_react2.useCallback)(() => {
2062
+ lastActivityTimeRef.current = now();
2063
+ updateLoopMode();
2064
+ }, [updateLoopMode]);
2065
+ const setInput = (0, import_react2.useCallback)(
2066
+ (path, value, shape) => {
2067
+ const numericValue = (0, import_value_json2.valueAsNumber)(value);
2068
+ const basePath = stripNamespace(
2069
+ normalisePath(path),
2070
+ namespaceRef.current
2071
+ );
2072
+ const poseValues = useLegacyPoseWeightFallback && numericValue != null ? poseWeightFallbackMap.get(basePath) : void 0;
2073
+ if (poseValues && numericValue != null) {
2074
+ const poseFaceId = assetBundle.pose?.config?.faceId ?? faceId ?? "face";
2075
+ const rigMap = rigInputMapRef.current;
2076
+ Object.entries(poseValues).forEach(([inputId, poseValue]) => {
2077
+ if (!Number.isFinite(poseValue)) {
2078
+ return;
2079
+ }
2080
+ const controlPath = resolvePoseControlInputPath({
2081
+ inputId,
2082
+ basePath: buildRigInputPath(
2083
+ poseFaceId,
2084
+ `/pose/control/${inputId}`
2085
+ ),
2086
+ rigInputPathMap: rigMap,
2087
+ hasNativePoseControlInput: true
2088
+ }) ?? buildRigInputPath(poseFaceId, `/pose/control/${inputId}`);
2089
+ setInput(controlPath, { float: Number(poseValue) * numericValue });
2090
+ });
2091
+ return;
2092
+ }
2093
+ markActivity();
2094
+ const namespacedPath = namespaceTypedPath(path, namespaceRef.current);
2095
+ if (isRuntimeDebugEnabled() && (namespacedPath.includes("animation/authoring.timeline.main") || namespacedPath.endsWith("/blink"))) {
2096
+ console.log("[vizij-runtime] stage input", {
2097
+ path,
2098
+ namespacedPath,
2099
+ value
2100
+ });
2101
+ }
2102
+ stagedInputsRef.current.set(namespacedPath, { value, shape });
2103
+ },
2104
+ [
2105
+ assetBundle.pose?.config?.faceId,
2106
+ faceId,
2107
+ markActivity,
2108
+ poseWeightFallbackMap,
2109
+ useLegacyPoseWeightFallback
2110
+ ]
2111
+ );
2112
+ const reportStatus = (0, import_react2.useCallback)(
2113
+ (updater) => {
2114
+ setStatus((prev) => {
2115
+ const next = updater(prev);
2116
+ onStatusChange?.(next);
2117
+ return next;
2118
+ });
2119
+ },
2120
+ [onStatusChange]
2121
+ );
2122
+ const pushError = (0, import_react2.useCallback)(
2123
+ (error) => {
2124
+ errorsRef.current = [...errorsRef.current, error];
2125
+ reportStatus((prev) => ({
2126
+ ...prev,
2127
+ error,
2128
+ errors: errorsRef.current
2129
+ }));
2130
+ console.warn("[vizij-runtime]", error.message, error.cause);
2131
+ },
2132
+ [reportStatus]
2133
+ );
2134
+ const resetErrors = (0, import_react2.useCallback)(() => {
2135
+ errorsRef.current = [];
2136
+ reportStatus((prev) => ({
2137
+ ...prev,
2138
+ error: null,
2139
+ errors: []
2140
+ }));
2141
+ }, [reportStatus]);
2142
+ (0, import_react2.useEffect)(() => {
2143
+ autostartRef.current = autostart;
2144
+ updateLoopMode();
2145
+ }, [autostart, updateLoopMode]);
2146
+ const clearControllers = (0, import_react2.useCallback)(() => {
2147
+ const existing = listControllers();
2148
+ existing.graphs.forEach((id) => {
2149
+ try {
2150
+ removeGraph(id);
2151
+ } catch (err) {
2152
+ pushError({
2153
+ message: `Failed to remove graph ${id}`,
2154
+ cause: err,
2155
+ phase: "registration",
2156
+ timestamp: performance.now()
2157
+ });
2158
+ }
2159
+ });
2160
+ existing.anims.forEach((id) => {
2161
+ try {
2162
+ removeAnimation(id);
2163
+ } catch (err) {
2164
+ pushError({
2165
+ message: `Failed to remove animation ${id}`,
2166
+ cause: err,
2167
+ phase: "registration",
2168
+ timestamp: performance.now()
2169
+ });
2170
+ }
2171
+ });
2172
+ registeredGraphsRef.current = [];
2173
+ registeredAnimationsRef.current = [];
2174
+ programControllerIdsRef.current.clear();
2175
+ mergedGraphRef.current = null;
2176
+ outputPathsRef.current = /* @__PURE__ */ new Set();
2177
+ baseOutputPathsRef.current = /* @__PURE__ */ new Set();
2178
+ namespacedOutputPathsRef.current = /* @__PURE__ */ new Set();
2179
+ rigPoseControlInputIdsRef.current = /* @__PURE__ */ new Set();
2180
+ clipOutputValuesRef.current.clear();
2181
+ clipAggregateValuesRef.current.clear();
2182
+ }, [listControllers, removeAnimation, removeGraph, pushError]);
2183
+ (0, import_react2.useEffect)(() => {
2184
+ namespaceRef.current = namespace;
2185
+ reportStatus((prev) => ({
2186
+ ...prev,
2187
+ namespace,
2188
+ faceId
2189
+ }));
2190
+ }, [namespace, faceId, reportStatus]);
2191
+ (0, import_react2.useEffect)(() => {
2192
+ driveOrchestratorRef.current = driveOrchestrator;
2193
+ }, [driveOrchestrator]);
2194
+ const glbAsset = effectiveAssetBundle.glb;
2195
+ const baseBundle = effectiveAssetBundle.bundle ?? null;
2196
+ (0, import_react2.useEffect)(() => {
2197
+ let cancelled = false;
2198
+ const plan = pendingPlanRef.current;
2199
+ if (plan && !plan.reloadAssets && status.rootId !== null) {
2200
+ reportStatus(
2201
+ (prev) => prev.loading ? { ...prev, loading: false } : prev
2202
+ );
2203
+ return () => {
2204
+ cancelled = true;
2205
+ };
2206
+ }
2207
+ resetErrors();
2208
+ reportStatus((prev) => ({
2209
+ ...prev,
2210
+ loading: true,
2211
+ rootId: null,
2212
+ ready: false,
2213
+ outputPaths: [],
2214
+ controllers: { graphs: [], anims: [] }
2215
+ }));
2216
+ setExtractedAnimations([]);
2217
+ const loadAssets = async () => {
2218
+ try {
2219
+ let world;
2220
+ let animatables;
2221
+ let bundle = baseBundle;
2222
+ let gltfAnimations;
2223
+ if (glbAsset.kind === "url") {
2224
+ const loaded = await (0, import_render.loadGLTFWithBundle)(
2225
+ glbAsset.src,
2226
+ [namespace],
2227
+ glbAsset.aggressiveImport ?? false,
2228
+ glbAsset.rootBounds
2229
+ );
2230
+ world = loaded.world;
2231
+ animatables = loaded.animatables;
2232
+ bundle = loaded.bundle ?? bundle;
2233
+ gltfAnimations = pickExtractedAnimations(loaded);
2234
+ } else if (glbAsset.kind === "blob") {
2235
+ const loaded = await (0, import_render.loadGLTFFromBlobWithBundle)(
2236
+ glbAsset.blob,
2237
+ [namespace],
2238
+ glbAsset.aggressiveImport ?? false,
2239
+ glbAsset.rootBounds
2240
+ );
2241
+ world = loaded.world;
2242
+ animatables = loaded.animatables;
2243
+ bundle = loaded.bundle ?? bundle;
2244
+ gltfAnimations = pickExtractedAnimations(loaded);
2245
+ } else {
2246
+ world = glbAsset.world;
2247
+ animatables = glbAsset.animatables;
2248
+ bundle = glbAsset.bundle ?? bundle;
2249
+ gltfAnimations = void 0;
2250
+ }
2251
+ if (cancelled) {
2252
+ return;
2253
+ }
2254
+ setExtractedBundle(bundle ?? null);
2255
+ setExtractedAnimations(convertExtractedAnimations(gltfAnimations));
2256
+ const rootId = findRootId(world);
2257
+ store.getState().addWorldElements(world, animatables, true);
2258
+ if (pendingPlanRef.current?.reloadAssets) {
2259
+ pendingPlanRef.current = {
2260
+ ...pendingPlanRef.current,
2261
+ reloadAssets: false
2262
+ };
2263
+ }
2264
+ reportStatus((prev) => ({
2265
+ ...prev,
2266
+ loading: false,
2267
+ rootId,
2268
+ namespace,
2269
+ faceId
2270
+ }));
2271
+ } catch (err) {
2272
+ if (cancelled) {
2273
+ return;
2274
+ }
2275
+ pushError({
2276
+ message: "Failed to load Vizij assets",
2277
+ cause: err,
2278
+ phase: "assets",
2279
+ timestamp: performance.now()
2280
+ });
2281
+ reportStatus((prev) => ({
2282
+ ...prev,
2283
+ loading: false
2284
+ }));
2285
+ }
2286
+ };
2287
+ loadAssets();
2288
+ return () => {
2289
+ cancelled = true;
2290
+ };
2291
+ }, [
2292
+ glbAsset,
2293
+ baseBundle,
2294
+ namespace,
2295
+ faceId,
2296
+ store,
2297
+ pushError,
2298
+ reportStatus,
2299
+ resetErrors,
2300
+ setExtractedBundle,
2301
+ setExtractedAnimations,
2302
+ status.rootId
2303
+ ]);
2304
+ (0, import_react2.useEffect)(() => {
2305
+ if (!ready && autoCreate) {
2306
+ createOrchestrator(createOptions).catch((err) => {
2307
+ pushError({
2308
+ message: "Failed to create orchestrator runtime",
2309
+ cause: err,
2310
+ phase: "orchestrator",
2311
+ timestamp: performance.now()
2312
+ });
2313
+ });
2314
+ }
2315
+ }, [ready, autoCreate, createOptions, createOrchestrator, pushError]);
2316
+ const registerControllers = (0, import_react2.useCallback)(async () => {
2317
+ clearControllers();
2318
+ if (isRuntimeDebugEnabled()) {
2319
+ console.log("[vizij-runtime] registerControllers", {
2320
+ hasRig: Boolean(assetBundle.rig),
2321
+ hasPose: Boolean(assetBundle.pose?.graph),
2322
+ animationCount: assetBundle.animations?.length ?? 0,
2323
+ animationIds: (assetBundle.animations ?? []).map((anim) => anim.id),
2324
+ namespace
2325
+ });
2326
+ }
2327
+ const baseOutputPaths = /* @__PURE__ */ new Set();
2328
+ const namespacedOutputPaths = /* @__PURE__ */ new Set();
2329
+ const recordOutputs = (paths) => {
2330
+ paths.forEach((path) => {
2331
+ const trimmed = path.trim();
2332
+ if (!trimmed) return;
2333
+ const basePath = stripNamespace(trimmed, namespace);
2334
+ baseOutputPaths.add(basePath);
2335
+ namespacedOutputPaths.add(namespaceTypedPath(trimmed, namespace));
2336
+ });
2337
+ };
2338
+ const graphConfigs = [];
2339
+ rigInputMapRef.current = {};
2340
+ rigPoseControlInputIdsRef.current = /* @__PURE__ */ new Set();
2341
+ poseControlBridgeValuesRef.current.clear();
2342
+ const rigAsset = assetBundle.rig;
2343
+ if (rigAsset) {
2344
+ const rigSpec = resolveGraphSpec(
2345
+ rigAsset,
2346
+ `${rigAsset.id ?? "rig"} graph`
2347
+ );
2348
+ if (!rigSpec) {
2349
+ pushError({
2350
+ message: "Rig graph is missing a usable spec or IR payload.",
2351
+ phase: "registration",
2352
+ timestamp: performance.now()
2353
+ });
2354
+ } else {
2355
+ const rigOutputs = collectOutputPaths(rigSpec);
2356
+ const rigInputs = collectInputPaths(rigSpec);
2357
+ const rigPoseControlInputIds = /* @__PURE__ */ new Set();
2358
+ rigInputs.forEach((path) => {
2359
+ const poseControlMatch = /^rig\/[^/]+\/pose\/control\/(.+)$/.exec(
2360
+ path.trim()
2361
+ );
2362
+ const inputId = (poseControlMatch?.[1] ?? "").trim();
2363
+ if (inputId.length > 0) {
2364
+ rigPoseControlInputIds.add(inputId);
2365
+ }
2366
+ });
2367
+ rigInputMapRef.current = collectInputPathMap(rigSpec);
2368
+ rigPoseControlInputIdsRef.current = rigPoseControlInputIds;
2369
+ if (isRuntimeDebugEnabled()) {
2370
+ const blinkKeys = Object.keys(rigInputMapRef.current).filter(
2371
+ (key) => key.toLowerCase().includes("blink")
2372
+ );
2373
+ const blinkMappings = blinkKeys.slice(0, 20).map((key) => `${key} => ${rigInputMapRef.current[key] ?? "?"}`);
2374
+ console.log("[vizij-runtime] rig input map sample", {
2375
+ blink: rigInputMapRef.current["blink"] ?? null,
2376
+ blinkKeys: blinkKeys.slice(0, 12),
2377
+ blinkMappings: blinkMappings.join(" | ")
2378
+ });
2379
+ }
2380
+ recordOutputs(rigOutputs);
2381
+ const rigSubs = rigAsset.subscriptions ?? {
2382
+ inputs: rigInputs,
2383
+ outputs: rigOutputs
2384
+ };
2385
+ graphConfigs.push({
2386
+ id: namespaceControllerId(rigAsset.id, namespace, "graph"),
2387
+ spec: stripNulls(namespaceGraphSpec(rigSpec, namespace)),
2388
+ subs: namespaceSubscriptions(rigSubs, namespace)
2389
+ });
2390
+ }
2391
+ }
2392
+ const poseGraphAsset = assetBundle.pose?.graph;
2393
+ if (poseGraphAsset) {
2394
+ const poseSpec = resolveGraphSpec(
2395
+ poseGraphAsset,
2396
+ `${poseGraphAsset.id ?? "pose"} graph`
2397
+ );
2398
+ if (poseSpec) {
2399
+ const poseOutputs = collectOutputPaths(poseSpec);
2400
+ const poseInputs = collectInputPaths(poseSpec);
2401
+ recordOutputs(poseOutputs);
2402
+ const poseSubs = poseGraphAsset.subscriptions ?? {
2403
+ inputs: poseInputs,
2404
+ outputs: poseOutputs
2405
+ };
2406
+ graphConfigs.push({
2407
+ id: namespaceControllerId(poseGraphAsset.id, namespace, "graph"),
2408
+ spec: stripNulls(namespaceGraphSpec(poseSpec, namespace)),
2409
+ subs: namespaceSubscriptions(poseSubs, namespace)
2410
+ });
2411
+ } else {
2412
+ console.warn(
2413
+ "[vizij-runtime] Pose graph is missing a usable spec or IR payload; skipping registration."
2414
+ );
2415
+ }
2416
+ }
2417
+ for (const animation of assetBundle.animations ?? []) {
2418
+ const bridgeOutputs = collectAnimationClipOutputPaths(
2419
+ animation.clip,
2420
+ faceId ?? void 0,
2421
+ rigInputMapRef.current
2422
+ );
2423
+ if (isRuntimeDebugEnabled()) {
2424
+ console.log("[vizij-runtime] animation output routing", {
2425
+ animationId: animation.id,
2426
+ bridgeOutputs,
2427
+ bridgeOutputsText: bridgeOutputs.join(" | ")
2428
+ });
2429
+ }
2430
+ recordOutputs(bridgeOutputs);
2431
+ }
2432
+ for (const program of resolvedProgramAssets) {
2433
+ const programSpec = resolveGraphSpec(
2434
+ program.graph,
2435
+ `${program.id ?? "program"} graph (outputs)`
2436
+ );
2437
+ if (!programSpec) {
2438
+ continue;
2439
+ }
2440
+ recordOutputs(collectOutputPaths(programSpec));
2441
+ }
2442
+ outputPathsRef.current = namespacedOutputPaths;
2443
+ baseOutputPathsRef.current = baseOutputPaths;
2444
+ namespacedOutputPathsRef.current = namespacedOutputPaths;
2445
+ const graphIds = [];
2446
+ try {
2447
+ if (graphConfigs.length > 1) {
2448
+ const mergedId = registerMergedGraph({
2449
+ id: namespaceControllerId(
2450
+ mergedGraphRef.current ?? `merged-${namespace}`,
2451
+ namespace,
2452
+ "merged"
2453
+ ) ?? void 0,
2454
+ graphs: graphConfigs,
2455
+ strategy: mergeStrategy ?? DEFAULT_MERGE
2456
+ });
2457
+ mergedGraphRef.current = mergedId;
2458
+ graphIds.push(mergedId);
2459
+ } else {
2460
+ graphConfigs.forEach((cfg) => {
2461
+ const id = registerGraph(cfg);
2462
+ graphIds.push(id);
2463
+ });
2464
+ }
2465
+ } catch (err) {
2466
+ pushError({
2467
+ message: "Failed to register rig graphs",
2468
+ cause: err,
2469
+ phase: "registration",
2470
+ timestamp: performance.now()
2471
+ });
2472
+ }
2473
+ registeredGraphsRef.current = graphIds;
2474
+ if (isRuntimeDebugEnabled()) {
2475
+ console.log("[vizij-runtime] registered graph ids", graphIds);
2476
+ }
2477
+ const animationIds = [];
2478
+ for (const anim of assetBundle.animations ?? []) {
2479
+ try {
2480
+ const controllerId = namespaceControllerId(anim.id, namespace, "animation") ?? anim.id;
2481
+ const animationPayload = anim.setup?.animation ?? toStoredAnimationClip(anim.id, anim.clip);
2482
+ const config = {
2483
+ id: controllerId,
2484
+ setup: {
2485
+ ...anim.setup ?? {},
2486
+ animation: animationPayload
2487
+ }
2488
+ };
2489
+ const id = registerAnimation(config);
2490
+ animationIds.push(id);
2491
+ } catch (err) {
2492
+ if (isRuntimeDebugEnabled()) {
2493
+ console.warn("[vizij-runtime] failed animation registration", {
2494
+ animationId: anim.id,
2495
+ error: err instanceof Error ? err.message : String(err)
2496
+ });
2497
+ }
2498
+ pushError({
2499
+ message: `Failed to register animation ${anim.id}`,
2500
+ cause: err,
2501
+ phase: "animation",
2502
+ timestamp: performance.now()
2503
+ });
2504
+ }
2505
+ }
2506
+ registeredAnimationsRef.current = animationIds;
2507
+ if (assetBundle.initialInputs) {
2508
+ Object.entries(assetBundle.initialInputs).forEach(([path, value]) => {
2509
+ try {
2510
+ setInput(path, value);
2511
+ } catch (err) {
2512
+ pushError({
2513
+ message: `Failed to stage initial input ${path}`,
2514
+ cause: err,
2515
+ phase: "registration",
2516
+ timestamp: performance.now()
2517
+ });
2518
+ }
2519
+ });
2520
+ }
2521
+ const controllers = listControllers();
2522
+ if (isRuntimeDebugEnabled()) {
2523
+ console.log("[vizij-runtime] controllers after register", {
2524
+ controllers,
2525
+ graphIds,
2526
+ animationIds
2527
+ });
2528
+ }
2529
+ reportStatus((prev) => ({
2530
+ ...prev,
2531
+ ready: true,
2532
+ controllers,
2533
+ outputPaths: Array.from(outputPathsRef.current)
2534
+ }));
2535
+ onRegisterControllers?.(controllers);
2536
+ }, [
2537
+ assetBundle,
2538
+ clearControllers,
2539
+ listControllers,
2540
+ mergeStrategy,
2541
+ namespace,
2542
+ onRegisterControllers,
2543
+ pushError,
2544
+ registerAnimation,
2545
+ registerGraph,
2546
+ registerMergedGraph,
2547
+ reportStatus,
2548
+ resolvedProgramAssets,
2549
+ setInput
2550
+ ]);
2551
+ (0, import_react2.useEffect)(() => {
2552
+ if (!ready || status.loading) {
2553
+ return;
2554
+ }
2555
+ const plan = pendingPlanRef.current;
2556
+ const hasRegistered = registeredGraphsRef.current.length > 0 || registeredAnimationsRef.current.length > 0 || programControllerIdsRef.current.size > 0;
2557
+ if (plan && !plan.reregisterGraphs && hasRegistered) {
2558
+ return;
2559
+ }
2560
+ registerControllers().catch((err) => {
2561
+ pushError({
2562
+ message: "Failed to register controllers",
2563
+ cause: err,
2564
+ phase: "registration",
2565
+ timestamp: performance.now()
2566
+ });
2567
+ });
2568
+ }, [ready, status.loading, graphUpdateToken, registerControllers, pushError]);
2569
+ (0, import_react2.useEffect)(() => {
2570
+ if (!frame) {
2571
+ return;
2572
+ }
2573
+ const writes = frame.merged_writes ?? [];
2574
+ if (!writes.length) {
2575
+ return;
2576
+ }
2577
+ const setWorldValues = store.getState().setValues;
2578
+ const namespaceValue = status.namespace;
2579
+ const currentValues = store.getState().values;
2580
+ const rigInputPathMap = rigInputMapRef.current;
2581
+ const rigPoseControlInputIds = rigPoseControlInputIdsRef.current;
2582
+ const batched = [];
2583
+ const namespacedOutputs = namespacedOutputPathsRef.current;
2584
+ const baseOutputs = baseOutputPathsRef.current;
2585
+ writes.forEach((write) => {
2586
+ const path = normalisePath(write.path);
2587
+ const basePath = stripNamespace(path, namespaceValue);
2588
+ const isTrackedOutput = namespacedOutputs.has(path) || baseOutputs.has(basePath);
2589
+ if (!isTrackedOutput) {
2590
+ return;
2591
+ }
2592
+ const raw = valueJSONToRaw(write.value);
2593
+ if (raw === void 0) {
2594
+ return;
2595
+ }
2596
+ const poseControlMatch = /^rig\/[^/]+\/pose\/control\/(.+)$/.exec(
2597
+ basePath
2598
+ );
2599
+ if (poseControlMatch && typeof raw === "number" && Number.isFinite(raw)) {
2600
+ const inputId = (poseControlMatch[1] ?? "").trim();
2601
+ const hasNativePoseControlInput = inputId.length > 0 && rigPoseControlInputIds.has(inputId);
2602
+ const mappedInputPath = inputId.length === 0 ? void 0 : resolvePoseControlInputPath({
2603
+ inputId,
2604
+ basePath,
2605
+ rigInputPathMap,
2606
+ hasNativePoseControlInput
2607
+ });
2608
+ if (mappedInputPath) {
2609
+ const bridgeKey = `${namespaceValue}:${mappedInputPath}`;
2610
+ const previousValue = poseControlBridgeValuesRef.current.get(bridgeKey);
2611
+ if (previousValue === void 0 || Math.abs(previousValue - raw) > POSE_CONTROL_BRIDGE_EPSILON) {
2612
+ poseControlBridgeValuesRef.current.set(bridgeKey, raw);
2613
+ setInput(mappedInputPath, { float: raw });
2614
+ }
2615
+ }
2616
+ }
2617
+ const targetPath = baseOutputs.has(basePath) ? basePath : path;
2618
+ const currentValue = currentValues.get(
2619
+ (0, import_utils.getLookup)(namespaceValue, targetPath)
2620
+ );
2621
+ const nextWrite = transformOutputWrite ? transformOutputWrite({
2622
+ id: targetPath,
2623
+ namespace: namespaceValue,
2624
+ value: raw,
2625
+ currentValue
2626
+ }) : {
2627
+ id: targetPath,
2628
+ namespace: namespaceValue,
2629
+ value: raw
2630
+ };
2631
+ if (nextWrite) {
2632
+ batched.push(nextWrite);
2633
+ }
2634
+ });
2635
+ if (batched.length > 0) {
2636
+ setWorldValues(batched);
2637
+ }
2638
+ }, [frame, status.namespace, store, transformOutputWrite]);
2639
+ const stagePoseNeutral = (0, import_react2.useCallback)(
2640
+ (force = false) => {
2641
+ const neutral = assetBundle.pose?.config?.neutralInputs ?? {};
2642
+ const rigMap = rigInputMapRef.current;
2643
+ const staged = /* @__PURE__ */ new Set();
2644
+ Object.entries(neutral).forEach(([id, value]) => {
2645
+ const path = rigMap[id];
2646
+ if (!path) {
2647
+ return;
2648
+ }
2649
+ const include = assetBundle.pose?.stageNeutralFilter;
2650
+ if (include && !include(id, path)) {
2651
+ return;
2652
+ }
2653
+ setInput(path, { float: Number.isFinite(value) ? value : 0 });
2654
+ staged.add(path);
2655
+ });
2656
+ if (force) {
2657
+ Object.entries(rigMap).forEach(([id, path]) => {
2658
+ if (staged.has(path)) {
2659
+ return;
2660
+ }
2661
+ const include = assetBundle.pose?.stageNeutralFilter;
2662
+ if (include && !include(id, path)) {
2663
+ return;
2664
+ }
2665
+ setInput(path, { float: 0 });
2666
+ });
2667
+ }
2668
+ },
2669
+ [assetBundle.pose?.config?.neutralInputs, setInput]
2670
+ );
2671
+ const setRendererValue = (0, import_react2.useCallback)(
2672
+ (id, ns, value) => {
2673
+ store.getState().setValue(id, ns, value);
2674
+ },
2675
+ [store]
2676
+ );
2677
+ const cancelAnimation = (0, import_react2.useCallback)((path) => {
2678
+ if (animationTweensRef.current.has(path)) {
2679
+ const entry = animationTweensRef.current.get(path);
2680
+ animationTweensRef.current.delete(path);
2681
+ entry?.resolve();
2682
+ }
2683
+ }, []);
2684
+ const advanceAnimationTweens = (0, import_react2.useCallback)(
2685
+ (dt) => {
2686
+ if (animationTweensRef.current.size === 0) {
2687
+ return;
2688
+ }
2689
+ const map = animationTweensRef.current;
2690
+ const toDelete = [];
2691
+ map.forEach((state, key) => {
2692
+ state.elapsed += dt;
2693
+ const progress = state.duration === 0 ? 1 : Math.min(state.elapsed / state.duration, 1);
2694
+ const eased = state.easing(progress);
2695
+ const value = state.from + (state.to - state.from) * eased;
2696
+ setInput(state.path, { float: value });
2697
+ if (progress >= 1) {
2698
+ toDelete.push(key);
2699
+ state.resolve();
2700
+ }
2701
+ });
2702
+ toDelete.forEach((key) => map.delete(key));
2703
+ },
2704
+ [setInput]
2705
+ );
2706
+ const resolveClipById = (0, import_react2.useCallback)(
2707
+ (id) => {
2708
+ return assetBundle.animations?.find((anim) => anim.id === id);
2709
+ },
2710
+ [assetBundle.animations]
2711
+ );
2712
+ const resolveClipPromise = (0, import_react2.useCallback)((state) => {
2713
+ state.resolve?.();
2714
+ state.resolve = null;
2715
+ state.completion = null;
2716
+ }, []);
2717
+ const ensureClipPromise = (0, import_react2.useCallback)((state) => {
2718
+ if (state.completion) {
2719
+ return state.completion;
2720
+ }
2721
+ const completion = new Promise((resolve) => {
2722
+ state.resolve = resolve;
2723
+ });
2724
+ state.completion = completion;
2725
+ return completion;
2726
+ }, []);
2727
+ const setAnimationInput = (0, import_react2.useCallback)(
2728
+ (path, value, options) => {
2729
+ setInput(path, { float: value });
2730
+ if (!options?.immediate) {
2731
+ return;
2732
+ }
2733
+ const namespacedPath = namespaceTypedPath(path, namespaceRef.current);
2734
+ const staged = stagedInputsRef.current.get(namespacedPath);
2735
+ if (staged) {
2736
+ orchestratorSetInput(namespacedPath, staged.value, staged.shape);
2737
+ stagedInputsRef.current.delete(namespacedPath);
2738
+ return;
2739
+ }
2740
+ orchestratorSetInput(namespacedPath, { float: value });
2741
+ },
2742
+ [orchestratorSetInput, setInput]
2743
+ );
2744
+ const clearAnimationInput = (0, import_react2.useCallback)(
2745
+ (path) => {
2746
+ const namespacedPath = namespaceTypedPath(path, namespaceRef.current);
2747
+ stagedInputsRef.current.delete(namespacedPath);
2748
+ removeInput(namespacedPath);
2749
+ },
2750
+ [removeInput]
2751
+ );
2752
+ const buildClipOutputValues = (0, import_react2.useCallback)(
2753
+ (clip, state) => sampleAnimationClipOutputValues(
2754
+ clip.clip,
2755
+ state.time,
2756
+ state.weight,
2757
+ faceId ?? void 0,
2758
+ rigInputMapRef.current
2759
+ ),
2760
+ [faceId]
2761
+ );
2762
+ const computeClipAggregateValues = (0, import_react2.useCallback)(() => {
2763
+ const aggregate = /* @__PURE__ */ new Map();
2764
+ clipOutputValuesRef.current.forEach((outputValues) => {
2765
+ outputValues.forEach((value, path) => {
2766
+ aggregate.set(path, (aggregate.get(path) ?? 0) + value);
2767
+ });
2768
+ });
2769
+ return aggregate;
2770
+ }, []);
2771
+ const stageClipAggregateValues = (0, import_react2.useCallback)(
2772
+ (nextAggregate, options) => {
2773
+ diffAnimationAggregateValues(
2774
+ clipAggregateValuesRef.current,
2775
+ nextAggregate,
2776
+ POSE_CONTROL_BRIDGE_EPSILON
2777
+ ).forEach((operation) => {
2778
+ if (operation.kind === "clear") {
2779
+ clearAnimationInput(operation.path);
2780
+ return;
2781
+ }
2782
+ setAnimationInput(operation.path, operation.value, options);
2783
+ });
2784
+ clipAggregateValuesRef.current = nextAggregate;
2785
+ },
2786
+ [clearAnimationInput, setAnimationInput]
2787
+ );
2788
+ const syncClipOutputs = (0, import_react2.useCallback)(
2789
+ (options) => {
2790
+ stageClipAggregateValues(computeClipAggregateValues(), options);
2791
+ },
2792
+ [computeClipAggregateValues, stageClipAggregateValues]
2793
+ );
2794
+ const writeClipOutputs = (0, import_react2.useCallback)(
2795
+ (clip, state, options) => {
2796
+ if (!animationSystemActiveRef.current) {
2797
+ return;
2798
+ }
2799
+ clipOutputValuesRef.current.set(
2800
+ clip.id,
2801
+ buildClipOutputValues(clip, state)
2802
+ );
2803
+ syncClipOutputs(options);
2804
+ },
2805
+ [buildClipOutputValues, syncClipOutputs]
2806
+ );
2807
+ const clearClipOutputs = (0, import_react2.useCallback)(
2808
+ (clipId, options) => {
2809
+ if (!clipOutputValuesRef.current.has(clipId)) {
2810
+ return;
2811
+ }
2812
+ clipOutputValuesRef.current.delete(clipId);
2813
+ syncClipOutputs(options);
2814
+ },
2815
+ [syncClipOutputs]
2816
+ );
2817
+ const createClipPlaybackState = (0, import_react2.useCallback)(
2818
+ (clip) => {
2819
+ const duration = resolveClipDurationSeconds(
2820
+ clip.clip
2821
+ );
2822
+ return {
2823
+ id: clip.id,
2824
+ time: 0,
2825
+ duration,
2826
+ speed: 1,
2827
+ weight: Number.isFinite(clip.weight) ? Number(clip.weight) : 1,
2828
+ loop: false,
2829
+ playing: false,
2830
+ resolve: null,
2831
+ completion: null
2832
+ };
2833
+ },
2834
+ []
2835
+ );
2836
+ const ensureClipPlaybackState = (0, import_react2.useCallback)(
2837
+ (id) => {
2838
+ const clip = resolveClipById(id);
2839
+ if (!clip) {
2840
+ return null;
2841
+ }
2842
+ const existing = clipPlaybackRef.current.get(id);
2843
+ if (existing) {
2844
+ existing.duration = resolveClipDurationSeconds(
2845
+ clip.clip,
2846
+ existing.duration
2847
+ );
2848
+ existing.time = clampAnimationTime(existing.time, existing.duration);
2849
+ return { clip, state: existing };
2850
+ }
2851
+ const next = createClipPlaybackState(clip);
2852
+ clipPlaybackRef.current.set(id, next);
2853
+ return { clip, state: next };
2854
+ },
2855
+ [createClipPlaybackState, resolveClipById]
2856
+ );
2857
+ const advanceClipPlayback = (0, import_react2.useCallback)(
2858
+ (dt) => {
2859
+ if (clipPlaybackRef.current.size === 0) {
2860
+ return;
2861
+ }
2862
+ const toDelete = [];
2863
+ clipPlaybackRef.current.forEach((state, key) => {
2864
+ const clip = resolveClipById(state.id);
2865
+ if (!clip) {
2866
+ toDelete.push(key);
2867
+ resolveClipPromise(state);
2868
+ return;
2869
+ }
2870
+ state.duration = resolveClipDurationSeconds(
2871
+ clip.clip,
2872
+ state.duration
2873
+ );
2874
+ const { time, completed } = advanceClipTime(
2875
+ {
2876
+ time: state.time,
2877
+ duration: state.duration,
2878
+ speed: state.speed,
2879
+ loop: state.loop,
2880
+ playing: state.playing
2881
+ },
2882
+ dt
2883
+ );
2884
+ state.time = clampAnimationTime(time, state.duration);
2885
+ if (state.playing || completed) {
2886
+ writeClipOutputs(clip, state);
2887
+ }
2888
+ if (completed) {
2889
+ toDelete.push(key);
2890
+ resolveClipPromise(state);
2891
+ }
2892
+ });
2893
+ toDelete.forEach((key) => {
2894
+ clipPlaybackRef.current.delete(key);
2895
+ if (animationSystemActiveRef.current) {
2896
+ clearClipOutputs(key);
2897
+ }
2898
+ });
2899
+ },
2900
+ [clearClipOutputs, resolveClipById, resolveClipPromise, writeClipOutputs]
2901
+ );
2902
+ const animateValue = (0, import_react2.useCallback)(
2903
+ (path, target, options) => {
2904
+ const targetValue = (0, import_value_json2.valueAsNumber)(target);
2905
+ const basePath = stripNamespace(
2906
+ normalisePath(path),
2907
+ namespaceRef.current
2908
+ );
2909
+ const poseValues = useLegacyPoseWeightFallback && targetValue != null ? poseWeightFallbackMap.get(basePath) : void 0;
2910
+ if (poseValues && targetValue != null) {
2911
+ const poseFaceId = assetBundle.pose?.config?.faceId ?? faceId ?? "face";
2912
+ const rigMap = rigInputMapRef.current;
2913
+ return Promise.all(
2914
+ Object.entries(poseValues).flatMap(([inputId, poseValue]) => {
2915
+ if (!Number.isFinite(poseValue)) {
2916
+ return [];
2917
+ }
2918
+ const controlPath = resolvePoseControlInputPath({
2919
+ inputId,
2920
+ basePath: buildRigInputPath(
2921
+ poseFaceId,
2922
+ `/pose/control/${inputId}`
2923
+ ),
2924
+ rigInputPathMap: rigMap,
2925
+ hasNativePoseControlInput: true
2926
+ }) ?? buildRigInputPath(poseFaceId, `/pose/control/${inputId}`);
2927
+ return [
2928
+ animateValue(
2929
+ controlPath,
2930
+ { float: Number(poseValue) * targetValue },
2931
+ options
2932
+ )
2933
+ ];
2934
+ })
2935
+ ).then(() => void 0);
2936
+ }
2937
+ const easing = resolveEasing(options?.easing);
2938
+ const duration = Math.max(0, options?.duration ?? DEFAULT_DURATION);
2939
+ cancelAnimation(path);
2940
+ const namespacedPath = namespaceTypedPath(path, namespaceRef.current);
2941
+ const current = getPathSnapshot(namespacedPath);
2942
+ const fromValue = (0, import_value_json2.valueAsNumber)(current);
2943
+ const toValue = (0, import_value_json2.valueAsNumber)(target);
2944
+ if (fromValue == null || toValue == null || duration === 0) {
2945
+ setInput(path, target);
2946
+ return Promise.resolve();
2947
+ }
2948
+ return new Promise((resolve) => {
2949
+ animationTweensRef.current.set(path, {
2950
+ // Keep the raw path here so tween updates go through setInput() once
2951
+ // and pick up the active namespace exactly once.
2952
+ path,
2953
+ from: fromValue,
2954
+ to: toValue,
2955
+ duration,
2956
+ elapsed: 0,
2957
+ easing,
2958
+ resolve
2959
+ });
2960
+ markActivity();
2961
+ });
2962
+ },
2963
+ [
2964
+ assetBundle.pose?.config?.faceId,
2965
+ cancelAnimation,
2966
+ faceId,
2967
+ getPathSnapshot,
2968
+ markActivity,
2969
+ poseWeightFallbackMap,
2970
+ setInput,
2971
+ useLegacyPoseWeightFallback
2972
+ ]
2973
+ );
2974
+ const playAnimation = (0, import_react2.useCallback)(
2975
+ (id, options) => {
2976
+ const ensured = ensureClipPlaybackState(id);
2977
+ if (!ensured) {
2978
+ return Promise.reject(
2979
+ new Error(`Animation ${id} is not part of the current asset bundle.`)
2980
+ );
2981
+ }
2982
+ const { clip, state } = ensured;
2983
+ const shouldReset = options?.reset === true;
2984
+ if (shouldReset) {
2985
+ resolveClipPromise(state);
2986
+ state.time = 0;
2987
+ }
2988
+ const speed = options?.speed ?? state.speed ?? 1;
2989
+ const weight = options?.weight ?? state.weight ?? clip.weight ?? 1;
2990
+ state.speed = Number.isFinite(speed) && speed > 0 ? speed : 1;
2991
+ state.weight = Number.isFinite(weight) ? Number(weight) : 1;
2992
+ state.duration = resolveClipDurationSeconds(
2993
+ clip.clip,
2994
+ state.duration
2995
+ );
2996
+ state.time = clampAnimationTime(state.time, state.duration);
2997
+ state.playing = true;
2998
+ const completion = ensureClipPromise(state);
2999
+ clipPlaybackRef.current.set(id, state);
3000
+ writeClipOutputs(clip, state);
3001
+ markActivity();
3002
+ return completion;
3003
+ },
3004
+ [
3005
+ ensureClipPlaybackState,
3006
+ ensureClipPromise,
3007
+ markActivity,
3008
+ resolveClipPromise,
3009
+ writeClipOutputs
3010
+ ]
3011
+ );
3012
+ const pauseAnimation = (0, import_react2.useCallback)(
3013
+ (id) => {
3014
+ const state = clipPlaybackRef.current.get(id);
3015
+ if (!state || !state.playing) {
3016
+ return;
3017
+ }
3018
+ state.playing = false;
3019
+ updateLoopMode();
3020
+ },
3021
+ [updateLoopMode]
3022
+ );
3023
+ const seekAnimation = (0, import_react2.useCallback)(
3024
+ (id, timeSeconds) => {
3025
+ const ensured = ensureClipPlaybackState(id);
3026
+ if (!ensured) {
3027
+ return;
3028
+ }
3029
+ const { clip, state } = ensured;
3030
+ state.time = clampAnimationTime(timeSeconds, state.duration);
3031
+ clipPlaybackRef.current.set(id, state);
3032
+ writeClipOutputs(clip, state, { immediate: true });
3033
+ },
3034
+ [ensureClipPlaybackState, writeClipOutputs]
3035
+ );
3036
+ const setAnimationLoop = (0, import_react2.useCallback)(
3037
+ (id, enabled) => {
3038
+ const ensured = ensureClipPlaybackState(id);
3039
+ if (!ensured) {
3040
+ return;
3041
+ }
3042
+ ensured.state.loop = Boolean(enabled);
3043
+ clipPlaybackRef.current.set(id, ensured.state);
3044
+ updateLoopMode();
3045
+ },
3046
+ [ensureClipPlaybackState, updateLoopMode]
3047
+ );
3048
+ const getAnimationState = (0, import_react2.useCallback)(
3049
+ (id) => {
3050
+ const state = clipPlaybackRef.current.get(id);
3051
+ if (!state) {
3052
+ return null;
3053
+ }
3054
+ return {
3055
+ time: state.time,
3056
+ duration: state.duration,
3057
+ playing: state.playing,
3058
+ loop: state.loop,
3059
+ speed: state.speed
3060
+ };
3061
+ },
3062
+ []
3063
+ );
3064
+ const stopAnimation = (0, import_react2.useCallback)(
3065
+ (id, options) => {
3066
+ const state = clipPlaybackRef.current.get(id);
3067
+ if (state) {
3068
+ clipPlaybackRef.current.delete(id);
3069
+ state.playing = false;
3070
+ resolveClipPromise(state);
3071
+ }
3072
+ if (options?.clearOutputs !== false) {
3073
+ clearClipOutputs(id);
3074
+ }
3075
+ updateLoopMode();
3076
+ },
3077
+ [clearClipOutputs, resolveClipPromise, updateLoopMode]
3078
+ );
3079
+ const refreshControllerStatus = (0, import_react2.useCallback)(() => {
3080
+ const controllers = listControllers();
3081
+ reportStatus((prev) => ({
3082
+ ...prev,
3083
+ controllers,
3084
+ outputPaths: Array.from(outputPathsRef.current)
3085
+ }));
3086
+ onRegisterControllers?.(controllers);
3087
+ }, [listControllers, onRegisterControllers, reportStatus]);
3088
+ const resolveProgramById = (0, import_react2.useCallback)(
3089
+ (id) => {
3090
+ return resolvedProgramAssets.find((program) => program.id === id);
3091
+ },
3092
+ [resolvedProgramAssets]
3093
+ );
3094
+ const buildProgramRegistrationConfig = (0, import_react2.useCallback)(
3095
+ (program) => {
3096
+ const graphSpec = resolveGraphSpec(
3097
+ program.graph,
3098
+ `${program.id ?? "program"} graph`
3099
+ );
3100
+ if (!graphSpec) {
3101
+ return null;
3102
+ }
3103
+ const outputs = collectOutputPaths(graphSpec);
3104
+ const inputs = collectInputPaths(graphSpec);
3105
+ const subs = program.graph.subscriptions ?? {
3106
+ inputs,
3107
+ outputs
3108
+ };
3109
+ return {
3110
+ id: namespaceControllerId(program.id, namespace, "graph"),
3111
+ spec: stripNulls(namespaceGraphSpec(graphSpec, namespace)),
3112
+ subs: namespaceSubscriptions(subs, namespace)
3113
+ };
3114
+ },
3115
+ [namespace]
3116
+ );
3117
+ const deriveProgramResetValues = (0, import_react2.useCallback)(
3118
+ (program) => {
3119
+ if (program.resetValues) {
3120
+ return Object.entries(program.resetValues).filter(([, value]) => Number.isFinite(value)).map(([path, value]) => ({ path, value }));
3121
+ }
3122
+ const graphSpec = resolveGraphSpec(
3123
+ program.graph,
3124
+ `${program.id ?? "program"} graph (reset)`
3125
+ );
3126
+ if (!graphSpec) {
3127
+ return [];
3128
+ }
3129
+ return collectOutputPaths(graphSpec).filter((path) => path.trim().length > 0).map((path) => {
3130
+ const defaultValue = inputConstraintsRef.current[path]?.defaultValue ?? 0;
3131
+ return {
3132
+ path,
3133
+ value: Number.isFinite(defaultValue) && defaultValue != null ? defaultValue : 0
3134
+ };
3135
+ });
3136
+ },
3137
+ []
3138
+ );
3139
+ const syncProgramPlaybackControllers = (0, import_react2.useCallback)(() => {
3140
+ if (!ready) {
3141
+ return;
3142
+ }
3143
+ const availableProgramIds = new Set(
3144
+ resolvedProgramAssets.map((program) => program.id)
3145
+ );
3146
+ Array.from(programPlaybackRef.current.keys()).forEach((id) => {
3147
+ if (availableProgramIds.has(id)) {
3148
+ return;
3149
+ }
3150
+ programPlaybackRef.current.delete(id);
3151
+ const controllerId = programControllerIdsRef.current.get(id);
3152
+ if (controllerId) {
3153
+ try {
3154
+ removeGraph(controllerId);
3155
+ } catch (err) {
3156
+ pushError({
3157
+ message: `Failed to remove program ${id}`,
3158
+ cause: err,
3159
+ phase: "registration",
3160
+ timestamp: performance.now()
3161
+ });
3162
+ }
3163
+ programControllerIdsRef.current.delete(id);
3164
+ }
3165
+ });
3166
+ programPlaybackRef.current.forEach((state, id) => {
3167
+ const program = resolveProgramById(id);
3168
+ const controllerId = programControllerIdsRef.current.get(id);
3169
+ if (!program) {
3170
+ return;
3171
+ }
3172
+ if (state.state !== "playing") {
3173
+ if (!controllerId) {
3174
+ return;
3175
+ }
3176
+ try {
3177
+ removeGraph(controllerId);
3178
+ } catch (err) {
3179
+ pushError({
3180
+ message: `Failed to pause program ${id}`,
3181
+ cause: err,
3182
+ phase: "registration",
3183
+ timestamp: performance.now()
3184
+ });
3185
+ }
3186
+ programControllerIdsRef.current.delete(id);
3187
+ return;
3188
+ }
3189
+ if (controllerId) {
3190
+ return;
3191
+ }
3192
+ const config = buildProgramRegistrationConfig(program);
3193
+ if (!config) {
3194
+ pushError({
3195
+ message: `Program ${id} is missing a usable graph payload.`,
3196
+ phase: "registration",
3197
+ timestamp: performance.now()
3198
+ });
3199
+ return;
3200
+ }
3201
+ try {
3202
+ const nextControllerId = registerGraph(config);
3203
+ programControllerIdsRef.current.set(id, nextControllerId);
3204
+ } catch (err) {
3205
+ pushError({
3206
+ message: `Failed to register program ${id}`,
3207
+ cause: err,
3208
+ phase: "registration",
3209
+ timestamp: performance.now()
3210
+ });
3211
+ }
3212
+ });
3213
+ refreshControllerStatus();
3214
+ }, [
3215
+ buildProgramRegistrationConfig,
3216
+ pushError,
3217
+ ready,
3218
+ refreshControllerStatus,
3219
+ registerGraph,
3220
+ removeGraph,
3221
+ resolvedProgramAssets,
3222
+ resolveProgramById
3223
+ ]);
3224
+ const playProgram = (0, import_react2.useCallback)(
3225
+ (id) => {
3226
+ if (!resolveProgramById(id)) {
3227
+ throw new Error(
3228
+ `Program ${id} is not part of the current asset bundle.`
3229
+ );
3230
+ }
3231
+ programPlaybackRef.current.set(id, {
3232
+ id,
3233
+ state: "playing"
3234
+ });
3235
+ syncProgramPlaybackControllers();
3236
+ markActivity();
3237
+ },
3238
+ [markActivity, resolveProgramById, syncProgramPlaybackControllers]
3239
+ );
3240
+ const pauseProgram = (0, import_react2.useCallback)(
3241
+ (id) => {
3242
+ if (!resolveProgramById(id)) {
3243
+ return;
3244
+ }
3245
+ programPlaybackRef.current.set(id, {
3246
+ id,
3247
+ state: "paused"
3248
+ });
3249
+ syncProgramPlaybackControllers();
3250
+ updateLoopMode();
3251
+ },
3252
+ [resolveProgramById, syncProgramPlaybackControllers, updateLoopMode]
3253
+ );
3254
+ const stopProgram = (0, import_react2.useCallback)(
3255
+ (id, options) => {
3256
+ const program = resolveProgramById(id);
3257
+ const controllerId = programControllerIdsRef.current.get(id);
3258
+ if (controllerId) {
3259
+ try {
3260
+ removeGraph(controllerId);
3261
+ } catch (err) {
3262
+ pushError({
3263
+ message: `Failed to stop program ${id}`,
3264
+ cause: err,
3265
+ phase: "registration",
3266
+ timestamp: performance.now()
3267
+ });
3268
+ }
3269
+ programControllerIdsRef.current.delete(id);
3270
+ }
3271
+ programPlaybackRef.current.set(id, {
3272
+ id,
3273
+ state: "stopped"
3274
+ });
3275
+ if (program && options?.resetOutputs !== false) {
3276
+ deriveProgramResetValues(program).forEach(({ path, value }) => {
3277
+ setInput(path, { float: value });
3278
+ });
3279
+ }
3280
+ refreshControllerStatus();
3281
+ updateLoopMode();
3282
+ },
3283
+ [
3284
+ deriveProgramResetValues,
3285
+ pushError,
3286
+ refreshControllerStatus,
3287
+ removeGraph,
3288
+ resolveProgramById,
3289
+ setInput,
3290
+ updateLoopMode
3291
+ ]
3292
+ );
3293
+ const getProgramState = (0, import_react2.useCallback)(
3294
+ (id) => {
3295
+ const state = programPlaybackRef.current.get(id);
3296
+ if (!state) {
3297
+ return null;
3298
+ }
3299
+ return { state: state.state };
3300
+ },
3301
+ []
3302
+ );
3303
+ (0, import_react2.useEffect)(() => {
3304
+ if (!ready || status.loading) {
3305
+ return;
3306
+ }
3307
+ syncProgramPlaybackControllers();
3308
+ }, [
3309
+ graphUpdateToken,
3310
+ ready,
3311
+ resolvedProgramAssets,
3312
+ status.loading,
3313
+ syncProgramPlaybackControllers
3314
+ ]);
3315
+ const setAnimationActive = (0, import_react2.useCallback)(
3316
+ (active) => {
3317
+ const next = Boolean(active);
3318
+ if (animationSystemActiveRef.current === next) {
3319
+ return;
3320
+ }
3321
+ animationSystemActiveRef.current = next;
3322
+ if (!next) {
3323
+ clipPlaybackRef.current.forEach((state) => {
3324
+ state.playing = false;
3325
+ });
3326
+ }
3327
+ updateLoopMode();
3328
+ },
3329
+ [updateLoopMode]
3330
+ );
3331
+ const isAnimationActive = (0, import_react2.useCallback)(
3332
+ () => animationSystemActiveRef.current,
3333
+ []
3334
+ );
3335
+ const registerInputDriver = (0, import_react2.useCallback)(
3336
+ (id, factory) => {
3337
+ const driver = factory({
3338
+ setInput,
3339
+ setRendererValue,
3340
+ namespace,
3341
+ faceId
3342
+ });
3343
+ const wrapped = {
3344
+ start: () => {
3345
+ try {
3346
+ driver.start();
3347
+ } catch (err) {
3348
+ pushError({
3349
+ message: `Input driver ${id} failed to start`,
3350
+ cause: err,
3351
+ phase: "driver",
3352
+ timestamp: performance.now()
3353
+ });
3354
+ }
3355
+ },
3356
+ stop: () => {
3357
+ try {
3358
+ driver.stop();
3359
+ } catch (err) {
3360
+ pushError({
3361
+ message: `Input driver ${id} failed to stop`,
3362
+ cause: err,
3363
+ phase: "driver",
3364
+ timestamp: performance.now()
3365
+ });
3366
+ }
3367
+ },
3368
+ dispose: () => {
3369
+ try {
3370
+ driver.dispose();
3371
+ } catch (err) {
3372
+ pushError({
3373
+ message: `Input driver ${id} failed to dispose`,
3374
+ cause: err,
3375
+ phase: "driver",
3376
+ timestamp: performance.now()
3377
+ });
3378
+ }
3379
+ }
3380
+ };
3381
+ return wrapped;
3382
+ },
3383
+ [faceId, namespace, pushError, setInput, setRendererValue]
3384
+ );
3385
+ const advanceAnimations = (0, import_react2.useCallback)(
3386
+ (dt) => {
3387
+ advanceAnimationTweens(dt);
3388
+ advanceClipPlayback(dt);
3389
+ },
3390
+ [advanceAnimationTweens, advanceClipPlayback]
3391
+ );
3392
+ const flushStagedInputs = (0, import_react2.useCallback)(() => {
3393
+ if (stagedInputsRef.current.size === 0) {
3394
+ return;
3395
+ }
3396
+ stagedInputsRef.current.forEach(({ value, shape }, path) => {
3397
+ orchestratorSetInput(path, value, shape);
3398
+ });
3399
+ stagedInputsRef.current.clear();
3400
+ }, [orchestratorSetInput]);
3401
+ const step = (0, import_react2.useCallback)(
3402
+ (dt, opts) => {
3403
+ if (dt > 0 && Number.isFinite(dt)) {
3404
+ const prev = avgStepDtRef.current ?? dt;
3405
+ const alpha = 0.1;
3406
+ avgStepDtRef.current = prev * (1 - alpha) + dt * alpha;
3407
+ }
3408
+ advanceAnimations(dt);
3409
+ flushStagedInputs();
3410
+ if (driveOrchestratorRef.current || opts?.forceRuntime) {
3411
+ stepRuntime(dt);
3412
+ }
3413
+ },
3414
+ [advanceAnimations, flushStagedInputs, stepRuntime]
3415
+ );
3416
+ (0, import_react2.useEffect)(() => {
3417
+ if (loopMode !== "active") {
3418
+ return;
3419
+ }
3420
+ let rafId = null;
3421
+ let lastTime = null;
3422
+ const tick = (timestamp) => {
3423
+ if (loopModeRef.current !== "active") {
3424
+ return;
3425
+ }
3426
+ if (lastTime == null) {
3427
+ lastTime = timestamp;
3428
+ }
3429
+ const dt = Math.max(0, (timestamp - lastTime) / 1e3);
3430
+ lastTime = timestamp;
3431
+ step(dt || 0);
3432
+ requestLoopMode(computeDesiredLoopMode());
3433
+ rafId = requestAnimationFrame(tick);
3434
+ };
3435
+ rafId = requestAnimationFrame(tick);
3436
+ return () => {
3437
+ if (rafId !== null) {
3438
+ cancelAnimationFrame(rafId);
3439
+ }
3440
+ };
3441
+ }, [loopMode, computeDesiredLoopMode, requestLoopMode, step]);
3442
+ (0, import_react2.useEffect)(() => {
3443
+ if (loopMode !== "idle-visible" && loopMode !== "idle-hidden") {
3444
+ return;
3445
+ }
3446
+ if (typeof window === "undefined") {
3447
+ return;
3448
+ }
3449
+ const fps = loopMode === "idle-visible" ? VISIBLE_IDLE_FPS : HIDDEN_IDLE_FPS;
3450
+ if (fps <= 0) {
3451
+ return;
3452
+ }
3453
+ let lastTime = now();
3454
+ const interval = 1e3 / fps;
3455
+ const tick = () => {
3456
+ if (loopModeRef.current !== "idle-visible" && loopModeRef.current !== "idle-hidden") {
3457
+ return;
3458
+ }
3459
+ const current = now();
3460
+ const dt = Math.max(0, (current - lastTime) / 1e3);
3461
+ lastTime = current;
3462
+ step(dt || 0);
3463
+ requestLoopMode(computeDesiredLoopMode());
3464
+ };
3465
+ const intervalId = window.setInterval(tick, interval);
3466
+ return () => {
3467
+ window.clearInterval(intervalId);
3468
+ };
3469
+ }, [loopMode, computeDesiredLoopMode, requestLoopMode, step]);
3470
+ (0, import_react2.useEffect)(() => {
3471
+ return () => {
3472
+ animationTweensRef.current.clear();
3473
+ clipPlaybackRef.current.clear();
3474
+ programPlaybackRef.current.clear();
3475
+ programControllerIdsRef.current.clear();
3476
+ clipOutputValuesRef.current.clear();
3477
+ clipAggregateValuesRef.current.clear();
3478
+ };
3479
+ }, []);
3480
+ (0, import_react2.useEffect)(() => {
3481
+ if (typeof window === "undefined") {
3482
+ return;
3483
+ }
3484
+ const id = window.setInterval(() => {
3485
+ const avg = avgStepDtRef.current;
3486
+ const stepHz = avg && Number.isFinite(avg) && avg > 0 ? 1 / avg : void 0;
3487
+ reportStatus(
3488
+ (prev) => prev.stepHz === stepHz ? prev : { ...prev, stepHz }
3489
+ );
3490
+ }, 500);
3491
+ return () => window.clearInterval(id);
3492
+ }, [reportStatus]);
3493
+ const setGraphBundle = (0, import_react2.useCallback)(
3494
+ (bundle, options) => {
3495
+ const baseAssetBundle = latestEffectiveAssetBundleRef.current;
3496
+ const nextAssetBundle = applyRuntimeGraphBundle(baseAssetBundle, bundle);
3497
+ const plan = resolveRuntimeUpdatePlan(
3498
+ baseAssetBundle,
3499
+ nextAssetBundle,
3500
+ options?.tier ?? updateTierRef.current
3501
+ );
3502
+ pendingPlanRef.current = plan;
3503
+ previousBundleRef.current = nextAssetBundle;
3504
+ latestEffectiveAssetBundleRef.current = nextAssetBundle;
3505
+ suppressNextBundlePlanRef.current = true;
3506
+ setAssetBundleOverride(nextAssetBundle);
3507
+ if (plan.reregisterGraphs) {
3508
+ setGraphUpdateToken((prev) => prev + 1);
3509
+ }
3510
+ if (plan.reloadAssets) {
3511
+ reportStatus((prev) => ({
3512
+ ...prev,
3513
+ loading: true,
3514
+ ready: false
3515
+ }));
3516
+ } else {
3517
+ reportStatus((prev) => ({
3518
+ ...prev,
3519
+ loading: false
3520
+ }));
3521
+ }
3522
+ },
3523
+ [reportStatus]
3524
+ );
3525
+ const contextValue = (0, import_react2.useMemo)(
3526
+ () => ({
3527
+ ...status,
3528
+ assetBundle,
3529
+ setInput,
3530
+ setGraphBundle,
3531
+ setValue: setRendererValue,
3532
+ stagePoseNeutral,
3533
+ animateValue,
3534
+ cancelAnimation,
3535
+ registerInputDriver,
3536
+ playAnimation,
3537
+ pauseAnimation,
3538
+ seekAnimation,
3539
+ setAnimationLoop,
3540
+ getAnimationState,
3541
+ stopAnimation,
3542
+ playProgram,
3543
+ pauseProgram,
3544
+ stopProgram,
3545
+ getProgramState,
3546
+ setAnimationActive,
3547
+ isAnimationActive,
3548
+ step,
3549
+ advanceAnimations,
3550
+ inputConstraints
3551
+ }),
3552
+ [
3553
+ status,
3554
+ assetBundle,
3555
+ setInput,
3556
+ setGraphBundle,
3557
+ setRendererValue,
3558
+ stagePoseNeutral,
3559
+ animateValue,
3560
+ cancelAnimation,
3561
+ registerInputDriver,
3562
+ playAnimation,
3563
+ pauseAnimation,
3564
+ seekAnimation,
3565
+ setAnimationLoop,
3566
+ getAnimationState,
3567
+ stopAnimation,
3568
+ playProgram,
3569
+ pauseProgram,
3570
+ stopProgram,
3571
+ getProgramState,
3572
+ setAnimationActive,
3573
+ isAnimationActive,
3574
+ step,
3575
+ advanceAnimations,
3576
+ inputConstraints
3577
+ ]
3578
+ );
3579
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(VizijRuntimeContext.Provider, { value: contextValue, children });
3580
+ }
3581
+
3582
+ // src/VizijRuntimeFace.tsx
3583
+ var import_react4 = require("react");
3584
+ var import_render2 = require("@vizij/render");
3585
+
3586
+ // src/hooks/useVizijRuntime.ts
3587
+ var import_react3 = require("react");
3588
+ function useVizijRuntime() {
3589
+ const ctx = (0, import_react3.useContext)(VizijRuntimeContext);
3590
+ if (!ctx) {
3591
+ throw new Error(
3592
+ "useVizijRuntime must be used within a VizijRuntimeProvider."
3593
+ );
3594
+ }
3595
+ return ctx;
3596
+ }
3597
+
3598
+ // src/VizijRuntimeFace.tsx
3599
+ var import_jsx_runtime2 = require("react/jsx-runtime");
3600
+ function VizijRuntimeFaceInner({
3601
+ namespaceOverride,
3602
+ ...props
3603
+ }) {
3604
+ const { rootId, namespace } = useVizijRuntime();
3605
+ if (!rootId) {
3606
+ return null;
3607
+ }
3608
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
3609
+ import_render2.Vizij,
3610
+ {
3611
+ ...props,
3612
+ rootId,
3613
+ namespace: namespaceOverride ?? namespace
3614
+ }
3615
+ );
3616
+ }
3617
+ var VizijRuntimeFace = (0, import_react4.memo)(VizijRuntimeFaceInner);
3618
+
3619
+ // src/hooks/useOptionalVizijRuntime.ts
3620
+ var import_react5 = require("react");
3621
+ function useOptionalVizijRuntime() {
3622
+ return (0, import_react5.useContext)(VizijRuntimeContext);
3623
+ }
3624
+
3625
+ // src/hooks/useVizijOutputs.ts
3626
+ var import_render3 = require("@vizij/render");
3627
+ var import_utils2 = require("@vizij/utils");
3628
+ function useVizijOutputs(paths) {
3629
+ const { namespace } = useVizijRuntime();
3630
+ return (0, import_render3.useVizijStore)((state) => {
3631
+ const result = {};
3632
+ paths.forEach((path) => {
3633
+ const lookup = (0, import_utils2.getLookup)(namespace, path);
3634
+ result[path] = state.values.get(lookup);
3635
+ });
3636
+ return result;
3637
+ });
3638
+ }
3639
+
3640
+ // src/hooks/useRigInput.ts
3641
+ var import_react6 = require("react");
3642
+ var import_render4 = require("@vizij/render");
3643
+ var import_utils3 = require("@vizij/utils");
3644
+ function useRigInput(path) {
3645
+ const { namespace, setInput } = useVizijRuntime();
3646
+ const value = (0, import_render4.useVizijStore)((state) => {
3647
+ return state.values.get((0, import_utils3.getLookup)(namespace, path));
3648
+ });
3649
+ const setter = (0, import_react6.useCallback)(
3650
+ (next, shape) => {
3651
+ setInput(path, next, shape);
3652
+ },
3653
+ [path, setInput]
3654
+ );
3655
+ return [value, setter];
3656
+ }
3657
+
3658
+ // src/utils/faceControls.ts
3659
+ var DEFAULT_GAZE_CONTROL = {
3660
+ min: -1,
3661
+ max: 1,
3662
+ defaultValue: 0
3663
+ };
3664
+ var DEFAULT_BLINK_CONTROL = {
3665
+ min: 0,
3666
+ max: 1,
3667
+ defaultValue: 0
3668
+ };
3669
+ var STANDARD_VIZIJ_EYE_PATHS = {
3670
+ leftX: "/standard/vizij/left_eye/pos/x",
3671
+ leftY: "/standard/vizij/left_eye/pos/y",
3672
+ rightX: "/standard/vizij/right_eye/pos/x",
3673
+ rightY: "/standard/vizij/right_eye/pos/y",
3674
+ leftUpper: "/standard/vizij/left_eye_top_eyelid/pos/y",
3675
+ rightUpper: "/standard/vizij/right_eye_top_eyelid/pos/y"
3676
+ };
3677
+ var LEGACY_STANDARD_EYE_PATHS = {
3678
+ leftX: "/standard/left_eye/pos/x",
3679
+ leftY: "/standard/left_eye/pos/y",
3680
+ rightX: "/standard/right_eye/pos/x",
3681
+ rightY: "/standard/right_eye/pos/y",
3682
+ leftUpper: "/standard/left_eye_top_eyelid/pos/y",
3683
+ rightUpper: "/standard/right_eye_top_eyelid/pos/y"
3684
+ };
3685
+ var PROPSRIG_EYE_PATHS = {
3686
+ leftX: "/propsrig/l_eye/translation/x",
3687
+ leftY: "/propsrig/l_eye/translation/y",
3688
+ rightX: "/propsrig/r_eye/translation/x",
3689
+ rightY: "/propsrig/r_eye/translation/y"
3690
+ };
3691
+ function clamp(value, min, max) {
3692
+ return Math.min(Math.max(value, min), max);
3693
+ }
3694
+ function normalizeInputPath(path) {
3695
+ const trimmed = path?.trim() ?? "";
3696
+ if (!trimmed) {
3697
+ return null;
3698
+ }
3699
+ if (trimmed.startsWith("/")) {
3700
+ return trimmed;
3701
+ }
3702
+ const rigMatch = trimmed.match(/^rig\/[^/]+(\/.*)$/);
3703
+ if (rigMatch?.[1]) {
3704
+ return rigMatch[1];
3705
+ }
3706
+ return `/${trimmed}`;
3707
+ }
3708
+ function buildControlsLookup(assetBundle) {
3709
+ const metadata = assetBundle.rig?.inputMetadata ?? [];
3710
+ const metadataByPath = /* @__PURE__ */ new Map();
3711
+ const pathSet = /* @__PURE__ */ new Set();
3712
+ metadata.forEach((entry) => {
3713
+ const normalized = normalizeInputPath(entry.path);
3714
+ if (!normalized) {
3715
+ return;
3716
+ }
3717
+ pathSet.add(normalized);
3718
+ if (!metadataByPath.has(normalized)) {
3719
+ metadataByPath.set(normalized, entry);
3720
+ }
3721
+ });
3722
+ return { pathSet, metadataByPath };
3723
+ }
3724
+ function resolveConstraint(absolutePath, relativePath, inputConstraints) {
3725
+ if (!inputConstraints) {
3726
+ return null;
3727
+ }
3728
+ const normalizedRelative = relativePath.replace(/^\//, "");
3729
+ const candidates = [
3730
+ absolutePath,
3731
+ relativePath,
3732
+ normalizedRelative,
3733
+ absolutePath.replace(/^rig\/[^/]+\//, "")
3734
+ ];
3735
+ for (const candidate of candidates) {
3736
+ const constraint = inputConstraints[candidate];
3737
+ if (constraint) {
3738
+ return constraint;
3739
+ }
3740
+ }
3741
+ return null;
3742
+ }
3743
+ function createControl(faceId, lookup, relativePath, inputConstraints, fallback) {
3744
+ const normalizedRelative = normalizeInputPath(relativePath);
3745
+ if (!normalizedRelative || !lookup.pathSet.has(normalizedRelative)) {
3746
+ return null;
3747
+ }
3748
+ const absolutePath = buildRigInputPath(faceId, normalizedRelative);
3749
+ const metadata = lookup.metadataByPath.get(normalizedRelative);
3750
+ const constraint = resolveConstraint(
3751
+ absolutePath,
3752
+ normalizedRelative,
3753
+ inputConstraints
3754
+ );
3755
+ const min = Number.isFinite(Number(constraint?.min ?? metadata?.range?.min)) ? Number(constraint?.min ?? metadata?.range?.min) : fallback.min;
3756
+ const max = Number.isFinite(Number(constraint?.max ?? metadata?.range?.max)) ? Number(constraint?.max ?? metadata?.range?.max) : fallback.max;
3757
+ const midpoint = min + (max - min) / 2;
3758
+ const defaultValue = Number.isFinite(
3759
+ Number(constraint?.defaultValue ?? metadata?.defaultValue)
3760
+ ) ? Number(constraint?.defaultValue ?? metadata?.defaultValue) : Number.isFinite(midpoint) ? midpoint : fallback.defaultValue;
3761
+ return {
3762
+ path: absolutePath,
3763
+ min,
3764
+ max,
3765
+ defaultValue
3766
+ };
3767
+ }
3768
+ function hasAll(controls, keys) {
3769
+ return keys.every((key) => Boolean(controls[key]));
3770
+ }
3771
+ function resolveFaceControls(assetBundle, runtimeFaceId, inputConstraints) {
3772
+ const faceId = assetBundle.faceId ?? assetBundle.pose?.config?.faceId ?? runtimeFaceId ?? "face";
3773
+ const lookup = buildControlsLookup(assetBundle);
3774
+ const standardVizijControls = {
3775
+ leftX: createControl(
3776
+ faceId,
3777
+ lookup,
3778
+ STANDARD_VIZIJ_EYE_PATHS.leftX,
3779
+ inputConstraints,
3780
+ DEFAULT_GAZE_CONTROL
3781
+ ),
3782
+ leftY: createControl(
3783
+ faceId,
3784
+ lookup,
3785
+ STANDARD_VIZIJ_EYE_PATHS.leftY,
3786
+ inputConstraints,
3787
+ DEFAULT_GAZE_CONTROL
3788
+ ),
3789
+ rightX: createControl(
3790
+ faceId,
3791
+ lookup,
3792
+ STANDARD_VIZIJ_EYE_PATHS.rightX,
3793
+ inputConstraints,
3794
+ DEFAULT_GAZE_CONTROL
3795
+ ),
3796
+ rightY: createControl(
3797
+ faceId,
3798
+ lookup,
3799
+ STANDARD_VIZIJ_EYE_PATHS.rightY,
3800
+ inputConstraints,
3801
+ DEFAULT_GAZE_CONTROL
3802
+ ),
3803
+ leftUpper: createControl(
3804
+ faceId,
3805
+ lookup,
3806
+ STANDARD_VIZIJ_EYE_PATHS.leftUpper,
3807
+ inputConstraints,
3808
+ DEFAULT_GAZE_CONTROL
3809
+ ),
3810
+ rightUpper: createControl(
3811
+ faceId,
3812
+ lookup,
3813
+ STANDARD_VIZIJ_EYE_PATHS.rightUpper,
3814
+ inputConstraints,
3815
+ DEFAULT_GAZE_CONTROL
3816
+ )
3817
+ };
3818
+ const legacyStandardControls = {
3819
+ leftX: createControl(
3820
+ faceId,
3821
+ lookup,
3822
+ LEGACY_STANDARD_EYE_PATHS.leftX,
3823
+ inputConstraints,
3824
+ DEFAULT_GAZE_CONTROL
3825
+ ),
3826
+ leftY: createControl(
3827
+ faceId,
3828
+ lookup,
3829
+ LEGACY_STANDARD_EYE_PATHS.leftY,
3830
+ inputConstraints,
3831
+ DEFAULT_GAZE_CONTROL
3832
+ ),
3833
+ rightX: createControl(
3834
+ faceId,
3835
+ lookup,
3836
+ LEGACY_STANDARD_EYE_PATHS.rightX,
3837
+ inputConstraints,
3838
+ DEFAULT_GAZE_CONTROL
3839
+ ),
3840
+ rightY: createControl(
3841
+ faceId,
3842
+ lookup,
3843
+ LEGACY_STANDARD_EYE_PATHS.rightY,
3844
+ inputConstraints,
3845
+ DEFAULT_GAZE_CONTROL
3846
+ ),
3847
+ leftUpper: createControl(
3848
+ faceId,
3849
+ lookup,
3850
+ LEGACY_STANDARD_EYE_PATHS.leftUpper,
3851
+ inputConstraints,
3852
+ DEFAULT_GAZE_CONTROL
3853
+ ),
3854
+ rightUpper: createControl(
3855
+ faceId,
3856
+ lookup,
3857
+ LEGACY_STANDARD_EYE_PATHS.rightUpper,
3858
+ inputConstraints,
3859
+ DEFAULT_GAZE_CONTROL
3860
+ )
3861
+ };
3862
+ const propsrigControls = {
3863
+ leftX: createControl(
3864
+ faceId,
3865
+ lookup,
3866
+ PROPSRIG_EYE_PATHS.leftX,
3867
+ inputConstraints,
3868
+ DEFAULT_GAZE_CONTROL
3869
+ ),
3870
+ leftY: createControl(
3871
+ faceId,
3872
+ lookup,
3873
+ PROPSRIG_EYE_PATHS.leftY,
3874
+ inputConstraints,
3875
+ DEFAULT_GAZE_CONTROL
3876
+ ),
3877
+ rightX: createControl(
3878
+ faceId,
3879
+ lookup,
3880
+ PROPSRIG_EYE_PATHS.rightX,
3881
+ inputConstraints,
3882
+ DEFAULT_GAZE_CONTROL
3883
+ ),
3884
+ rightY: createControl(
3885
+ faceId,
3886
+ lookup,
3887
+ PROPSRIG_EYE_PATHS.rightY,
3888
+ inputConstraints,
3889
+ DEFAULT_GAZE_CONTROL
3890
+ )
3891
+ };
3892
+ const coupledGazeControls = {
3893
+ leftX: createControl(
3894
+ faceId,
3895
+ lookup,
3896
+ "/gaze/left_right",
3897
+ inputConstraints,
3898
+ DEFAULT_GAZE_CONTROL
3899
+ ),
3900
+ leftY: createControl(
3901
+ faceId,
3902
+ lookup,
3903
+ "/gaze/up_down",
3904
+ inputConstraints,
3905
+ DEFAULT_GAZE_CONTROL
3906
+ ),
3907
+ rightX: createControl(
3908
+ faceId,
3909
+ lookup,
3910
+ "/gaze/left_right_copy",
3911
+ inputConstraints,
3912
+ DEFAULT_GAZE_CONTROL
3913
+ ) ?? createControl(
3914
+ faceId,
3915
+ lookup,
3916
+ "/gaze/left_right",
3917
+ inputConstraints,
3918
+ DEFAULT_GAZE_CONTROL
3919
+ ),
3920
+ rightY: createControl(
3921
+ faceId,
3922
+ lookup,
3923
+ "/gaze/up_down_copy",
3924
+ inputConstraints,
3925
+ DEFAULT_GAZE_CONTROL
3926
+ ) ?? createControl(
3927
+ faceId,
3928
+ lookup,
3929
+ "/gaze/up_down",
3930
+ inputConstraints,
3931
+ DEFAULT_GAZE_CONTROL
3932
+ )
3933
+ };
3934
+ const blinkFromLids = createControl(
3935
+ faceId,
3936
+ lookup,
3937
+ "/lids/blink",
3938
+ inputConstraints,
3939
+ DEFAULT_BLINK_CONTROL
3940
+ );
3941
+ const blinkDirect = createControl(
3942
+ faceId,
3943
+ lookup,
3944
+ "/blink",
3945
+ inputConstraints,
3946
+ DEFAULT_BLINK_CONTROL
3947
+ );
3948
+ if (hasAll(standardVizijControls, ["leftX", "leftY", "rightX", "rightY"])) {
3949
+ return {
3950
+ faceId,
3951
+ gazeSource: "standard-vizij",
3952
+ blinkSource: blinkFromLids ? "lids" : blinkDirect ? "blink" : "none",
3953
+ eyes: {
3954
+ leftX: standardVizijControls.leftX,
3955
+ leftY: standardVizijControls.leftY,
3956
+ rightX: standardVizijControls.rightX,
3957
+ rightY: standardVizijControls.rightY
3958
+ },
3959
+ eyelids: {
3960
+ leftUpper: standardVizijControls.leftUpper,
3961
+ rightUpper: standardVizijControls.rightUpper
3962
+ },
3963
+ blink: blinkFromLids ?? blinkDirect
3964
+ };
3965
+ }
3966
+ if (hasAll(legacyStandardControls, ["leftX", "leftY", "rightX", "rightY"])) {
3967
+ return {
3968
+ faceId,
3969
+ gazeSource: "standard",
3970
+ blinkSource: blinkDirect ? "blink" : blinkFromLids ? "lids" : "none",
3971
+ eyes: {
3972
+ leftX: legacyStandardControls.leftX,
3973
+ leftY: legacyStandardControls.leftY,
3974
+ rightX: legacyStandardControls.rightX,
3975
+ rightY: legacyStandardControls.rightY
3976
+ },
3977
+ eyelids: {
3978
+ leftUpper: legacyStandardControls.leftUpper,
3979
+ rightUpper: legacyStandardControls.rightUpper
3980
+ },
3981
+ blink: blinkDirect ?? blinkFromLids
3982
+ };
3983
+ }
3984
+ if (hasAll(propsrigControls, ["leftX", "leftY", "rightX", "rightY"])) {
3985
+ return {
3986
+ faceId,
3987
+ gazeSource: "propsrig",
3988
+ blinkSource: blinkDirect ? "blink" : blinkFromLids ? "lids" : "none",
3989
+ eyes: {
3990
+ leftX: propsrigControls.leftX,
3991
+ leftY: propsrigControls.leftY,
3992
+ rightX: propsrigControls.rightX,
3993
+ rightY: propsrigControls.rightY
3994
+ },
3995
+ eyelids: {
3996
+ leftUpper: null,
3997
+ rightUpper: null
3998
+ },
3999
+ blink: blinkDirect ?? blinkFromLids
4000
+ };
4001
+ }
4002
+ if (hasAll(coupledGazeControls, ["leftX", "leftY", "rightX", "rightY"])) {
4003
+ return {
4004
+ faceId,
4005
+ gazeSource: "coupled-gaze",
4006
+ blinkSource: blinkFromLids ? "lids" : blinkDirect ? "blink" : "none",
4007
+ eyes: {
4008
+ leftX: coupledGazeControls.leftX,
4009
+ leftY: coupledGazeControls.leftY,
4010
+ rightX: coupledGazeControls.rightX,
4011
+ rightY: coupledGazeControls.rightY
4012
+ },
4013
+ eyelids: {
4014
+ leftUpper: null,
4015
+ rightUpper: null
4016
+ },
4017
+ blink: blinkFromLids ?? blinkDirect
4018
+ };
4019
+ }
4020
+ return {
4021
+ faceId,
4022
+ gazeSource: "none",
4023
+ blinkSource: blinkFromLids ? "lids" : blinkDirect ? "blink" : "none",
4024
+ eyes: {
4025
+ leftX: null,
4026
+ leftY: null,
4027
+ rightX: null,
4028
+ rightY: null
4029
+ },
4030
+ eyelids: {
4031
+ leftUpper: null,
4032
+ rightUpper: null
4033
+ },
4034
+ blink: blinkFromLids ?? blinkDirect
4035
+ };
4036
+ }
4037
+ function mapNormalizedControlValue(control, normalizedValue) {
4038
+ const value = clamp(normalizedValue, -1, 1);
4039
+ if (value === 0) {
4040
+ return control.defaultValue;
4041
+ }
4042
+ if (value > 0) {
4043
+ return control.defaultValue + (control.max - control.defaultValue) * value;
4044
+ }
4045
+ return control.defaultValue + (control.defaultValue - control.min) * value;
4046
+ }
4047
+ function mapUnitControlValue(control, unitValue) {
4048
+ const value = clamp(unitValue, 0, 1);
4049
+ return control.defaultValue + (control.max - control.defaultValue) * value;
4050
+ }
4051
+ // Annotate the CommonJS export names for ESM import in node:
4052
+ 0 && (module.exports = {
4053
+ EMOTION_POSE_KEYS,
4054
+ EXPRESSIVE_EMOTION_POSE_KEYS,
4055
+ POSE_WEIGHT_INPUT_PATH_PREFIX,
4056
+ VISEME_POSE_KEYS,
4057
+ VizijRuntimeFace,
4058
+ VizijRuntimeProvider,
4059
+ buildPoseWeightInputPathSegment,
4060
+ buildPoseWeightPathMap,
4061
+ buildPoseWeightRelativePath,
4062
+ buildRigInputPath,
4063
+ buildSemanticPoseWeightPathMap,
4064
+ filterPosesBySemanticKind,
4065
+ getPoseSemanticKey,
4066
+ mapNormalizedControlValue,
4067
+ mapUnitControlValue,
4068
+ normalizePoseSemanticKey,
4069
+ resolveFaceControls,
4070
+ resolvePoseMembership,
4071
+ resolvePoseSemantics,
4072
+ resolveRuntimeUpdatePlan,
4073
+ useOptionalVizijRuntime,
4074
+ useRigInput,
4075
+ useVizijOutputs,
4076
+ useVizijRuntime
4077
+ });