@vizij/runtime-react 0.1.0 → 0.3.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 CHANGED
@@ -20,8 +20,27 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
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,
23
27
  VizijRuntimeFace: () => VizijRuntimeFace,
24
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,
25
44
  useRigInput: () => useRigInput,
26
45
  useVizijOutputs: () => useVizijOutputs,
27
46
  useVizijRuntime: () => useVizijRuntime
@@ -31,14 +50,486 @@ module.exports = __toCommonJS(index_exports);
31
50
  // src/VizijRuntimeProvider.tsx
32
51
  var import_react2 = require("react");
33
52
  var import_render = require("@vizij/render");
34
- var import_orchestrator_react = require("@vizij/orchestrator-react");
35
53
  var import_node_graph_authoring = require("@vizij/node-graph-authoring");
36
54
  var import_value_json2 = require("@vizij/value-json");
55
+ var import_utils = require("@vizij/utils");
56
+ var import_animation_module = require("@vizij/animation-module");
57
+
58
+ // src/engine/aroraEngine.ts
59
+ var import_runtime = require("@vizij/runtime");
60
+
61
+ // src/engine/graphSpecDiff.ts
62
+ function asRecords(value) {
63
+ return Array.isArray(value) ? value : [];
64
+ }
65
+ function nodeId(node) {
66
+ return typeof node.id === "string" ? node.id : void 0;
67
+ }
68
+ function endpointId(endpoint) {
69
+ const id = endpoint?.node_id;
70
+ return typeof id === "string" ? id : void 0;
71
+ }
72
+ function endpointInput(endpoint) {
73
+ const input = endpoint?.input;
74
+ return typeof input === "string" ? input : void 0;
75
+ }
76
+ function stable(value) {
77
+ return JSON.stringify(sortKeys(value));
78
+ }
79
+ function sortKeys(value) {
80
+ if (Array.isArray(value)) return value.map(sortKeys);
81
+ if (value && typeof value === "object") {
82
+ const out = {};
83
+ for (const key of Object.keys(value).sort()) {
84
+ out[key] = sortKeys(value[key]);
85
+ }
86
+ return out;
87
+ }
88
+ return value;
89
+ }
90
+ function edgeKey(edge) {
91
+ return stable([
92
+ endpointId(edge.from),
93
+ edge.from?.output ?? "out",
94
+ endpointId(edge.to),
95
+ endpointInput(edge.to),
96
+ edge.selector ?? null
97
+ ]);
98
+ }
99
+ function graphSpecDiff(prev, next) {
100
+ const prevNodes = /* @__PURE__ */ new Map();
101
+ for (const node of asRecords(prev.nodes)) {
102
+ const id = nodeId(node);
103
+ if (id) prevNodes.set(id, node);
104
+ }
105
+ const nextNodes = /* @__PURE__ */ new Map();
106
+ for (const node of asRecords(next.nodes)) {
107
+ const id = nodeId(node);
108
+ if (id) nextNodes.set(id, node);
109
+ }
110
+ const prevEdges = asRecords(prev.edges);
111
+ const nextEdges = asRecords(next.edges);
112
+ const remove_nodes = [...prevNodes.keys()].filter((id) => !nextNodes.has(id));
113
+ const upsertIds = /* @__PURE__ */ new Set();
114
+ for (const [id, node] of nextNodes) {
115
+ const before = prevNodes.get(id);
116
+ if (!before || stable(before) !== stable(node)) upsertIds.add(id);
117
+ }
118
+ const prevEdgeKeys = new Set(prevEdges.map(edgeKey));
119
+ const nextEdgeKeys = new Set(nextEdges.map(edgeKey));
120
+ const removedEdges = prevEdges.filter((e) => !nextEdgeKeys.has(edgeKey(e)));
121
+ const changedEdges = [
122
+ ...nextEdges.filter((e) => !prevEdgeKeys.has(edgeKey(e))),
123
+ ...removedEdges
124
+ ];
125
+ for (const edge of changedEdges) {
126
+ for (const id of [endpointId(edge.from), endpointId(edge.to)]) {
127
+ if (id && nextNodes.has(id)) upsertIds.add(id);
128
+ }
129
+ }
130
+ const incidentToUpsert = (edge) => {
131
+ const from = endpointId(edge.from);
132
+ const to = endpointId(edge.to);
133
+ return from !== void 0 && upsertIds.has(from) || to !== void 0 && upsertIds.has(to);
134
+ };
135
+ const upsert_nodes = [...upsertIds].map((id) => nextNodes.get(id));
136
+ const upsert_edges = nextEdges.filter(incidentToUpsert);
137
+ const removeNodeSet = new Set(remove_nodes);
138
+ const remove_edges = removedEdges.flatMap((edge) => {
139
+ const to = endpointId(edge.to);
140
+ const input = endpointInput(edge.to);
141
+ if (!to || !input || removeNodeSet.has(to) || upsertIds.has(to)) return [];
142
+ return [{ node_id: to, input }];
143
+ });
144
+ if (upsert_nodes.length === 0 && remove_nodes.length === 0 && upsert_edges.length === 0 && remove_edges.length === 0) {
145
+ return null;
146
+ }
147
+ return { upsert_nodes, remove_nodes, upsert_edges, remove_edges };
148
+ }
149
+
150
+ // src/engine/aroraEngine.ts
151
+ var BUILT_IN_PREFIX = "arora/";
152
+ function isBuiltInPath(path) {
153
+ return path.startsWith(BUILT_IN_PREFIX);
154
+ }
155
+ var RUN_PERIOD_MS = 1e3 / 60;
156
+ var initPromise = null;
157
+ function ensureWasmInit(input) {
158
+ if (!initPromise) {
159
+ initPromise = (0, import_runtime.init)(input);
160
+ }
161
+ return initPromise;
162
+ }
163
+ var DeviceSlot = class {
164
+ constructor() {
165
+ this.handle = null;
166
+ /** Module loading a boot must wait out before it reads `modules`. */
167
+ this.modulesLoading = null;
168
+ /**
169
+ * When set, a booted device is handed to its own loop (`device.run`) at
170
+ * this period. `null`: the host drives `step()` itself.
171
+ */
172
+ this.runPeriodMs = null;
173
+ /**
174
+ * Serializes device operations. Every `recompose` chains behind the
175
+ * previous op so two never run concurrently — a bundle whose import fans
176
+ * out into several graph registrations recomposes in a burst. Failures are
177
+ * swallowed on the chain so one failed op doesn't wedge later ones; each
178
+ * caller still sees its own rejection through the promise `enqueue`
179
+ * returns.
180
+ */
181
+ this.opChain = Promise.resolve();
182
+ }
183
+ get current() {
184
+ return this.handle;
185
+ }
186
+ /**
187
+ * The modules every subsequent device boot loads. The live device (if any)
188
+ * is not touched: a later `recompose` that sees the list changed rebuilds.
189
+ */
190
+ setModules(modules) {
191
+ this.modules = modules && modules.length > 0 ? modules : void 0;
192
+ }
193
+ /**
194
+ * Announce module loading in flight: boots wait for `loading` to settle
195
+ * before reading the module list, so a device never boots without modules
196
+ * that were already on their way. Pass a promise that cannot reject (the
197
+ * owner handles its own failures) — boots resume either way.
198
+ */
199
+ waitForModules(loading) {
200
+ this.modulesLoading = loading;
201
+ }
202
+ /**
203
+ * Hand the live device to its own loop, if there is one and `runPeriodMs`
204
+ * says so — for a host whose drive mode turns self-paced after boot.
205
+ */
206
+ startRun() {
207
+ if (this.handle) {
208
+ this.maybeRun(this.handle.device);
209
+ }
210
+ }
211
+ maybeRun(device) {
212
+ if (this.runPeriodMs === null || device.running) {
213
+ return;
214
+ }
215
+ device.run(this.runPeriodMs).catch((error) => {
216
+ this.onRunEnded?.(error);
217
+ });
218
+ }
219
+ enqueue(op) {
220
+ const run = this.opChain.then(op, op);
221
+ this.opChain = run.then(
222
+ () => void 0,
223
+ () => void 0
224
+ );
225
+ return run;
226
+ }
227
+ async boot(spec) {
228
+ await ensureWasmInit();
229
+ if (this.modulesLoading) {
230
+ await this.modulesLoading;
231
+ }
232
+ const device = await (0, import_runtime.startRuntime)(spec, void 0, this.modules);
233
+ this.handle = { device, spec, modules: this.modules };
234
+ this.onDeviceStarted?.(this.handle);
235
+ this.maybeRun(device);
236
+ return this.handle;
237
+ }
238
+ /**
239
+ * Rebuild the device because its module set changed since boot — the one
240
+ * change a live device cannot take. Carries the store across (snapshot
241
+ * minus built-in keys) and replays module setup via `onDeviceStarted`. The
242
+ * old device's wrapper is freed; if it was self-paced its loop keeps
243
+ * ticking unreachable — which is why boots wait for announced modules
244
+ * instead of leaning on this path.
245
+ */
246
+ async rebuild(old, spec) {
247
+ const carried = {};
248
+ for (const [path, value] of Object.entries(old.device.snapshot())) {
249
+ if (!isBuiltInPath(path)) {
250
+ carried[path] = value;
251
+ }
252
+ }
253
+ const device = await (0, import_runtime.startRuntime)(spec, void 0, this.modules);
254
+ if (Object.keys(carried).length > 0) {
255
+ device.writeValues(carried);
256
+ }
257
+ this.handle = { device, spec, modules: this.modules };
258
+ old.device.dispose();
259
+ this.onDeviceStarted?.(this.handle);
260
+ this.maybeRun(device);
261
+ return this.handle;
262
+ }
263
+ /**
264
+ * Make the device run `spec`: boot it if none is live, otherwise swap the
265
+ * graph in place (the store is the same store, so nothing is carried).
266
+ * No-op if the spec is identical by reference. Serialized behind any
267
+ * in-flight op (see `opChain`).
268
+ */
269
+ recompose(spec) {
270
+ return this.enqueue(async () => {
271
+ const old = this.handle;
272
+ if (!old) {
273
+ return this.boot(spec);
274
+ }
275
+ if (old.modules !== this.modules) {
276
+ return this.rebuild(old, spec);
277
+ }
278
+ if (old.spec === spec) {
279
+ return old;
280
+ }
281
+ const edits = graphSpecDiff(old.spec, spec);
282
+ if (edits) {
283
+ try {
284
+ await old.device.applyGraphEdits(edits);
285
+ } catch (err) {
286
+ console.warn(
287
+ "[vizij-runtime] applyGraphEdits failed; reloading the whole graph",
288
+ err
289
+ );
290
+ await old.device.loadGraph(spec);
291
+ }
292
+ }
293
+ old.spec = spec;
294
+ return old;
295
+ });
296
+ }
297
+ };
298
+
299
+ // src/utils/composeGraph.ts
300
+ function asArray(value) {
301
+ return Array.isArray(value) ? value.filter((n) => n && typeof n === "object") : [];
302
+ }
303
+ function prefixId(sourceId, id) {
304
+ return typeof id === "string" && id ? `${sourceId}::${id}` : id;
305
+ }
306
+ function prefixEndpoint(sourceId, endpoint) {
307
+ if (!endpoint || typeof endpoint !== "object") {
308
+ return endpoint;
309
+ }
310
+ const record = endpoint;
311
+ if (typeof record.node_id !== "string") {
312
+ return endpoint;
313
+ }
314
+ return { ...record, node_id: prefixId(sourceId, record.node_id) };
315
+ }
316
+ function outputPathsOf(spec) {
317
+ const paths = [];
318
+ for (const node of asArray(spec.nodes)) {
319
+ if (String(node.type ?? "").toLowerCase() !== "output") {
320
+ continue;
321
+ }
322
+ const path = node.params?.path;
323
+ if (typeof path === "string" && path.trim()) {
324
+ paths.push(path.trim());
325
+ }
326
+ }
327
+ return paths;
328
+ }
329
+ function composeGraphSpecs(sources) {
330
+ const nodes = [];
331
+ const edges = [];
332
+ const outputOwners = /* @__PURE__ */ new Map();
333
+ for (const { sourceId, spec } of sources) {
334
+ for (const path of outputPathsOf(spec)) {
335
+ const owner = outputOwners.get(path);
336
+ if (owner && owner !== sourceId) {
337
+ console.warn(
338
+ `[vizij-runtime] output path "${path}" is written by both "${owner}" and "${sourceId}"; last writer wins ("${sourceId}"). There is no combiner node for additive merges yet.`
339
+ );
340
+ }
341
+ outputOwners.set(path, sourceId);
342
+ }
343
+ for (const node of asArray(spec.nodes)) {
344
+ nodes.push({ ...node, id: prefixId(sourceId, node.id) });
345
+ }
346
+ for (const edge of asArray(spec.edges)) {
347
+ edges.push({
348
+ ...edge,
349
+ from: prefixEndpoint(sourceId, edge.from),
350
+ to: prefixEndpoint(sourceId, edge.to)
351
+ });
352
+ }
353
+ }
354
+ return { nodes, edges };
355
+ }
37
356
 
38
357
  // src/context.ts
39
358
  var import_react = require("react");
40
359
  var VizijRuntimeContext = (0, import_react.createContext)(null);
41
360
 
361
+ // src/memoryInvestigation.ts
362
+ function getGlobalMemoryInvestigation() {
363
+ return globalThis;
364
+ }
365
+ function updateRuntimeMap(updater) {
366
+ const globalObj = getGlobalMemoryInvestigation();
367
+ if (!globalObj.__VIZIJ_MEMORY_INVESTIGATION__?.enabled) {
368
+ return;
369
+ }
370
+ if (!globalObj.__vizijMemoryDebugState) {
371
+ globalObj.__vizijMemoryDebugState = {};
372
+ }
373
+ if (!globalObj.__vizijMemoryDebugState.runtimes) {
374
+ globalObj.__vizijMemoryDebugState.runtimes = {};
375
+ }
376
+ updater(globalObj.__vizijMemoryDebugState.runtimes);
377
+ }
378
+ function setRuntimeDebugState(runtimeId, state) {
379
+ updateRuntimeMap((runtimeMap) => {
380
+ runtimeMap[runtimeId] = state;
381
+ });
382
+ }
383
+ function clearRuntimeDebugState(runtimeId) {
384
+ updateRuntimeMap((runtimeMap) => {
385
+ delete runtimeMap[runtimeId];
386
+ });
387
+ }
388
+
389
+ // src/updatePolicy.ts
390
+ var blobIdentityMap = /* @__PURE__ */ new WeakMap();
391
+ var nextBlobIdentity = 0;
392
+ function getBlobIdentity(blob) {
393
+ if (!blob) {
394
+ return "missing";
395
+ }
396
+ let identity = blobIdentityMap.get(blob);
397
+ if (identity === void 0) {
398
+ identity = nextBlobIdentity++;
399
+ blobIdentityMap.set(blob, identity);
400
+ }
401
+ return `${identity}:${blob.size}:${blob.type}`;
402
+ }
403
+ function applyRuntimeGraphBundle(base, bundle) {
404
+ const next = {
405
+ ...base
406
+ };
407
+ const hasRigOverride = Object.prototype.hasOwnProperty.call(bundle, "rig");
408
+ const hasPoseOverride = Object.prototype.hasOwnProperty.call(bundle, "pose");
409
+ const hasAnimationsOverride = Object.prototype.hasOwnProperty.call(
410
+ bundle,
411
+ "animations"
412
+ );
413
+ const hasProgramsOverride = Object.prototype.hasOwnProperty.call(
414
+ bundle,
415
+ "programs"
416
+ );
417
+ if (hasRigOverride) {
418
+ next.rig = bundle.rig;
419
+ }
420
+ if (hasPoseOverride) {
421
+ next.pose = bundle.pose;
422
+ }
423
+ if (hasAnimationsOverride) {
424
+ next.animations = Array.isArray(bundle.animations) ? bundle.animations : void 0;
425
+ }
426
+ if (hasProgramsOverride) {
427
+ next.programs = Array.isArray(bundle.programs) ? bundle.programs : void 0;
428
+ }
429
+ return next;
430
+ }
431
+ function normalizeSpecPayload(value) {
432
+ if (!value) {
433
+ return "";
434
+ }
435
+ try {
436
+ return JSON.stringify(value);
437
+ } catch {
438
+ return String(value);
439
+ }
440
+ }
441
+ function glbSignature(glb) {
442
+ if (glb.kind === "url") {
443
+ const importOptions = normalizeSpecPayload({
444
+ aggressiveImport: glb.aggressiveImport ?? false,
445
+ rootBounds: glb.rootBounds ?? null
446
+ });
447
+ return `url:${glb.src}:${importOptions}`;
448
+ }
449
+ if (glb.kind === "blob") {
450
+ const importOptions = normalizeSpecPayload({
451
+ aggressiveImport: glb.aggressiveImport ?? false,
452
+ rootBounds: glb.rootBounds ?? null
453
+ });
454
+ return `blob:${getBlobIdentity(glb.blob)}:${importOptions}`;
455
+ }
456
+ return `world:${normalizeSpecPayload(glb.world)}`;
457
+ }
458
+ function graphSignature(graph) {
459
+ if (!graph) {
460
+ return "";
461
+ }
462
+ const id = graph.id ?? "";
463
+ return `${id}:${normalizeSpecPayload(graph.spec ?? graph.ir ?? null)}`;
464
+ }
465
+ function poseSignature(pose) {
466
+ if (!pose) {
467
+ return "";
468
+ }
469
+ const graph = pose.graph;
470
+ const config = pose.config;
471
+ const graphPart = graph ? graphSignature({ id: graph.id, spec: graph.spec }) : "";
472
+ const configPart = config ? normalizeSpecPayload(config) : "";
473
+ return `${graphPart}:${configPart}`;
474
+ }
475
+ function animationsSignature(animations) {
476
+ if (!Array.isArray(animations) || animations.length === 0) {
477
+ return "";
478
+ }
479
+ return animations.map((animation) => {
480
+ const id = animation.id ?? "";
481
+ const clipSignature = normalizeSpecPayload(animation.clip ?? null);
482
+ const setupSignature = normalizeSpecPayload(animation.setup ?? null);
483
+ const weightSignature = animation.weight == null ? "" : String(animation.weight);
484
+ return `${id}:${clipSignature}:${setupSignature}:${weightSignature}`;
485
+ }).sort().join("|");
486
+ }
487
+ function programsSignature(programs) {
488
+ if (!Array.isArray(programs) || programs.length === 0) {
489
+ return "";
490
+ }
491
+ return programs.map((program) => {
492
+ const id = program.id ?? "";
493
+ const label = program.label ?? "";
494
+ const graphId = program.graph?.id ?? "";
495
+ const graphPayload = normalizeSpecPayload(
496
+ program.graph?.spec ?? program.graph?.ir ?? null
497
+ );
498
+ const resetValues = normalizeSpecPayload(program.resetValues ?? null);
499
+ return `${id}:${label}:${graphId}:${graphPayload}:${resetValues}`;
500
+ }).sort().join("|");
501
+ }
502
+ function resolveRuntimeUpdatePlan(previous, next, tier) {
503
+ if (!previous) {
504
+ return { reloadAssets: true, reregisterGraphs: false };
505
+ }
506
+ const glbChanged = glbSignature(previous.glb) !== glbSignature(next.glb);
507
+ const rigChanged = graphSignature(previous.rig) !== graphSignature(next.rig);
508
+ const poseChanged = poseSignature(previous.pose) !== poseSignature(next.pose);
509
+ const rigReferenceChanged = previous.rig?.id !== next.rig?.id || previous.rig?.spec !== next.rig?.spec || previous.rig?.ir !== next.rig?.ir;
510
+ const poseReferenceChanged = previous.pose?.graph?.id !== next.pose?.graph?.id || previous.pose?.graph?.spec !== next.pose?.graph?.spec || previous.pose?.config !== next.pose?.config;
511
+ const graphsChanged = rigChanged || poseChanged || rigReferenceChanged || poseReferenceChanged;
512
+ const animationsChanged = animationsSignature(previous.animations) !== animationsSignature(next.animations);
513
+ const programsChanged = programsSignature(previous.programs) !== programsSignature(next.programs);
514
+ const controllersChanged = graphsChanged || animationsChanged || programsChanged;
515
+ if (tier === "assets") {
516
+ return { reloadAssets: true, reregisterGraphs: false };
517
+ }
518
+ if (tier === "graphs") {
519
+ if (glbChanged) {
520
+ return { reloadAssets: true, reregisterGraphs: false };
521
+ }
522
+ return { reloadAssets: false, reregisterGraphs: controllersChanged };
523
+ }
524
+ if (glbChanged) {
525
+ return { reloadAssets: true, reregisterGraphs: false };
526
+ }
527
+ if (controllersChanged) {
528
+ return { reloadAssets: false, reregisterGraphs: true };
529
+ }
530
+ return { reloadAssets: false, reregisterGraphs: false };
531
+ }
532
+
42
533
  // src/utils/graph.ts
43
534
  function getNodes(spec) {
44
535
  if (!spec || typeof spec !== "object") {
@@ -83,6 +574,12 @@ function collectInputPaths(spec) {
83
574
  }
84
575
  function collectInputPathMap(spec) {
85
576
  const map = {};
577
+ const addVariant = (key, path, force = false) => {
578
+ if (!key || !force && map[key]) {
579
+ return;
580
+ }
581
+ map[key] = path;
582
+ };
86
583
  const nodes = getNodes(spec);
87
584
  nodes.forEach((node) => {
88
585
  if (String(node.type ?? "").toLowerCase() !== "input") {
@@ -94,11 +591,465 @@ function collectInputPathMap(spec) {
94
591
  }
95
592
  const id = String(node.id ?? "");
96
593
  const key = id.startsWith("input_") ? id.slice("input_".length) : id || path.trim();
97
- map[key] = path.trim();
594
+ const trimmedPath = path.trim();
595
+ addVariant(key, trimmedPath);
596
+ if (key.startsWith("direct_")) {
597
+ addVariant(key.slice("direct_".length), trimmedPath, true);
598
+ }
599
+ if (key.startsWith("pose_control_")) {
600
+ addVariant(key.slice("pose_control_".length), trimmedPath);
601
+ }
98
602
  });
99
603
  return map;
100
604
  }
101
605
 
606
+ // src/utils/posePaths.ts
607
+ var POSE_WEIGHT_INPUT_PATH_PREFIX = "/poses/";
608
+ var VISEME_POSE_KEYS = [
609
+ "a",
610
+ "at",
611
+ "b",
612
+ "e",
613
+ "e_2",
614
+ "f",
615
+ "i",
616
+ "k",
617
+ "m",
618
+ "o",
619
+ "o_2",
620
+ "p",
621
+ "r",
622
+ "s",
623
+ "t",
624
+ "t_2",
625
+ "u"
626
+ ];
627
+ var EXPRESSIVE_EMOTION_POSE_KEYS = [
628
+ "concerned",
629
+ "happy",
630
+ "sad",
631
+ "sleepy",
632
+ "surprise"
633
+ ];
634
+ var EMOTION_POSE_KEYS = [
635
+ "concerned",
636
+ "happy",
637
+ "neutral",
638
+ "sad",
639
+ "sleepy",
640
+ "surprise",
641
+ "angry"
642
+ ];
643
+ var VISEME_GROUP_NEEDLES = ["viseme", "phoneme", "lip", "mouth"];
644
+ var EMOTION_GROUP_NEEDLES = ["emotion", "expression", "mood", "affect"];
645
+ var VISEME_POSE_KEY_SET = new Set(VISEME_POSE_KEYS);
646
+ var EMOTION_POSE_KEY_SET = new Set(EMOTION_POSE_KEYS);
647
+ var POSE_KEY_ALIASES = {
648
+ concern: "concerned",
649
+ surprised: "surprise"
650
+ };
651
+ function buildRigInputPath(faceId, path) {
652
+ let trimmed = path.startsWith("/") ? path.slice(1) : path;
653
+ if (!trimmed) {
654
+ return `rig/${faceId}`;
655
+ }
656
+ while (trimmed.startsWith("rig/")) {
657
+ const segments = trimmed.split("/");
658
+ if (segments.length >= 3) {
659
+ const existingFaceId = segments[1];
660
+ const remainder = segments.slice(2).join("/");
661
+ if (existingFaceId === faceId) {
662
+ return trimmed;
663
+ }
664
+ trimmed = remainder || "";
665
+ } else {
666
+ trimmed = segments.slice(1).join("/");
667
+ }
668
+ }
669
+ const suffix = trimmed ? `/${trimmed}` : "";
670
+ return `rig/${faceId}${suffix}`;
671
+ }
672
+ function normalizePoseWeightPathSegment(value) {
673
+ const trimmed = value?.trim() ?? "";
674
+ if (!trimmed) {
675
+ return "pose";
676
+ }
677
+ const normalized = trimmed.replace(/[^a-zA-Z0-9_-]+/g, "_").replace(/^_+|_+$/g, "");
678
+ return normalized || "pose";
679
+ }
680
+ function buildPoseWeightInputPathSegment(poseId) {
681
+ return normalizePoseWeightPathSegment(poseId);
682
+ }
683
+ function buildPoseWeightRelativePath(poseId) {
684
+ return `${POSE_WEIGHT_INPUT_PATH_PREFIX}${buildPoseWeightInputPathSegment(
685
+ poseId
686
+ )}.weight`;
687
+ }
688
+ function buildPoseWeightPathMap(poses, faceId) {
689
+ const faceSegment = faceId?.trim() || "face";
690
+ const map = /* @__PURE__ */ new Map();
691
+ poses.forEach((pose) => {
692
+ map.set(
693
+ pose.id,
694
+ buildRigInputPath(faceSegment, buildPoseWeightRelativePath(pose.id))
695
+ );
696
+ });
697
+ return map;
698
+ }
699
+ function normalizePoseSemanticKey(value) {
700
+ const trimmed = value?.trim() ?? "";
701
+ if (!trimmed) {
702
+ return null;
703
+ }
704
+ const normalized = trimmed.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").replace(/_+/g, "_");
705
+ if (!normalized) {
706
+ return null;
707
+ }
708
+ return POSE_KEY_ALIASES[normalized] ?? normalized;
709
+ }
710
+ function derivePoseSemanticKeyFromId(poseId) {
711
+ const trimmed = poseId?.trim() ?? "";
712
+ if (!trimmed) {
713
+ return null;
714
+ }
715
+ const stripped = trimmed.replace(/^pose_d_/i, "").replace(/^pose_/i, "").replace(/^d_/i, "").replace(/_d$/i, "");
716
+ return normalizePoseSemanticKey(stripped);
717
+ }
718
+ function getPoseSemanticKey(pose) {
719
+ return normalizePoseSemanticKey(pose.name) ?? derivePoseSemanticKeyFromId(pose.id) ?? null;
720
+ }
721
+ function normalizePoseGroupPath(value) {
722
+ const trimmed = value?.trim() ?? "";
723
+ if (!trimmed) {
724
+ return null;
725
+ }
726
+ return trimmed.replace(/^\/+|\/+$/g, "").replace(/\/+/g, "/");
727
+ }
728
+ function sanitizePoseGroupId(value, fallback) {
729
+ const normalized = (value ?? "").trim().replace(/[^a-zA-Z0-9_/-]+/g, "_").replace(/^\/+|\/+$/g, "").replace(/\/+/g, "_");
730
+ if (!normalized) {
731
+ return fallback.replace(/\//g, "_");
732
+ }
733
+ return normalized;
734
+ }
735
+ function buildPoseGroupLookup(groups) {
736
+ const byId = /* @__PURE__ */ new Map();
737
+ const byPath = /* @__PURE__ */ new Map();
738
+ const orderById = /* @__PURE__ */ new Map();
739
+ (groups ?? []).forEach((group, index) => {
740
+ const path = normalizePoseGroupPath(group.path ?? group.name ?? group.id);
741
+ if (!path) {
742
+ return;
743
+ }
744
+ const id = sanitizePoseGroupId(group.id, path);
745
+ 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;
746
+ const normalized = {
747
+ ...group,
748
+ id,
749
+ path,
750
+ name: humanizedName
751
+ };
752
+ byId.set(id, normalized);
753
+ if (!orderById.has(id)) {
754
+ orderById.set(id, index);
755
+ }
756
+ if (!byPath.has(path)) {
757
+ byPath.set(path, normalized);
758
+ }
759
+ });
760
+ return { byId, byPath, orderById };
761
+ }
762
+ function orderPoseMembershipIds(groupIds, groups) {
763
+ const { orderById } = buildPoseGroupLookup(groups);
764
+ const unique = Array.from(
765
+ new Set(
766
+ Array.from(groupIds).map((groupId) => groupId.trim()).filter((groupId) => groupId.length > 0)
767
+ )
768
+ );
769
+ unique.sort((left, right) => {
770
+ const leftIndex = orderById.get(left);
771
+ const rightIndex = orderById.get(right);
772
+ if (leftIndex !== void 0 && rightIndex !== void 0) {
773
+ return leftIndex - rightIndex;
774
+ }
775
+ if (leftIndex !== void 0) {
776
+ return -1;
777
+ }
778
+ if (rightIndex !== void 0) {
779
+ return 1;
780
+ }
781
+ const leftPath = normalizePoseGroupPath(left) ?? left;
782
+ const rightPath = normalizePoseGroupPath(right) ?? right;
783
+ const byPath = leftPath.localeCompare(rightPath);
784
+ if (byPath !== 0) {
785
+ return byPath;
786
+ }
787
+ return left.localeCompare(right);
788
+ });
789
+ return unique;
790
+ }
791
+ function valueHasNeedle(value, needles) {
792
+ const normalized = normalizePoseSemanticKey(value);
793
+ if (!normalized) {
794
+ return false;
795
+ }
796
+ return needles.some((needle) => normalized.includes(needle));
797
+ }
798
+ function resolvePoseMembership(pose, groups) {
799
+ const { byId, byPath } = buildPoseGroupLookup(groups);
800
+ const resolvedGroupIds = [];
801
+ const pathById = /* @__PURE__ */ new Map();
802
+ const addMembership = (groupId, path) => {
803
+ if (!groupId) {
804
+ return;
805
+ }
806
+ if (!resolvedGroupIds.includes(groupId)) {
807
+ resolvedGroupIds.push(groupId);
808
+ }
809
+ if (!path) {
810
+ return;
811
+ }
812
+ const existingPath = pathById.get(groupId);
813
+ const normalizedGroupIdPath = normalizePoseGroupPath(groupId);
814
+ const normalizedExistingPath = normalizePoseGroupPath(existingPath) ?? existingPath ?? null;
815
+ const normalizedIncomingPath = normalizePoseGroupPath(path) ?? path ?? null;
816
+ const shouldPromotePath = normalizedExistingPath === null || normalizedGroupIdPath !== null && normalizedExistingPath === normalizedGroupIdPath && normalizedIncomingPath !== normalizedGroupIdPath;
817
+ if (shouldPromotePath) {
818
+ pathById.set(groupId, path);
819
+ }
820
+ };
821
+ const addByPath = (rawPath) => {
822
+ const normalizedPath = normalizePoseGroupPath(rawPath);
823
+ if (!normalizedPath) {
824
+ return;
825
+ }
826
+ const existing = byPath.get(normalizedPath);
827
+ if (existing) {
828
+ addMembership(existing.id, existing.path);
829
+ return;
830
+ }
831
+ addMembership(sanitizePoseGroupId(null, normalizedPath), normalizedPath);
832
+ };
833
+ const addById = (rawId) => {
834
+ const trimmed = rawId?.trim() ?? "";
835
+ if (!trimmed) {
836
+ return;
837
+ }
838
+ const normalizedPath = normalizePoseGroupPath(trimmed);
839
+ const normalizedId = sanitizePoseGroupId(trimmed, trimmed);
840
+ const matchedById = byId.get(trimmed) ?? byId.get(normalizedId);
841
+ if (matchedById) {
842
+ addMembership(matchedById.id, matchedById.path);
843
+ return;
844
+ }
845
+ if (normalizedPath) {
846
+ const matchedByPath = byPath.get(normalizedPath);
847
+ if (matchedByPath) {
848
+ addMembership(matchedByPath.id, matchedByPath.path);
849
+ return;
850
+ }
851
+ }
852
+ addMembership(
853
+ normalizedId,
854
+ normalizedPath && normalizedPath.length > 0 ? normalizedPath : null
855
+ );
856
+ };
857
+ pose.groupIds?.forEach((groupId) => addById(groupId));
858
+ addById(pose.groupId);
859
+ addByPath(pose.group);
860
+ const orderedGroupIds = orderPoseMembershipIds(resolvedGroupIds, groups);
861
+ const primaryGroupId = orderedGroupIds[0] ?? null;
862
+ const primaryGroupPath = primaryGroupId ? byId.get(primaryGroupId)?.path ?? pathById.get(primaryGroupId) ?? null : null;
863
+ const groupPathsById = {};
864
+ orderedGroupIds.forEach((groupId) => {
865
+ const path = byId.get(groupId)?.path ?? pathById.get(groupId) ?? null;
866
+ if (path) {
867
+ groupPathsById[groupId] = path;
868
+ }
869
+ });
870
+ return {
871
+ groupIds: orderedGroupIds,
872
+ primaryGroupId,
873
+ primaryGroupPath,
874
+ groupPathsById
875
+ };
876
+ }
877
+ function poseMatchesGroupKind(pose, groups, needles) {
878
+ const membership = resolvePoseMembership(pose, groups);
879
+ if (valueHasNeedle(pose.group, needles) || valueHasNeedle(pose.groupId, needles)) {
880
+ return true;
881
+ }
882
+ if (pose.groupIds?.some((groupId) => valueHasNeedle(groupId, needles))) {
883
+ return true;
884
+ }
885
+ return membership.groupIds.some((groupId) => {
886
+ const path = membership.groupPathsById[groupId] ?? null;
887
+ const group = (groups ?? []).find((entry) => entry.id === groupId) ?? null;
888
+ return valueHasNeedle(groupId, needles) || valueHasNeedle(path, needles) || valueHasNeedle(group?.name, needles);
889
+ });
890
+ }
891
+ function resolvePoseSemantics(pose, groups) {
892
+ const membership = resolvePoseMembership(pose, groups);
893
+ const key = getPoseSemanticKey(pose);
894
+ const looksLikeVisemeGroup = poseMatchesGroupKind(
895
+ pose,
896
+ groups,
897
+ VISEME_GROUP_NEEDLES
898
+ );
899
+ const looksLikeEmotionGroup = poseMatchesGroupKind(
900
+ pose,
901
+ groups,
902
+ EMOTION_GROUP_NEEDLES
903
+ );
904
+ let kind = "other";
905
+ if (looksLikeVisemeGroup || key && VISEME_POSE_KEY_SET.has(key)) {
906
+ kind = "viseme";
907
+ } else if (looksLikeEmotionGroup || key && EMOTION_POSE_KEY_SET.has(key)) {
908
+ kind = "emotion";
909
+ }
910
+ return { key, kind, membership };
911
+ }
912
+ function filterPosesBySemanticKind(poses, groups, kind) {
913
+ return poses.filter(
914
+ (pose) => resolvePoseSemantics(pose, groups).kind === kind
915
+ );
916
+ }
917
+ function buildSemanticPoseWeightPathMap(poses, groups, faceId, kind) {
918
+ const map = /* @__PURE__ */ new Map();
919
+ const pathMap = buildPoseWeightPathMap(poses, faceId);
920
+ poses.forEach((pose) => {
921
+ const semantics = resolvePoseSemantics(pose, groups);
922
+ const path = pathMap.get(pose.id);
923
+ if (semantics.kind !== kind || !semantics.key || !path || map.has(semantics.key)) {
924
+ return;
925
+ }
926
+ map.set(semantics.key, path);
927
+ });
928
+ return map;
929
+ }
930
+
931
+ // src/utils/poseRuntime.ts
932
+ function shouldUseLegacyPoseWeightFallback(hasPoseGraph) {
933
+ return !hasPoseGraph;
934
+ }
935
+ function resolvePoseControlInputPath({
936
+ inputId,
937
+ basePath,
938
+ rigInputPathMap,
939
+ hasNativePoseControlInput
940
+ }) {
941
+ if (!inputId.trim()) {
942
+ return void 0;
943
+ }
944
+ return rigInputPathMap[inputId] ?? rigInputPathMap[`pose_control_${inputId}`] ?? rigInputPathMap[`direct_${inputId}`] ?? (hasNativePoseControlInput ? basePath : void 0);
945
+ }
946
+
947
+ // src/utils/clipPlayback.ts
948
+ function toFiniteNumber(value, fallback) {
949
+ const parsed = Number(value);
950
+ return Number.isFinite(parsed) ? parsed : fallback;
951
+ }
952
+ function lastKeyframeTime(track) {
953
+ const keyframes = Array.isArray(track.keyframes) ? track.keyframes : [];
954
+ let maxTime = 0;
955
+ keyframes.forEach((keyframe) => {
956
+ const time = Number(keyframe?.time);
957
+ if (Number.isFinite(time) && time > maxTime) {
958
+ maxTime = time;
959
+ }
960
+ });
961
+ return maxTime;
962
+ }
963
+ function resolveClipDurationSeconds(clip, fallbackDurationSeconds = 0) {
964
+ const fallback = Math.max(0, toFiniteNumber(fallbackDurationSeconds, 0));
965
+ if (!clip || typeof clip !== "object") {
966
+ return fallback;
967
+ }
968
+ const clipDuration = Number(clip.duration);
969
+ if (Number.isFinite(clipDuration) && clipDuration > 0) {
970
+ return clipDuration;
971
+ }
972
+ const tracks = Array.isArray(clip.tracks) ? clip.tracks : [];
973
+ let maxTime = 0;
974
+ tracks.forEach((track) => {
975
+ const time = lastKeyframeTime(track);
976
+ if (time > maxTime) {
977
+ maxTime = time;
978
+ }
979
+ });
980
+ return maxTime > 0 ? maxTime : fallback;
981
+ }
982
+
983
+ // src/utils/animationBridge.ts
984
+ function resolveAnimationBridgeOutputPaths(channel, faceId, rigInputMap) {
985
+ const normalizedChannel = channel.trim().replace(/^\/+/, "");
986
+ if (!normalizedChannel) {
987
+ return [];
988
+ }
989
+ const outputPaths = /* @__PURE__ */ new Set([normalizedChannel]);
990
+ if (normalizedChannel.startsWith("animation/")) {
991
+ return Array.from(outputPaths).sort(
992
+ (left, right) => left.localeCompare(right)
993
+ );
994
+ }
995
+ if (rigInputMap && Object.keys(rigInputMap).length > 0) {
996
+ const candidateKeys = /* @__PURE__ */ new Set([normalizedChannel]);
997
+ const rigChannelMatch2 = /^rig\/[^/]+\/(.+)$/.exec(normalizedChannel);
998
+ if (rigChannelMatch2?.[1]) {
999
+ candidateKeys.add(rigChannelMatch2[1]);
1000
+ }
1001
+ candidateKeys.forEach((key) => {
1002
+ const mapped = rigInputMap[key];
1003
+ const normalized = mapped?.trim().replace(/^\/+/, "");
1004
+ if (normalized) {
1005
+ outputPaths.add(normalized);
1006
+ }
1007
+ });
1008
+ const suffix = normalizedChannel.includes("/") ? normalizedChannel : `/${normalizedChannel}`;
1009
+ Object.values(rigInputMap).forEach((mappedPath) => {
1010
+ const normalized = mappedPath?.trim().replace(/^\/+/, "");
1011
+ if (!normalized) {
1012
+ return;
1013
+ }
1014
+ if (normalized === normalizedChannel || normalized.endsWith(suffix)) {
1015
+ outputPaths.add(normalized);
1016
+ }
1017
+ });
1018
+ }
1019
+ if (!faceId) {
1020
+ return Array.from(outputPaths).sort(
1021
+ (left, right) => left.localeCompare(right)
1022
+ );
1023
+ }
1024
+ const rigChannelMatch = /^rig\/[^/]+\/(.+)$/.exec(normalizedChannel);
1025
+ if (rigChannelMatch?.[1]) {
1026
+ outputPaths.add(`rig/${faceId}/${rigChannelMatch[1]}`);
1027
+ } else if (!normalizedChannel.startsWith("rig/")) {
1028
+ outputPaths.add(`rig/${faceId}/${normalizedChannel}`);
1029
+ }
1030
+ return Array.from(outputPaths).sort(
1031
+ (left, right) => left.localeCompare(right)
1032
+ );
1033
+ }
1034
+ function collectAnimationClipOutputPaths(clip, faceId, rigInputMap) {
1035
+ const outputPaths = /* @__PURE__ */ new Set();
1036
+ const tracks = Array.isArray(clip.tracks) ? clip.tracks : [];
1037
+ tracks.forEach((track) => {
1038
+ const channel = typeof track.channel === "string" ? track.channel.trim() : "";
1039
+ if (!channel) {
1040
+ return;
1041
+ }
1042
+ resolveAnimationBridgeOutputPaths(channel, faceId, rigInputMap).forEach(
1043
+ (path) => {
1044
+ outputPaths.add(path);
1045
+ }
1046
+ );
1047
+ });
1048
+ return Array.from(outputPaths).sort(
1049
+ (left, right) => left.localeCompare(right)
1050
+ );
1051
+ }
1052
+
102
1053
  // src/utils/valueConversion.ts
103
1054
  var import_value_json = require("@vizij/value-json");
104
1055
  function numericArrayToRaw(arr) {
@@ -145,27 +1096,28 @@ function valueJSONToRaw(value) {
145
1096
  if (Array.isArray(value)) {
146
1097
  return value.map((entry) => valueJSONToRaw(entry));
147
1098
  }
148
- if (typeof value === "object" && !("type" in value)) {
149
- const entries = Object.entries(value).map(([key, entry]) => [
150
- key,
151
- valueJSONToRaw(entry)
152
- ]);
153
- return Object.fromEntries(entries);
154
- }
155
- if (!(0, import_value_json.isNormalizedValue)(value)) {
1099
+ const normalized = (0, import_value_json.isNormalizedValue)(value) ? value : (0, import_value_json.fromAroraValueJSON)(value);
1100
+ if (normalized === void 0) {
1101
+ if (typeof value === "object" && !("type" in value)) {
1102
+ const entries = Object.entries(value).map(([key, entry]) => [
1103
+ key,
1104
+ valueJSONToRaw(entry)
1105
+ ]);
1106
+ return Object.fromEntries(entries);
1107
+ }
156
1108
  return void 0;
157
1109
  }
158
- switch (value.type) {
1110
+ switch (normalized.type) {
159
1111
  case "float": {
160
- const num = (0, import_value_json.valueAsNumber)(value);
1112
+ const num = (0, import_value_json.valueAsNumber)(normalized);
161
1113
  return typeof num === "number" ? num : void 0;
162
1114
  }
163
1115
  case "bool": {
164
- const boolVal = (0, import_value_json.valueAsBool)(value);
1116
+ const boolVal = (0, import_value_json.valueAsBool)(normalized);
165
1117
  return typeof boolVal === "boolean" ? boolVal : void 0;
166
1118
  }
167
1119
  case "text": {
168
- const text = (0, import_value_json.valueAsText)(value);
1120
+ const text = (0, import_value_json.valueAsText)(normalized);
169
1121
  return typeof text === "string" ? text : void 0;
170
1122
  }
171
1123
  case "vec2":
@@ -173,11 +1125,11 @@ function valueJSONToRaw(value) {
173
1125
  case "vec4":
174
1126
  case "quat":
175
1127
  case "vector": {
176
- const vec = (0, import_value_json.valueAsVector)(value);
1128
+ const vec = (0, import_value_json.valueAsVector)(normalized);
177
1129
  return vec ? numericArrayToRaw(vec) : void 0;
178
1130
  }
179
1131
  case "colorrgba": {
180
- const color = (0, import_value_json.valueAsColorRgba)(value);
1132
+ const color = (0, import_value_json.valueAsColorRgba)(normalized);
181
1133
  if (!color) {
182
1134
  return void 0;
183
1135
  }
@@ -185,7 +1137,7 @@ function valueJSONToRaw(value) {
185
1137
  return { r, g, b, a };
186
1138
  }
187
1139
  case "transform": {
188
- const transform = (0, import_value_json.valueAsTransform)(value);
1140
+ const transform = (0, import_value_json.valueAsTransform)(normalized);
189
1141
  if (!transform) {
190
1142
  return void 0;
191
1143
  }
@@ -195,15 +1147,20 @@ function valueJSONToRaw(value) {
195
1147
  scale: numericArrayToRaw(transform.scale)
196
1148
  };
197
1149
  }
1150
+ case "array":
1151
+ case "list":
1152
+ case "tuple":
1153
+ return (normalized.data ?? []).map(
1154
+ (entry) => valueJSONToRaw(entry)
1155
+ );
198
1156
  case "record": {
199
- const entries = Object.entries(value.data ?? {}).map(([key, entry]) => [
200
- key,
201
- valueJSONToRaw(entry)
202
- ]);
1157
+ const entries = Object.entries(normalized.data ?? {}).map(
1158
+ ([key, entry]) => [key, valueJSONToRaw(entry)]
1159
+ );
203
1160
  return Object.fromEntries(entries);
204
1161
  }
205
1162
  case "enum": {
206
- const [tag, inner] = value.data;
1163
+ const [tag, inner] = normalized.data;
207
1164
  return {
208
1165
  tag,
209
1166
  value: valueJSONToRaw(inner)
@@ -214,16 +1171,620 @@ function valueJSONToRaw(value) {
214
1171
  }
215
1172
  }
216
1173
 
1174
+ // src/engine/animationModule.ts
1175
+ var ANIMATION_MODULE_FN = {
1176
+ loadAnimation: "76697a69-6a00-0000-0f00-000000000001",
1177
+ createPlayer: "76697a69-6a00-0000-0f00-000000000002",
1178
+ addInstance: "76697a69-6a00-0000-0f00-000000000003",
1179
+ step: "76697a69-6a00-0000-0f00-000000000004",
1180
+ play: "76697a69-6a00-0000-0f00-000000000005",
1181
+ pause: "76697a69-6a00-0000-0f00-000000000006",
1182
+ stop: "76697a69-6a00-0000-0f00-000000000007",
1183
+ seek: "76697a69-6a00-0000-0f00-000000000008",
1184
+ setSpeed: "76697a69-6a00-0000-0f00-000000000009",
1185
+ setLoop: "76697a69-6a00-0000-0f00-00000000000a",
1186
+ setWeight: "76697a69-6a00-0000-0f00-00000000000b",
1187
+ removeInstance: "76697a69-6a00-0000-0f00-00000000000c",
1188
+ playerStates: "76697a69-6a00-0000-0f00-00000000000d"
1189
+ };
1190
+ var ANIMATION_MODULE_PARAM = {
1191
+ clip: "76697a69-6a00-0000-0f01-000000000001",
1192
+ playerName: "76697a69-6a00-0000-0f02-000000000001",
1193
+ player: "76697a69-6a00-0000-0f03-000000000001",
1194
+ anim: "76697a69-6a00-0000-0f03-000000000002",
1195
+ dtNs: "76697a69-6a00-0000-0f04-000000000001",
1196
+ playPlayer: "76697a69-6a00-0000-0f05-000000000001",
1197
+ pausePlayer: "76697a69-6a00-0000-0f06-000000000001",
1198
+ stopPlayer: "76697a69-6a00-0000-0f07-000000000001",
1199
+ seekPlayer: "76697a69-6a00-0000-0f08-000000000001",
1200
+ seekTimeNs: "76697a69-6a00-0000-0f08-000000000002",
1201
+ speedPlayer: "76697a69-6a00-0000-0f09-000000000001",
1202
+ speedValue: "76697a69-6a00-0000-0f09-000000000002",
1203
+ loopPlayer: "76697a69-6a00-0000-0f0a-000000000001",
1204
+ loopMode: "76697a69-6a00-0000-0f0a-000000000002",
1205
+ weightPlayer: "76697a69-6a00-0000-0f0b-000000000001",
1206
+ weightInstance: "76697a69-6a00-0000-0f0b-000000000002",
1207
+ weightValue: "76697a69-6a00-0000-0f0b-000000000003",
1208
+ removePlayer: "76697a69-6a00-0000-0f0c-000000000001",
1209
+ removeInstance: "76697a69-6a00-0000-0f0c-000000000002"
1210
+ };
1211
+ var ANIMATION_MODULE_TYPE = {
1212
+ clip: "76697a69-6a00-0000-0000-000000000100",
1213
+ track: "76697a69-6a00-0000-0000-000000000101",
1214
+ keypoint: "76697a69-6a00-0000-0000-000000000102",
1215
+ transitionHandle: "76697a69-6a00-0000-0000-000000000103",
1216
+ trackOutput: "76697a69-6a00-0000-0000-000000000110",
1217
+ playerState: "76697a69-6a00-0000-0000-000000000111"
1218
+ };
1219
+ var ANIMATION_MODULE_FIELD = {
1220
+ clipName: "76697a69-6a00-0000-0100-000000000001",
1221
+ clipDuration: "76697a69-6a00-0000-0100-000000000002",
1222
+ clipTracks: "76697a69-6a00-0000-0100-000000000003",
1223
+ trackId: "76697a69-6a00-0000-0101-000000000001",
1224
+ trackName: "76697a69-6a00-0000-0101-000000000002",
1225
+ trackAnimatable: "76697a69-6a00-0000-0101-000000000003",
1226
+ trackPoints: "76697a69-6a00-0000-0101-000000000004",
1227
+ keypointId: "76697a69-6a00-0000-0102-000000000001",
1228
+ keypointStamp: "76697a69-6a00-0000-0102-000000000002",
1229
+ keypointValue: "76697a69-6a00-0000-0102-000000000003",
1230
+ keypointTransitionsIn: "76697a69-6a00-0000-0102-000000000004",
1231
+ keypointTransitionsOut: "76697a69-6a00-0000-0102-000000000005",
1232
+ handleX: "76697a69-6a00-0000-0103-000000000001",
1233
+ handleY: "76697a69-6a00-0000-0103-000000000002",
1234
+ outputTrackId: "76697a69-6a00-0000-0110-000000000001",
1235
+ outputDefaultKey: "76697a69-6a00-0000-0110-000000000002",
1236
+ outputValue: "76697a69-6a00-0000-0110-000000000003",
1237
+ statePlayer: "76697a69-6a00-0000-0111-000000000001",
1238
+ stateState: "76697a69-6a00-0000-0111-000000000002",
1239
+ stateTimeNs: "76697a69-6a00-0000-0111-000000000003",
1240
+ stateDurationNs: "76697a69-6a00-0000-0111-000000000004",
1241
+ stateSpeed: "76697a69-6a00-0000-0111-000000000005"
1242
+ };
1243
+ var field = (id, value) => ({
1244
+ id,
1245
+ value
1246
+ });
1247
+ function storedClipToModuleValue(clip, resolveKeys) {
1248
+ const name = typeof clip.name === "string" && clip.name.trim().length > 0 ? clip.name.trim() : typeof clip.id === "string" ? clip.id : "";
1249
+ const durationMs = Number(clip.duration);
1250
+ const duration = Number.isFinite(durationMs) && durationMs > 0 ? Math.round(durationMs) : 1;
1251
+ const tracks = (Array.isArray(clip.tracks) ? clip.tracks : []).map((rawTrack, index) => {
1252
+ if (!rawTrack || typeof rawTrack !== "object") {
1253
+ return null;
1254
+ }
1255
+ const animatableId = typeof rawTrack.animatableId === "string" ? rawTrack.animatableId.trim() : "";
1256
+ if (!animatableId) {
1257
+ return null;
1258
+ }
1259
+ const trackId = typeof rawTrack.id === "string" && rawTrack.id.trim().length > 0 ? rawTrack.id.trim() : `track-${index}`;
1260
+ const trackName = typeof rawTrack.name === "string" && rawTrack.name.trim().length > 0 ? rawTrack.name.trim() : trackId;
1261
+ const points = (Array.isArray(rawTrack.points) ? rawTrack.points : []).map((rawPoint, pointIndex) => {
1262
+ if (!rawPoint || typeof rawPoint !== "object") {
1263
+ return null;
1264
+ }
1265
+ const stamp = Number(rawPoint.stamp);
1266
+ const value = Number(rawPoint.value);
1267
+ if (!Number.isFinite(stamp) || !Number.isFinite(value)) {
1268
+ return null;
1269
+ }
1270
+ const pointId = typeof rawPoint.id === "string" && rawPoint.id.trim().length > 0 ? rawPoint.id.trim() : `${trackId}:point-${pointIndex}`;
1271
+ const handles = (handle) => ({
1272
+ structs: {
1273
+ id: ANIMATION_MODULE_TYPE.transitionHandle,
1274
+ elements: handle ? [
1275
+ {
1276
+ fields: [
1277
+ field(ANIMATION_MODULE_FIELD.handleX, {
1278
+ f32: handle.x
1279
+ }),
1280
+ field(ANIMATION_MODULE_FIELD.handleY, {
1281
+ f32: handle.y
1282
+ })
1283
+ ]
1284
+ }
1285
+ ] : []
1286
+ }
1287
+ });
1288
+ return {
1289
+ fields: [
1290
+ field(ANIMATION_MODULE_FIELD.keypointId, { str: pointId }),
1291
+ field(ANIMATION_MODULE_FIELD.keypointStamp, {
1292
+ f32: Math.max(0, Math.min(1, stamp))
1293
+ }),
1294
+ field(ANIMATION_MODULE_FIELD.keypointValue, { f32: value }),
1295
+ field(
1296
+ ANIMATION_MODULE_FIELD.keypointTransitionsIn,
1297
+ handles(rawPoint.transitions?.in)
1298
+ ),
1299
+ field(
1300
+ ANIMATION_MODULE_FIELD.keypointTransitionsOut,
1301
+ handles(rawPoint.transitions?.out)
1302
+ )
1303
+ ]
1304
+ };
1305
+ }).filter(Boolean);
1306
+ if (points.length === 0) {
1307
+ return null;
1308
+ }
1309
+ const targets = resolveKeys?.(animatableId) ?? [animatableId];
1310
+ return targets.map((target, targetIndex) => ({
1311
+ fields: [
1312
+ field(ANIMATION_MODULE_FIELD.trackId, {
1313
+ str: targetIndex === 0 ? trackId : `${trackId}~${targetIndex}`
1314
+ }),
1315
+ field(ANIMATION_MODULE_FIELD.trackName, { str: trackName }),
1316
+ field(ANIMATION_MODULE_FIELD.trackAnimatable, { str: target }),
1317
+ field(ANIMATION_MODULE_FIELD.trackPoints, {
1318
+ structs: {
1319
+ id: ANIMATION_MODULE_TYPE.keypoint,
1320
+ elements: points
1321
+ }
1322
+ })
1323
+ ]
1324
+ }));
1325
+ }).filter(Boolean).flat();
1326
+ return {
1327
+ struct: {
1328
+ id: ANIMATION_MODULE_TYPE.clip,
1329
+ fields: [
1330
+ field(ANIMATION_MODULE_FIELD.clipName, { str: name }),
1331
+ field(ANIMATION_MODULE_FIELD.clipDuration, { u32: duration }),
1332
+ field(ANIMATION_MODULE_FIELD.clipTracks, {
1333
+ structs: {
1334
+ id: ANIMATION_MODULE_TYPE.track,
1335
+ elements: tracks
1336
+ }
1337
+ })
1338
+ ]
1339
+ }
1340
+ };
1341
+ }
1342
+ function loadAnimationCall(clipValue) {
1343
+ return {
1344
+ id: ANIMATION_MODULE_FN.loadAnimation,
1345
+ args: [field(ANIMATION_MODULE_PARAM.clip, clipValue)]
1346
+ };
1347
+ }
1348
+ function createPlayerCall(name) {
1349
+ return {
1350
+ id: ANIMATION_MODULE_FN.createPlayer,
1351
+ args: [field(ANIMATION_MODULE_PARAM.playerName, { str: name })]
1352
+ };
1353
+ }
1354
+ function addInstanceCall(player, anim) {
1355
+ return {
1356
+ id: ANIMATION_MODULE_FN.addInstance,
1357
+ args: [
1358
+ field(ANIMATION_MODULE_PARAM.player, { u32: player }),
1359
+ field(ANIMATION_MODULE_PARAM.anim, { u32: anim })
1360
+ ]
1361
+ };
1362
+ }
1363
+ function playCall(player) {
1364
+ return {
1365
+ id: ANIMATION_MODULE_FN.play,
1366
+ args: [field(ANIMATION_MODULE_PARAM.playPlayer, { u32: player })]
1367
+ };
1368
+ }
1369
+ function pauseCall(player) {
1370
+ return {
1371
+ id: ANIMATION_MODULE_FN.pause,
1372
+ args: [field(ANIMATION_MODULE_PARAM.pausePlayer, { u32: player })]
1373
+ };
1374
+ }
1375
+ function stopCall(player) {
1376
+ return {
1377
+ id: ANIMATION_MODULE_FN.stop,
1378
+ args: [field(ANIMATION_MODULE_PARAM.stopPlayer, { u32: player })]
1379
+ };
1380
+ }
1381
+ function seekCall(player, timeNs) {
1382
+ return {
1383
+ id: ANIMATION_MODULE_FN.seek,
1384
+ args: [
1385
+ field(ANIMATION_MODULE_PARAM.seekPlayer, { u32: player }),
1386
+ field(ANIMATION_MODULE_PARAM.seekTimeNs, {
1387
+ u64: Math.max(0, Math.round(timeNs))
1388
+ })
1389
+ ]
1390
+ };
1391
+ }
1392
+ function setSpeedCall(player, speed) {
1393
+ return {
1394
+ id: ANIMATION_MODULE_FN.setSpeed,
1395
+ args: [
1396
+ field(ANIMATION_MODULE_PARAM.speedPlayer, { u32: player }),
1397
+ field(ANIMATION_MODULE_PARAM.speedValue, { f32: speed })
1398
+ ]
1399
+ };
1400
+ }
1401
+ function setLoopCall(player, mode) {
1402
+ return {
1403
+ id: ANIMATION_MODULE_FN.setLoop,
1404
+ args: [
1405
+ field(ANIMATION_MODULE_PARAM.loopPlayer, { u32: player }),
1406
+ field(ANIMATION_MODULE_PARAM.loopMode, { str: mode })
1407
+ ]
1408
+ };
1409
+ }
1410
+ function setWeightCall(player, instance, weight) {
1411
+ return {
1412
+ id: ANIMATION_MODULE_FN.setWeight,
1413
+ args: [
1414
+ field(ANIMATION_MODULE_PARAM.weightPlayer, { u32: player }),
1415
+ field(ANIMATION_MODULE_PARAM.weightInstance, { u32: instance }),
1416
+ field(ANIMATION_MODULE_PARAM.weightValue, { f32: weight })
1417
+ ]
1418
+ };
1419
+ }
1420
+ function callResultU32(result) {
1421
+ const ret = result.ret;
1422
+ if (ret && typeof ret === "object" && "u32" in ret) {
1423
+ const value = Number(ret.u32);
1424
+ return Number.isFinite(value) ? value : null;
1425
+ }
1426
+ return null;
1427
+ }
1428
+ var ANIMATION_PLAYERS_PATH = "vizij/animations/players";
1429
+ var ANIMATIONS_SOURCE_ID = "animations";
1430
+ function animationsGraphSource() {
1431
+ return {
1432
+ sourceId: ANIMATIONS_SOURCE_ID,
1433
+ spec: {
1434
+ nodes: [
1435
+ { id: "dt", type: "input", params: { path: "arora/dt" } },
1436
+ {
1437
+ id: "step",
1438
+ type: "externalfunction",
1439
+ params: {
1440
+ function: ANIMATION_MODULE_FN.step,
1441
+ param_ids: [ANIMATION_MODULE_PARAM.dtNs]
1442
+ }
1443
+ },
1444
+ {
1445
+ id: "apply",
1446
+ type: "output",
1447
+ params: {
1448
+ key_field: ANIMATION_MODULE_FIELD.outputDefaultKey,
1449
+ value_field: ANIMATION_MODULE_FIELD.outputValue
1450
+ }
1451
+ },
1452
+ {
1453
+ id: "states",
1454
+ type: "externalfunction",
1455
+ params: { function: ANIMATION_MODULE_FN.playerStates, param_ids: [] }
1456
+ },
1457
+ {
1458
+ id: "states-out",
1459
+ type: "output",
1460
+ params: { path: ANIMATION_PLAYERS_PATH }
1461
+ }
1462
+ ],
1463
+ edges: [
1464
+ { from: { node_id: "dt" }, to: { node_id: "step", input: "args_0" } },
1465
+ { from: { node_id: "step" }, to: { node_id: "apply", input: "in" } },
1466
+ {
1467
+ from: { node_id: "states" },
1468
+ to: { node_id: "states-out", input: "in" }
1469
+ }
1470
+ ]
1471
+ }
1472
+ };
1473
+ }
1474
+ function fieldNumber(value) {
1475
+ if (!value || typeof value !== "object") {
1476
+ return null;
1477
+ }
1478
+ for (const key of ["u64", "u32", "f32", "f64"]) {
1479
+ if (key in value) {
1480
+ const parsed = Number(value[key]);
1481
+ return Number.isFinite(parsed) ? parsed : null;
1482
+ }
1483
+ }
1484
+ return null;
1485
+ }
1486
+ function decodePlayerStates(raw) {
1487
+ if (!raw || typeof raw !== "object" || !("structs" in raw)) {
1488
+ return [];
1489
+ }
1490
+ const structs = raw.structs;
1491
+ const elements = Array.isArray(structs?.elements) ? structs.elements : [];
1492
+ const states = [];
1493
+ for (const element of elements) {
1494
+ const fields = element?.fields;
1495
+ if (!Array.isArray(fields)) {
1496
+ continue;
1497
+ }
1498
+ const byId = /* @__PURE__ */ new Map();
1499
+ for (const entry of fields) {
1500
+ const id = entry?.id;
1501
+ const value = entry?.value;
1502
+ if (typeof id === "string" && value && typeof value === "object") {
1503
+ byId.set(id, value);
1504
+ }
1505
+ }
1506
+ const player = fieldNumber(byId.get(ANIMATION_MODULE_FIELD.statePlayer));
1507
+ if (player === null) {
1508
+ continue;
1509
+ }
1510
+ const stateValue = byId.get(ANIMATION_MODULE_FIELD.stateState);
1511
+ states.push({
1512
+ player,
1513
+ state: stateValue && "str" in stateValue ? String(stateValue.str) : "",
1514
+ time: (fieldNumber(byId.get(ANIMATION_MODULE_FIELD.stateTimeNs)) ?? 0) / 1e9,
1515
+ duration: (fieldNumber(byId.get(ANIMATION_MODULE_FIELD.stateDurationNs)) ?? 0) / 1e9,
1516
+ speed: fieldNumber(byId.get(ANIMATION_MODULE_FIELD.stateSpeed)) ?? 1
1517
+ });
1518
+ }
1519
+ return states;
1520
+ }
1521
+
1522
+ // src/engine/animationModuleHost.ts
1523
+ var AnimationModuleHost = class {
1524
+ constructor(getDevice, resolveKeys = (key) => [key]) {
1525
+ this.getDevice = getDevice;
1526
+ this.resolveKeys = resolveKeys;
1527
+ this.clips = /* @__PURE__ */ new Map();
1528
+ }
1529
+ /** Whether any clip is currently marked playing (drives source lifecycle). */
1530
+ hasPlaying() {
1531
+ for (const entry of this.clips.values()) {
1532
+ if (entry.playing) {
1533
+ return true;
1534
+ }
1535
+ }
1536
+ return false;
1537
+ }
1538
+ /** Whether a clip id is known to the host. */
1539
+ has(clipId) {
1540
+ return this.clips.has(clipId);
1541
+ }
1542
+ /** The clip's module `PlayerId` on the live device (feedback correlation). */
1543
+ playerIdOf(clipId) {
1544
+ return this.clips.get(clipId)?.playerId ?? null;
1545
+ }
1546
+ /** The clip's transport state as last commanded. */
1547
+ transportOf(clipId) {
1548
+ const entry = this.clips.get(clipId);
1549
+ if (!entry) {
1550
+ return null;
1551
+ }
1552
+ return {
1553
+ playing: entry.playing,
1554
+ speed: entry.speed,
1555
+ loop: entry.loop,
1556
+ weight: entry.weight
1557
+ };
1558
+ }
1559
+ /**
1560
+ * Declare the clips the bundle carries, as data. Keeps playing/transport
1561
+ * state for clips that survive; clips no longer present are dropped. Does
1562
+ * not touch the device — loading is lazy (on first play), so muted/unused
1563
+ * clips never enter the module, and conversion happens at load with the
1564
+ * key resolver's state at that moment.
1565
+ */
1566
+ setClips(clips) {
1567
+ const nextIds = new Set(clips.map((clip) => clip.id));
1568
+ for (const id of Array.from(this.clips.keys())) {
1569
+ if (!nextIds.has(id)) {
1570
+ this.clips.delete(id);
1571
+ }
1572
+ }
1573
+ for (const clip of clips) {
1574
+ const existing = this.clips.get(clip.id);
1575
+ if (existing) {
1576
+ existing.stored = clip.stored;
1577
+ existing.animId = null;
1578
+ existing.playerId = null;
1579
+ existing.instId = null;
1580
+ } else {
1581
+ this.clips.set(clip.id, {
1582
+ stored: clip.stored,
1583
+ playing: false,
1584
+ speed: 1,
1585
+ loop: "loop",
1586
+ weight: 1,
1587
+ animId: null,
1588
+ playerId: null,
1589
+ instId: null
1590
+ });
1591
+ }
1592
+ }
1593
+ }
1594
+ /**
1595
+ * Mark a clip playing, ensure it is loaded into the live device with a
1596
+ * player + instance, and issue `play` (a resume after `pause` needs it; on
1597
+ * a fresh player it is a no-op). Idempotent. Resolves once the calls have
1598
+ * dispatched (or immediately when there is no live device yet — the
1599
+ * provider registers the source, which boots the device and replays via
1600
+ * `replayInto`).
1601
+ */
1602
+ async play(clipId) {
1603
+ const entry = this.clips.get(clipId);
1604
+ if (!entry) {
1605
+ return;
1606
+ }
1607
+ entry.playing = true;
1608
+ const device = this.getDevice();
1609
+ if (!device) {
1610
+ return;
1611
+ }
1612
+ await this.ensureLoaded(device, entry);
1613
+ if (entry.playerId !== null) {
1614
+ await device.call(playCall(entry.playerId));
1615
+ }
1616
+ }
1617
+ /** Hold the clip's playhead (`pause` on its player). */
1618
+ pause(clipId) {
1619
+ const entry = this.clips.get(clipId);
1620
+ if (!entry) {
1621
+ return;
1622
+ }
1623
+ entry.playing = false;
1624
+ this.dispatch(
1625
+ (device) => entry.playerId !== null ? device.call(pauseCall(entry.playerId)) : null
1626
+ );
1627
+ }
1628
+ /**
1629
+ * Stop a clip: not playing, playhead reset to the window start (`stop` on
1630
+ * its player — the next tick emits the clip's t=0 pose). The clip stays
1631
+ * loaded for a cheap replay.
1632
+ */
1633
+ stop(clipId) {
1634
+ const entry = this.clips.get(clipId);
1635
+ if (!entry) {
1636
+ return;
1637
+ }
1638
+ entry.playing = false;
1639
+ this.dispatch(
1640
+ (device) => entry.playerId !== null ? device.call(stopCall(entry.playerId)) : null
1641
+ );
1642
+ }
1643
+ /** Move the clip's playhead to `seconds`. */
1644
+ seek(clipId, seconds) {
1645
+ const entry = this.clips.get(clipId);
1646
+ if (!entry) {
1647
+ return;
1648
+ }
1649
+ this.dispatch(
1650
+ (device) => entry.playerId !== null ? device.call(seekCall(entry.playerId, seconds * 1e9)) : null
1651
+ );
1652
+ }
1653
+ /** Set the clip's playback speed multiplier. */
1654
+ setSpeed(clipId, speed) {
1655
+ const entry = this.clips.get(clipId);
1656
+ if (!entry) {
1657
+ return;
1658
+ }
1659
+ entry.speed = speed;
1660
+ this.dispatch(
1661
+ (device) => entry.playerId !== null ? device.call(setSpeedCall(entry.playerId, speed)) : null
1662
+ );
1663
+ }
1664
+ /** Set how the clip's player time maps into clip time. */
1665
+ setLoop(clipId, mode) {
1666
+ const entry = this.clips.get(clipId);
1667
+ if (!entry) {
1668
+ return;
1669
+ }
1670
+ entry.loop = mode;
1671
+ this.dispatch(
1672
+ (device) => entry.playerId !== null ? device.call(setLoopCall(entry.playerId, mode)) : null
1673
+ );
1674
+ }
1675
+ /** Set the clip instance's blend weight. */
1676
+ setWeight(clipId, weight) {
1677
+ const entry = this.clips.get(clipId);
1678
+ if (!entry) {
1679
+ return;
1680
+ }
1681
+ entry.weight = weight;
1682
+ this.dispatch(
1683
+ (device) => entry.playerId !== null && entry.instId !== null ? device.call(setWeightCall(entry.playerId, entry.instId, weight)) : null
1684
+ );
1685
+ }
1686
+ /**
1687
+ * Re-issue setup and transport for every playing clip against a freshly
1688
+ * started device (module guest state did not survive the rebuild). Voids
1689
+ * stale ids first. Called from `DeviceSlot.onDeviceStarted`.
1690
+ */
1691
+ replayInto(device) {
1692
+ for (const entry of this.clips.values()) {
1693
+ entry.animId = null;
1694
+ entry.playerId = null;
1695
+ entry.instId = null;
1696
+ if (entry.playing) {
1697
+ void this.ensureLoaded(device, entry);
1698
+ }
1699
+ }
1700
+ }
1701
+ /** Fire-and-forget a transport call against the live device, if any. */
1702
+ dispatch(issue) {
1703
+ const device = this.getDevice();
1704
+ if (!device) {
1705
+ return;
1706
+ }
1707
+ void issue(device)?.catch(() => {
1708
+ });
1709
+ }
1710
+ async ensureLoaded(device, entry) {
1711
+ if (entry.instId !== null) {
1712
+ return;
1713
+ }
1714
+ if (entry.animId === null) {
1715
+ const anim = await device.call(
1716
+ loadAnimationCall(
1717
+ storedClipToModuleValue(entry.stored, this.resolveKeys)
1718
+ )
1719
+ );
1720
+ entry.animId = callResultU32(anim);
1721
+ }
1722
+ if (entry.playerId === null) {
1723
+ const player = await device.call(createPlayerCall(""));
1724
+ entry.playerId = callResultU32(player);
1725
+ }
1726
+ if (entry.instId === null && entry.animId !== null && entry.playerId !== null) {
1727
+ const inst = await device.call(
1728
+ addInstanceCall(entry.playerId, entry.animId)
1729
+ );
1730
+ entry.instId = callResultU32(inst);
1731
+ }
1732
+ const followups = [];
1733
+ if (entry.playerId !== null) {
1734
+ if (entry.speed !== 1) {
1735
+ followups.push(setSpeedCall(entry.playerId, entry.speed));
1736
+ }
1737
+ if (entry.loop !== "loop") {
1738
+ followups.push(setLoopCall(entry.playerId, entry.loop));
1739
+ }
1740
+ if (entry.instId !== null && entry.weight !== 1) {
1741
+ followups.push(
1742
+ setWeightCall(entry.playerId, entry.instId, entry.weight)
1743
+ );
1744
+ }
1745
+ }
1746
+ for (const call of followups) {
1747
+ await device.call(call);
1748
+ }
1749
+ }
1750
+ };
1751
+
217
1752
  // src/VizijRuntimeProvider.tsx
218
1753
  var import_jsx_runtime = require("react/jsx-runtime");
219
1754
  var ACTIVE_GRACE_MS = 250;
220
1755
  var VISIBLE_IDLE_FPS = 30;
221
1756
  var HIDDEN_IDLE_FPS = 1;
1757
+ var IMPLICIT_PAUSE_GAP_S = 5;
1758
+ var inferImplicitPause = (dt) => {
1759
+ if (dt <= IMPLICIT_PAUSE_GAP_S) {
1760
+ return dt;
1761
+ }
1762
+ console.warn(
1763
+ `[vizij-runtime] step loop woke after a ${dt.toFixed(
1764
+ 1
1765
+ )}s wall-clock gap; treating it as a suspension (dt = 0)`
1766
+ );
1767
+ return 0;
1768
+ };
222
1769
  var DEFAULT_MERGE = {
223
1770
  outputs: "add",
224
1771
  intermediate: "add"
225
1772
  };
226
1773
  var DEFAULT_DURATION = 0.35;
1774
+ var POSE_CONTROL_BRIDGE_EPSILON = 1e-6;
1775
+ var runtimeDebugInstanceSequence = 0;
1776
+ var DEV_MODE = (() => {
1777
+ const nodeEnv = globalThis.process?.env?.NODE_ENV;
1778
+ return typeof nodeEnv === "string" && nodeEnv === "development";
1779
+ })();
1780
+ function isRuntimeDebugEnabled() {
1781
+ if (DEV_MODE) {
1782
+ return true;
1783
+ }
1784
+ return Boolean(
1785
+ globalThis.__VIZIJ_RUNTIME_DEBUG__
1786
+ );
1787
+ }
227
1788
  var EASINGS = {
228
1789
  linear: (t) => t,
229
1790
  easeIn: (t) => t * t,
@@ -240,12 +1801,19 @@ function resolveEasing(easing) {
240
1801
  return EASINGS.linear;
241
1802
  }
242
1803
  function findRootId(world) {
1804
+ let fallback = null;
243
1805
  for (const entry of Object.values(world)) {
244
- if (entry && typeof entry === "object" && entry.type === "group" && entry.rootBounds && entry.id) {
1806
+ if (!entry || typeof entry !== "object" || entry.type !== "group") {
1807
+ continue;
1808
+ }
1809
+ if (entry.rootBounds && entry.id) {
245
1810
  return entry.id;
246
1811
  }
1812
+ if (!fallback && entry.id) {
1813
+ fallback = entry.id;
1814
+ }
247
1815
  }
248
- return null;
1816
+ return fallback;
249
1817
  }
250
1818
  function normalisePath(path) {
251
1819
  if (!path) {
@@ -415,6 +1983,30 @@ function namespaceGraphSpec(spec, namespace) {
415
1983
  nodes: nextNodes
416
1984
  };
417
1985
  }
1986
+ function stripNulls(value) {
1987
+ if (value === null) {
1988
+ return void 0;
1989
+ }
1990
+ if (Array.isArray(value)) {
1991
+ const next2 = value.map((entry) => stripNulls(entry)).filter((entry) => entry !== void 0 && entry !== null);
1992
+ return next2;
1993
+ }
1994
+ if (typeof value !== "object" || value === void 0) {
1995
+ return value;
1996
+ }
1997
+ const next = {};
1998
+ Object.entries(value).forEach(([key, entry]) => {
1999
+ if (entry === null) {
2000
+ return;
2001
+ }
2002
+ const cleaned = stripNulls(entry);
2003
+ if (cleaned === void 0) {
2004
+ return;
2005
+ }
2006
+ next[key] = cleaned;
2007
+ });
2008
+ return next;
2009
+ }
418
2010
  var now = () => typeof performance !== "undefined" ? performance.now() : Date.now();
419
2011
  function pickBundleGraph(bundle, preferredKinds) {
420
2012
  if (!bundle?.graphs || bundle.graphs.length === 0) {
@@ -585,26 +2177,49 @@ function convertExtractedAnimations(clips) {
585
2177
  }
586
2178
  const parsedValueSize = track.valueSize != null ? Number(track.valueSize) : NaN;
587
2179
  const valueSize = Number.isFinite(parsedValueSize) && parsedValueSize > 0 ? parsedValueSize : 1;
588
- if (values.length !== times.length * valueSize) {
2180
+ const interpolationRaw = typeof track.interpolation === "string" ? track.interpolation.trim().toLowerCase() : "linear";
2181
+ const interpolation = interpolationRaw === "step" ? "step" : interpolationRaw === "cubic" || interpolationRaw === "cubicspline" ? "cubic" : "linear";
2182
+ const isCubic = interpolation === "cubic";
2183
+ const hasTripletTangents = isCubic && values.length === times.length * valueSize * 3;
2184
+ const hasFlatValues = values.length === times.length * valueSize;
2185
+ if (!hasTripletTangents && !hasFlatValues) {
589
2186
  return;
590
2187
  }
591
2188
  const rawIndex = track.componentIndex != null ? Number(track.componentIndex) : 0;
592
2189
  const componentIndex = Number.isInteger(rawIndex) && rawIndex >= 0 ? Math.min(rawIndex, valueSize - 1) : 0;
593
2190
  const keyframes = [];
594
2191
  times.forEach((time, index) => {
595
- const base = index * valueSize + componentIndex;
596
- const value = values[base];
2192
+ const flatBase = index * valueSize + componentIndex;
2193
+ const valueBase = hasTripletTangents ? index * valueSize * 3 + valueSize + componentIndex : flatBase;
2194
+ const value = values[valueBase];
597
2195
  if (!Number.isFinite(value)) {
598
2196
  return;
599
2197
  }
600
- keyframes.push({ time, value });
2198
+ const keyframe = {
2199
+ time,
2200
+ value
2201
+ };
2202
+ if (hasTripletTangents) {
2203
+ const inBase = index * valueSize * 3 + componentIndex;
2204
+ const outBase = index * valueSize * 3 + valueSize * 2 + componentIndex;
2205
+ const inTangent = values[inBase];
2206
+ const outTangent = values[outBase];
2207
+ if (Number.isFinite(inTangent)) {
2208
+ keyframe.inTangent = inTangent;
2209
+ }
2210
+ if (Number.isFinite(outTangent)) {
2211
+ keyframe.outTangent = outTangent;
2212
+ }
2213
+ }
2214
+ keyframes.push(keyframe);
601
2215
  });
602
2216
  if (keyframes.length === 0) {
603
2217
  return;
604
2218
  }
605
2219
  convertedTracks.push({
606
2220
  channel: channelId,
607
- keyframes
2221
+ keyframes,
2222
+ interpolation
608
2223
  });
609
2224
  });
610
2225
  if (convertedTracks.length === 0) {
@@ -648,12 +2263,15 @@ function pickExtractedAnimations(asset) {
648
2263
  }
649
2264
  return animations;
650
2265
  }
651
- function mergeAnimationLists(explicit, fromBundle) {
652
- if (!explicit?.length && fromBundle.length === 0) {
2266
+ function mergeAnimationLists(explicit, hasExplicitOverride, fromBundle) {
2267
+ if (!hasExplicitOverride) {
2268
+ return fromBundle.length > 0 ? fromBundle : void 0;
2269
+ }
2270
+ if (!Array.isArray(explicit)) {
653
2271
  return void 0;
654
2272
  }
655
- if (!explicit?.length) {
656
- return fromBundle.length > 0 ? fromBundle : void 0;
2273
+ if (explicit.length === 0) {
2274
+ return [];
657
2275
  }
658
2276
  if (fromBundle.length === 0) {
659
2277
  return explicit;
@@ -671,21 +2289,153 @@ function mergeAnimationLists(explicit, fromBundle) {
671
2289
  }
672
2290
  return changed ? merged : explicit;
673
2291
  }
2292
+ function extractProgramResetValues(value) {
2293
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
2294
+ return void 0;
2295
+ }
2296
+ const entries = Object.entries(value).filter(
2297
+ ([, rawValue]) => Number.isFinite(Number(rawValue))
2298
+ );
2299
+ if (entries.length === 0) {
2300
+ return void 0;
2301
+ }
2302
+ return Object.fromEntries(
2303
+ entries.map(([path, rawValue]) => [path, Number(rawValue)])
2304
+ );
2305
+ }
2306
+ function convertBundlePrograms(entries) {
2307
+ if (!Array.isArray(entries) || entries.length === 0) {
2308
+ return [];
2309
+ }
2310
+ return entries.filter((entry) => normaliseBundleKind(entry?.kind) === "motiongraph").map((entry) => {
2311
+ const graph = convertBundleGraph(entry);
2312
+ if (!graph) {
2313
+ return null;
2314
+ }
2315
+ const metadata = entry.metadata && typeof entry.metadata === "object" && !Array.isArray(entry.metadata) ? entry.metadata : void 0;
2316
+ return {
2317
+ id: entry.id,
2318
+ label: typeof entry.label === "string" ? entry.label : void 0,
2319
+ graph,
2320
+ metadata,
2321
+ resetValues: extractProgramResetValues(metadata?.resetValues)
2322
+ };
2323
+ }).filter(Boolean);
2324
+ }
2325
+ function resolveConstraintDefault(path, namespace, inputConstraints) {
2326
+ const trimmed = path.trim();
2327
+ if (!trimmed) {
2328
+ return void 0;
2329
+ }
2330
+ const stripped = stripRigFacePrefix(trimmed);
2331
+ const relativePath = stripped ? `/${stripped}` : "";
2332
+ const candidates = [
2333
+ namespaceTypedPath(trimmed, namespace),
2334
+ trimmed,
2335
+ stripped ? namespaceTypedPath(stripped, namespace) : void 0,
2336
+ stripped || void 0,
2337
+ relativePath || void 0
2338
+ ];
2339
+ for (const candidate of candidates) {
2340
+ if (!candidate) {
2341
+ continue;
2342
+ }
2343
+ const defaultValue = inputConstraints[candidate]?.defaultValue;
2344
+ if (Number.isFinite(defaultValue)) {
2345
+ return Number(defaultValue);
2346
+ }
2347
+ }
2348
+ return void 0;
2349
+ }
2350
+ function deriveProgramInputSeedValues(args) {
2351
+ const graphSpec = resolveGraphSpec(
2352
+ args.program.graph,
2353
+ `${args.program.id ?? "program"} graph (seed defaults)`
2354
+ );
2355
+ if (!graphSpec) {
2356
+ return [];
2357
+ }
2358
+ return collectInputPaths(graphSpec).map((path) => path.trim()).filter((path) => path.length > 0).flatMap((path) => {
2359
+ const namespacedPath = namespaceTypedPath(path, args.namespace);
2360
+ if (args.stagedInputs.has(namespacedPath)) {
2361
+ return [];
2362
+ }
2363
+ if (args.getPathSnapshot(namespacedPath) !== void 0) {
2364
+ return [];
2365
+ }
2366
+ const defaultValue = resolveConstraintDefault(
2367
+ path,
2368
+ args.namespace,
2369
+ args.inputConstraints
2370
+ );
2371
+ if (!Number.isFinite(defaultValue)) {
2372
+ return [];
2373
+ }
2374
+ return [{ path, value: { float: Number(defaultValue) } }];
2375
+ });
2376
+ }
2377
+ function mergeProgramLists(explicit, hasExplicitOverride, fromBundle) {
2378
+ if (!hasExplicitOverride) {
2379
+ return fromBundle.length > 0 ? fromBundle : void 0;
2380
+ }
2381
+ if (!Array.isArray(explicit)) {
2382
+ return void 0;
2383
+ }
2384
+ if (explicit.length === 0) {
2385
+ return [];
2386
+ }
2387
+ if (fromBundle.length === 0) {
2388
+ return explicit;
2389
+ }
2390
+ const seen = new Set(explicit.map((program) => program.id));
2391
+ let changed = false;
2392
+ const merged = [...explicit];
2393
+ for (const program of fromBundle) {
2394
+ if (!program.id || seen.has(program.id)) {
2395
+ continue;
2396
+ }
2397
+ merged.push(program);
2398
+ seen.add(program.id);
2399
+ changed = true;
2400
+ }
2401
+ return changed ? merged : explicit;
2402
+ }
674
2403
  function mergeAssetBundle(base, extracted, extractedAnimations) {
675
2404
  const resolvedBundle = base.bundle ?? extracted ?? null;
2405
+ const hasBaseRigOverride = Object.prototype.hasOwnProperty.call(base, "rig");
2406
+ const hasBasePoseOverride = Object.prototype.hasOwnProperty.call(
2407
+ base,
2408
+ "pose"
2409
+ );
2410
+ const hasBaseAnimationsOverride = Object.prototype.hasOwnProperty.call(
2411
+ base,
2412
+ "animations"
2413
+ );
2414
+ const hasBaseProgramsOverride = Object.prototype.hasOwnProperty.call(
2415
+ base,
2416
+ "programs"
2417
+ );
676
2418
  const rigFromBundle = convertBundleGraph(
677
2419
  pickBundleGraph(resolvedBundle, ["rig"])
678
2420
  );
679
- const resolvedRig = base.rig ?? rigFromBundle ?? void 0;
2421
+ const resolvedRig = hasBaseRigOverride ? base.rig : base.rig ?? rigFromBundle ?? void 0;
680
2422
  const basePose = base.pose;
2423
+ const hasBasePoseGraphOverride = Boolean(
2424
+ basePose && Object.prototype.hasOwnProperty.call(basePose, "graph")
2425
+ );
2426
+ const hasBasePoseConfigOverride = Boolean(
2427
+ basePose && Object.prototype.hasOwnProperty.call(basePose, "config")
2428
+ );
681
2429
  const poseStageFilter = basePose?.stageNeutralFilter;
682
- const poseGraphFromBundle = basePose?.graph ? null : convertBundleGraph(
2430
+ const poseGraphFromBundle = hasBasePoseOverride && !basePose ? null : hasBasePoseGraphOverride ? null : convertBundleGraph(
683
2431
  pickBundleGraph(resolvedBundle, ["pose-driver", "pose"])
684
2432
  );
685
- const resolvedPoseGraph = basePose?.graph ?? poseGraphFromBundle ?? void 0;
686
- const resolvedPoseConfig = basePose?.config ?? resolvedBundle?.poses?.config ?? void 0;
2433
+ const resolvedPoseGraph = hasBasePoseGraphOverride ? basePose?.graph : basePose?.graph ?? poseGraphFromBundle ?? void 0;
2434
+ const resolvedPoseConfig = hasBasePoseConfigOverride ? basePose?.config : basePose?.config ?? resolvedBundle?.poses?.config ?? void 0;
687
2435
  let resolvedPose = basePose;
688
- if (basePose) {
2436
+ if (hasBasePoseOverride && !basePose) {
2437
+ resolvedPose = void 0;
2438
+ } else if (basePose) {
689
2439
  const nextPose = { ...basePose };
690
2440
  let changed = false;
691
2441
  if (resolvedPoseGraph && basePose.graph !== resolvedPoseGraph) {
@@ -713,15 +2463,22 @@ function mergeAssetBundle(base, extracted, extractedAnimations) {
713
2463
  );
714
2464
  let resolvedAnimations = mergeAnimationLists(
715
2465
  base.animations,
2466
+ hasBaseAnimationsOverride,
716
2467
  animationsFromBundle
717
2468
  );
718
2469
  const animationsFromAsset = extractedAnimations && extractedAnimations.length > 0 ? extractedAnimations : [];
719
2470
  if (animationsFromAsset.length > 0) {
720
2471
  resolvedAnimations = mergeAnimationLists(
721
2472
  resolvedAnimations,
2473
+ true,
722
2474
  animationsFromAsset
723
2475
  );
724
2476
  }
2477
+ const programsFromBundle = mergeProgramLists(
2478
+ base.programs,
2479
+ hasBaseProgramsOverride,
2480
+ convertBundlePrograms(resolvedBundle?.graphs)
2481
+ );
725
2482
  const merged = {
726
2483
  ...base
727
2484
  };
@@ -732,124 +2489,223 @@ function mergeAssetBundle(base, extracted, extractedAnimations) {
732
2489
  }
733
2490
  merged.pose = resolvedPose;
734
2491
  merged.animations = resolvedAnimations;
2492
+ merged.programs = programsFromBundle;
735
2493
  merged.bundle = resolvedBundle;
736
2494
  return merged;
737
2495
  }
2496
+ function normalizeStoredAnimationInterpolation(interpolation) {
2497
+ const mode = typeof interpolation === "string" ? interpolation.trim().toLowerCase() : "linear";
2498
+ if (mode === "step") {
2499
+ return "step";
2500
+ }
2501
+ if (mode === "cubic" || mode === "cubicspline") {
2502
+ return "cubic";
2503
+ }
2504
+ return "linear";
2505
+ }
2506
+ function buildStoredAnimationTransitions(mode) {
2507
+ if (mode === "cubic") {
2508
+ return void 0;
2509
+ }
2510
+ if (mode === "step") {
2511
+ return {
2512
+ in: { x: 1, y: 1 },
2513
+ out: { x: 1, y: 0 }
2514
+ };
2515
+ }
2516
+ return {
2517
+ in: { x: 1, y: 1 },
2518
+ out: { x: 0, y: 0 }
2519
+ };
2520
+ }
2521
+ function toStoredAnimationClip(fallbackId, clip) {
2522
+ const clipId = typeof clip.id === "string" && clip.id.trim().length > 0 ? clip.id.trim() : fallbackId;
2523
+ const clipName = typeof clip.name === "string" && clip.name.trim().length > 0 ? clip.name.trim() : clipId;
2524
+ const durationSeconds = resolveClipDurationSeconds(clip, 0);
2525
+ const durationMs = Math.max(1, Math.round(durationSeconds * 1e3));
2526
+ const tracks = (Array.isArray(clip.tracks) ? clip.tracks : []).map((rawTrack, trackIndex) => {
2527
+ const channel = typeof rawTrack.channel === "string" ? rawTrack.channel.trim() : "";
2528
+ if (!channel) {
2529
+ return null;
2530
+ }
2531
+ const keyframes = (Array.isArray(rawTrack.keyframes) ? rawTrack.keyframes : []).map((keyframe) => {
2532
+ const time = Number(keyframe.time);
2533
+ const value = Number(keyframe.value);
2534
+ const keyframeId = keyframe["id"];
2535
+ const keyframeInterpolation = keyframe["interpolation"];
2536
+ if (!Number.isFinite(time) || !Number.isFinite(value)) {
2537
+ return null;
2538
+ }
2539
+ return {
2540
+ id: typeof keyframeId === "string" && keyframeId.trim().length > 0 ? keyframeId.trim() : `${clipId}:track-${trackIndex.toString().padStart(4, "0")}:point-${time.toFixed(6)}`,
2541
+ time,
2542
+ value,
2543
+ mode: normalizeStoredAnimationInterpolation(
2544
+ keyframeInterpolation ?? rawTrack.interpolation
2545
+ )
2546
+ };
2547
+ }).filter(Boolean);
2548
+ if (keyframes.length === 0) {
2549
+ return null;
2550
+ }
2551
+ keyframes.sort((left, right) => {
2552
+ if (left.time !== right.time) {
2553
+ return left.time - right.time;
2554
+ }
2555
+ return left.id.localeCompare(right.id);
2556
+ });
2557
+ const rawTrackId = rawTrack["id"];
2558
+ const rawTrackName = rawTrack["name"];
2559
+ const trackId = typeof rawTrackId === "string" && rawTrackId.trim().length > 0 ? rawTrackId.trim() : `${clipId}:track-${trackIndex.toString().padStart(4, "0")}`;
2560
+ const trackName = typeof rawTrackName === "string" && rawTrackName.trim().length > 0 ? rawTrackName.trim() : channel.replace(/^\/+/, "") || trackId;
2561
+ const denominator = durationSeconds > 0 ? durationSeconds : 1;
2562
+ return {
2563
+ id: trackId,
2564
+ name: trackName,
2565
+ animatableId: channel,
2566
+ points: keyframes.map((keyframe) => {
2567
+ const stamp = Math.max(0, Math.min(1, keyframe.time / denominator));
2568
+ const transitions = buildStoredAnimationTransitions(keyframe.mode);
2569
+ return {
2570
+ id: keyframe.id,
2571
+ stamp,
2572
+ value: keyframe.value,
2573
+ ...transitions ? { transitions } : {}
2574
+ };
2575
+ })
2576
+ };
2577
+ }).filter(Boolean);
2578
+ return {
2579
+ id: clipId,
2580
+ name: clipName,
2581
+ duration: durationMs,
2582
+ groups: {},
2583
+ tracks
2584
+ };
2585
+ }
738
2586
  function VizijRuntimeProvider({
739
2587
  assetBundle,
740
2588
  children,
741
2589
  namespace: namespaceProp,
742
2590
  faceId: faceIdProp,
2591
+ updateTier = "auto",
743
2592
  autoCreate = true,
744
- createOptions,
745
2593
  autostart = false,
746
- driveOrchestrator = true,
2594
+ driveRuntime = true,
747
2595
  mergeStrategy,
748
2596
  onRegisterControllers,
749
2597
  onStatusChange,
750
- orchestratorScope = "auto"
2598
+ transformOutputWrite
751
2599
  }) {
752
2600
  const storeRef = (0, import_react2.useRef)(null);
753
2601
  if (!storeRef.current) {
754
2602
  storeRef.current = (0, import_render.createVizijStore)();
755
2603
  }
756
- const parentOrchestrator = (0, import_react2.useContext)(import_orchestrator_react.OrchestratorContext);
757
- const hasParentOrchestrator = Boolean(parentOrchestrator);
758
- const shouldProvideOrchestrator = orchestratorScope === "isolated" || !hasParentOrchestrator && orchestratorScope !== "shared";
759
- if (orchestratorScope === "shared" && !hasParentOrchestrator) {
760
- console.warn(
761
- '[vizij-runtime] orchestratorScope="shared" requires an OrchestratorProvider higher in the tree; falling back to an isolated provider.'
762
- );
763
- }
764
- const runtimeTree = /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_render.VizijContext.Provider, { value: storeRef.current, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2604
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_render.VizijContext.Provider, { value: storeRef.current, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
765
2605
  VizijRuntimeProviderInner,
766
2606
  {
767
2607
  assetBundle,
768
2608
  namespace: namespaceProp,
769
2609
  faceId: faceIdProp,
2610
+ updateTier,
770
2611
  autoCreate,
771
2612
  autostart,
772
- driveOrchestrator,
773
- createOptions,
2613
+ driveRuntime,
774
2614
  mergeStrategy,
775
2615
  onRegisterControllers,
776
2616
  onStatusChange,
2617
+ transformOutputWrite,
777
2618
  store: storeRef.current,
778
2619
  children
779
2620
  }
780
2621
  ) });
781
- if (!shouldProvideOrchestrator) {
782
- return runtimeTree;
783
- }
784
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
785
- import_orchestrator_react.OrchestratorProvider,
786
- {
787
- autoCreate,
788
- createOptions,
789
- autostart: false,
790
- children: runtimeTree
791
- }
792
- );
793
2622
  }
794
2623
  function VizijRuntimeProviderInner({
795
2624
  assetBundle: initialAssetBundle,
796
2625
  namespace: namespaceProp,
797
2626
  faceId: faceIdProp,
2627
+ updateTier,
798
2628
  mergeStrategy,
799
2629
  onRegisterControllers,
800
2630
  onStatusChange,
2631
+ transformOutputWrite,
801
2632
  store,
802
2633
  children,
803
2634
  autoCreate,
804
2635
  autostart,
805
- createOptions,
806
- driveOrchestrator
2636
+ driveRuntime
807
2637
  }) {
2638
+ const [assetBundleOverride, setAssetBundleOverride] = (0, import_react2.useState)(null);
2639
+ const [graphUpdateToken, setGraphUpdateToken] = (0, import_react2.useState)(0);
2640
+ const effectiveAssetBundle = assetBundleOverride ?? initialAssetBundle;
2641
+ const latestEffectiveAssetBundleRef = (0, import_react2.useRef)(effectiveAssetBundle);
808
2642
  const [extractedBundle, setExtractedBundle] = (0, import_react2.useState)(() => {
809
- if (initialAssetBundle.bundle) {
810
- return initialAssetBundle.bundle;
2643
+ if (effectiveAssetBundle.bundle) {
2644
+ return effectiveAssetBundle.bundle;
811
2645
  }
812
- if (initialAssetBundle.glb.kind === "world" && initialAssetBundle.glb.bundle) {
813
- return initialAssetBundle.glb.bundle;
2646
+ if (effectiveAssetBundle.glb.kind === "world" && effectiveAssetBundle.glb.bundle) {
2647
+ return effectiveAssetBundle.glb.bundle;
814
2648
  }
815
2649
  return null;
816
2650
  });
817
2651
  const [extractedAnimations, setExtractedAnimations] = (0, import_react2.useState)([]);
2652
+ const previousBundleRef = (0, import_react2.useRef)(null);
2653
+ const suppressNextBundlePlanRef = (0, import_react2.useRef)(false);
2654
+ const pendingPlanRef = (0, import_react2.useRef)(null);
2655
+ const updateTierRef = (0, import_react2.useRef)(updateTier);
818
2656
  (0, import_react2.useEffect)(() => {
819
- if (initialAssetBundle.bundle) {
820
- setExtractedBundle(initialAssetBundle.bundle);
2657
+ if (effectiveAssetBundle.bundle) {
2658
+ setExtractedBundle(effectiveAssetBundle.bundle);
821
2659
  return;
822
2660
  }
823
- if (initialAssetBundle.glb.kind === "world") {
824
- setExtractedBundle(initialAssetBundle.glb.bundle ?? null);
2661
+ if (effectiveAssetBundle.glb.kind === "world") {
2662
+ setExtractedBundle(effectiveAssetBundle.glb.bundle ?? null);
825
2663
  } else {
826
2664
  setExtractedBundle(null);
827
2665
  }
828
- }, [initialAssetBundle]);
2666
+ }, [effectiveAssetBundle]);
2667
+ (0, import_react2.useEffect)(() => {
2668
+ updateTierRef.current = updateTier;
2669
+ }, [updateTier]);
829
2670
  const assetBundle = (0, import_react2.useMemo)(
830
2671
  () => mergeAssetBundle(
831
- initialAssetBundle,
2672
+ effectiveAssetBundle,
832
2673
  extractedBundle,
833
2674
  extractedAnimations
834
2675
  ),
835
- [initialAssetBundle, extractedBundle, extractedAnimations]
2676
+ [effectiveAssetBundle, extractedBundle, extractedAnimations]
836
2677
  );
837
- const {
838
- ready,
839
- createOrchestrator,
840
- registerGraph,
841
- registerMergedGraph,
842
- registerAnimation,
843
- removeGraph,
844
- removeAnimation,
845
- listControllers,
846
- setInput: orchestratorSetInput,
847
- getPathSnapshot,
848
- step: stepRuntime
849
- } = (0, import_orchestrator_react.useOrchestrator)();
850
- const frame = (0, import_orchestrator_react.useOrchFrame)();
2678
+ (0, import_react2.useEffect)(() => {
2679
+ latestEffectiveAssetBundleRef.current = effectiveAssetBundle;
2680
+ }, [effectiveAssetBundle]);
2681
+ (0, import_react2.useEffect)(() => {
2682
+ if (suppressNextBundlePlanRef.current) {
2683
+ suppressNextBundlePlanRef.current = false;
2684
+ previousBundleRef.current = effectiveAssetBundle;
2685
+ return;
2686
+ }
2687
+ const plan = resolveRuntimeUpdatePlan(
2688
+ previousBundleRef.current,
2689
+ effectiveAssetBundle,
2690
+ updateTierRef.current
2691
+ );
2692
+ pendingPlanRef.current = plan;
2693
+ previousBundleRef.current = effectiveAssetBundle;
2694
+ if (plan.reregisterGraphs) {
2695
+ setGraphUpdateToken((prev) => prev + 1);
2696
+ }
2697
+ }, [effectiveAssetBundle]);
2698
+ const deviceSlotRef = (0, import_react2.useRef)(null);
2699
+ if (!deviceSlotRef.current) {
2700
+ deviceSlotRef.current = new DeviceSlot();
2701
+ }
2702
+ const deviceSlot = deviceSlotRef.current;
2703
+ const [ready, setReady] = (0, import_react2.useState)(false);
2704
+ const graphSourcesRef = (0, import_react2.useRef)([]);
851
2705
  const namespace = namespaceProp ?? assetBundle.namespace ?? "default";
852
2706
  const faceId = faceIdProp ?? assetBundle.faceId ?? assetBundle.pose?.config?.faceId ?? assetBundle.pose?.config?.faceId ?? void 0;
2707
+ const faceIdRef = (0, import_react2.useRef)(faceId);
2708
+ faceIdRef.current = faceId;
853
2709
  const [status, setStatus] = (0, import_react2.useState)({
854
2710
  loading: true,
855
2711
  ready: false,
@@ -862,20 +2718,68 @@ function VizijRuntimeProviderInner({
862
2718
  stepHz: void 0,
863
2719
  controllers: { graphs: [], anims: [] }
864
2720
  });
2721
+ const runtimeDebugInstanceIdRef = (0, import_react2.useRef)(
2722
+ `vizij-runtime:${runtimeDebugInstanceSequence++}`
2723
+ );
865
2724
  const errorsRef = (0, import_react2.useRef)([]);
866
2725
  const outputPathsRef = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
867
2726
  const baseOutputPathsRef = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
868
2727
  const namespacedOutputPathsRef = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
869
2728
  const namespaceRef = (0, import_react2.useRef)(namespace);
870
- const driveOrchestratorRef = (0, import_react2.useRef)(driveOrchestrator);
2729
+ const driveRuntimeRef = (0, import_react2.useRef)(driveRuntime);
871
2730
  const rigInputMapRef = (0, import_react2.useRef)({});
2731
+ const rigPoseControlInputIdsRef = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
872
2732
  const registeredGraphsRef = (0, import_react2.useRef)([]);
873
2733
  const registeredAnimationsRef = (0, import_react2.useRef)([]);
874
2734
  const mergedGraphRef = (0, import_react2.useRef)(null);
2735
+ const poseControlBridgeValuesRef = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
2736
+ const poseWeightFallbackMap = (0, import_react2.useMemo)(() => {
2737
+ const map = /* @__PURE__ */ new Map();
2738
+ const poseConfig = assetBundle.pose?.config;
2739
+ if (!poseConfig) {
2740
+ return map;
2741
+ }
2742
+ const posePaths = buildPoseWeightPathMap(
2743
+ poseConfig.poses ?? [],
2744
+ poseConfig.faceId ?? faceId ?? "face"
2745
+ );
2746
+ (poseConfig.poses ?? []).forEach((pose) => {
2747
+ const posePath = posePaths.get(pose.id);
2748
+ if (!posePath) {
2749
+ return;
2750
+ }
2751
+ const values = Object.fromEntries(
2752
+ Object.entries(pose.values ?? {}).filter(
2753
+ ([, value]) => Number.isFinite(value)
2754
+ )
2755
+ );
2756
+ map.set(posePath, values);
2757
+ });
2758
+ return map;
2759
+ }, [assetBundle.pose?.config, faceId]);
2760
+ const useLegacyPoseWeightFallback = (0, import_react2.useMemo)(
2761
+ () => shouldUseLegacyPoseWeightFallback(Boolean(assetBundle.pose?.graph)),
2762
+ [assetBundle.pose?.graph]
2763
+ );
2764
+ const resolvedProgramAssets = (0, import_react2.useMemo)(
2765
+ () => assetBundle.programs && assetBundle.programs.length > 0 ? assetBundle.programs : convertBundlePrograms(assetBundle.bundle?.graphs),
2766
+ [assetBundle.bundle?.graphs, assetBundle.programs]
2767
+ );
875
2768
  const [inputConstraints, setInputConstraints] = (0, import_react2.useState)({});
2769
+ const inputConstraintsRef = (0, import_react2.useRef)({});
876
2770
  const avgStepDtRef = (0, import_react2.useRef)(null);
2771
+ const inputDriverIdsRef = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
877
2772
  const animationTweensRef = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
878
2773
  const clipPlaybackRef = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
2774
+ const programPlaybackRef = (0, import_react2.useRef)(
2775
+ /* @__PURE__ */ new Map()
2776
+ );
2777
+ const programControllerIdsRef = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
2778
+ const animationModuleRef = (0, import_react2.useRef)(null);
2779
+ const animationHostRef = (0, import_react2.useRef)(null);
2780
+ const animationsSourceRegisteredRef = (0, import_react2.useRef)(false);
2781
+ const animationGapWarnedRef = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
2782
+ const animationSystemActiveRef = (0, import_react2.useRef)(true);
879
2783
  const stagedInputsRef = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
880
2784
  const autostartRef = (0, import_react2.useRef)(autostart);
881
2785
  const lastActivityTimeRef = (0, import_react2.useRef)(now());
@@ -890,9 +2794,62 @@ function VizijRuntimeProviderInner({
890
2794
  runtimeMountedRef.current = false;
891
2795
  };
892
2796
  }, []);
2797
+ const publishRuntimeDebugState = (0, import_react2.useCallback)(() => {
2798
+ const storeState = store.getState();
2799
+ setRuntimeDebugState(runtimeDebugInstanceIdRef.current, {
2800
+ namespace,
2801
+ faceId: faceId ?? null,
2802
+ rootId: status.rootId,
2803
+ ready: status.ready,
2804
+ loading: status.loading,
2805
+ autostart,
2806
+ driveRuntime,
2807
+ loopMode,
2808
+ outputCount: status.outputPaths.length,
2809
+ graphControllerCount: status.controllers.graphs.length,
2810
+ animationControllerCount: status.controllers.anims.length,
2811
+ registeredGraphCount: registeredGraphsRef.current.length,
2812
+ registeredAnimationCount: registeredAnimationsRef.current.length,
2813
+ programControllerCount: programControllerIdsRef.current.size,
2814
+ animationTweenCount: animationTweensRef.current.size,
2815
+ clipPlaybackCount: clipPlaybackRef.current.size,
2816
+ programPlaybackCount: programPlaybackRef.current.size,
2817
+ stagedInputCount: stagedInputsRef.current.size,
2818
+ activeDriverCount: inputDriverIdsRef.current.size,
2819
+ worldEntryCount: Object.keys(storeState.world ?? {}).length,
2820
+ animatableCount: Object.keys(storeState.animatables ?? {}).length,
2821
+ valuesSize: storeState.values instanceof Map ? storeState.values.size : null,
2822
+ stepHz: status.stepHz ?? null
2823
+ });
2824
+ }, [
2825
+ autostart,
2826
+ driveRuntime,
2827
+ faceId,
2828
+ loopMode,
2829
+ namespace,
2830
+ status.controllers.anims.length,
2831
+ status.controllers.graphs.length,
2832
+ status.loading,
2833
+ status.outputPaths.length,
2834
+ status.ready,
2835
+ status.rootId,
2836
+ status.stepHz,
2837
+ store
2838
+ ]);
2839
+ (0, import_react2.useEffect)(() => {
2840
+ publishRuntimeDebugState();
2841
+ const unsubscribe = store.subscribe(() => {
2842
+ publishRuntimeDebugState();
2843
+ });
2844
+ return () => {
2845
+ unsubscribe();
2846
+ clearRuntimeDebugState(runtimeDebugInstanceIdRef.current);
2847
+ };
2848
+ }, [publishRuntimeDebugState, store]);
893
2849
  (0, import_react2.useEffect)(() => {
894
2850
  const rigAsset = assetBundle.rig;
895
2851
  if (!rigAsset) {
2852
+ inputConstraintsRef.current = {};
896
2853
  setInputConstraints({});
897
2854
  return;
898
2855
  }
@@ -905,15 +2862,8 @@ function VizijRuntimeProviderInner({
905
2862
  rigAsset.inputMetadata,
906
2863
  namespace
907
2864
  );
2865
+ inputConstraintsRef.current = constraints;
908
2866
  setInputConstraints(constraints);
909
- const isDevEnv = typeof globalThis !== "undefined" && Boolean(globalThis?.process?.env?.NODE_ENV !== "production");
910
- if (isDevEnv) {
911
- const size = Object.keys(constraints).length;
912
- console.log("[vizij-runtime] input constraints computed", size, {
913
- namespace,
914
- rigId: rigAsset.id
915
- });
916
- }
917
2867
  }, [assetBundle.rig, namespace]);
918
2868
  const requestLoopMode = (0, import_react2.useCallback)((mode) => {
919
2869
  if (!runtimeMountedRef.current) {
@@ -922,7 +2872,28 @@ function VizijRuntimeProviderInner({
922
2872
  setLoopMode((prev) => prev === mode ? prev : mode);
923
2873
  }, []);
924
2874
  const hasActiveAnimations = (0, import_react2.useCallback)(() => {
925
- return animationTweensRef.current.size > 0 || clipPlaybackRef.current.size > 0;
2875
+ if (animationTweensRef.current.size > 0) {
2876
+ return true;
2877
+ }
2878
+ if (!animationSystemActiveRef.current) {
2879
+ for (const state of programPlaybackRef.current.values()) {
2880
+ if (state.state === "playing") {
2881
+ return true;
2882
+ }
2883
+ }
2884
+ return false;
2885
+ }
2886
+ for (const state of clipPlaybackRef.current.values()) {
2887
+ if (state.playing) {
2888
+ return true;
2889
+ }
2890
+ }
2891
+ for (const state of programPlaybackRef.current.values()) {
2892
+ if (state.state === "playing") {
2893
+ return true;
2894
+ }
2895
+ }
2896
+ return false;
926
2897
  }, []);
927
2898
  const computeDesiredLoopMode = (0, import_react2.useCallback)(() => {
928
2899
  const hasAnimations = hasActiveAnimations();
@@ -944,11 +2915,50 @@ function VizijRuntimeProviderInner({
944
2915
  }, [updateLoopMode]);
945
2916
  const setInput = (0, import_react2.useCallback)(
946
2917
  (path, value, shape) => {
2918
+ const numericValue = (0, import_value_json2.valueAsNumber)(value);
2919
+ const basePath = stripNamespace(
2920
+ normalisePath(path),
2921
+ namespaceRef.current
2922
+ );
2923
+ const poseValues = useLegacyPoseWeightFallback && numericValue != null ? poseWeightFallbackMap.get(basePath) : void 0;
2924
+ if (poseValues && numericValue != null) {
2925
+ const poseFaceId = assetBundle.pose?.config?.faceId ?? faceId ?? "face";
2926
+ const rigMap = rigInputMapRef.current;
2927
+ Object.entries(poseValues).forEach(([inputId, poseValue]) => {
2928
+ if (!Number.isFinite(poseValue)) {
2929
+ return;
2930
+ }
2931
+ const controlPath = resolvePoseControlInputPath({
2932
+ inputId,
2933
+ basePath: buildRigInputPath(
2934
+ poseFaceId,
2935
+ `/pose/control/${inputId}`
2936
+ ),
2937
+ rigInputPathMap: rigMap,
2938
+ hasNativePoseControlInput: true
2939
+ }) ?? buildRigInputPath(poseFaceId, `/pose/control/${inputId}`);
2940
+ setInput(controlPath, { float: Number(poseValue) * numericValue });
2941
+ });
2942
+ return;
2943
+ }
947
2944
  markActivity();
948
2945
  const namespacedPath = namespaceTypedPath(path, namespaceRef.current);
2946
+ if (isRuntimeDebugEnabled() && (namespacedPath.includes("animation/authoring.timeline.main") || namespacedPath.endsWith("/blink"))) {
2947
+ console.log("[vizij-runtime] stage input", {
2948
+ path,
2949
+ namespacedPath,
2950
+ value
2951
+ });
2952
+ }
949
2953
  stagedInputsRef.current.set(namespacedPath, { value, shape });
950
2954
  },
951
- [markActivity]
2955
+ [
2956
+ assetBundle.pose?.config?.faceId,
2957
+ faceId,
2958
+ markActivity,
2959
+ poseWeightFallbackMap,
2960
+ useLegacyPoseWeightFallback
2961
+ ]
952
2962
  );
953
2963
  const reportStatus = (0, import_react2.useCallback)(
954
2964
  (updater) => {
@@ -958,32 +2968,262 @@ function VizijRuntimeProviderInner({
958
2968
  return next;
959
2969
  });
960
2970
  },
961
- [onStatusChange]
2971
+ [onStatusChange]
2972
+ );
2973
+ const pushError = (0, import_react2.useCallback)(
2974
+ (error) => {
2975
+ errorsRef.current = [...errorsRef.current, error];
2976
+ reportStatus((prev) => ({
2977
+ ...prev,
2978
+ error,
2979
+ errors: errorsRef.current
2980
+ }));
2981
+ console.warn("[vizij-runtime]", error.message, error.cause);
2982
+ },
2983
+ [reportStatus]
2984
+ );
2985
+ const resetErrors = (0, import_react2.useCallback)(() => {
2986
+ errorsRef.current = [];
2987
+ reportStatus((prev) => ({
2988
+ ...prev,
2989
+ error: null,
2990
+ errors: []
2991
+ }));
2992
+ }, [reportStatus]);
2993
+ (0, import_react2.useEffect)(() => {
2994
+ autostartRef.current = autostart;
2995
+ updateLoopMode();
2996
+ }, [autostart, updateLoopMode]);
2997
+ const pendingWritesRef = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
2998
+ const getDevice = (0, import_react2.useCallback)(
2999
+ () => deviceSlot.current?.device ?? null,
3000
+ [deviceSlot]
3001
+ );
3002
+ const ensureAnimationHost = (0, import_react2.useCallback)(() => {
3003
+ if (!animationModuleRef.current) {
3004
+ return null;
3005
+ }
3006
+ if (!animationHostRef.current) {
3007
+ animationHostRef.current = new AnimationModuleHost(
3008
+ getDevice,
3009
+ (key) => resolveAnimationBridgeOutputPaths(
3010
+ key,
3011
+ faceIdRef.current ?? void 0,
3012
+ rigInputMapRef.current
3013
+ )
3014
+ );
3015
+ }
3016
+ return animationHostRef.current;
3017
+ }, [getDevice]);
3018
+ const applyEngineChangesRef = (0, import_react2.useRef)(() => {
3019
+ });
3020
+ const recomposeDevice = (0, import_react2.useCallback)(() => {
3021
+ const spec = composeGraphSpecs(graphSourcesRef.current);
3022
+ deviceSlot.recompose(spec).then((handle) => {
3023
+ if (pendingWritesRef.current.size > 0) {
3024
+ handle.device.writeValues(
3025
+ Object.fromEntries(pendingWritesRef.current)
3026
+ );
3027
+ pendingWritesRef.current.clear();
3028
+ }
3029
+ }).catch((err) => {
3030
+ pushError({
3031
+ message: "Failed to (re)compose the arora device",
3032
+ cause: err,
3033
+ phase: "engine",
3034
+ timestamp: performance.now()
3035
+ });
3036
+ });
3037
+ }, [deviceSlot, pushError]);
3038
+ const initEngine = (0, import_react2.useCallback)(async () => {
3039
+ await ensureWasmInit();
3040
+ setReady(true);
3041
+ }, []);
3042
+ const hasBundleAnimations = (assetBundle.animations?.length ?? 0) > 0;
3043
+ (0, import_react2.useEffect)(() => {
3044
+ if (!hasBundleAnimations || animationModuleRef.current) {
3045
+ return;
3046
+ }
3047
+ let cancelled = false;
3048
+ const loading = (0, import_animation_module.loadAnimationModule)().then((module2) => {
3049
+ if (cancelled) {
3050
+ return;
3051
+ }
3052
+ animationModuleRef.current = module2;
3053
+ deviceSlot.setModules([module2]);
3054
+ deviceSlot.onDeviceStarted = (handle) => {
3055
+ animationHostRef.current?.replayInto(handle.device);
3056
+ };
3057
+ ensureAnimationHost();
3058
+ }).catch((err) => {
3059
+ pushError({
3060
+ message: "Failed to load the animation module",
3061
+ cause: err,
3062
+ phase: "engine",
3063
+ timestamp: performance.now()
3064
+ });
3065
+ });
3066
+ deviceSlot.waitForModules(loading);
3067
+ return () => {
3068
+ cancelled = true;
3069
+ };
3070
+ }, [deviceSlot, ensureAnimationHost, hasBundleAnimations, pushError]);
3071
+ const removeGraph = (0, import_react2.useCallback)(
3072
+ (id) => {
3073
+ graphSourcesRef.current = graphSourcesRef.current.filter(
3074
+ (s) => s.sourceId !== id && !s.sourceId.startsWith(`${id}#`)
3075
+ );
3076
+ recomposeDevice();
3077
+ },
3078
+ [recomposeDevice]
3079
+ );
3080
+ const registerGraph = (0, import_react2.useCallback)(
3081
+ (cfg) => {
3082
+ const id = cfg.id ?? `graph-${graphSourcesRef.current.length}`;
3083
+ graphSourcesRef.current = [
3084
+ ...graphSourcesRef.current.filter((s) => s.sourceId !== id),
3085
+ { sourceId: id, spec: cfg.spec ?? {} }
3086
+ ];
3087
+ recomposeDevice();
3088
+ return id;
3089
+ },
3090
+ [recomposeDevice]
3091
+ );
3092
+ const registerMergedGraph = (0, import_react2.useCallback)(
3093
+ (cfg) => {
3094
+ const id = cfg.id ?? `merged-${graphSourcesRef.current.length}`;
3095
+ const members = cfg.graphs.map((graph, index) => ({
3096
+ sourceId: `${id}#${graph.id ?? index}`,
3097
+ spec: graph.spec ?? {}
3098
+ }));
3099
+ graphSourcesRef.current = [
3100
+ ...graphSourcesRef.current.filter(
3101
+ (s) => s.sourceId !== id && !s.sourceId.startsWith(`${id}#`)
3102
+ ),
3103
+ ...members
3104
+ ];
3105
+ recomposeDevice();
3106
+ return id;
3107
+ },
3108
+ [recomposeDevice]
3109
+ );
3110
+ const registerAnimation = (0, import_react2.useCallback)(
3111
+ (cfg) => cfg.id ?? `animation-${registeredAnimationsRef.current.length}`,
3112
+ []
3113
+ );
3114
+ const removeAnimation = (0, import_react2.useCallback)((_id) => {
3115
+ }, []);
3116
+ const setAnimationsSourceRegistered = (0, import_react2.useCallback)(
3117
+ (registered) => {
3118
+ if (registered === animationsSourceRegisteredRef.current) {
3119
+ return;
3120
+ }
3121
+ animationsSourceRegisteredRef.current = registered;
3122
+ if (registered) {
3123
+ graphSourcesRef.current = [
3124
+ ...graphSourcesRef.current.filter(
3125
+ (s) => s.sourceId !== animationsGraphSource().sourceId
3126
+ ),
3127
+ animationsGraphSource()
3128
+ ];
3129
+ } else {
3130
+ graphSourcesRef.current = graphSourcesRef.current.filter(
3131
+ (s) => s.sourceId !== animationsGraphSource().sourceId
3132
+ );
3133
+ }
3134
+ recomposeDevice();
3135
+ },
3136
+ [recomposeDevice]
3137
+ );
3138
+ const listControllers = (0, import_react2.useCallback)(
3139
+ () => ({
3140
+ graphs: [...registeredGraphsRef.current],
3141
+ anims: [...registeredAnimationsRef.current]
3142
+ }),
3143
+ []
3144
+ );
3145
+ const deviceSetInput = (0, import_react2.useCallback)(
3146
+ (path, value, _shape) => {
3147
+ const handle = deviceSlot.current;
3148
+ if (handle) {
3149
+ handle.device.setValue(path, value);
3150
+ } else {
3151
+ pendingWritesRef.current.set(path, value);
3152
+ }
3153
+ },
3154
+ [deviceSlot]
3155
+ );
3156
+ const getPathSnapshot = (0, import_react2.useCallback)(
3157
+ (path) => {
3158
+ const handle = deviceSlot.current;
3159
+ if (!handle) {
3160
+ return pendingWritesRef.current.get(path);
3161
+ }
3162
+ return handle.device.readValues([path])[path] ?? void 0;
3163
+ },
3164
+ [deviceSlot]
3165
+ );
3166
+ const getStoreSnapshot = (0, import_react2.useCallback)(() => {
3167
+ const handle = deviceSlot.current;
3168
+ if (!handle) {
3169
+ return void 0;
3170
+ }
3171
+ return handle.device.snapshot();
3172
+ }, [deviceSlot]);
3173
+ const stepListenersRef = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
3174
+ const subscribeToStep = (0, import_react2.useCallback)((listener) => {
3175
+ const listeners = stepListenersRef.current;
3176
+ listeners.add(listener);
3177
+ return () => {
3178
+ listeners.delete(listener);
3179
+ };
3180
+ }, []);
3181
+ const stepRuntime = (0, import_react2.useCallback)(
3182
+ (dt) => {
3183
+ const handle = deviceSlot.current;
3184
+ if (!handle) {
3185
+ return;
3186
+ }
3187
+ if (!handle.device.running) {
3188
+ handle.device.step(dt * 1e3);
3189
+ }
3190
+ const changes = handle.device.drainChanges();
3191
+ applyEngineChangesRef.current(changes);
3192
+ if (storeChangeListenersRef.current.size > 0) {
3193
+ const paths = Object.keys(changes);
3194
+ if (paths.length > 0) {
3195
+ storeChangeListenersRef.current.forEach((listener) => {
3196
+ try {
3197
+ listener(changes);
3198
+ } catch (err) {
3199
+ console.error("[vizij-runtime] store-change listener error", err);
3200
+ }
3201
+ });
3202
+ }
3203
+ }
3204
+ stepListenersRef.current.forEach((listener) => {
3205
+ try {
3206
+ listener();
3207
+ } catch (err) {
3208
+ console.error("[vizij-runtime] step listener error", err);
3209
+ }
3210
+ });
3211
+ },
3212
+ [deviceSlot]
962
3213
  );
963
- const pushError = (0, import_react2.useCallback)(
964
- (error) => {
965
- errorsRef.current = [...errorsRef.current, error];
966
- reportStatus((prev) => ({
967
- ...prev,
968
- error,
969
- errors: errorsRef.current
970
- }));
971
- console.warn("[vizij-runtime]", error.message, error.cause);
3214
+ const storeChangeListenersRef = (0, import_react2.useRef)(
3215
+ /* @__PURE__ */ new Set()
3216
+ );
3217
+ const subscribeToStoreChanges = (0, import_react2.useCallback)(
3218
+ (listener) => {
3219
+ const listeners = storeChangeListenersRef.current;
3220
+ listeners.add(listener);
3221
+ return () => {
3222
+ listeners.delete(listener);
3223
+ };
972
3224
  },
973
- [reportStatus]
3225
+ []
974
3226
  );
975
- const resetErrors = (0, import_react2.useCallback)(() => {
976
- errorsRef.current = [];
977
- reportStatus((prev) => ({
978
- ...prev,
979
- error: null,
980
- errors: []
981
- }));
982
- }, [reportStatus]);
983
- (0, import_react2.useEffect)(() => {
984
- autostartRef.current = autostart;
985
- updateLoopMode();
986
- }, [autostart, updateLoopMode]);
987
3227
  const clearControllers = (0, import_react2.useCallback)(() => {
988
3228
  const existing = listControllers();
989
3229
  existing.graphs.forEach((id) => {
@@ -1012,10 +3252,12 @@ function VizijRuntimeProviderInner({
1012
3252
  });
1013
3253
  registeredGraphsRef.current = [];
1014
3254
  registeredAnimationsRef.current = [];
3255
+ programControllerIdsRef.current.clear();
1015
3256
  mergedGraphRef.current = null;
1016
3257
  outputPathsRef.current = /* @__PURE__ */ new Set();
1017
3258
  baseOutputPathsRef.current = /* @__PURE__ */ new Set();
1018
3259
  namespacedOutputPathsRef.current = /* @__PURE__ */ new Set();
3260
+ rigPoseControlInputIdsRef.current = /* @__PURE__ */ new Set();
1019
3261
  }, [listControllers, removeAnimation, removeGraph, pushError]);
1020
3262
  (0, import_react2.useEffect)(() => {
1021
3263
  namespaceRef.current = namespace;
@@ -1026,12 +3268,37 @@ function VizijRuntimeProviderInner({
1026
3268
  }));
1027
3269
  }, [namespace, faceId, reportStatus]);
1028
3270
  (0, import_react2.useEffect)(() => {
1029
- driveOrchestratorRef.current = driveOrchestrator;
1030
- }, [driveOrchestrator]);
1031
- const glbAsset = initialAssetBundle.glb;
1032
- const baseBundle = initialAssetBundle.bundle ?? null;
3271
+ driveRuntimeRef.current = driveRuntime;
3272
+ }, [driveRuntime]);
3273
+ (0, import_react2.useEffect)(() => {
3274
+ deviceSlot.onRunEnded = (error) => {
3275
+ pushError({
3276
+ message: "The device's run loop ended: stepping failed",
3277
+ cause: error,
3278
+ phase: "engine",
3279
+ timestamp: performance.now()
3280
+ });
3281
+ };
3282
+ if (driveRuntime) {
3283
+ deviceSlot.runPeriodMs = RUN_PERIOD_MS;
3284
+ deviceSlot.startRun();
3285
+ } else {
3286
+ deviceSlot.runPeriodMs = null;
3287
+ }
3288
+ }, [deviceSlot, driveRuntime, pushError]);
3289
+ const glbAsset = effectiveAssetBundle.glb;
3290
+ const baseBundle = effectiveAssetBundle.bundle ?? null;
1033
3291
  (0, import_react2.useEffect)(() => {
1034
3292
  let cancelled = false;
3293
+ const plan = pendingPlanRef.current;
3294
+ if (plan && !plan.reloadAssets && status.rootId !== null) {
3295
+ reportStatus(
3296
+ (prev) => prev.loading ? { ...prev, loading: false } : prev
3297
+ );
3298
+ return () => {
3299
+ cancelled = true;
3300
+ };
3301
+ }
1035
3302
  resetErrors();
1036
3303
  reportStatus((prev) => ({
1037
3304
  ...prev,
@@ -1083,6 +3350,12 @@ function VizijRuntimeProviderInner({
1083
3350
  setExtractedAnimations(convertExtractedAnimations(gltfAnimations));
1084
3351
  const rootId = findRootId(world);
1085
3352
  store.getState().addWorldElements(world, animatables, true);
3353
+ if (pendingPlanRef.current?.reloadAssets) {
3354
+ pendingPlanRef.current = {
3355
+ ...pendingPlanRef.current,
3356
+ reloadAssets: false
3357
+ };
3358
+ }
1086
3359
  reportStatus((prev) => ({
1087
3360
  ...prev,
1088
3361
  loading: false,
@@ -1120,22 +3393,32 @@ function VizijRuntimeProviderInner({
1120
3393
  reportStatus,
1121
3394
  resetErrors,
1122
3395
  setExtractedBundle,
1123
- setExtractedAnimations
3396
+ setExtractedAnimations,
3397
+ status.rootId
1124
3398
  ]);
1125
3399
  (0, import_react2.useEffect)(() => {
1126
3400
  if (!ready && autoCreate) {
1127
- createOrchestrator(createOptions).catch((err) => {
3401
+ initEngine().catch((err) => {
1128
3402
  pushError({
1129
- message: "Failed to create orchestrator runtime",
3403
+ message: "Failed to initialize the engine",
1130
3404
  cause: err,
1131
- phase: "orchestrator",
3405
+ phase: "engine",
1132
3406
  timestamp: performance.now()
1133
3407
  });
1134
3408
  });
1135
3409
  }
1136
- }, [ready, autoCreate, createOptions, createOrchestrator, pushError]);
3410
+ }, [ready, autoCreate, initEngine, pushError]);
1137
3411
  const registerControllers = (0, import_react2.useCallback)(async () => {
1138
3412
  clearControllers();
3413
+ if (isRuntimeDebugEnabled()) {
3414
+ console.log("[vizij-runtime] registerControllers", {
3415
+ hasRig: Boolean(assetBundle.rig),
3416
+ hasPose: Boolean(assetBundle.pose?.graph),
3417
+ animationCount: assetBundle.animations?.length ?? 0,
3418
+ animationIds: (assetBundle.animations ?? []).map((anim) => anim.id),
3419
+ namespace
3420
+ });
3421
+ }
1139
3422
  const baseOutputPaths = /* @__PURE__ */ new Set();
1140
3423
  const namespacedOutputPaths = /* @__PURE__ */ new Set();
1141
3424
  const recordOutputs = (paths) => {
@@ -1147,39 +3430,60 @@ function VizijRuntimeProviderInner({
1147
3430
  namespacedOutputPaths.add(namespaceTypedPath(trimmed, namespace));
1148
3431
  });
1149
3432
  };
3433
+ const graphConfigs = [];
3434
+ rigInputMapRef.current = {};
3435
+ rigPoseControlInputIdsRef.current = /* @__PURE__ */ new Set();
3436
+ poseControlBridgeValuesRef.current.clear();
1150
3437
  const rigAsset = assetBundle.rig;
1151
- if (!rigAsset) {
1152
- pushError({
1153
- message: "Asset bundle is missing a rig graph.",
1154
- phase: "registration",
1155
- timestamp: performance.now()
1156
- });
1157
- return;
1158
- }
1159
- const rigSpec = resolveGraphSpec(rigAsset, `${rigAsset.id ?? "rig"} graph`);
1160
- if (!rigSpec) {
1161
- pushError({
1162
- message: "Rig graph is missing a usable spec or IR payload.",
1163
- phase: "registration",
1164
- timestamp: performance.now()
1165
- });
1166
- return;
1167
- }
1168
- const rigOutputs = collectOutputPaths(rigSpec);
1169
- const rigInputs = collectInputPaths(rigSpec);
1170
- rigInputMapRef.current = collectInputPathMap(rigSpec);
1171
- recordOutputs(rigOutputs);
1172
- const rigSubs = rigAsset.subscriptions ?? {
1173
- inputs: rigInputs,
1174
- outputs: rigOutputs
1175
- };
1176
- const graphConfigs = [
1177
- {
1178
- id: namespaceControllerId(rigAsset.id, namespace, "graph"),
1179
- spec: namespaceGraphSpec(rigSpec, namespace),
1180
- subs: namespaceSubscriptions(rigSubs, namespace)
3438
+ if (rigAsset) {
3439
+ const rigSpec = resolveGraphSpec(
3440
+ rigAsset,
3441
+ `${rigAsset.id ?? "rig"} graph`
3442
+ );
3443
+ if (!rigSpec) {
3444
+ pushError({
3445
+ message: "Rig graph is missing a usable spec or IR payload.",
3446
+ phase: "registration",
3447
+ timestamp: performance.now()
3448
+ });
3449
+ } else {
3450
+ const rigOutputs = collectOutputPaths(rigSpec);
3451
+ const rigInputs = collectInputPaths(rigSpec);
3452
+ const rigPoseControlInputIds = /* @__PURE__ */ new Set();
3453
+ rigInputs.forEach((path) => {
3454
+ const poseControlMatch = /^rig\/[^/]+\/pose\/control\/(.+)$/.exec(
3455
+ path.trim()
3456
+ );
3457
+ const inputId = (poseControlMatch?.[1] ?? "").trim();
3458
+ if (inputId.length > 0) {
3459
+ rigPoseControlInputIds.add(inputId);
3460
+ }
3461
+ });
3462
+ rigInputMapRef.current = collectInputPathMap(rigSpec);
3463
+ rigPoseControlInputIdsRef.current = rigPoseControlInputIds;
3464
+ if (isRuntimeDebugEnabled()) {
3465
+ const blinkKeys = Object.keys(rigInputMapRef.current).filter(
3466
+ (key) => key.toLowerCase().includes("blink")
3467
+ );
3468
+ const blinkMappings = blinkKeys.slice(0, 20).map((key) => `${key} => ${rigInputMapRef.current[key] ?? "?"}`);
3469
+ console.log("[vizij-runtime] rig input map sample", {
3470
+ blink: rigInputMapRef.current["blink"] ?? null,
3471
+ blinkKeys: blinkKeys.slice(0, 12),
3472
+ blinkMappings: blinkMappings.join(" | ")
3473
+ });
3474
+ }
3475
+ recordOutputs(rigOutputs);
3476
+ const rigSubs = rigAsset.subscriptions ?? {
3477
+ inputs: rigInputs,
3478
+ outputs: rigOutputs
3479
+ };
3480
+ graphConfigs.push({
3481
+ id: namespaceControllerId(rigAsset.id, namespace, "graph"),
3482
+ spec: stripNulls(namespaceGraphSpec(rigSpec, namespace)),
3483
+ subs: namespaceSubscriptions(rigSubs, namespace)
3484
+ });
1181
3485
  }
1182
- ];
3486
+ }
1183
3487
  const poseGraphAsset = assetBundle.pose?.graph;
1184
3488
  if (poseGraphAsset) {
1185
3489
  const poseSpec = resolveGraphSpec(
@@ -1196,7 +3500,7 @@ function VizijRuntimeProviderInner({
1196
3500
  };
1197
3501
  graphConfigs.push({
1198
3502
  id: namespaceControllerId(poseGraphAsset.id, namespace, "graph"),
1199
- spec: namespaceGraphSpec(poseSpec, namespace),
3503
+ spec: stripNulls(namespaceGraphSpec(poseSpec, namespace)),
1200
3504
  subs: namespaceSubscriptions(poseSubs, namespace)
1201
3505
  });
1202
3506
  } else {
@@ -1205,6 +3509,31 @@ function VizijRuntimeProviderInner({
1205
3509
  );
1206
3510
  }
1207
3511
  }
3512
+ for (const animation of assetBundle.animations ?? []) {
3513
+ const bridgeOutputs = collectAnimationClipOutputPaths(
3514
+ animation.clip,
3515
+ faceId ?? void 0,
3516
+ rigInputMapRef.current
3517
+ );
3518
+ if (isRuntimeDebugEnabled()) {
3519
+ console.log("[vizij-runtime] animation output routing", {
3520
+ animationId: animation.id,
3521
+ bridgeOutputs,
3522
+ bridgeOutputsText: bridgeOutputs.join(" | ")
3523
+ });
3524
+ }
3525
+ recordOutputs(bridgeOutputs);
3526
+ }
3527
+ for (const program of resolvedProgramAssets) {
3528
+ const programSpec = resolveGraphSpec(
3529
+ program.graph,
3530
+ `${program.id ?? "program"} graph (outputs)`
3531
+ );
3532
+ if (!programSpec) {
3533
+ continue;
3534
+ }
3535
+ recordOutputs(collectOutputPaths(programSpec));
3536
+ }
1208
3537
  outputPathsRef.current = namespacedOutputPaths;
1209
3538
  baseOutputPathsRef.current = baseOutputPaths;
1210
3539
  namespacedOutputPathsRef.current = namespacedOutputPaths;
@@ -1237,20 +3566,24 @@ function VizijRuntimeProviderInner({
1237
3566
  });
1238
3567
  }
1239
3568
  registeredGraphsRef.current = graphIds;
3569
+ if (isRuntimeDebugEnabled()) {
3570
+ console.log("[vizij-runtime] registered graph ids", graphIds);
3571
+ }
1240
3572
  const animationIds = [];
3573
+ const hostClips = [];
1241
3574
  for (const anim of assetBundle.animations ?? []) {
1242
3575
  try {
1243
- const controllerId = namespaceControllerId(anim.id, namespace, "animation") ?? anim.id;
1244
- const config = {
1245
- id: controllerId,
1246
- setup: {
1247
- animation: anim.clip,
1248
- ...anim.setup ?? {}
1249
- }
1250
- };
1251
- const id = registerAnimation(config);
1252
- animationIds.push(id);
3576
+ const stored = anim.setup?.animation ?? toStoredAnimationClip(anim.id, anim.clip);
3577
+ hostClips.push({ id: anim.id, stored });
3578
+ registerAnimation({ id: anim.id });
3579
+ animationIds.push(anim.id);
1253
3580
  } catch (err) {
3581
+ if (isRuntimeDebugEnabled()) {
3582
+ console.warn("[vizij-runtime] failed animation registration", {
3583
+ animationId: anim.id,
3584
+ error: err instanceof Error ? err.message : String(err)
3585
+ });
3586
+ }
1254
3587
  pushError({
1255
3588
  message: `Failed to register animation ${anim.id}`,
1256
3589
  cause: err,
@@ -1259,6 +3592,7 @@ function VizijRuntimeProviderInner({
1259
3592
  });
1260
3593
  }
1261
3594
  }
3595
+ ensureAnimationHost()?.setClips(hostClips);
1262
3596
  registeredAnimationsRef.current = animationIds;
1263
3597
  if (assetBundle.initialInputs) {
1264
3598
  Object.entries(assetBundle.initialInputs).forEach(([path, value]) => {
@@ -1275,6 +3609,13 @@ function VizijRuntimeProviderInner({
1275
3609
  });
1276
3610
  }
1277
3611
  const controllers = listControllers();
3612
+ if (isRuntimeDebugEnabled()) {
3613
+ console.log("[vizij-runtime] controllers after register", {
3614
+ controllers,
3615
+ graphIds,
3616
+ animationIds
3617
+ });
3618
+ }
1278
3619
  reportStatus((prev) => ({
1279
3620
  ...prev,
1280
3621
  ready: true,
@@ -1285,6 +3626,7 @@ function VizijRuntimeProviderInner({
1285
3626
  }, [
1286
3627
  assetBundle,
1287
3628
  clearControllers,
3629
+ ensureAnimationHost,
1288
3630
  listControllers,
1289
3631
  mergeStrategy,
1290
3632
  namespace,
@@ -1294,12 +3636,18 @@ function VizijRuntimeProviderInner({
1294
3636
  registerGraph,
1295
3637
  registerMergedGraph,
1296
3638
  reportStatus,
3639
+ resolvedProgramAssets,
1297
3640
  setInput
1298
3641
  ]);
1299
3642
  (0, import_react2.useEffect)(() => {
1300
3643
  if (!ready || status.loading) {
1301
3644
  return;
1302
3645
  }
3646
+ const plan = pendingPlanRef.current;
3647
+ const hasRegistered = registeredGraphsRef.current.length > 0 || registeredAnimationsRef.current.length > 0 || programControllerIdsRef.current.size > 0;
3648
+ if (plan && !plan.reregisterGraphs && hasRegistered) {
3649
+ return;
3650
+ }
1303
3651
  registerControllers().catch((err) => {
1304
3652
  pushError({
1305
3653
  message: "Failed to register controllers",
@@ -1308,37 +3656,79 @@ function VizijRuntimeProviderInner({
1308
3656
  timestamp: performance.now()
1309
3657
  });
1310
3658
  });
1311
- }, [ready, status.loading, registerControllers, pushError]);
3659
+ }, [ready, status.loading, graphUpdateToken, registerControllers, pushError]);
1312
3660
  (0, import_react2.useEffect)(() => {
1313
- if (!frame) {
1314
- return;
1315
- }
1316
- const writes = frame.merged_writes ?? [];
1317
- if (!writes.length) {
1318
- return;
1319
- }
1320
- const setWorldValues = store.getState().setValues;
1321
- const namespaceValue = status.namespace;
1322
- const batched = [];
1323
- const namespacedOutputs = namespacedOutputPathsRef.current;
1324
- const baseOutputs = baseOutputPathsRef.current;
1325
- writes.forEach((write) => {
1326
- const path = normalisePath(write.path);
1327
- if (!namespacedOutputs.has(path)) {
3661
+ applyEngineChangesRef.current = (changes) => {
3662
+ const entries = Object.entries(changes);
3663
+ if (!entries.length) {
1328
3664
  return;
1329
3665
  }
1330
- const raw = valueJSONToRaw(write.value);
1331
- if (raw === void 0) {
1332
- return;
3666
+ const setWorldValues = store.getState().setValues;
3667
+ const namespaceValue = status.namespace;
3668
+ const currentValues = store.getState().values;
3669
+ const rigInputPathMap = rigInputMapRef.current;
3670
+ const rigPoseControlInputIds = rigPoseControlInputIdsRef.current;
3671
+ const batched = [];
3672
+ const namespacedOutputs = namespacedOutputPathsRef.current;
3673
+ const baseOutputs = baseOutputPathsRef.current;
3674
+ entries.forEach(([changedPath, changedValue]) => {
3675
+ if (changedValue === null || isBuiltInPath(changedPath)) {
3676
+ return;
3677
+ }
3678
+ const path = normalisePath(changedPath);
3679
+ const basePath = stripNamespace(path, namespaceValue);
3680
+ const isTrackedOutput = namespacedOutputs.has(path) || baseOutputs.has(basePath);
3681
+ if (!isTrackedOutput) {
3682
+ return;
3683
+ }
3684
+ const raw = valueJSONToRaw(changedValue);
3685
+ if (raw === void 0) {
3686
+ return;
3687
+ }
3688
+ const poseControlMatch = /^rig\/[^/]+\/pose\/control\/(.+)$/.exec(
3689
+ basePath
3690
+ );
3691
+ if (poseControlMatch && typeof raw === "number" && Number.isFinite(raw)) {
3692
+ const inputId = (poseControlMatch[1] ?? "").trim();
3693
+ const hasNativePoseControlInput = inputId.length > 0 && rigPoseControlInputIds.has(inputId);
3694
+ const mappedInputPath = inputId.length === 0 ? void 0 : resolvePoseControlInputPath({
3695
+ inputId,
3696
+ basePath,
3697
+ rigInputPathMap,
3698
+ hasNativePoseControlInput
3699
+ });
3700
+ if (mappedInputPath) {
3701
+ const bridgeKey = `${namespaceValue}:${mappedInputPath}`;
3702
+ const previousValue = poseControlBridgeValuesRef.current.get(bridgeKey);
3703
+ if (previousValue === void 0 || Math.abs(previousValue - raw) > POSE_CONTROL_BRIDGE_EPSILON) {
3704
+ poseControlBridgeValuesRef.current.set(bridgeKey, raw);
3705
+ setInput(mappedInputPath, { float: raw });
3706
+ }
3707
+ }
3708
+ }
3709
+ const targetPath = baseOutputs.has(basePath) ? basePath : path;
3710
+ const currentValue = currentValues.get(
3711
+ (0, import_utils.getLookup)(namespaceValue, targetPath)
3712
+ );
3713
+ const nextWrite = transformOutputWrite ? transformOutputWrite({
3714
+ id: targetPath,
3715
+ namespace: namespaceValue,
3716
+ value: raw,
3717
+ currentValue
3718
+ }) : {
3719
+ id: targetPath,
3720
+ namespace: namespaceValue,
3721
+ value: raw
3722
+ };
3723
+ if (nextWrite) {
3724
+ batched.push(nextWrite);
3725
+ }
3726
+ });
3727
+ if (batched.length > 0) {
3728
+ setWorldValues(batched);
1333
3729
  }
1334
- const basePath = stripNamespace(path, namespaceValue);
1335
- const targetPath = baseOutputs.has(basePath) ? basePath : path;
1336
- batched.push({ id: targetPath, namespace: namespaceValue, value: raw });
1337
- });
1338
- if (batched.length > 0) {
1339
- setWorldValues(batched);
1340
- }
1341
- }, [frame, status.namespace, store]);
3730
+ };
3731
+ }, [status.namespace, store, transformOutputWrite]);
1342
3732
  const stagePoseNeutral = (0, import_react2.useCallback)(
1343
3733
  (force = false) => {
1344
3734
  const neutral = assetBundle.pose?.config?.neutralInputs ?? {};
@@ -1404,165 +3794,583 @@ function VizijRuntimeProviderInner({
1404
3794
  });
1405
3795
  toDelete.forEach((key) => map.delete(key));
1406
3796
  },
1407
- [setInput]
3797
+ [setInput]
3798
+ );
3799
+ const resolveClipById = (0, import_react2.useCallback)(
3800
+ (id) => {
3801
+ return assetBundle.animations?.find((anim) => anim.id === id);
3802
+ },
3803
+ [assetBundle.animations]
3804
+ );
3805
+ const resolveClipPromise = (0, import_react2.useCallback)((state) => {
3806
+ state.resolve?.();
3807
+ state.resolve = null;
3808
+ state.completion = null;
3809
+ }, []);
3810
+ const ensureClipPromise = (0, import_react2.useCallback)((state) => {
3811
+ if (state.completion) {
3812
+ return state.completion;
3813
+ }
3814
+ const completion = new Promise((resolve) => {
3815
+ state.resolve = resolve;
3816
+ });
3817
+ state.completion = completion;
3818
+ return completion;
3819
+ }, []);
3820
+ const ensureClipState = (0, import_react2.useCallback)(
3821
+ (id) => {
3822
+ const clip = resolveClipById(id);
3823
+ if (!clip) {
3824
+ return null;
3825
+ }
3826
+ const duration = resolveClipDurationSeconds(
3827
+ clip.clip
3828
+ );
3829
+ const existing = clipPlaybackRef.current.get(id);
3830
+ if (existing) {
3831
+ existing.duration = duration;
3832
+ return { clip, state: existing };
3833
+ }
3834
+ const state = {
3835
+ id,
3836
+ duration,
3837
+ speed: 1,
3838
+ weight: 1,
3839
+ loop: true,
3840
+ playing: false,
3841
+ resolve: null,
3842
+ completion: null
3843
+ };
3844
+ clipPlaybackRef.current.set(id, state);
3845
+ return { clip, state };
3846
+ },
3847
+ [resolveClipById]
3848
+ );
3849
+ const animateValue = (0, import_react2.useCallback)(
3850
+ (path, target, options) => {
3851
+ const targetValue = (0, import_value_json2.valueAsNumber)(target);
3852
+ const basePath = stripNamespace(
3853
+ normalisePath(path),
3854
+ namespaceRef.current
3855
+ );
3856
+ const poseValues = useLegacyPoseWeightFallback && targetValue != null ? poseWeightFallbackMap.get(basePath) : void 0;
3857
+ if (poseValues && targetValue != null) {
3858
+ const poseFaceId = assetBundle.pose?.config?.faceId ?? faceId ?? "face";
3859
+ const rigMap = rigInputMapRef.current;
3860
+ return Promise.all(
3861
+ Object.entries(poseValues).flatMap(([inputId, poseValue]) => {
3862
+ if (!Number.isFinite(poseValue)) {
3863
+ return [];
3864
+ }
3865
+ const controlPath = resolvePoseControlInputPath({
3866
+ inputId,
3867
+ basePath: buildRigInputPath(
3868
+ poseFaceId,
3869
+ `/pose/control/${inputId}`
3870
+ ),
3871
+ rigInputPathMap: rigMap,
3872
+ hasNativePoseControlInput: true
3873
+ }) ?? buildRigInputPath(poseFaceId, `/pose/control/${inputId}`);
3874
+ return [
3875
+ animateValue(
3876
+ controlPath,
3877
+ { float: Number(poseValue) * targetValue },
3878
+ options
3879
+ )
3880
+ ];
3881
+ })
3882
+ ).then(() => void 0);
3883
+ }
3884
+ const easing = resolveEasing(options?.easing);
3885
+ const duration = Math.max(0, options?.duration ?? DEFAULT_DURATION);
3886
+ cancelAnimation(path);
3887
+ const namespacedPath = namespaceTypedPath(path, namespaceRef.current);
3888
+ const current = getPathSnapshot(namespacedPath);
3889
+ const fromValue = (0, import_value_json2.valueAsNumber)(current);
3890
+ const toValue = (0, import_value_json2.valueAsNumber)(target);
3891
+ if (fromValue == null || toValue == null || duration === 0) {
3892
+ setInput(path, target);
3893
+ return Promise.resolve();
3894
+ }
3895
+ return new Promise((resolve) => {
3896
+ animationTweensRef.current.set(path, {
3897
+ // Keep the raw path here so tween updates go through setInput() once
3898
+ // and pick up the active namespace exactly once.
3899
+ path,
3900
+ from: fromValue,
3901
+ to: toValue,
3902
+ duration,
3903
+ elapsed: 0,
3904
+ easing,
3905
+ resolve
3906
+ });
3907
+ markActivity();
3908
+ });
3909
+ },
3910
+ [
3911
+ assetBundle.pose?.config?.faceId,
3912
+ cancelAnimation,
3913
+ faceId,
3914
+ getPathSnapshot,
3915
+ markActivity,
3916
+ poseWeightFallbackMap,
3917
+ setInput,
3918
+ useLegacyPoseWeightFallback
3919
+ ]
3920
+ );
3921
+ const warnAnimationGap = (0, import_react2.useCallback)((key, message) => {
3922
+ if (animationGapWarnedRef.current.has(key)) {
3923
+ return;
3924
+ }
3925
+ animationGapWarnedRef.current.add(key);
3926
+ console.warn(`[vizij-runtime] ${message}`);
3927
+ }, []);
3928
+ const syncAnimationsSource = (0, import_react2.useCallback)(() => {
3929
+ const host = animationHostRef.current;
3930
+ setAnimationsSourceRegistered(host ? host.hasPlaying() : false);
3931
+ updateLoopMode();
3932
+ }, [setAnimationsSourceRegistered, updateLoopMode]);
3933
+ const readPlayerStates = (0, import_react2.useCallback)(() => {
3934
+ const device = deviceSlot.current?.device;
3935
+ if (!device) {
3936
+ return [];
3937
+ }
3938
+ const raw = device.readValues([ANIMATION_PLAYERS_PATH])[ANIMATION_PLAYERS_PATH];
3939
+ return decodePlayerStates(raw);
3940
+ }, [deviceSlot]);
3941
+ const settleFinishedClips = (0, import_react2.useCallback)(() => {
3942
+ let pending = false;
3943
+ clipPlaybackRef.current.forEach((state) => {
3944
+ if (state.completion && !state.loop) {
3945
+ pending = true;
3946
+ }
3947
+ });
3948
+ const host = animationHostRef.current;
3949
+ if (!pending || !host) {
3950
+ return;
3951
+ }
3952
+ const states = readPlayerStates();
3953
+ if (states.length === 0) {
3954
+ return;
3955
+ }
3956
+ clipPlaybackRef.current.forEach((state, id) => {
3957
+ if (!state.completion || state.loop) {
3958
+ return;
3959
+ }
3960
+ const playerId = host.playerIdOf(id);
3961
+ if (playerId === null) {
3962
+ return;
3963
+ }
3964
+ const feedback = states.find((entry) => entry.player === playerId);
3965
+ if (!feedback) {
3966
+ return;
3967
+ }
3968
+ const duration = feedback.duration > 0 ? feedback.duration : state.duration;
3969
+ const atEnd = duration > 0 && feedback.time >= duration - 1e-3;
3970
+ if (feedback.state === "stopped" || atEnd) {
3971
+ state.playing = false;
3972
+ resolveClipPromise(state);
3973
+ host.pause(id);
3974
+ syncAnimationsSource();
3975
+ }
3976
+ });
3977
+ }, [readPlayerStates, resolveClipPromise, syncAnimationsSource]);
3978
+ const playAnimation = (0, import_react2.useCallback)(
3979
+ (id, options) => {
3980
+ const ensured = ensureClipState(id);
3981
+ if (!ensured) {
3982
+ return Promise.reject(
3983
+ new Error(`Animation ${id} is not part of the current asset bundle.`)
3984
+ );
3985
+ }
3986
+ const { state } = ensured;
3987
+ const host = ensureAnimationHost();
3988
+ state.speed = options?.speed ?? state.speed;
3989
+ state.weight = options?.weight ?? state.weight;
3990
+ state.playing = true;
3991
+ const completion = ensureClipPromise(state);
3992
+ clipPlaybackRef.current.set(id, state);
3993
+ if (host) {
3994
+ if (options?.reset === true) {
3995
+ host.seek(id, 0);
3996
+ }
3997
+ host.setSpeed(id, state.speed);
3998
+ host.setWeight(id, state.weight);
3999
+ host.setLoop(id, state.loop ? "loop" : "once");
4000
+ void host.play(id).catch((err) => {
4001
+ pushError({
4002
+ message: `Failed to start animation ${id} on the device`,
4003
+ cause: err,
4004
+ phase: "animation",
4005
+ timestamp: performance.now()
4006
+ });
4007
+ });
4008
+ } else {
4009
+ warnAnimationGap(
4010
+ "module-not-ready",
4011
+ "playAnimation called before the animation module finished loading; playback starts once it is ready."
4012
+ );
4013
+ }
4014
+ syncAnimationsSource();
4015
+ markActivity();
4016
+ return completion;
4017
+ },
4018
+ [
4019
+ ensureAnimationHost,
4020
+ ensureClipPromise,
4021
+ ensureClipState,
4022
+ markActivity,
4023
+ pushError,
4024
+ syncAnimationsSource,
4025
+ warnAnimationGap
4026
+ ]
4027
+ );
4028
+ const pauseAnimation = (0, import_react2.useCallback)(
4029
+ (id) => {
4030
+ const state = clipPlaybackRef.current.get(id);
4031
+ if (!state || !state.playing) {
4032
+ return;
4033
+ }
4034
+ state.playing = false;
4035
+ animationHostRef.current?.pause(id);
4036
+ syncAnimationsSource();
4037
+ },
4038
+ [syncAnimationsSource]
4039
+ );
4040
+ const seekAnimation = (0, import_react2.useCallback)((id, timeSeconds) => {
4041
+ if (!clipPlaybackRef.current.has(id)) {
4042
+ return;
4043
+ }
4044
+ animationHostRef.current?.seek(id, Math.max(0, timeSeconds));
4045
+ }, []);
4046
+ const setAnimationLoop = (0, import_react2.useCallback)(
4047
+ (id, enabled) => {
4048
+ const ensured = ensureClipState(id);
4049
+ if (!ensured) {
4050
+ return;
4051
+ }
4052
+ ensured.state.loop = Boolean(enabled);
4053
+ animationHostRef.current?.setLoop(id, enabled ? "loop" : "once");
4054
+ updateLoopMode();
4055
+ },
4056
+ [ensureClipState, updateLoopMode]
4057
+ );
4058
+ const getAnimationState = (0, import_react2.useCallback)(
4059
+ (id) => {
4060
+ const state = clipPlaybackRef.current.get(id);
4061
+ if (!state) {
4062
+ return null;
4063
+ }
4064
+ const playerId = animationHostRef.current?.playerIdOf(id) ?? null;
4065
+ const feedback = playerId !== null ? readPlayerStates().find((entry) => entry.player === playerId) : void 0;
4066
+ return {
4067
+ time: feedback?.time ?? 0,
4068
+ duration: feedback && feedback.duration > 0 ? feedback.duration : state.duration,
4069
+ playing: feedback ? feedback.state === "playing" : state.playing,
4070
+ loop: state.loop,
4071
+ speed: feedback?.speed ?? state.speed
4072
+ };
4073
+ },
4074
+ [readPlayerStates]
4075
+ );
4076
+ const stopAnimation = (0, import_react2.useCallback)(
4077
+ (id, options) => {
4078
+ const state = clipPlaybackRef.current.get(id);
4079
+ if (state) {
4080
+ clipPlaybackRef.current.delete(id);
4081
+ state.playing = false;
4082
+ resolveClipPromise(state);
4083
+ }
4084
+ const host = animationHostRef.current;
4085
+ if (options?.clearOutputs === false) {
4086
+ host?.pause(id);
4087
+ syncAnimationsSource();
4088
+ } else {
4089
+ host?.stop(id);
4090
+ const unsubscribe = subscribeToStep(() => {
4091
+ unsubscribe();
4092
+ syncAnimationsSource();
4093
+ });
4094
+ }
4095
+ },
4096
+ [resolveClipPromise, subscribeToStep, syncAnimationsSource]
4097
+ );
4098
+ const refreshControllerStatus = (0, import_react2.useCallback)(() => {
4099
+ const controllers = listControllers();
4100
+ reportStatus((prev) => ({
4101
+ ...prev,
4102
+ controllers,
4103
+ outputPaths: Array.from(outputPathsRef.current)
4104
+ }));
4105
+ onRegisterControllers?.(controllers);
4106
+ }, [listControllers, onRegisterControllers, reportStatus]);
4107
+ const resolveProgramById = (0, import_react2.useCallback)(
4108
+ (id) => {
4109
+ return resolvedProgramAssets.find((program) => program.id === id);
4110
+ },
4111
+ [resolvedProgramAssets]
1408
4112
  );
1409
- const sampleTrack = (0, import_react2.useCallback)(
1410
- (track, time) => {
1411
- const keyframes = Array.isArray(track.keyframes) ? track.keyframes : [];
1412
- if (!keyframes.length) {
1413
- return 0;
1414
- }
1415
- const first = keyframes[0];
1416
- if (!first) {
1417
- return 0;
1418
- }
1419
- if (time <= Number(first.time ?? 0)) {
1420
- return Number(first.value ?? 0);
1421
- }
1422
- for (let i = 0; i < keyframes.length - 1; i += 1) {
1423
- const current = keyframes[i] ?? {};
1424
- const next = keyframes[i + 1] ?? {};
1425
- const start = Number(current.time ?? 0);
1426
- const end = Number(next.time ?? start);
1427
- if (time >= start && time <= end) {
1428
- const range = end - start || 1;
1429
- const factor = (time - start) / range;
1430
- const currentValue = Number(current.value ?? 0);
1431
- const nextValue = Number(next.value ?? currentValue);
1432
- return currentValue + (nextValue - currentValue) * factor;
1433
- }
4113
+ const buildProgramRegistrationConfig = (0, import_react2.useCallback)(
4114
+ (program) => {
4115
+ const graphSpec = resolveGraphSpec(
4116
+ program.graph,
4117
+ `${program.id ?? "program"} graph`
4118
+ );
4119
+ if (!graphSpec) {
4120
+ return null;
4121
+ }
4122
+ const outputs = collectOutputPaths(graphSpec);
4123
+ const inputs = collectInputPaths(graphSpec);
4124
+ const subs = program.graph.subscriptions ?? {
4125
+ inputs,
4126
+ outputs
4127
+ };
4128
+ return {
4129
+ id: namespaceControllerId(program.id, namespace, "graph"),
4130
+ spec: stripNulls(namespaceGraphSpec(graphSpec, namespace)),
4131
+ subs: namespaceSubscriptions(subs, namespace)
4132
+ };
4133
+ },
4134
+ [namespace]
4135
+ );
4136
+ const deriveProgramResetValues = (0, import_react2.useCallback)(
4137
+ (program) => {
4138
+ if (program.resetValues) {
4139
+ return Object.entries(program.resetValues).filter(([, value]) => Number.isFinite(value)).map(([path, value]) => ({ path, value }));
4140
+ }
4141
+ const graphSpec = resolveGraphSpec(
4142
+ program.graph,
4143
+ `${program.id ?? "program"} graph (reset)`
4144
+ );
4145
+ if (!graphSpec) {
4146
+ return [];
1434
4147
  }
1435
- const last = keyframes[keyframes.length - 1];
1436
- return Number(last?.value ?? 0);
4148
+ return collectOutputPaths(graphSpec).filter((path) => path.trim().length > 0).map((path) => {
4149
+ const defaultValue = inputConstraintsRef.current[path]?.defaultValue ?? 0;
4150
+ return {
4151
+ path,
4152
+ value: Number.isFinite(defaultValue) && defaultValue != null ? defaultValue : 0
4153
+ };
4154
+ });
1437
4155
  },
1438
4156
  []
1439
4157
  );
1440
- const advanceClipPlayback = (0, import_react2.useCallback)(
1441
- (dt) => {
1442
- if (clipPlaybackRef.current.size === 0) {
4158
+ const syncProgramPlaybackControllers = (0, import_react2.useCallback)(() => {
4159
+ if (!ready) {
4160
+ return;
4161
+ }
4162
+ const availableProgramIds = new Set(
4163
+ resolvedProgramAssets.map((program) => program.id)
4164
+ );
4165
+ Array.from(programPlaybackRef.current.keys()).forEach((id) => {
4166
+ if (availableProgramIds.has(id)) {
1443
4167
  return;
1444
4168
  }
1445
- const map = clipPlaybackRef.current;
1446
- const toDelete = [];
1447
- map.forEach((state, key) => {
1448
- state.time += dt * state.speed;
1449
- const clip = assetBundle.animations?.find(
1450
- (anim) => anim.id === state.id
1451
- );
1452
- if (!clip) {
1453
- toDelete.push(key);
1454
- state.resolve();
4169
+ programPlaybackRef.current.delete(id);
4170
+ const controllerId = programControllerIdsRef.current.get(id);
4171
+ if (controllerId) {
4172
+ try {
4173
+ removeGraph(controllerId);
4174
+ } catch (err) {
4175
+ pushError({
4176
+ message: `Failed to remove program ${id}`,
4177
+ cause: err,
4178
+ phase: "registration",
4179
+ timestamp: performance.now()
4180
+ });
4181
+ }
4182
+ programControllerIdsRef.current.delete(id);
4183
+ }
4184
+ });
4185
+ programPlaybackRef.current.forEach((state, id) => {
4186
+ const program = resolveProgramById(id);
4187
+ const controllerId = programControllerIdsRef.current.get(id);
4188
+ if (!program) {
4189
+ return;
4190
+ }
4191
+ if (state.state !== "playing") {
4192
+ if (!controllerId) {
1455
4193
  return;
1456
4194
  }
1457
- const clipData = clip.clip;
1458
- const duration = Number(clipData?.duration ?? state.duration);
1459
- state.duration = Number.isFinite(duration) && duration > 0 ? duration : state.duration;
1460
- if (state.time >= state.duration) {
1461
- toDelete.push(key);
1462
- state.time = state.duration;
4195
+ try {
4196
+ removeGraph(controllerId);
4197
+ } catch (err) {
4198
+ pushError({
4199
+ message: `Failed to pause program ${id}`,
4200
+ cause: err,
4201
+ phase: "registration",
4202
+ timestamp: performance.now()
4203
+ });
1463
4204
  }
1464
- const tracks = Array.isArray(clipData?.tracks) ? clipData.tracks : [];
1465
- tracks.forEach((track) => {
1466
- const value = sampleTrack(track, state.time) * state.weight;
1467
- const path = `animation/${clip.id}/${track.channel}`;
1468
- setInput(path, { float: value });
4205
+ programControllerIdsRef.current.delete(id);
4206
+ return;
4207
+ }
4208
+ if (controllerId) {
4209
+ return;
4210
+ }
4211
+ const config = buildProgramRegistrationConfig(program);
4212
+ if (!config) {
4213
+ pushError({
4214
+ message: `Program ${id} is missing a usable graph payload.`,
4215
+ phase: "registration",
4216
+ timestamp: performance.now()
1469
4217
  });
1470
- if (toDelete.includes(key)) {
1471
- state.resolve();
1472
- }
4218
+ return;
4219
+ }
4220
+ try {
4221
+ const nextControllerId = registerGraph(config);
4222
+ programControllerIdsRef.current.set(id, nextControllerId);
4223
+ } catch (err) {
4224
+ pushError({
4225
+ message: `Failed to register program ${id}`,
4226
+ cause: err,
4227
+ phase: "registration",
4228
+ timestamp: performance.now()
4229
+ });
4230
+ }
4231
+ });
4232
+ refreshControllerStatus();
4233
+ }, [
4234
+ buildProgramRegistrationConfig,
4235
+ pushError,
4236
+ ready,
4237
+ refreshControllerStatus,
4238
+ registerGraph,
4239
+ removeGraph,
4240
+ resolvedProgramAssets,
4241
+ resolveProgramById
4242
+ ]);
4243
+ const playProgram = (0, import_react2.useCallback)(
4244
+ (id) => {
4245
+ const program = resolveProgramById(id);
4246
+ if (!program) {
4247
+ throw new Error(
4248
+ `Program ${id} is not part of the current asset bundle.`
4249
+ );
4250
+ }
4251
+ deriveProgramInputSeedValues({
4252
+ program,
4253
+ namespace,
4254
+ inputConstraints: inputConstraintsRef.current,
4255
+ getPathSnapshot,
4256
+ stagedInputs: stagedInputsRef.current
4257
+ }).forEach(({ path, value }) => {
4258
+ setInput(path, value);
1473
4259
  });
1474
- toDelete.forEach((key) => {
1475
- clipPlaybackRef.current.delete(key);
1476
- const clip = assetBundle.animations?.find((anim) => anim.id === key);
1477
- if (clip) {
1478
- const clipData = clip.clip;
1479
- const tracks = Array.isArray(clipData?.tracks) ? clipData.tracks : [];
1480
- tracks.forEach((track) => {
1481
- const path = `animation/${clip.id}/${track.channel}`;
1482
- setInput(path, { float: 0 });
1483
- });
1484
- }
4260
+ programPlaybackRef.current.set(id, {
4261
+ id,
4262
+ state: "playing"
1485
4263
  });
4264
+ syncProgramPlaybackControllers();
4265
+ markActivity();
1486
4266
  },
1487
- [assetBundle.animations, sampleTrack, setInput]
4267
+ [
4268
+ getPathSnapshot,
4269
+ markActivity,
4270
+ namespace,
4271
+ resolveProgramById,
4272
+ setInput,
4273
+ syncProgramPlaybackControllers
4274
+ ]
1488
4275
  );
1489
- const animateValue = (0, import_react2.useCallback)(
1490
- (path, target, options) => {
1491
- const easing = resolveEasing(options?.easing);
1492
- const duration = Math.max(0, options?.duration ?? DEFAULT_DURATION);
1493
- cancelAnimation(path);
1494
- const namespacedPath = namespaceTypedPath(path, namespaceRef.current);
1495
- const current = getPathSnapshot(namespacedPath);
1496
- const fromValue = (0, import_value_json2.valueAsNumber)(current);
1497
- const toValue = (0, import_value_json2.valueAsNumber)(target);
1498
- if (fromValue == null || toValue == null || duration === 0) {
1499
- setInput(path, target);
1500
- return Promise.resolve();
4276
+ const pauseProgram = (0, import_react2.useCallback)(
4277
+ (id) => {
4278
+ if (!resolveProgramById(id)) {
4279
+ return;
1501
4280
  }
1502
- return new Promise((resolve) => {
1503
- animationTweensRef.current.set(path, {
1504
- path: namespacedPath,
1505
- from: fromValue,
1506
- to: toValue,
1507
- duration,
1508
- elapsed: 0,
1509
- easing,
1510
- resolve
1511
- });
1512
- markActivity();
4281
+ programPlaybackRef.current.set(id, {
4282
+ id,
4283
+ state: "paused"
1513
4284
  });
4285
+ syncProgramPlaybackControllers();
4286
+ updateLoopMode();
1514
4287
  },
1515
- [cancelAnimation, getPathSnapshot, markActivity, setInput]
4288
+ [resolveProgramById, syncProgramPlaybackControllers, updateLoopMode]
1516
4289
  );
1517
- const playAnimation = (0, import_react2.useCallback)(
4290
+ const stopProgram = (0, import_react2.useCallback)(
1518
4291
  (id, options) => {
1519
- const clip = assetBundle.animations?.find((anim) => anim.id === id);
1520
- if (!clip) {
1521
- return Promise.reject(
1522
- new Error(`Animation ${id} is not part of the current asset bundle.`)
1523
- );
1524
- }
1525
- if (clipPlaybackRef.current.has(id)) {
1526
- clipPlaybackRef.current.delete(id);
4292
+ const program = resolveProgramById(id);
4293
+ const controllerId = programControllerIdsRef.current.get(id);
4294
+ if (controllerId) {
4295
+ try {
4296
+ removeGraph(controllerId);
4297
+ } catch (err) {
4298
+ pushError({
4299
+ message: `Failed to stop program ${id}`,
4300
+ cause: err,
4301
+ phase: "registration",
4302
+ timestamp: performance.now()
4303
+ });
4304
+ }
4305
+ programControllerIdsRef.current.delete(id);
1527
4306
  }
1528
- return new Promise((resolve) => {
1529
- const speed = options?.speed ?? 1;
1530
- const weight = options?.weight ?? clip.weight ?? 1;
1531
- const clipData = clip.clip;
1532
- clipPlaybackRef.current.set(id, {
1533
- id,
1534
- time: options?.reset ? 0 : 0,
1535
- duration: Number(clipData?.duration ?? 0),
1536
- speed: Number.isFinite(speed) && speed > 0 ? speed : 1,
1537
- weight,
1538
- resolve
1539
- });
1540
- markActivity();
4307
+ programPlaybackRef.current.set(id, {
4308
+ id,
4309
+ state: "stopped"
1541
4310
  });
4311
+ if (program && options?.resetOutputs !== false) {
4312
+ deriveProgramResetValues(program).forEach(({ path, value }) => {
4313
+ setInput(path, { float: value });
4314
+ });
4315
+ }
4316
+ refreshControllerStatus();
4317
+ updateLoopMode();
1542
4318
  },
1543
- [assetBundle.animations, markActivity]
4319
+ [
4320
+ deriveProgramResetValues,
4321
+ pushError,
4322
+ refreshControllerStatus,
4323
+ removeGraph,
4324
+ resolveProgramById,
4325
+ setInput,
4326
+ updateLoopMode
4327
+ ]
1544
4328
  );
1545
- const stopAnimation = (0, import_react2.useCallback)(
4329
+ const getProgramState = (0, import_react2.useCallback)(
1546
4330
  (id) => {
1547
- const clip = assetBundle.animations?.find((anim) => anim.id === id);
1548
- const state = clipPlaybackRef.current.get(id);
1549
- if (state) {
1550
- clipPlaybackRef.current.delete(id);
1551
- state.resolve();
4331
+ const state = programPlaybackRef.current.get(id);
4332
+ if (!state) {
4333
+ return null;
1552
4334
  }
1553
- if (clip) {
1554
- const clipData = clip.clip;
1555
- const tracks = Array.isArray(clipData?.tracks) ? clipData.tracks : [];
1556
- tracks.forEach((track) => {
1557
- const path = `animation/${clip.id}/${track.channel}`;
1558
- setInput(path, { float: 0 });
4335
+ return { state: state.state };
4336
+ },
4337
+ []
4338
+ );
4339
+ (0, import_react2.useEffect)(() => {
4340
+ if (!ready || status.loading) {
4341
+ return;
4342
+ }
4343
+ syncProgramPlaybackControllers();
4344
+ }, [
4345
+ graphUpdateToken,
4346
+ ready,
4347
+ resolvedProgramAssets,
4348
+ status.loading,
4349
+ syncProgramPlaybackControllers
4350
+ ]);
4351
+ const setAnimationActive = (0, import_react2.useCallback)(
4352
+ (active) => {
4353
+ const next = Boolean(active);
4354
+ if (animationSystemActiveRef.current === next) {
4355
+ return;
4356
+ }
4357
+ animationSystemActiveRef.current = next;
4358
+ if (!next) {
4359
+ clipPlaybackRef.current.forEach((state) => {
4360
+ state.playing = false;
1559
4361
  });
1560
4362
  }
4363
+ updateLoopMode();
1561
4364
  },
1562
- [assetBundle.animations, setInput]
4365
+ [updateLoopMode]
4366
+ );
4367
+ const isAnimationActive = (0, import_react2.useCallback)(
4368
+ () => animationSystemActiveRef.current,
4369
+ []
1563
4370
  );
1564
4371
  const registerInputDriver = (0, import_react2.useCallback)(
1565
4372
  (id, factory) => {
4373
+ inputDriverIdsRef.current.add(id);
1566
4374
  const driver = factory({
1567
4375
  setInput,
1568
4376
  setRendererValue,
@@ -1604,6 +4412,8 @@ function VizijRuntimeProviderInner({
1604
4412
  phase: "driver",
1605
4413
  timestamp: performance.now()
1606
4414
  });
4415
+ } finally {
4416
+ inputDriverIdsRef.current.delete(id);
1607
4417
  }
1608
4418
  }
1609
4419
  };
@@ -1611,22 +4421,15 @@ function VizijRuntimeProviderInner({
1611
4421
  },
1612
4422
  [faceId, namespace, pushError, setInput, setRendererValue]
1613
4423
  );
1614
- const advanceAnimations = (0, import_react2.useCallback)(
1615
- (dt) => {
1616
- advanceAnimationTweens(dt);
1617
- advanceClipPlayback(dt);
1618
- },
1619
- [advanceAnimationTweens, advanceClipPlayback]
1620
- );
1621
4424
  const flushStagedInputs = (0, import_react2.useCallback)(() => {
1622
4425
  if (stagedInputsRef.current.size === 0) {
1623
4426
  return;
1624
4427
  }
1625
4428
  stagedInputsRef.current.forEach(({ value, shape }, path) => {
1626
- orchestratorSetInput(path, value, shape);
4429
+ deviceSetInput(path, value, shape);
1627
4430
  });
1628
4431
  stagedInputsRef.current.clear();
1629
- }, [orchestratorSetInput]);
4432
+ }, [deviceSetInput]);
1630
4433
  const step = (0, import_react2.useCallback)(
1631
4434
  (dt, opts) => {
1632
4435
  if (dt > 0 && Number.isFinite(dt)) {
@@ -1634,13 +4437,19 @@ function VizijRuntimeProviderInner({
1634
4437
  const alpha = 0.1;
1635
4438
  avgStepDtRef.current = prev * (1 - alpha) + dt * alpha;
1636
4439
  }
1637
- advanceAnimations(dt);
4440
+ advanceAnimationTweens(dt);
1638
4441
  flushStagedInputs();
1639
- if (driveOrchestratorRef.current || opts?.forceRuntime) {
4442
+ if (driveRuntimeRef.current || opts?.forceRuntime) {
1640
4443
  stepRuntime(dt);
1641
4444
  }
4445
+ settleFinishedClips();
1642
4446
  },
1643
- [advanceAnimations, flushStagedInputs, stepRuntime]
4447
+ [
4448
+ advanceAnimationTweens,
4449
+ flushStagedInputs,
4450
+ settleFinishedClips,
4451
+ stepRuntime
4452
+ ]
1644
4453
  );
1645
4454
  (0, import_react2.useEffect)(() => {
1646
4455
  if (loopMode !== "active") {
@@ -1655,7 +4464,7 @@ function VizijRuntimeProviderInner({
1655
4464
  if (lastTime == null) {
1656
4465
  lastTime = timestamp;
1657
4466
  }
1658
- const dt = Math.max(0, (timestamp - lastTime) / 1e3);
4467
+ const dt = inferImplicitPause(Math.max(0, (timestamp - lastTime) / 1e3));
1659
4468
  lastTime = timestamp;
1660
4469
  step(dt || 0);
1661
4470
  requestLoopMode(computeDesiredLoopMode());
@@ -1686,7 +4495,7 @@ function VizijRuntimeProviderInner({
1686
4495
  return;
1687
4496
  }
1688
4497
  const current = now();
1689
- const dt = Math.max(0, (current - lastTime) / 1e3);
4498
+ const dt = inferImplicitPause(Math.max(0, (current - lastTime) / 1e3));
1690
4499
  lastTime = current;
1691
4500
  step(dt || 0);
1692
4501
  requestLoopMode(computeDesiredLoopMode());
@@ -1700,6 +4509,8 @@ function VizijRuntimeProviderInner({
1700
4509
  return () => {
1701
4510
  animationTweensRef.current.clear();
1702
4511
  clipPlaybackRef.current.clear();
4512
+ programPlaybackRef.current.clear();
4513
+ programControllerIdsRef.current.clear();
1703
4514
  };
1704
4515
  }, []);
1705
4516
  (0, import_react2.useEffect)(() => {
@@ -1715,35 +4526,95 @@ function VizijRuntimeProviderInner({
1715
4526
  }, 500);
1716
4527
  return () => window.clearInterval(id);
1717
4528
  }, [reportStatus]);
4529
+ const setGraphBundle = (0, import_react2.useCallback)(
4530
+ (bundle, options) => {
4531
+ const baseAssetBundle = latestEffectiveAssetBundleRef.current;
4532
+ const nextAssetBundle = applyRuntimeGraphBundle(baseAssetBundle, bundle);
4533
+ const plan = resolveRuntimeUpdatePlan(
4534
+ baseAssetBundle,
4535
+ nextAssetBundle,
4536
+ options?.tier ?? updateTierRef.current
4537
+ );
4538
+ pendingPlanRef.current = plan;
4539
+ previousBundleRef.current = nextAssetBundle;
4540
+ latestEffectiveAssetBundleRef.current = nextAssetBundle;
4541
+ suppressNextBundlePlanRef.current = true;
4542
+ setAssetBundleOverride(nextAssetBundle);
4543
+ if (plan.reregisterGraphs) {
4544
+ setGraphUpdateToken((prev) => prev + 1);
4545
+ }
4546
+ if (plan.reloadAssets) {
4547
+ reportStatus((prev) => ({
4548
+ ...prev,
4549
+ loading: true,
4550
+ ready: false
4551
+ }));
4552
+ } else {
4553
+ reportStatus((prev) => ({
4554
+ ...prev,
4555
+ loading: false
4556
+ }));
4557
+ }
4558
+ },
4559
+ [reportStatus]
4560
+ );
1718
4561
  const contextValue = (0, import_react2.useMemo)(
1719
4562
  () => ({
1720
4563
  ...status,
1721
4564
  assetBundle,
1722
4565
  setInput,
4566
+ getValueSnapshot: getPathSnapshot,
4567
+ getStoreSnapshot,
4568
+ subscribeToStoreChanges,
4569
+ subscribeToStep,
4570
+ setGraphBundle,
1723
4571
  setValue: setRendererValue,
1724
4572
  stagePoseNeutral,
1725
4573
  animateValue,
1726
4574
  cancelAnimation,
1727
4575
  registerInputDriver,
1728
4576
  playAnimation,
4577
+ pauseAnimation,
4578
+ seekAnimation,
4579
+ setAnimationLoop,
4580
+ getAnimationState,
1729
4581
  stopAnimation,
4582
+ playProgram,
4583
+ pauseProgram,
4584
+ stopProgram,
4585
+ getProgramState,
4586
+ setAnimationActive,
4587
+ isAnimationActive,
1730
4588
  step,
1731
- advanceAnimations,
1732
4589
  inputConstraints
1733
4590
  }),
1734
4591
  [
1735
4592
  status,
1736
4593
  assetBundle,
1737
4594
  setInput,
4595
+ getPathSnapshot,
4596
+ getStoreSnapshot,
4597
+ subscribeToStoreChanges,
4598
+ subscribeToStep,
4599
+ setGraphBundle,
1738
4600
  setRendererValue,
1739
4601
  stagePoseNeutral,
1740
4602
  animateValue,
1741
4603
  cancelAnimation,
1742
4604
  registerInputDriver,
1743
4605
  playAnimation,
4606
+ pauseAnimation,
4607
+ seekAnimation,
4608
+ setAnimationLoop,
4609
+ getAnimationState,
1744
4610
  stopAnimation,
4611
+ playProgram,
4612
+ pauseProgram,
4613
+ stopProgram,
4614
+ getProgramState,
4615
+ setAnimationActive,
4616
+ isAnimationActive,
1745
4617
  step,
1746
- advanceAnimations,
1747
4618
  inputConstraints
1748
4619
  ]
1749
4620
  );
@@ -1787,15 +4658,21 @@ function VizijRuntimeFaceInner({
1787
4658
  }
1788
4659
  var VizijRuntimeFace = (0, import_react4.memo)(VizijRuntimeFaceInner);
1789
4660
 
4661
+ // src/hooks/useOptionalVizijRuntime.ts
4662
+ var import_react5 = require("react");
4663
+ function useOptionalVizijRuntime() {
4664
+ return (0, import_react5.useContext)(VizijRuntimeContext);
4665
+ }
4666
+
1790
4667
  // src/hooks/useVizijOutputs.ts
1791
4668
  var import_render3 = require("@vizij/render");
1792
- var import_utils = require("@vizij/utils");
4669
+ var import_utils2 = require("@vizij/utils");
1793
4670
  function useVizijOutputs(paths) {
1794
4671
  const { namespace } = useVizijRuntime();
1795
4672
  return (0, import_render3.useVizijStore)((state) => {
1796
4673
  const result = {};
1797
4674
  paths.forEach((path) => {
1798
- const lookup = (0, import_utils.getLookup)(namespace, path);
4675
+ const lookup = (0, import_utils2.getLookup)(namespace, path);
1799
4676
  result[path] = state.values.get(lookup);
1800
4677
  });
1801
4678
  return result;
@@ -1803,15 +4680,15 @@ function useVizijOutputs(paths) {
1803
4680
  }
1804
4681
 
1805
4682
  // src/hooks/useRigInput.ts
1806
- var import_react5 = require("react");
4683
+ var import_react6 = require("react");
1807
4684
  var import_render4 = require("@vizij/render");
1808
- var import_utils2 = require("@vizij/utils");
4685
+ var import_utils3 = require("@vizij/utils");
1809
4686
  function useRigInput(path) {
1810
4687
  const { namespace, setInput } = useVizijRuntime();
1811
4688
  const value = (0, import_render4.useVizijStore)((state) => {
1812
- return state.values.get((0, import_utils2.getLookup)(namespace, path));
4689
+ return state.values.get((0, import_utils3.getLookup)(namespace, path));
1813
4690
  });
1814
- const setter = (0, import_react5.useCallback)(
4691
+ const setter = (0, import_react6.useCallback)(
1815
4692
  (next, shape) => {
1816
4693
  setInput(path, next, shape);
1817
4694
  },
@@ -1819,10 +4696,423 @@ function useRigInput(path) {
1819
4696
  );
1820
4697
  return [value, setter];
1821
4698
  }
4699
+
4700
+ // src/utils/faceControls.ts
4701
+ var DEFAULT_GAZE_CONTROL = {
4702
+ min: -1,
4703
+ max: 1,
4704
+ defaultValue: 0
4705
+ };
4706
+ var DEFAULT_BLINK_CONTROL = {
4707
+ min: 0,
4708
+ max: 1,
4709
+ defaultValue: 0
4710
+ };
4711
+ var STANDARD_VIZIJ_EYE_PATHS = {
4712
+ leftX: "/standard/vizij/left_eye/pos/x",
4713
+ leftY: "/standard/vizij/left_eye/pos/y",
4714
+ rightX: "/standard/vizij/right_eye/pos/x",
4715
+ rightY: "/standard/vizij/right_eye/pos/y",
4716
+ leftUpper: "/standard/vizij/left_eye_top_eyelid/pos/y",
4717
+ rightUpper: "/standard/vizij/right_eye_top_eyelid/pos/y"
4718
+ };
4719
+ var LEGACY_STANDARD_EYE_PATHS = {
4720
+ leftX: "/standard/left_eye/pos/x",
4721
+ leftY: "/standard/left_eye/pos/y",
4722
+ rightX: "/standard/right_eye/pos/x",
4723
+ rightY: "/standard/right_eye/pos/y",
4724
+ leftUpper: "/standard/left_eye_top_eyelid/pos/y",
4725
+ rightUpper: "/standard/right_eye_top_eyelid/pos/y"
4726
+ };
4727
+ var PROPSRIG_EYE_PATHS = {
4728
+ leftX: "/propsrig/l_eye/translation/x",
4729
+ leftY: "/propsrig/l_eye/translation/y",
4730
+ rightX: "/propsrig/r_eye/translation/x",
4731
+ rightY: "/propsrig/r_eye/translation/y"
4732
+ };
4733
+ function clamp(value, min, max) {
4734
+ return Math.min(Math.max(value, min), max);
4735
+ }
4736
+ function normalizeInputPath(path) {
4737
+ const trimmed = path?.trim() ?? "";
4738
+ if (!trimmed) {
4739
+ return null;
4740
+ }
4741
+ if (trimmed.startsWith("/")) {
4742
+ return trimmed;
4743
+ }
4744
+ const rigMatch = trimmed.match(/^rig\/[^/]+(\/.*)$/);
4745
+ if (rigMatch?.[1]) {
4746
+ return rigMatch[1];
4747
+ }
4748
+ return `/${trimmed}`;
4749
+ }
4750
+ function buildControlsLookup(assetBundle) {
4751
+ const metadata = assetBundle.rig?.inputMetadata ?? [];
4752
+ const metadataByPath = /* @__PURE__ */ new Map();
4753
+ const pathSet = /* @__PURE__ */ new Set();
4754
+ metadata.forEach((entry) => {
4755
+ const normalized = normalizeInputPath(entry.path);
4756
+ if (!normalized) {
4757
+ return;
4758
+ }
4759
+ pathSet.add(normalized);
4760
+ if (!metadataByPath.has(normalized)) {
4761
+ metadataByPath.set(normalized, entry);
4762
+ }
4763
+ });
4764
+ return { pathSet, metadataByPath };
4765
+ }
4766
+ function resolveConstraint(absolutePath, relativePath, inputConstraints) {
4767
+ if (!inputConstraints) {
4768
+ return null;
4769
+ }
4770
+ const normalizedRelative = relativePath.replace(/^\//, "");
4771
+ const candidates = [
4772
+ absolutePath,
4773
+ relativePath,
4774
+ normalizedRelative,
4775
+ absolutePath.replace(/^rig\/[^/]+\//, "")
4776
+ ];
4777
+ for (const candidate of candidates) {
4778
+ const constraint = inputConstraints[candidate];
4779
+ if (constraint) {
4780
+ return constraint;
4781
+ }
4782
+ }
4783
+ return null;
4784
+ }
4785
+ function createControl(faceId, lookup, relativePath, inputConstraints, fallback) {
4786
+ const normalizedRelative = normalizeInputPath(relativePath);
4787
+ if (!normalizedRelative || !lookup.pathSet.has(normalizedRelative)) {
4788
+ return null;
4789
+ }
4790
+ const absolutePath = buildRigInputPath(faceId, normalizedRelative);
4791
+ const metadata = lookup.metadataByPath.get(normalizedRelative);
4792
+ const constraint = resolveConstraint(
4793
+ absolutePath,
4794
+ normalizedRelative,
4795
+ inputConstraints
4796
+ );
4797
+ const min = Number.isFinite(Number(constraint?.min ?? metadata?.range?.min)) ? Number(constraint?.min ?? metadata?.range?.min) : fallback.min;
4798
+ const max = Number.isFinite(Number(constraint?.max ?? metadata?.range?.max)) ? Number(constraint?.max ?? metadata?.range?.max) : fallback.max;
4799
+ const midpoint = min + (max - min) / 2;
4800
+ const defaultValue = Number.isFinite(
4801
+ Number(constraint?.defaultValue ?? metadata?.defaultValue)
4802
+ ) ? Number(constraint?.defaultValue ?? metadata?.defaultValue) : Number.isFinite(midpoint) ? midpoint : fallback.defaultValue;
4803
+ return {
4804
+ path: absolutePath,
4805
+ min,
4806
+ max,
4807
+ defaultValue
4808
+ };
4809
+ }
4810
+ function hasAll(controls, keys) {
4811
+ return keys.every((key) => Boolean(controls[key]));
4812
+ }
4813
+ function resolveFaceControls(assetBundle, runtimeFaceId, inputConstraints) {
4814
+ const faceId = assetBundle.faceId ?? assetBundle.pose?.config?.faceId ?? runtimeFaceId ?? "face";
4815
+ const lookup = buildControlsLookup(assetBundle);
4816
+ const standardVizijControls = {
4817
+ leftX: createControl(
4818
+ faceId,
4819
+ lookup,
4820
+ STANDARD_VIZIJ_EYE_PATHS.leftX,
4821
+ inputConstraints,
4822
+ DEFAULT_GAZE_CONTROL
4823
+ ),
4824
+ leftY: createControl(
4825
+ faceId,
4826
+ lookup,
4827
+ STANDARD_VIZIJ_EYE_PATHS.leftY,
4828
+ inputConstraints,
4829
+ DEFAULT_GAZE_CONTROL
4830
+ ),
4831
+ rightX: createControl(
4832
+ faceId,
4833
+ lookup,
4834
+ STANDARD_VIZIJ_EYE_PATHS.rightX,
4835
+ inputConstraints,
4836
+ DEFAULT_GAZE_CONTROL
4837
+ ),
4838
+ rightY: createControl(
4839
+ faceId,
4840
+ lookup,
4841
+ STANDARD_VIZIJ_EYE_PATHS.rightY,
4842
+ inputConstraints,
4843
+ DEFAULT_GAZE_CONTROL
4844
+ ),
4845
+ leftUpper: createControl(
4846
+ faceId,
4847
+ lookup,
4848
+ STANDARD_VIZIJ_EYE_PATHS.leftUpper,
4849
+ inputConstraints,
4850
+ DEFAULT_GAZE_CONTROL
4851
+ ),
4852
+ rightUpper: createControl(
4853
+ faceId,
4854
+ lookup,
4855
+ STANDARD_VIZIJ_EYE_PATHS.rightUpper,
4856
+ inputConstraints,
4857
+ DEFAULT_GAZE_CONTROL
4858
+ )
4859
+ };
4860
+ const legacyStandardControls = {
4861
+ leftX: createControl(
4862
+ faceId,
4863
+ lookup,
4864
+ LEGACY_STANDARD_EYE_PATHS.leftX,
4865
+ inputConstraints,
4866
+ DEFAULT_GAZE_CONTROL
4867
+ ),
4868
+ leftY: createControl(
4869
+ faceId,
4870
+ lookup,
4871
+ LEGACY_STANDARD_EYE_PATHS.leftY,
4872
+ inputConstraints,
4873
+ DEFAULT_GAZE_CONTROL
4874
+ ),
4875
+ rightX: createControl(
4876
+ faceId,
4877
+ lookup,
4878
+ LEGACY_STANDARD_EYE_PATHS.rightX,
4879
+ inputConstraints,
4880
+ DEFAULT_GAZE_CONTROL
4881
+ ),
4882
+ rightY: createControl(
4883
+ faceId,
4884
+ lookup,
4885
+ LEGACY_STANDARD_EYE_PATHS.rightY,
4886
+ inputConstraints,
4887
+ DEFAULT_GAZE_CONTROL
4888
+ ),
4889
+ leftUpper: createControl(
4890
+ faceId,
4891
+ lookup,
4892
+ LEGACY_STANDARD_EYE_PATHS.leftUpper,
4893
+ inputConstraints,
4894
+ DEFAULT_GAZE_CONTROL
4895
+ ),
4896
+ rightUpper: createControl(
4897
+ faceId,
4898
+ lookup,
4899
+ LEGACY_STANDARD_EYE_PATHS.rightUpper,
4900
+ inputConstraints,
4901
+ DEFAULT_GAZE_CONTROL
4902
+ )
4903
+ };
4904
+ const propsrigControls = {
4905
+ leftX: createControl(
4906
+ faceId,
4907
+ lookup,
4908
+ PROPSRIG_EYE_PATHS.leftX,
4909
+ inputConstraints,
4910
+ DEFAULT_GAZE_CONTROL
4911
+ ),
4912
+ leftY: createControl(
4913
+ faceId,
4914
+ lookup,
4915
+ PROPSRIG_EYE_PATHS.leftY,
4916
+ inputConstraints,
4917
+ DEFAULT_GAZE_CONTROL
4918
+ ),
4919
+ rightX: createControl(
4920
+ faceId,
4921
+ lookup,
4922
+ PROPSRIG_EYE_PATHS.rightX,
4923
+ inputConstraints,
4924
+ DEFAULT_GAZE_CONTROL
4925
+ ),
4926
+ rightY: createControl(
4927
+ faceId,
4928
+ lookup,
4929
+ PROPSRIG_EYE_PATHS.rightY,
4930
+ inputConstraints,
4931
+ DEFAULT_GAZE_CONTROL
4932
+ )
4933
+ };
4934
+ const coupledGazeControls = {
4935
+ leftX: createControl(
4936
+ faceId,
4937
+ lookup,
4938
+ "/gaze/left_right",
4939
+ inputConstraints,
4940
+ DEFAULT_GAZE_CONTROL
4941
+ ),
4942
+ leftY: createControl(
4943
+ faceId,
4944
+ lookup,
4945
+ "/gaze/up_down",
4946
+ inputConstraints,
4947
+ DEFAULT_GAZE_CONTROL
4948
+ ),
4949
+ rightX: createControl(
4950
+ faceId,
4951
+ lookup,
4952
+ "/gaze/left_right_copy",
4953
+ inputConstraints,
4954
+ DEFAULT_GAZE_CONTROL
4955
+ ) ?? createControl(
4956
+ faceId,
4957
+ lookup,
4958
+ "/gaze/left_right",
4959
+ inputConstraints,
4960
+ DEFAULT_GAZE_CONTROL
4961
+ ),
4962
+ rightY: createControl(
4963
+ faceId,
4964
+ lookup,
4965
+ "/gaze/up_down_copy",
4966
+ inputConstraints,
4967
+ DEFAULT_GAZE_CONTROL
4968
+ ) ?? createControl(
4969
+ faceId,
4970
+ lookup,
4971
+ "/gaze/up_down",
4972
+ inputConstraints,
4973
+ DEFAULT_GAZE_CONTROL
4974
+ )
4975
+ };
4976
+ const blinkFromLids = createControl(
4977
+ faceId,
4978
+ lookup,
4979
+ "/lids/blink",
4980
+ inputConstraints,
4981
+ DEFAULT_BLINK_CONTROL
4982
+ );
4983
+ const blinkDirect = createControl(
4984
+ faceId,
4985
+ lookup,
4986
+ "/blink",
4987
+ inputConstraints,
4988
+ DEFAULT_BLINK_CONTROL
4989
+ );
4990
+ if (hasAll(standardVizijControls, ["leftX", "leftY", "rightX", "rightY"])) {
4991
+ return {
4992
+ faceId,
4993
+ gazeSource: "standard-vizij",
4994
+ blinkSource: blinkFromLids ? "lids" : blinkDirect ? "blink" : "none",
4995
+ eyes: {
4996
+ leftX: standardVizijControls.leftX,
4997
+ leftY: standardVizijControls.leftY,
4998
+ rightX: standardVizijControls.rightX,
4999
+ rightY: standardVizijControls.rightY
5000
+ },
5001
+ eyelids: {
5002
+ leftUpper: standardVizijControls.leftUpper,
5003
+ rightUpper: standardVizijControls.rightUpper
5004
+ },
5005
+ blink: blinkFromLids ?? blinkDirect
5006
+ };
5007
+ }
5008
+ if (hasAll(legacyStandardControls, ["leftX", "leftY", "rightX", "rightY"])) {
5009
+ return {
5010
+ faceId,
5011
+ gazeSource: "standard",
5012
+ blinkSource: blinkDirect ? "blink" : blinkFromLids ? "lids" : "none",
5013
+ eyes: {
5014
+ leftX: legacyStandardControls.leftX,
5015
+ leftY: legacyStandardControls.leftY,
5016
+ rightX: legacyStandardControls.rightX,
5017
+ rightY: legacyStandardControls.rightY
5018
+ },
5019
+ eyelids: {
5020
+ leftUpper: legacyStandardControls.leftUpper,
5021
+ rightUpper: legacyStandardControls.rightUpper
5022
+ },
5023
+ blink: blinkDirect ?? blinkFromLids
5024
+ };
5025
+ }
5026
+ if (hasAll(propsrigControls, ["leftX", "leftY", "rightX", "rightY"])) {
5027
+ return {
5028
+ faceId,
5029
+ gazeSource: "propsrig",
5030
+ blinkSource: blinkDirect ? "blink" : blinkFromLids ? "lids" : "none",
5031
+ eyes: {
5032
+ leftX: propsrigControls.leftX,
5033
+ leftY: propsrigControls.leftY,
5034
+ rightX: propsrigControls.rightX,
5035
+ rightY: propsrigControls.rightY
5036
+ },
5037
+ eyelids: {
5038
+ leftUpper: null,
5039
+ rightUpper: null
5040
+ },
5041
+ blink: blinkDirect ?? blinkFromLids
5042
+ };
5043
+ }
5044
+ if (hasAll(coupledGazeControls, ["leftX", "leftY", "rightX", "rightY"])) {
5045
+ return {
5046
+ faceId,
5047
+ gazeSource: "coupled-gaze",
5048
+ blinkSource: blinkFromLids ? "lids" : blinkDirect ? "blink" : "none",
5049
+ eyes: {
5050
+ leftX: coupledGazeControls.leftX,
5051
+ leftY: coupledGazeControls.leftY,
5052
+ rightX: coupledGazeControls.rightX,
5053
+ rightY: coupledGazeControls.rightY
5054
+ },
5055
+ eyelids: {
5056
+ leftUpper: null,
5057
+ rightUpper: null
5058
+ },
5059
+ blink: blinkFromLids ?? blinkDirect
5060
+ };
5061
+ }
5062
+ return {
5063
+ faceId,
5064
+ gazeSource: "none",
5065
+ blinkSource: blinkFromLids ? "lids" : blinkDirect ? "blink" : "none",
5066
+ eyes: {
5067
+ leftX: null,
5068
+ leftY: null,
5069
+ rightX: null,
5070
+ rightY: null
5071
+ },
5072
+ eyelids: {
5073
+ leftUpper: null,
5074
+ rightUpper: null
5075
+ },
5076
+ blink: blinkFromLids ?? blinkDirect
5077
+ };
5078
+ }
5079
+ function mapNormalizedControlValue(control, normalizedValue) {
5080
+ const value = clamp(normalizedValue, -1, 1);
5081
+ if (value === 0) {
5082
+ return control.defaultValue;
5083
+ }
5084
+ if (value > 0) {
5085
+ return control.defaultValue + (control.max - control.defaultValue) * value;
5086
+ }
5087
+ return control.defaultValue + (control.defaultValue - control.min) * value;
5088
+ }
5089
+ function mapUnitControlValue(control, unitValue) {
5090
+ const value = clamp(unitValue, 0, 1);
5091
+ return control.defaultValue + (control.max - control.defaultValue) * value;
5092
+ }
1822
5093
  // Annotate the CommonJS export names for ESM import in node:
1823
5094
  0 && (module.exports = {
5095
+ EMOTION_POSE_KEYS,
5096
+ EXPRESSIVE_EMOTION_POSE_KEYS,
5097
+ POSE_WEIGHT_INPUT_PATH_PREFIX,
5098
+ VISEME_POSE_KEYS,
1824
5099
  VizijRuntimeFace,
1825
5100
  VizijRuntimeProvider,
5101
+ buildPoseWeightInputPathSegment,
5102
+ buildPoseWeightPathMap,
5103
+ buildPoseWeightRelativePath,
5104
+ buildRigInputPath,
5105
+ buildSemanticPoseWeightPathMap,
5106
+ filterPosesBySemanticKind,
5107
+ getPoseSemanticKey,
5108
+ mapNormalizedControlValue,
5109
+ mapUnitControlValue,
5110
+ normalizePoseSemanticKey,
5111
+ resolveFaceControls,
5112
+ resolvePoseMembership,
5113
+ resolvePoseSemantics,
5114
+ resolveRuntimeUpdatePlan,
5115
+ useOptionalVizijRuntime,
1826
5116
  useRigInput,
1827
5117
  useVizijOutputs,
1828
5118
  useVizijRuntime