@vizij/runtime-react 0.2.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.js CHANGED
@@ -1,33 +1,364 @@
1
1
  // src/VizijRuntimeProvider.tsx
2
- import {
3
- useCallback,
4
- useContext,
5
- useEffect,
6
- useMemo,
7
- useRef,
8
- useState
9
- } from "react";
2
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
10
3
  import {
11
4
  VizijContext,
12
5
  createVizijStore,
13
6
  loadGLTFWithBundle,
14
7
  loadGLTFFromBlobWithBundle
15
8
  } from "@vizij/render";
16
- import {
17
- OrchestratorProvider,
18
- OrchestratorContext,
19
- useOrchestrator,
20
- useOrchFrame
21
- } from "@vizij/orchestrator-react";
22
9
  import { compileIrGraph } from "@vizij/node-graph-authoring";
23
10
  import { valueAsNumber as valueAsNumber2 } from "@vizij/value-json";
24
11
  import { getLookup } from "@vizij/utils";
12
+ import { loadAnimationModule } from "@vizij/animation-module";
13
+
14
+ // src/engine/aroraEngine.ts
15
+ import {
16
+ init,
17
+ startRuntime
18
+ } from "@vizij/runtime";
19
+
20
+ // src/engine/graphSpecDiff.ts
21
+ function asRecords(value) {
22
+ return Array.isArray(value) ? value : [];
23
+ }
24
+ function nodeId(node) {
25
+ return typeof node.id === "string" ? node.id : void 0;
26
+ }
27
+ function endpointId(endpoint) {
28
+ const id = endpoint?.node_id;
29
+ return typeof id === "string" ? id : void 0;
30
+ }
31
+ function endpointInput(endpoint) {
32
+ const input = endpoint?.input;
33
+ return typeof input === "string" ? input : void 0;
34
+ }
35
+ function stable(value) {
36
+ return JSON.stringify(sortKeys(value));
37
+ }
38
+ function sortKeys(value) {
39
+ if (Array.isArray(value)) return value.map(sortKeys);
40
+ if (value && typeof value === "object") {
41
+ const out = {};
42
+ for (const key of Object.keys(value).sort()) {
43
+ out[key] = sortKeys(value[key]);
44
+ }
45
+ return out;
46
+ }
47
+ return value;
48
+ }
49
+ function edgeKey(edge) {
50
+ return stable([
51
+ endpointId(edge.from),
52
+ edge.from?.output ?? "out",
53
+ endpointId(edge.to),
54
+ endpointInput(edge.to),
55
+ edge.selector ?? null
56
+ ]);
57
+ }
58
+ function graphSpecDiff(prev, next) {
59
+ const prevNodes = /* @__PURE__ */ new Map();
60
+ for (const node of asRecords(prev.nodes)) {
61
+ const id = nodeId(node);
62
+ if (id) prevNodes.set(id, node);
63
+ }
64
+ const nextNodes = /* @__PURE__ */ new Map();
65
+ for (const node of asRecords(next.nodes)) {
66
+ const id = nodeId(node);
67
+ if (id) nextNodes.set(id, node);
68
+ }
69
+ const prevEdges = asRecords(prev.edges);
70
+ const nextEdges = asRecords(next.edges);
71
+ const remove_nodes = [...prevNodes.keys()].filter((id) => !nextNodes.has(id));
72
+ const upsertIds = /* @__PURE__ */ new Set();
73
+ for (const [id, node] of nextNodes) {
74
+ const before = prevNodes.get(id);
75
+ if (!before || stable(before) !== stable(node)) upsertIds.add(id);
76
+ }
77
+ const prevEdgeKeys = new Set(prevEdges.map(edgeKey));
78
+ const nextEdgeKeys = new Set(nextEdges.map(edgeKey));
79
+ const removedEdges = prevEdges.filter((e) => !nextEdgeKeys.has(edgeKey(e)));
80
+ const changedEdges = [
81
+ ...nextEdges.filter((e) => !prevEdgeKeys.has(edgeKey(e))),
82
+ ...removedEdges
83
+ ];
84
+ for (const edge of changedEdges) {
85
+ for (const id of [endpointId(edge.from), endpointId(edge.to)]) {
86
+ if (id && nextNodes.has(id)) upsertIds.add(id);
87
+ }
88
+ }
89
+ const incidentToUpsert = (edge) => {
90
+ const from = endpointId(edge.from);
91
+ const to = endpointId(edge.to);
92
+ return from !== void 0 && upsertIds.has(from) || to !== void 0 && upsertIds.has(to);
93
+ };
94
+ const upsert_nodes = [...upsertIds].map((id) => nextNodes.get(id));
95
+ const upsert_edges = nextEdges.filter(incidentToUpsert);
96
+ const removeNodeSet = new Set(remove_nodes);
97
+ const remove_edges = removedEdges.flatMap((edge) => {
98
+ const to = endpointId(edge.to);
99
+ const input = endpointInput(edge.to);
100
+ if (!to || !input || removeNodeSet.has(to) || upsertIds.has(to)) return [];
101
+ return [{ node_id: to, input }];
102
+ });
103
+ if (upsert_nodes.length === 0 && remove_nodes.length === 0 && upsert_edges.length === 0 && remove_edges.length === 0) {
104
+ return null;
105
+ }
106
+ return { upsert_nodes, remove_nodes, upsert_edges, remove_edges };
107
+ }
108
+
109
+ // src/engine/aroraEngine.ts
110
+ var BUILT_IN_PREFIX = "arora/";
111
+ function isBuiltInPath(path) {
112
+ return path.startsWith(BUILT_IN_PREFIX);
113
+ }
114
+ var RUN_PERIOD_MS = 1e3 / 60;
115
+ var initPromise = null;
116
+ function ensureWasmInit(input) {
117
+ if (!initPromise) {
118
+ initPromise = init(input);
119
+ }
120
+ return initPromise;
121
+ }
122
+ var DeviceSlot = class {
123
+ constructor() {
124
+ this.handle = null;
125
+ /** Module loading a boot must wait out before it reads `modules`. */
126
+ this.modulesLoading = null;
127
+ /**
128
+ * When set, a booted device is handed to its own loop (`device.run`) at
129
+ * this period. `null`: the host drives `step()` itself.
130
+ */
131
+ this.runPeriodMs = null;
132
+ /**
133
+ * Serializes device operations. Every `recompose` chains behind the
134
+ * previous op so two never run concurrently — a bundle whose import fans
135
+ * out into several graph registrations recomposes in a burst. Failures are
136
+ * swallowed on the chain so one failed op doesn't wedge later ones; each
137
+ * caller still sees its own rejection through the promise `enqueue`
138
+ * returns.
139
+ */
140
+ this.opChain = Promise.resolve();
141
+ }
142
+ get current() {
143
+ return this.handle;
144
+ }
145
+ /**
146
+ * The modules every subsequent device boot loads. The live device (if any)
147
+ * is not touched: a later `recompose` that sees the list changed rebuilds.
148
+ */
149
+ setModules(modules) {
150
+ this.modules = modules && modules.length > 0 ? modules : void 0;
151
+ }
152
+ /**
153
+ * Announce module loading in flight: boots wait for `loading` to settle
154
+ * before reading the module list, so a device never boots without modules
155
+ * that were already on their way. Pass a promise that cannot reject (the
156
+ * owner handles its own failures) — boots resume either way.
157
+ */
158
+ waitForModules(loading) {
159
+ this.modulesLoading = loading;
160
+ }
161
+ /**
162
+ * Hand the live device to its own loop, if there is one and `runPeriodMs`
163
+ * says so — for a host whose drive mode turns self-paced after boot.
164
+ */
165
+ startRun() {
166
+ if (this.handle) {
167
+ this.maybeRun(this.handle.device);
168
+ }
169
+ }
170
+ maybeRun(device) {
171
+ if (this.runPeriodMs === null || device.running) {
172
+ return;
173
+ }
174
+ device.run(this.runPeriodMs).catch((error) => {
175
+ this.onRunEnded?.(error);
176
+ });
177
+ }
178
+ enqueue(op) {
179
+ const run = this.opChain.then(op, op);
180
+ this.opChain = run.then(
181
+ () => void 0,
182
+ () => void 0
183
+ );
184
+ return run;
185
+ }
186
+ async boot(spec) {
187
+ await ensureWasmInit();
188
+ if (this.modulesLoading) {
189
+ await this.modulesLoading;
190
+ }
191
+ const device = await startRuntime(spec, void 0, this.modules);
192
+ this.handle = { device, spec, modules: this.modules };
193
+ this.onDeviceStarted?.(this.handle);
194
+ this.maybeRun(device);
195
+ return this.handle;
196
+ }
197
+ /**
198
+ * Rebuild the device because its module set changed since boot — the one
199
+ * change a live device cannot take. Carries the store across (snapshot
200
+ * minus built-in keys) and replays module setup via `onDeviceStarted`. The
201
+ * old device's wrapper is freed; if it was self-paced its loop keeps
202
+ * ticking unreachable — which is why boots wait for announced modules
203
+ * instead of leaning on this path.
204
+ */
205
+ async rebuild(old, spec) {
206
+ const carried = {};
207
+ for (const [path, value] of Object.entries(old.device.snapshot())) {
208
+ if (!isBuiltInPath(path)) {
209
+ carried[path] = value;
210
+ }
211
+ }
212
+ const device = await startRuntime(spec, void 0, this.modules);
213
+ if (Object.keys(carried).length > 0) {
214
+ device.writeValues(carried);
215
+ }
216
+ this.handle = { device, spec, modules: this.modules };
217
+ old.device.dispose();
218
+ this.onDeviceStarted?.(this.handle);
219
+ this.maybeRun(device);
220
+ return this.handle;
221
+ }
222
+ /**
223
+ * Make the device run `spec`: boot it if none is live, otherwise swap the
224
+ * graph in place (the store is the same store, so nothing is carried).
225
+ * No-op if the spec is identical by reference. Serialized behind any
226
+ * in-flight op (see `opChain`).
227
+ */
228
+ recompose(spec) {
229
+ return this.enqueue(async () => {
230
+ const old = this.handle;
231
+ if (!old) {
232
+ return this.boot(spec);
233
+ }
234
+ if (old.modules !== this.modules) {
235
+ return this.rebuild(old, spec);
236
+ }
237
+ if (old.spec === spec) {
238
+ return old;
239
+ }
240
+ const edits = graphSpecDiff(old.spec, spec);
241
+ if (edits) {
242
+ try {
243
+ await old.device.applyGraphEdits(edits);
244
+ } catch (err) {
245
+ console.warn(
246
+ "[vizij-runtime] applyGraphEdits failed; reloading the whole graph",
247
+ err
248
+ );
249
+ await old.device.loadGraph(spec);
250
+ }
251
+ }
252
+ old.spec = spec;
253
+ return old;
254
+ });
255
+ }
256
+ };
257
+
258
+ // src/utils/composeGraph.ts
259
+ function asArray(value) {
260
+ return Array.isArray(value) ? value.filter((n) => n && typeof n === "object") : [];
261
+ }
262
+ function prefixId(sourceId, id) {
263
+ return typeof id === "string" && id ? `${sourceId}::${id}` : id;
264
+ }
265
+ function prefixEndpoint(sourceId, endpoint) {
266
+ if (!endpoint || typeof endpoint !== "object") {
267
+ return endpoint;
268
+ }
269
+ const record = endpoint;
270
+ if (typeof record.node_id !== "string") {
271
+ return endpoint;
272
+ }
273
+ return { ...record, node_id: prefixId(sourceId, record.node_id) };
274
+ }
275
+ function outputPathsOf(spec) {
276
+ const paths = [];
277
+ for (const node of asArray(spec.nodes)) {
278
+ if (String(node.type ?? "").toLowerCase() !== "output") {
279
+ continue;
280
+ }
281
+ const path = node.params?.path;
282
+ if (typeof path === "string" && path.trim()) {
283
+ paths.push(path.trim());
284
+ }
285
+ }
286
+ return paths;
287
+ }
288
+ function composeGraphSpecs(sources) {
289
+ const nodes = [];
290
+ const edges = [];
291
+ const outputOwners = /* @__PURE__ */ new Map();
292
+ for (const { sourceId, spec } of sources) {
293
+ for (const path of outputPathsOf(spec)) {
294
+ const owner = outputOwners.get(path);
295
+ if (owner && owner !== sourceId) {
296
+ console.warn(
297
+ `[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.`
298
+ );
299
+ }
300
+ outputOwners.set(path, sourceId);
301
+ }
302
+ for (const node of asArray(spec.nodes)) {
303
+ nodes.push({ ...node, id: prefixId(sourceId, node.id) });
304
+ }
305
+ for (const edge of asArray(spec.edges)) {
306
+ edges.push({
307
+ ...edge,
308
+ from: prefixEndpoint(sourceId, edge.from),
309
+ to: prefixEndpoint(sourceId, edge.to)
310
+ });
311
+ }
312
+ }
313
+ return { nodes, edges };
314
+ }
25
315
 
26
316
  // src/context.ts
27
317
  import { createContext } from "react";
28
318
  var VizijRuntimeContext = createContext(null);
29
319
 
320
+ // src/memoryInvestigation.ts
321
+ function getGlobalMemoryInvestigation() {
322
+ return globalThis;
323
+ }
324
+ function updateRuntimeMap(updater) {
325
+ const globalObj = getGlobalMemoryInvestigation();
326
+ if (!globalObj.__VIZIJ_MEMORY_INVESTIGATION__?.enabled) {
327
+ return;
328
+ }
329
+ if (!globalObj.__vizijMemoryDebugState) {
330
+ globalObj.__vizijMemoryDebugState = {};
331
+ }
332
+ if (!globalObj.__vizijMemoryDebugState.runtimes) {
333
+ globalObj.__vizijMemoryDebugState.runtimes = {};
334
+ }
335
+ updater(globalObj.__vizijMemoryDebugState.runtimes);
336
+ }
337
+ function setRuntimeDebugState(runtimeId, state) {
338
+ updateRuntimeMap((runtimeMap) => {
339
+ runtimeMap[runtimeId] = state;
340
+ });
341
+ }
342
+ function clearRuntimeDebugState(runtimeId) {
343
+ updateRuntimeMap((runtimeMap) => {
344
+ delete runtimeMap[runtimeId];
345
+ });
346
+ }
347
+
30
348
  // src/updatePolicy.ts
349
+ var blobIdentityMap = /* @__PURE__ */ new WeakMap();
350
+ var nextBlobIdentity = 0;
351
+ function getBlobIdentity(blob) {
352
+ if (!blob) {
353
+ return "missing";
354
+ }
355
+ let identity = blobIdentityMap.get(blob);
356
+ if (identity === void 0) {
357
+ identity = nextBlobIdentity++;
358
+ blobIdentityMap.set(blob, identity);
359
+ }
360
+ return `${identity}:${blob.size}:${blob.type}`;
361
+ }
31
362
  function applyRuntimeGraphBundle(base, bundle) {
32
363
  const next = {
33
364
  ...base
@@ -43,18 +374,10 @@ function applyRuntimeGraphBundle(base, bundle) {
43
374
  "programs"
44
375
  );
45
376
  if (hasRigOverride) {
46
- if (bundle.rig) {
47
- next.rig = bundle.rig;
48
- } else {
49
- delete next.rig;
50
- }
377
+ next.rig = bundle.rig;
51
378
  }
52
379
  if (hasPoseOverride) {
53
- if (bundle.pose) {
54
- next.pose = bundle.pose;
55
- } else {
56
- delete next.pose;
57
- }
380
+ next.pose = bundle.pose;
58
381
  }
59
382
  if (hasAnimationsOverride) {
60
383
  next.animations = Array.isArray(bundle.animations) ? bundle.animations : void 0;
@@ -76,10 +399,18 @@ function normalizeSpecPayload(value) {
76
399
  }
77
400
  function glbSignature(glb) {
78
401
  if (glb.kind === "url") {
79
- return `url:${glb.src}`;
402
+ const importOptions = normalizeSpecPayload({
403
+ aggressiveImport: glb.aggressiveImport ?? false,
404
+ rootBounds: glb.rootBounds ?? null
405
+ });
406
+ return `url:${glb.src}:${importOptions}`;
80
407
  }
81
408
  if (glb.kind === "blob") {
82
- return `blob:${glb.blob?.size ?? 0}`;
409
+ const importOptions = normalizeSpecPayload({
410
+ aggressiveImport: glb.aggressiveImport ?? false,
411
+ rootBounds: glb.rootBounds ?? null
412
+ });
413
+ return `blob:${getBlobIdentity(glb.blob)}:${importOptions}`;
83
414
  }
84
415
  return `world:${normalizeSpecPayload(glb.world)}`;
85
416
  }
@@ -573,65 +904,20 @@ function resolvePoseControlInputPath({
573
904
  }
574
905
 
575
906
  // src/utils/clipPlayback.ts
576
- var EPSILON = 1e-6;
577
907
  function toFiniteNumber(value, fallback) {
578
908
  const parsed = Number(value);
579
909
  return Number.isFinite(parsed) ? parsed : fallback;
580
910
  }
581
- function normaliseInterpolation(interpolation) {
582
- const mode = typeof interpolation === "string" ? interpolation.trim().toLowerCase() : "linear";
583
- if (mode === "step") {
584
- return "step";
585
- }
586
- if (mode === "cubic" || mode === "cubicspline") {
587
- return "cubic";
588
- }
589
- return "linear";
590
- }
591
- function asNumericKeyframe(keyframe) {
592
- if (!keyframe || typeof keyframe !== "object") {
593
- return null;
594
- }
595
- const time = Number(keyframe.time);
596
- const value = Number(keyframe.value);
597
- if (!Number.isFinite(time) || !Number.isFinite(value)) {
598
- return null;
599
- }
600
- const inTangentRaw = keyframe.inTangent;
601
- const outTangentRaw = keyframe.outTangent;
602
- const inTangent = inTangentRaw == null || Number.isFinite(Number(inTangentRaw)) ? inTangentRaw : void 0;
603
- const outTangent = outTangentRaw == null || Number.isFinite(Number(outTangentRaw)) ? outTangentRaw : void 0;
604
- return {
605
- time,
606
- value,
607
- inTangent,
608
- outTangent
609
- };
610
- }
611
- function getNumericKeyframes(track) {
911
+ function lastKeyframeTime(track) {
612
912
  const keyframes = Array.isArray(track.keyframes) ? track.keyframes : [];
613
- const numeric = keyframes.map((keyframe) => asNumericKeyframe(keyframe)).filter((keyframe) => Boolean(keyframe));
614
- if (numeric.length <= 1) {
615
- return numeric;
616
- }
617
- return [...numeric].sort((a, b) => a.time - b.time);
618
- }
619
- function resolveTangent(value, fallback) {
620
- const parsed = Number(value);
621
- if (Number.isFinite(parsed)) {
622
- return parsed;
623
- }
624
- return fallback;
625
- }
626
- function sampleHermite(startValue, endValue, outTangent, inTangent, factor, duration) {
627
- const t = factor;
628
- const t2 = t * t;
629
- const t3 = t2 * t;
630
- const h00 = 2 * t3 - 3 * t2 + 1;
631
- const h10 = t3 - 2 * t2 + t;
632
- const h01 = -2 * t3 + 3 * t2;
633
- const h11 = t3 - t2;
634
- return h00 * startValue + h10 * outTangent * duration + h01 * endValue + h11 * inTangent * duration;
913
+ let maxTime = 0;
914
+ keyframes.forEach((keyframe) => {
915
+ const time = Number(keyframe?.time);
916
+ if (Number.isFinite(time) && time > maxTime) {
917
+ maxTime = time;
918
+ }
919
+ });
920
+ return maxTime;
635
921
  }
636
922
  function resolveClipDurationSeconds(clip, fallbackDurationSeconds = 0) {
637
923
  const fallback = Math.max(0, toFiniteNumber(fallbackDurationSeconds, 0));
@@ -645,109 +931,13 @@ function resolveClipDurationSeconds(clip, fallbackDurationSeconds = 0) {
645
931
  const tracks = Array.isArray(clip.tracks) ? clip.tracks : [];
646
932
  let maxTime = 0;
647
933
  tracks.forEach((track) => {
648
- const keyframes = getNumericKeyframes(track);
649
- const last = keyframes[keyframes.length - 1];
650
- if (last && last.time > maxTime) {
651
- maxTime = last.time;
934
+ const time = lastKeyframeTime(track);
935
+ if (time > maxTime) {
936
+ maxTime = time;
652
937
  }
653
938
  });
654
939
  return maxTime > 0 ? maxTime : fallback;
655
940
  }
656
- function clampAnimationTime(time, duration) {
657
- if (!Number.isFinite(duration) || duration <= 0) {
658
- return 0;
659
- }
660
- if (!Number.isFinite(time)) {
661
- return 0;
662
- }
663
- if (time <= 0) {
664
- return 0;
665
- }
666
- if (time >= duration) {
667
- return duration;
668
- }
669
- return time;
670
- }
671
- function advanceClipTime(state, dt) {
672
- const duration = Math.max(0, toFiniteNumber(state.duration, 0));
673
- const speed = Number.isFinite(state.speed) && state.speed > 0 ? state.speed : 1;
674
- const currentTime = clampAnimationTime(state.time, duration);
675
- const delta = Number.isFinite(dt) && dt > 0 ? Math.max(0, dt) * speed : 0;
676
- if (!state.playing || delta <= 0) {
677
- return { time: currentTime, completed: false };
678
- }
679
- if (duration <= 0) {
680
- return { time: 0, completed: true };
681
- }
682
- const nextTime = currentTime + delta;
683
- if (state.loop) {
684
- if (nextTime < duration) {
685
- return { time: nextTime, completed: false };
686
- }
687
- const wrapped = (nextTime % duration + duration) % duration;
688
- return { time: wrapped, completed: false };
689
- }
690
- if (nextTime >= duration - EPSILON) {
691
- return { time: duration, completed: true };
692
- }
693
- return { time: nextTime, completed: false };
694
- }
695
- function sampleTrackAtTime(track, timeSeconds) {
696
- const keyframes = getNumericKeyframes(track);
697
- if (keyframes.length === 0) {
698
- return 0;
699
- }
700
- if (keyframes.length === 1) {
701
- return keyframes[0].value;
702
- }
703
- const mode = normaliseInterpolation(track.interpolation);
704
- const time = Number.isFinite(timeSeconds) ? timeSeconds : 0;
705
- const first = keyframes[0];
706
- if (time <= first.time + EPSILON) {
707
- return first.value;
708
- }
709
- const last = keyframes[keyframes.length - 1];
710
- if (time >= last.time - EPSILON) {
711
- return last.value;
712
- }
713
- for (let i = 0; i < keyframes.length - 1; i += 1) {
714
- const current = keyframes[i];
715
- const next = keyframes[i + 1];
716
- const start = current.time;
717
- const end = next.time;
718
- const duration = end - start;
719
- if (duration <= EPSILON) {
720
- if (time <= end + EPSILON) {
721
- return next.value;
722
- }
723
- continue;
724
- }
725
- if (Math.abs(time - end) <= EPSILON) {
726
- return next.value;
727
- }
728
- if (time < end) {
729
- const factor = (time - start) / duration;
730
- if (mode === "step") {
731
- return current.value;
732
- }
733
- if (mode === "cubic") {
734
- const slope = (next.value - current.value) / duration;
735
- const outTangent = resolveTangent(current.outTangent, slope);
736
- const inTangent = resolveTangent(next.inTangent, slope);
737
- return sampleHermite(
738
- current.value,
739
- next.value,
740
- outTangent,
741
- inTangent,
742
- factor,
743
- duration
744
- );
745
- }
746
- return current.value + (next.value - current.value) * factor;
747
- }
748
- }
749
- return last.value;
750
- }
751
941
 
752
942
  // src/utils/animationBridge.ts
753
943
  function resolveAnimationBridgeOutputPaths(channel, faceId, rigInputMap) {
@@ -818,56 +1008,10 @@ function collectAnimationClipOutputPaths(clip, faceId, rigInputMap) {
818
1008
  (left, right) => left.localeCompare(right)
819
1009
  );
820
1010
  }
821
- function sampleAnimationClipOutputValues(clip, timeSeconds, weight = 1, faceId, rigInputMap) {
822
- const appliedWeight = Number.isFinite(weight) && weight >= 0 ? Number(weight) : 1;
823
- const outputValues = /* @__PURE__ */ new Map();
824
- const tracks = Array.isArray(clip.tracks) ? clip.tracks : [];
825
- tracks.forEach((track) => {
826
- const channel = typeof track.channel === "string" ? track.channel.trim() : "";
827
- if (!channel) {
828
- return;
829
- }
830
- const sampledValue = sampleTrackAtTime(
831
- track,
832
- timeSeconds
833
- );
834
- const weightedValue = sampledValue * appliedWeight;
835
- resolveAnimationBridgeOutputPaths(channel, faceId, rigInputMap).forEach(
836
- (path) => {
837
- outputValues.set(path, (outputValues.get(path) ?? 0) + weightedValue);
838
- }
839
- );
840
- });
841
- return outputValues;
842
- }
843
- function diffAnimationAggregateValues(previousAggregate, nextAggregate, epsilon = 1e-6) {
844
- const operations = [];
845
- const changedPaths = /* @__PURE__ */ new Set();
846
- previousAggregate.forEach((previousValue, path) => {
847
- const nextValue = nextAggregate.get(path);
848
- if (nextValue === void 0 || Math.abs(nextValue - previousValue) > epsilon) {
849
- changedPaths.add(path);
850
- }
851
- });
852
- nextAggregate.forEach((nextValue, path) => {
853
- const previousValue = previousAggregate.get(path);
854
- if (previousValue === void 0 || Math.abs(nextValue - previousValue) > epsilon) {
855
- changedPaths.add(path);
856
- }
857
- });
858
- changedPaths.forEach((path) => {
859
- const nextValue = nextAggregate.get(path);
860
- if (nextValue === void 0) {
861
- operations.push({ kind: "clear", path });
862
- return;
863
- }
864
- operations.push({ kind: "set", path, value: nextValue });
865
- });
866
- return operations;
867
- }
868
1011
 
869
1012
  // src/utils/valueConversion.ts
870
1013
  import {
1014
+ fromAroraValueJSON,
871
1015
  isNormalizedValue,
872
1016
  valueAsBool,
873
1017
  valueAsColorRgba,
@@ -920,27 +1064,28 @@ function valueJSONToRaw(value) {
920
1064
  if (Array.isArray(value)) {
921
1065
  return value.map((entry) => valueJSONToRaw(entry));
922
1066
  }
923
- if (typeof value === "object" && !("type" in value)) {
924
- const entries = Object.entries(value).map(([key, entry]) => [
925
- key,
926
- valueJSONToRaw(entry)
927
- ]);
928
- return Object.fromEntries(entries);
929
- }
930
- if (!isNormalizedValue(value)) {
1067
+ const normalized = isNormalizedValue(value) ? value : fromAroraValueJSON(value);
1068
+ if (normalized === void 0) {
1069
+ if (typeof value === "object" && !("type" in value)) {
1070
+ const entries = Object.entries(value).map(([key, entry]) => [
1071
+ key,
1072
+ valueJSONToRaw(entry)
1073
+ ]);
1074
+ return Object.fromEntries(entries);
1075
+ }
931
1076
  return void 0;
932
1077
  }
933
- switch (value.type) {
1078
+ switch (normalized.type) {
934
1079
  case "float": {
935
- const num = valueAsNumber(value);
1080
+ const num = valueAsNumber(normalized);
936
1081
  return typeof num === "number" ? num : void 0;
937
1082
  }
938
1083
  case "bool": {
939
- const boolVal = valueAsBool(value);
1084
+ const boolVal = valueAsBool(normalized);
940
1085
  return typeof boolVal === "boolean" ? boolVal : void 0;
941
1086
  }
942
1087
  case "text": {
943
- const text = valueAsText(value);
1088
+ const text = valueAsText(normalized);
944
1089
  return typeof text === "string" ? text : void 0;
945
1090
  }
946
1091
  case "vec2":
@@ -948,11 +1093,11 @@ function valueJSONToRaw(value) {
948
1093
  case "vec4":
949
1094
  case "quat":
950
1095
  case "vector": {
951
- const vec = valueAsVector(value);
1096
+ const vec = valueAsVector(normalized);
952
1097
  return vec ? numericArrayToRaw(vec) : void 0;
953
1098
  }
954
1099
  case "colorrgba": {
955
- const color = valueAsColorRgba(value);
1100
+ const color = valueAsColorRgba(normalized);
956
1101
  if (!color) {
957
1102
  return void 0;
958
1103
  }
@@ -960,7 +1105,7 @@ function valueJSONToRaw(value) {
960
1105
  return { r, g, b, a };
961
1106
  }
962
1107
  case "transform": {
963
- const transform = valueAsTransform(value);
1108
+ const transform = valueAsTransform(normalized);
964
1109
  if (!transform) {
965
1110
  return void 0;
966
1111
  }
@@ -970,15 +1115,20 @@ function valueJSONToRaw(value) {
970
1115
  scale: numericArrayToRaw(transform.scale)
971
1116
  };
972
1117
  }
1118
+ case "array":
1119
+ case "list":
1120
+ case "tuple":
1121
+ return (normalized.data ?? []).map(
1122
+ (entry) => valueJSONToRaw(entry)
1123
+ );
973
1124
  case "record": {
974
- const entries = Object.entries(value.data ?? {}).map(([key, entry]) => [
975
- key,
976
- valueJSONToRaw(entry)
977
- ]);
1125
+ const entries = Object.entries(normalized.data ?? {}).map(
1126
+ ([key, entry]) => [key, valueJSONToRaw(entry)]
1127
+ );
978
1128
  return Object.fromEntries(entries);
979
1129
  }
980
1130
  case "enum": {
981
- const [tag, inner] = value.data;
1131
+ const [tag, inner] = normalized.data;
982
1132
  return {
983
1133
  tag,
984
1134
  value: valueJSONToRaw(inner)
@@ -989,17 +1139,608 @@ function valueJSONToRaw(value) {
989
1139
  }
990
1140
  }
991
1141
 
1142
+ // src/engine/animationModule.ts
1143
+ var ANIMATION_MODULE_FN = {
1144
+ loadAnimation: "76697a69-6a00-0000-0f00-000000000001",
1145
+ createPlayer: "76697a69-6a00-0000-0f00-000000000002",
1146
+ addInstance: "76697a69-6a00-0000-0f00-000000000003",
1147
+ step: "76697a69-6a00-0000-0f00-000000000004",
1148
+ play: "76697a69-6a00-0000-0f00-000000000005",
1149
+ pause: "76697a69-6a00-0000-0f00-000000000006",
1150
+ stop: "76697a69-6a00-0000-0f00-000000000007",
1151
+ seek: "76697a69-6a00-0000-0f00-000000000008",
1152
+ setSpeed: "76697a69-6a00-0000-0f00-000000000009",
1153
+ setLoop: "76697a69-6a00-0000-0f00-00000000000a",
1154
+ setWeight: "76697a69-6a00-0000-0f00-00000000000b",
1155
+ removeInstance: "76697a69-6a00-0000-0f00-00000000000c",
1156
+ playerStates: "76697a69-6a00-0000-0f00-00000000000d"
1157
+ };
1158
+ var ANIMATION_MODULE_PARAM = {
1159
+ clip: "76697a69-6a00-0000-0f01-000000000001",
1160
+ playerName: "76697a69-6a00-0000-0f02-000000000001",
1161
+ player: "76697a69-6a00-0000-0f03-000000000001",
1162
+ anim: "76697a69-6a00-0000-0f03-000000000002",
1163
+ dtNs: "76697a69-6a00-0000-0f04-000000000001",
1164
+ playPlayer: "76697a69-6a00-0000-0f05-000000000001",
1165
+ pausePlayer: "76697a69-6a00-0000-0f06-000000000001",
1166
+ stopPlayer: "76697a69-6a00-0000-0f07-000000000001",
1167
+ seekPlayer: "76697a69-6a00-0000-0f08-000000000001",
1168
+ seekTimeNs: "76697a69-6a00-0000-0f08-000000000002",
1169
+ speedPlayer: "76697a69-6a00-0000-0f09-000000000001",
1170
+ speedValue: "76697a69-6a00-0000-0f09-000000000002",
1171
+ loopPlayer: "76697a69-6a00-0000-0f0a-000000000001",
1172
+ loopMode: "76697a69-6a00-0000-0f0a-000000000002",
1173
+ weightPlayer: "76697a69-6a00-0000-0f0b-000000000001",
1174
+ weightInstance: "76697a69-6a00-0000-0f0b-000000000002",
1175
+ weightValue: "76697a69-6a00-0000-0f0b-000000000003",
1176
+ removePlayer: "76697a69-6a00-0000-0f0c-000000000001",
1177
+ removeInstance: "76697a69-6a00-0000-0f0c-000000000002"
1178
+ };
1179
+ var ANIMATION_MODULE_TYPE = {
1180
+ clip: "76697a69-6a00-0000-0000-000000000100",
1181
+ track: "76697a69-6a00-0000-0000-000000000101",
1182
+ keypoint: "76697a69-6a00-0000-0000-000000000102",
1183
+ transitionHandle: "76697a69-6a00-0000-0000-000000000103",
1184
+ trackOutput: "76697a69-6a00-0000-0000-000000000110",
1185
+ playerState: "76697a69-6a00-0000-0000-000000000111"
1186
+ };
1187
+ var ANIMATION_MODULE_FIELD = {
1188
+ clipName: "76697a69-6a00-0000-0100-000000000001",
1189
+ clipDuration: "76697a69-6a00-0000-0100-000000000002",
1190
+ clipTracks: "76697a69-6a00-0000-0100-000000000003",
1191
+ trackId: "76697a69-6a00-0000-0101-000000000001",
1192
+ trackName: "76697a69-6a00-0000-0101-000000000002",
1193
+ trackAnimatable: "76697a69-6a00-0000-0101-000000000003",
1194
+ trackPoints: "76697a69-6a00-0000-0101-000000000004",
1195
+ keypointId: "76697a69-6a00-0000-0102-000000000001",
1196
+ keypointStamp: "76697a69-6a00-0000-0102-000000000002",
1197
+ keypointValue: "76697a69-6a00-0000-0102-000000000003",
1198
+ keypointTransitionsIn: "76697a69-6a00-0000-0102-000000000004",
1199
+ keypointTransitionsOut: "76697a69-6a00-0000-0102-000000000005",
1200
+ handleX: "76697a69-6a00-0000-0103-000000000001",
1201
+ handleY: "76697a69-6a00-0000-0103-000000000002",
1202
+ outputTrackId: "76697a69-6a00-0000-0110-000000000001",
1203
+ outputDefaultKey: "76697a69-6a00-0000-0110-000000000002",
1204
+ outputValue: "76697a69-6a00-0000-0110-000000000003",
1205
+ statePlayer: "76697a69-6a00-0000-0111-000000000001",
1206
+ stateState: "76697a69-6a00-0000-0111-000000000002",
1207
+ stateTimeNs: "76697a69-6a00-0000-0111-000000000003",
1208
+ stateDurationNs: "76697a69-6a00-0000-0111-000000000004",
1209
+ stateSpeed: "76697a69-6a00-0000-0111-000000000005"
1210
+ };
1211
+ var field = (id, value) => ({
1212
+ id,
1213
+ value
1214
+ });
1215
+ function storedClipToModuleValue(clip, resolveKeys) {
1216
+ const name = typeof clip.name === "string" && clip.name.trim().length > 0 ? clip.name.trim() : typeof clip.id === "string" ? clip.id : "";
1217
+ const durationMs = Number(clip.duration);
1218
+ const duration = Number.isFinite(durationMs) && durationMs > 0 ? Math.round(durationMs) : 1;
1219
+ const tracks = (Array.isArray(clip.tracks) ? clip.tracks : []).map((rawTrack, index) => {
1220
+ if (!rawTrack || typeof rawTrack !== "object") {
1221
+ return null;
1222
+ }
1223
+ const animatableId = typeof rawTrack.animatableId === "string" ? rawTrack.animatableId.trim() : "";
1224
+ if (!animatableId) {
1225
+ return null;
1226
+ }
1227
+ const trackId = typeof rawTrack.id === "string" && rawTrack.id.trim().length > 0 ? rawTrack.id.trim() : `track-${index}`;
1228
+ const trackName = typeof rawTrack.name === "string" && rawTrack.name.trim().length > 0 ? rawTrack.name.trim() : trackId;
1229
+ const points = (Array.isArray(rawTrack.points) ? rawTrack.points : []).map((rawPoint, pointIndex) => {
1230
+ if (!rawPoint || typeof rawPoint !== "object") {
1231
+ return null;
1232
+ }
1233
+ const stamp = Number(rawPoint.stamp);
1234
+ const value = Number(rawPoint.value);
1235
+ if (!Number.isFinite(stamp) || !Number.isFinite(value)) {
1236
+ return null;
1237
+ }
1238
+ const pointId = typeof rawPoint.id === "string" && rawPoint.id.trim().length > 0 ? rawPoint.id.trim() : `${trackId}:point-${pointIndex}`;
1239
+ const handles = (handle) => ({
1240
+ structs: {
1241
+ id: ANIMATION_MODULE_TYPE.transitionHandle,
1242
+ elements: handle ? [
1243
+ {
1244
+ fields: [
1245
+ field(ANIMATION_MODULE_FIELD.handleX, {
1246
+ f32: handle.x
1247
+ }),
1248
+ field(ANIMATION_MODULE_FIELD.handleY, {
1249
+ f32: handle.y
1250
+ })
1251
+ ]
1252
+ }
1253
+ ] : []
1254
+ }
1255
+ });
1256
+ return {
1257
+ fields: [
1258
+ field(ANIMATION_MODULE_FIELD.keypointId, { str: pointId }),
1259
+ field(ANIMATION_MODULE_FIELD.keypointStamp, {
1260
+ f32: Math.max(0, Math.min(1, stamp))
1261
+ }),
1262
+ field(ANIMATION_MODULE_FIELD.keypointValue, { f32: value }),
1263
+ field(
1264
+ ANIMATION_MODULE_FIELD.keypointTransitionsIn,
1265
+ handles(rawPoint.transitions?.in)
1266
+ ),
1267
+ field(
1268
+ ANIMATION_MODULE_FIELD.keypointTransitionsOut,
1269
+ handles(rawPoint.transitions?.out)
1270
+ )
1271
+ ]
1272
+ };
1273
+ }).filter(Boolean);
1274
+ if (points.length === 0) {
1275
+ return null;
1276
+ }
1277
+ const targets = resolveKeys?.(animatableId) ?? [animatableId];
1278
+ return targets.map((target, targetIndex) => ({
1279
+ fields: [
1280
+ field(ANIMATION_MODULE_FIELD.trackId, {
1281
+ str: targetIndex === 0 ? trackId : `${trackId}~${targetIndex}`
1282
+ }),
1283
+ field(ANIMATION_MODULE_FIELD.trackName, { str: trackName }),
1284
+ field(ANIMATION_MODULE_FIELD.trackAnimatable, { str: target }),
1285
+ field(ANIMATION_MODULE_FIELD.trackPoints, {
1286
+ structs: {
1287
+ id: ANIMATION_MODULE_TYPE.keypoint,
1288
+ elements: points
1289
+ }
1290
+ })
1291
+ ]
1292
+ }));
1293
+ }).filter(Boolean).flat();
1294
+ return {
1295
+ struct: {
1296
+ id: ANIMATION_MODULE_TYPE.clip,
1297
+ fields: [
1298
+ field(ANIMATION_MODULE_FIELD.clipName, { str: name }),
1299
+ field(ANIMATION_MODULE_FIELD.clipDuration, { u32: duration }),
1300
+ field(ANIMATION_MODULE_FIELD.clipTracks, {
1301
+ structs: {
1302
+ id: ANIMATION_MODULE_TYPE.track,
1303
+ elements: tracks
1304
+ }
1305
+ })
1306
+ ]
1307
+ }
1308
+ };
1309
+ }
1310
+ function loadAnimationCall(clipValue) {
1311
+ return {
1312
+ id: ANIMATION_MODULE_FN.loadAnimation,
1313
+ args: [field(ANIMATION_MODULE_PARAM.clip, clipValue)]
1314
+ };
1315
+ }
1316
+ function createPlayerCall(name) {
1317
+ return {
1318
+ id: ANIMATION_MODULE_FN.createPlayer,
1319
+ args: [field(ANIMATION_MODULE_PARAM.playerName, { str: name })]
1320
+ };
1321
+ }
1322
+ function addInstanceCall(player, anim) {
1323
+ return {
1324
+ id: ANIMATION_MODULE_FN.addInstance,
1325
+ args: [
1326
+ field(ANIMATION_MODULE_PARAM.player, { u32: player }),
1327
+ field(ANIMATION_MODULE_PARAM.anim, { u32: anim })
1328
+ ]
1329
+ };
1330
+ }
1331
+ function playCall(player) {
1332
+ return {
1333
+ id: ANIMATION_MODULE_FN.play,
1334
+ args: [field(ANIMATION_MODULE_PARAM.playPlayer, { u32: player })]
1335
+ };
1336
+ }
1337
+ function pauseCall(player) {
1338
+ return {
1339
+ id: ANIMATION_MODULE_FN.pause,
1340
+ args: [field(ANIMATION_MODULE_PARAM.pausePlayer, { u32: player })]
1341
+ };
1342
+ }
1343
+ function stopCall(player) {
1344
+ return {
1345
+ id: ANIMATION_MODULE_FN.stop,
1346
+ args: [field(ANIMATION_MODULE_PARAM.stopPlayer, { u32: player })]
1347
+ };
1348
+ }
1349
+ function seekCall(player, timeNs) {
1350
+ return {
1351
+ id: ANIMATION_MODULE_FN.seek,
1352
+ args: [
1353
+ field(ANIMATION_MODULE_PARAM.seekPlayer, { u32: player }),
1354
+ field(ANIMATION_MODULE_PARAM.seekTimeNs, {
1355
+ u64: Math.max(0, Math.round(timeNs))
1356
+ })
1357
+ ]
1358
+ };
1359
+ }
1360
+ function setSpeedCall(player, speed) {
1361
+ return {
1362
+ id: ANIMATION_MODULE_FN.setSpeed,
1363
+ args: [
1364
+ field(ANIMATION_MODULE_PARAM.speedPlayer, { u32: player }),
1365
+ field(ANIMATION_MODULE_PARAM.speedValue, { f32: speed })
1366
+ ]
1367
+ };
1368
+ }
1369
+ function setLoopCall(player, mode) {
1370
+ return {
1371
+ id: ANIMATION_MODULE_FN.setLoop,
1372
+ args: [
1373
+ field(ANIMATION_MODULE_PARAM.loopPlayer, { u32: player }),
1374
+ field(ANIMATION_MODULE_PARAM.loopMode, { str: mode })
1375
+ ]
1376
+ };
1377
+ }
1378
+ function setWeightCall(player, instance, weight) {
1379
+ return {
1380
+ id: ANIMATION_MODULE_FN.setWeight,
1381
+ args: [
1382
+ field(ANIMATION_MODULE_PARAM.weightPlayer, { u32: player }),
1383
+ field(ANIMATION_MODULE_PARAM.weightInstance, { u32: instance }),
1384
+ field(ANIMATION_MODULE_PARAM.weightValue, { f32: weight })
1385
+ ]
1386
+ };
1387
+ }
1388
+ function callResultU32(result) {
1389
+ const ret = result.ret;
1390
+ if (ret && typeof ret === "object" && "u32" in ret) {
1391
+ const value = Number(ret.u32);
1392
+ return Number.isFinite(value) ? value : null;
1393
+ }
1394
+ return null;
1395
+ }
1396
+ var ANIMATION_PLAYERS_PATH = "vizij/animations/players";
1397
+ var ANIMATIONS_SOURCE_ID = "animations";
1398
+ function animationsGraphSource() {
1399
+ return {
1400
+ sourceId: ANIMATIONS_SOURCE_ID,
1401
+ spec: {
1402
+ nodes: [
1403
+ { id: "dt", type: "input", params: { path: "arora/dt" } },
1404
+ {
1405
+ id: "step",
1406
+ type: "externalfunction",
1407
+ params: {
1408
+ function: ANIMATION_MODULE_FN.step,
1409
+ param_ids: [ANIMATION_MODULE_PARAM.dtNs]
1410
+ }
1411
+ },
1412
+ {
1413
+ id: "apply",
1414
+ type: "output",
1415
+ params: {
1416
+ key_field: ANIMATION_MODULE_FIELD.outputDefaultKey,
1417
+ value_field: ANIMATION_MODULE_FIELD.outputValue
1418
+ }
1419
+ },
1420
+ {
1421
+ id: "states",
1422
+ type: "externalfunction",
1423
+ params: { function: ANIMATION_MODULE_FN.playerStates, param_ids: [] }
1424
+ },
1425
+ {
1426
+ id: "states-out",
1427
+ type: "output",
1428
+ params: { path: ANIMATION_PLAYERS_PATH }
1429
+ }
1430
+ ],
1431
+ edges: [
1432
+ { from: { node_id: "dt" }, to: { node_id: "step", input: "args_0" } },
1433
+ { from: { node_id: "step" }, to: { node_id: "apply", input: "in" } },
1434
+ {
1435
+ from: { node_id: "states" },
1436
+ to: { node_id: "states-out", input: "in" }
1437
+ }
1438
+ ]
1439
+ }
1440
+ };
1441
+ }
1442
+ function fieldNumber(value) {
1443
+ if (!value || typeof value !== "object") {
1444
+ return null;
1445
+ }
1446
+ for (const key of ["u64", "u32", "f32", "f64"]) {
1447
+ if (key in value) {
1448
+ const parsed = Number(value[key]);
1449
+ return Number.isFinite(parsed) ? parsed : null;
1450
+ }
1451
+ }
1452
+ return null;
1453
+ }
1454
+ function decodePlayerStates(raw) {
1455
+ if (!raw || typeof raw !== "object" || !("structs" in raw)) {
1456
+ return [];
1457
+ }
1458
+ const structs = raw.structs;
1459
+ const elements = Array.isArray(structs?.elements) ? structs.elements : [];
1460
+ const states = [];
1461
+ for (const element of elements) {
1462
+ const fields = element?.fields;
1463
+ if (!Array.isArray(fields)) {
1464
+ continue;
1465
+ }
1466
+ const byId = /* @__PURE__ */ new Map();
1467
+ for (const entry of fields) {
1468
+ const id = entry?.id;
1469
+ const value = entry?.value;
1470
+ if (typeof id === "string" && value && typeof value === "object") {
1471
+ byId.set(id, value);
1472
+ }
1473
+ }
1474
+ const player = fieldNumber(byId.get(ANIMATION_MODULE_FIELD.statePlayer));
1475
+ if (player === null) {
1476
+ continue;
1477
+ }
1478
+ const stateValue = byId.get(ANIMATION_MODULE_FIELD.stateState);
1479
+ states.push({
1480
+ player,
1481
+ state: stateValue && "str" in stateValue ? String(stateValue.str) : "",
1482
+ time: (fieldNumber(byId.get(ANIMATION_MODULE_FIELD.stateTimeNs)) ?? 0) / 1e9,
1483
+ duration: (fieldNumber(byId.get(ANIMATION_MODULE_FIELD.stateDurationNs)) ?? 0) / 1e9,
1484
+ speed: fieldNumber(byId.get(ANIMATION_MODULE_FIELD.stateSpeed)) ?? 1
1485
+ });
1486
+ }
1487
+ return states;
1488
+ }
1489
+
1490
+ // src/engine/animationModuleHost.ts
1491
+ var AnimationModuleHost = class {
1492
+ constructor(getDevice, resolveKeys = (key) => [key]) {
1493
+ this.getDevice = getDevice;
1494
+ this.resolveKeys = resolveKeys;
1495
+ this.clips = /* @__PURE__ */ new Map();
1496
+ }
1497
+ /** Whether any clip is currently marked playing (drives source lifecycle). */
1498
+ hasPlaying() {
1499
+ for (const entry of this.clips.values()) {
1500
+ if (entry.playing) {
1501
+ return true;
1502
+ }
1503
+ }
1504
+ return false;
1505
+ }
1506
+ /** Whether a clip id is known to the host. */
1507
+ has(clipId) {
1508
+ return this.clips.has(clipId);
1509
+ }
1510
+ /** The clip's module `PlayerId` on the live device (feedback correlation). */
1511
+ playerIdOf(clipId) {
1512
+ return this.clips.get(clipId)?.playerId ?? null;
1513
+ }
1514
+ /** The clip's transport state as last commanded. */
1515
+ transportOf(clipId) {
1516
+ const entry = this.clips.get(clipId);
1517
+ if (!entry) {
1518
+ return null;
1519
+ }
1520
+ return {
1521
+ playing: entry.playing,
1522
+ speed: entry.speed,
1523
+ loop: entry.loop,
1524
+ weight: entry.weight
1525
+ };
1526
+ }
1527
+ /**
1528
+ * Declare the clips the bundle carries, as data. Keeps playing/transport
1529
+ * state for clips that survive; clips no longer present are dropped. Does
1530
+ * not touch the device — loading is lazy (on first play), so muted/unused
1531
+ * clips never enter the module, and conversion happens at load with the
1532
+ * key resolver's state at that moment.
1533
+ */
1534
+ setClips(clips) {
1535
+ const nextIds = new Set(clips.map((clip) => clip.id));
1536
+ for (const id of Array.from(this.clips.keys())) {
1537
+ if (!nextIds.has(id)) {
1538
+ this.clips.delete(id);
1539
+ }
1540
+ }
1541
+ for (const clip of clips) {
1542
+ const existing = this.clips.get(clip.id);
1543
+ if (existing) {
1544
+ existing.stored = clip.stored;
1545
+ existing.animId = null;
1546
+ existing.playerId = null;
1547
+ existing.instId = null;
1548
+ } else {
1549
+ this.clips.set(clip.id, {
1550
+ stored: clip.stored,
1551
+ playing: false,
1552
+ speed: 1,
1553
+ loop: "loop",
1554
+ weight: 1,
1555
+ animId: null,
1556
+ playerId: null,
1557
+ instId: null
1558
+ });
1559
+ }
1560
+ }
1561
+ }
1562
+ /**
1563
+ * Mark a clip playing, ensure it is loaded into the live device with a
1564
+ * player + instance, and issue `play` (a resume after `pause` needs it; on
1565
+ * a fresh player it is a no-op). Idempotent. Resolves once the calls have
1566
+ * dispatched (or immediately when there is no live device yet — the
1567
+ * provider registers the source, which boots the device and replays via
1568
+ * `replayInto`).
1569
+ */
1570
+ async play(clipId) {
1571
+ const entry = this.clips.get(clipId);
1572
+ if (!entry) {
1573
+ return;
1574
+ }
1575
+ entry.playing = true;
1576
+ const device = this.getDevice();
1577
+ if (!device) {
1578
+ return;
1579
+ }
1580
+ await this.ensureLoaded(device, entry);
1581
+ if (entry.playerId !== null) {
1582
+ await device.call(playCall(entry.playerId));
1583
+ }
1584
+ }
1585
+ /** Hold the clip's playhead (`pause` on its player). */
1586
+ pause(clipId) {
1587
+ const entry = this.clips.get(clipId);
1588
+ if (!entry) {
1589
+ return;
1590
+ }
1591
+ entry.playing = false;
1592
+ this.dispatch(
1593
+ (device) => entry.playerId !== null ? device.call(pauseCall(entry.playerId)) : null
1594
+ );
1595
+ }
1596
+ /**
1597
+ * Stop a clip: not playing, playhead reset to the window start (`stop` on
1598
+ * its player — the next tick emits the clip's t=0 pose). The clip stays
1599
+ * loaded for a cheap replay.
1600
+ */
1601
+ stop(clipId) {
1602
+ const entry = this.clips.get(clipId);
1603
+ if (!entry) {
1604
+ return;
1605
+ }
1606
+ entry.playing = false;
1607
+ this.dispatch(
1608
+ (device) => entry.playerId !== null ? device.call(stopCall(entry.playerId)) : null
1609
+ );
1610
+ }
1611
+ /** Move the clip's playhead to `seconds`. */
1612
+ seek(clipId, seconds) {
1613
+ const entry = this.clips.get(clipId);
1614
+ if (!entry) {
1615
+ return;
1616
+ }
1617
+ this.dispatch(
1618
+ (device) => entry.playerId !== null ? device.call(seekCall(entry.playerId, seconds * 1e9)) : null
1619
+ );
1620
+ }
1621
+ /** Set the clip's playback speed multiplier. */
1622
+ setSpeed(clipId, speed) {
1623
+ const entry = this.clips.get(clipId);
1624
+ if (!entry) {
1625
+ return;
1626
+ }
1627
+ entry.speed = speed;
1628
+ this.dispatch(
1629
+ (device) => entry.playerId !== null ? device.call(setSpeedCall(entry.playerId, speed)) : null
1630
+ );
1631
+ }
1632
+ /** Set how the clip's player time maps into clip time. */
1633
+ setLoop(clipId, mode) {
1634
+ const entry = this.clips.get(clipId);
1635
+ if (!entry) {
1636
+ return;
1637
+ }
1638
+ entry.loop = mode;
1639
+ this.dispatch(
1640
+ (device) => entry.playerId !== null ? device.call(setLoopCall(entry.playerId, mode)) : null
1641
+ );
1642
+ }
1643
+ /** Set the clip instance's blend weight. */
1644
+ setWeight(clipId, weight) {
1645
+ const entry = this.clips.get(clipId);
1646
+ if (!entry) {
1647
+ return;
1648
+ }
1649
+ entry.weight = weight;
1650
+ this.dispatch(
1651
+ (device) => entry.playerId !== null && entry.instId !== null ? device.call(setWeightCall(entry.playerId, entry.instId, weight)) : null
1652
+ );
1653
+ }
1654
+ /**
1655
+ * Re-issue setup and transport for every playing clip against a freshly
1656
+ * started device (module guest state did not survive the rebuild). Voids
1657
+ * stale ids first. Called from `DeviceSlot.onDeviceStarted`.
1658
+ */
1659
+ replayInto(device) {
1660
+ for (const entry of this.clips.values()) {
1661
+ entry.animId = null;
1662
+ entry.playerId = null;
1663
+ entry.instId = null;
1664
+ if (entry.playing) {
1665
+ void this.ensureLoaded(device, entry);
1666
+ }
1667
+ }
1668
+ }
1669
+ /** Fire-and-forget a transport call against the live device, if any. */
1670
+ dispatch(issue) {
1671
+ const device = this.getDevice();
1672
+ if (!device) {
1673
+ return;
1674
+ }
1675
+ void issue(device)?.catch(() => {
1676
+ });
1677
+ }
1678
+ async ensureLoaded(device, entry) {
1679
+ if (entry.instId !== null) {
1680
+ return;
1681
+ }
1682
+ if (entry.animId === null) {
1683
+ const anim = await device.call(
1684
+ loadAnimationCall(
1685
+ storedClipToModuleValue(entry.stored, this.resolveKeys)
1686
+ )
1687
+ );
1688
+ entry.animId = callResultU32(anim);
1689
+ }
1690
+ if (entry.playerId === null) {
1691
+ const player = await device.call(createPlayerCall(""));
1692
+ entry.playerId = callResultU32(player);
1693
+ }
1694
+ if (entry.instId === null && entry.animId !== null && entry.playerId !== null) {
1695
+ const inst = await device.call(
1696
+ addInstanceCall(entry.playerId, entry.animId)
1697
+ );
1698
+ entry.instId = callResultU32(inst);
1699
+ }
1700
+ const followups = [];
1701
+ if (entry.playerId !== null) {
1702
+ if (entry.speed !== 1) {
1703
+ followups.push(setSpeedCall(entry.playerId, entry.speed));
1704
+ }
1705
+ if (entry.loop !== "loop") {
1706
+ followups.push(setLoopCall(entry.playerId, entry.loop));
1707
+ }
1708
+ if (entry.instId !== null && entry.weight !== 1) {
1709
+ followups.push(
1710
+ setWeightCall(entry.playerId, entry.instId, entry.weight)
1711
+ );
1712
+ }
1713
+ }
1714
+ for (const call of followups) {
1715
+ await device.call(call);
1716
+ }
1717
+ }
1718
+ };
1719
+
992
1720
  // src/VizijRuntimeProvider.tsx
993
1721
  import { jsx } from "react/jsx-runtime";
994
1722
  var ACTIVE_GRACE_MS = 250;
995
1723
  var VISIBLE_IDLE_FPS = 30;
996
1724
  var HIDDEN_IDLE_FPS = 1;
1725
+ var IMPLICIT_PAUSE_GAP_S = 5;
1726
+ var inferImplicitPause = (dt) => {
1727
+ if (dt <= IMPLICIT_PAUSE_GAP_S) {
1728
+ return dt;
1729
+ }
1730
+ console.warn(
1731
+ `[vizij-runtime] step loop woke after a ${dt.toFixed(
1732
+ 1
1733
+ )}s wall-clock gap; treating it as a suspension (dt = 0)`
1734
+ );
1735
+ return 0;
1736
+ };
997
1737
  var DEFAULT_MERGE = {
998
1738
  outputs: "add",
999
1739
  intermediate: "add"
1000
1740
  };
1001
1741
  var DEFAULT_DURATION = 0.35;
1002
1742
  var POSE_CONTROL_BRIDGE_EPSILON = 1e-6;
1743
+ var runtimeDebugInstanceSequence = 0;
1003
1744
  var DEV_MODE = (() => {
1004
1745
  const nodeEnv = globalThis.process?.env?.NODE_ENV;
1005
1746
  return typeof nodeEnv === "string" && nodeEnv === "development";
@@ -1490,12 +2231,15 @@ function pickExtractedAnimations(asset) {
1490
2231
  }
1491
2232
  return animations;
1492
2233
  }
1493
- function mergeAnimationLists(explicit, fromBundle) {
1494
- if (!explicit?.length && fromBundle.length === 0) {
2234
+ function mergeAnimationLists(explicit, hasExplicitOverride, fromBundle) {
2235
+ if (!hasExplicitOverride) {
2236
+ return fromBundle.length > 0 ? fromBundle : void 0;
2237
+ }
2238
+ if (!Array.isArray(explicit)) {
1495
2239
  return void 0;
1496
2240
  }
1497
- if (!explicit?.length) {
1498
- return fromBundle.length > 0 ? fromBundle : void 0;
2241
+ if (explicit.length === 0) {
2242
+ return [];
1499
2243
  }
1500
2244
  if (fromBundle.length === 0) {
1501
2245
  return explicit;
@@ -1546,13 +2290,68 @@ function convertBundlePrograms(entries) {
1546
2290
  };
1547
2291
  }).filter(Boolean);
1548
2292
  }
1549
- function mergeProgramLists(explicit, fromBundle) {
1550
- if (!explicit?.length && fromBundle.length === 0) {
2293
+ function resolveConstraintDefault(path, namespace, inputConstraints) {
2294
+ const trimmed = path.trim();
2295
+ if (!trimmed) {
1551
2296
  return void 0;
1552
2297
  }
1553
- if (!explicit?.length) {
2298
+ const stripped = stripRigFacePrefix(trimmed);
2299
+ const relativePath = stripped ? `/${stripped}` : "";
2300
+ const candidates = [
2301
+ namespaceTypedPath(trimmed, namespace),
2302
+ trimmed,
2303
+ stripped ? namespaceTypedPath(stripped, namespace) : void 0,
2304
+ stripped || void 0,
2305
+ relativePath || void 0
2306
+ ];
2307
+ for (const candidate of candidates) {
2308
+ if (!candidate) {
2309
+ continue;
2310
+ }
2311
+ const defaultValue = inputConstraints[candidate]?.defaultValue;
2312
+ if (Number.isFinite(defaultValue)) {
2313
+ return Number(defaultValue);
2314
+ }
2315
+ }
2316
+ return void 0;
2317
+ }
2318
+ function deriveProgramInputSeedValues(args) {
2319
+ const graphSpec = resolveGraphSpec(
2320
+ args.program.graph,
2321
+ `${args.program.id ?? "program"} graph (seed defaults)`
2322
+ );
2323
+ if (!graphSpec) {
2324
+ return [];
2325
+ }
2326
+ return collectInputPaths(graphSpec).map((path) => path.trim()).filter((path) => path.length > 0).flatMap((path) => {
2327
+ const namespacedPath = namespaceTypedPath(path, args.namespace);
2328
+ if (args.stagedInputs.has(namespacedPath)) {
2329
+ return [];
2330
+ }
2331
+ if (args.getPathSnapshot(namespacedPath) !== void 0) {
2332
+ return [];
2333
+ }
2334
+ const defaultValue = resolveConstraintDefault(
2335
+ path,
2336
+ args.namespace,
2337
+ args.inputConstraints
2338
+ );
2339
+ if (!Number.isFinite(defaultValue)) {
2340
+ return [];
2341
+ }
2342
+ return [{ path, value: { float: Number(defaultValue) } }];
2343
+ });
2344
+ }
2345
+ function mergeProgramLists(explicit, hasExplicitOverride, fromBundle) {
2346
+ if (!hasExplicitOverride) {
1554
2347
  return fromBundle.length > 0 ? fromBundle : void 0;
1555
2348
  }
2349
+ if (!Array.isArray(explicit)) {
2350
+ return void 0;
2351
+ }
2352
+ if (explicit.length === 0) {
2353
+ return [];
2354
+ }
1556
2355
  if (fromBundle.length === 0) {
1557
2356
  return explicit;
1558
2357
  }
@@ -1571,10 +2370,23 @@ function mergeProgramLists(explicit, fromBundle) {
1571
2370
  }
1572
2371
  function mergeAssetBundle(base, extracted, extractedAnimations) {
1573
2372
  const resolvedBundle = base.bundle ?? extracted ?? null;
2373
+ const hasBaseRigOverride = Object.prototype.hasOwnProperty.call(base, "rig");
2374
+ const hasBasePoseOverride = Object.prototype.hasOwnProperty.call(
2375
+ base,
2376
+ "pose"
2377
+ );
2378
+ const hasBaseAnimationsOverride = Object.prototype.hasOwnProperty.call(
2379
+ base,
2380
+ "animations"
2381
+ );
2382
+ const hasBaseProgramsOverride = Object.prototype.hasOwnProperty.call(
2383
+ base,
2384
+ "programs"
2385
+ );
1574
2386
  const rigFromBundle = convertBundleGraph(
1575
2387
  pickBundleGraph(resolvedBundle, ["rig"])
1576
2388
  );
1577
- const resolvedRig = base.rig ?? rigFromBundle ?? void 0;
2389
+ const resolvedRig = hasBaseRigOverride ? base.rig : base.rig ?? rigFromBundle ?? void 0;
1578
2390
  const basePose = base.pose;
1579
2391
  const hasBasePoseGraphOverride = Boolean(
1580
2392
  basePose && Object.prototype.hasOwnProperty.call(basePose, "graph")
@@ -1583,13 +2395,15 @@ function mergeAssetBundle(base, extracted, extractedAnimations) {
1583
2395
  basePose && Object.prototype.hasOwnProperty.call(basePose, "config")
1584
2396
  );
1585
2397
  const poseStageFilter = basePose?.stageNeutralFilter;
1586
- const poseGraphFromBundle = hasBasePoseGraphOverride ? null : convertBundleGraph(
2398
+ const poseGraphFromBundle = hasBasePoseOverride && !basePose ? null : hasBasePoseGraphOverride ? null : convertBundleGraph(
1587
2399
  pickBundleGraph(resolvedBundle, ["pose-driver", "pose"])
1588
2400
  );
1589
2401
  const resolvedPoseGraph = hasBasePoseGraphOverride ? basePose?.graph : basePose?.graph ?? poseGraphFromBundle ?? void 0;
1590
2402
  const resolvedPoseConfig = hasBasePoseConfigOverride ? basePose?.config : basePose?.config ?? resolvedBundle?.poses?.config ?? void 0;
1591
2403
  let resolvedPose = basePose;
1592
- if (basePose) {
2404
+ if (hasBasePoseOverride && !basePose) {
2405
+ resolvedPose = void 0;
2406
+ } else if (basePose) {
1593
2407
  const nextPose = { ...basePose };
1594
2408
  let changed = false;
1595
2409
  if (resolvedPoseGraph && basePose.graph !== resolvedPoseGraph) {
@@ -1617,17 +2431,20 @@ function mergeAssetBundle(base, extracted, extractedAnimations) {
1617
2431
  );
1618
2432
  let resolvedAnimations = mergeAnimationLists(
1619
2433
  base.animations,
2434
+ hasBaseAnimationsOverride,
1620
2435
  animationsFromBundle
1621
2436
  );
1622
2437
  const animationsFromAsset = extractedAnimations && extractedAnimations.length > 0 ? extractedAnimations : [];
1623
2438
  if (animationsFromAsset.length > 0) {
1624
2439
  resolvedAnimations = mergeAnimationLists(
1625
2440
  resolvedAnimations,
2441
+ true,
1626
2442
  animationsFromAsset
1627
2443
  );
1628
2444
  }
1629
2445
  const programsFromBundle = mergeProgramLists(
1630
2446
  base.programs,
2447
+ hasBaseProgramsOverride,
1631
2448
  convertBundlePrograms(resolvedBundle?.graphs)
1632
2449
  );
1633
2450
  const merged = {
@@ -1741,28 +2558,18 @@ function VizijRuntimeProvider({
1741
2558
  faceId: faceIdProp,
1742
2559
  updateTier = "auto",
1743
2560
  autoCreate = true,
1744
- createOptions,
1745
2561
  autostart = false,
1746
- driveOrchestrator = true,
2562
+ driveRuntime = true,
1747
2563
  mergeStrategy,
1748
2564
  onRegisterControllers,
1749
2565
  onStatusChange,
1750
- transformOutputWrite,
1751
- orchestratorScope = "auto"
2566
+ transformOutputWrite
1752
2567
  }) {
1753
2568
  const storeRef = useRef(null);
1754
2569
  if (!storeRef.current) {
1755
2570
  storeRef.current = createVizijStore();
1756
2571
  }
1757
- const parentOrchestrator = useContext(OrchestratorContext);
1758
- const hasParentOrchestrator = Boolean(parentOrchestrator);
1759
- const shouldProvideOrchestrator = orchestratorScope === "isolated" || !hasParentOrchestrator && orchestratorScope !== "shared";
1760
- if (orchestratorScope === "shared" && !hasParentOrchestrator) {
1761
- console.warn(
1762
- '[vizij-runtime] orchestratorScope="shared" requires an OrchestratorProvider higher in the tree; falling back to an isolated provider.'
1763
- );
1764
- }
1765
- const runtimeTree = /* @__PURE__ */ jsx(VizijContext.Provider, { value: storeRef.current, children: /* @__PURE__ */ jsx(
2572
+ return /* @__PURE__ */ jsx(VizijContext.Provider, { value: storeRef.current, children: /* @__PURE__ */ jsx(
1766
2573
  VizijRuntimeProviderInner,
1767
2574
  {
1768
2575
  assetBundle,
@@ -1771,8 +2578,7 @@ function VizijRuntimeProvider({
1771
2578
  updateTier,
1772
2579
  autoCreate,
1773
2580
  autostart,
1774
- driveOrchestrator,
1775
- createOptions,
2581
+ driveRuntime,
1776
2582
  mergeStrategy,
1777
2583
  onRegisterControllers,
1778
2584
  onStatusChange,
@@ -1781,18 +2587,6 @@ function VizijRuntimeProvider({
1781
2587
  children
1782
2588
  }
1783
2589
  ) });
1784
- if (!shouldProvideOrchestrator) {
1785
- return runtimeTree;
1786
- }
1787
- return /* @__PURE__ */ jsx(
1788
- OrchestratorProvider,
1789
- {
1790
- autoCreate,
1791
- createOptions,
1792
- autostart: false,
1793
- children: runtimeTree
1794
- }
1795
- );
1796
2590
  }
1797
2591
  function VizijRuntimeProviderInner({
1798
2592
  assetBundle: initialAssetBundle,
@@ -1807,8 +2601,7 @@ function VizijRuntimeProviderInner({
1807
2601
  children,
1808
2602
  autoCreate,
1809
2603
  autostart,
1810
- createOptions,
1811
- driveOrchestrator
2604
+ driveRuntime
1812
2605
  }) {
1813
2606
  const [assetBundleOverride, setAssetBundleOverride] = useState(null);
1814
2607
  const [graphUpdateToken, setGraphUpdateToken] = useState(0);
@@ -1870,23 +2663,17 @@ function VizijRuntimeProviderInner({
1870
2663
  setGraphUpdateToken((prev) => prev + 1);
1871
2664
  }
1872
2665
  }, [effectiveAssetBundle]);
1873
- const {
1874
- ready,
1875
- createOrchestrator,
1876
- registerGraph,
1877
- registerMergedGraph,
1878
- registerAnimation,
1879
- removeGraph,
1880
- removeAnimation,
1881
- removeInput,
1882
- listControllers,
1883
- setInput: orchestratorSetInput,
1884
- getPathSnapshot,
1885
- step: stepRuntime
1886
- } = useOrchestrator();
1887
- const frame = useOrchFrame();
2666
+ const deviceSlotRef = useRef(null);
2667
+ if (!deviceSlotRef.current) {
2668
+ deviceSlotRef.current = new DeviceSlot();
2669
+ }
2670
+ const deviceSlot = deviceSlotRef.current;
2671
+ const [ready, setReady] = useState(false);
2672
+ const graphSourcesRef = useRef([]);
1888
2673
  const namespace = namespaceProp ?? assetBundle.namespace ?? "default";
1889
2674
  const faceId = faceIdProp ?? assetBundle.faceId ?? assetBundle.pose?.config?.faceId ?? assetBundle.pose?.config?.faceId ?? void 0;
2675
+ const faceIdRef = useRef(faceId);
2676
+ faceIdRef.current = faceId;
1890
2677
  const [status, setStatus] = useState({
1891
2678
  loading: true,
1892
2679
  ready: false,
@@ -1899,12 +2686,15 @@ function VizijRuntimeProviderInner({
1899
2686
  stepHz: void 0,
1900
2687
  controllers: { graphs: [], anims: [] }
1901
2688
  });
2689
+ const runtimeDebugInstanceIdRef = useRef(
2690
+ `vizij-runtime:${runtimeDebugInstanceSequence++}`
2691
+ );
1902
2692
  const errorsRef = useRef([]);
1903
2693
  const outputPathsRef = useRef(/* @__PURE__ */ new Set());
1904
2694
  const baseOutputPathsRef = useRef(/* @__PURE__ */ new Set());
1905
2695
  const namespacedOutputPathsRef = useRef(/* @__PURE__ */ new Set());
1906
2696
  const namespaceRef = useRef(namespace);
1907
- const driveOrchestratorRef = useRef(driveOrchestrator);
2697
+ const driveRuntimeRef = useRef(driveRuntime);
1908
2698
  const rigInputMapRef = useRef({});
1909
2699
  const rigPoseControlInputIdsRef = useRef(/* @__PURE__ */ new Set());
1910
2700
  const registeredGraphsRef = useRef([]);
@@ -1946,16 +2736,17 @@ function VizijRuntimeProviderInner({
1946
2736
  const [inputConstraints, setInputConstraints] = useState({});
1947
2737
  const inputConstraintsRef = useRef({});
1948
2738
  const avgStepDtRef = useRef(null);
2739
+ const inputDriverIdsRef = useRef(/* @__PURE__ */ new Set());
1949
2740
  const animationTweensRef = useRef(/* @__PURE__ */ new Map());
1950
2741
  const clipPlaybackRef = useRef(/* @__PURE__ */ new Map());
1951
2742
  const programPlaybackRef = useRef(
1952
2743
  /* @__PURE__ */ new Map()
1953
2744
  );
1954
2745
  const programControllerIdsRef = useRef(/* @__PURE__ */ new Map());
1955
- const clipOutputValuesRef = useRef(
1956
- /* @__PURE__ */ new Map()
1957
- );
1958
- const clipAggregateValuesRef = useRef(/* @__PURE__ */ new Map());
2746
+ const animationModuleRef = useRef(null);
2747
+ const animationHostRef = useRef(null);
2748
+ const animationsSourceRegisteredRef = useRef(false);
2749
+ const animationGapWarnedRef = useRef(/* @__PURE__ */ new Set());
1959
2750
  const animationSystemActiveRef = useRef(true);
1960
2751
  const stagedInputsRef = useRef(/* @__PURE__ */ new Map());
1961
2752
  const autostartRef = useRef(autostart);
@@ -1971,6 +2762,58 @@ function VizijRuntimeProviderInner({
1971
2762
  runtimeMountedRef.current = false;
1972
2763
  };
1973
2764
  }, []);
2765
+ const publishRuntimeDebugState = useCallback(() => {
2766
+ const storeState = store.getState();
2767
+ setRuntimeDebugState(runtimeDebugInstanceIdRef.current, {
2768
+ namespace,
2769
+ faceId: faceId ?? null,
2770
+ rootId: status.rootId,
2771
+ ready: status.ready,
2772
+ loading: status.loading,
2773
+ autostart,
2774
+ driveRuntime,
2775
+ loopMode,
2776
+ outputCount: status.outputPaths.length,
2777
+ graphControllerCount: status.controllers.graphs.length,
2778
+ animationControllerCount: status.controllers.anims.length,
2779
+ registeredGraphCount: registeredGraphsRef.current.length,
2780
+ registeredAnimationCount: registeredAnimationsRef.current.length,
2781
+ programControllerCount: programControllerIdsRef.current.size,
2782
+ animationTweenCount: animationTweensRef.current.size,
2783
+ clipPlaybackCount: clipPlaybackRef.current.size,
2784
+ programPlaybackCount: programPlaybackRef.current.size,
2785
+ stagedInputCount: stagedInputsRef.current.size,
2786
+ activeDriverCount: inputDriverIdsRef.current.size,
2787
+ worldEntryCount: Object.keys(storeState.world ?? {}).length,
2788
+ animatableCount: Object.keys(storeState.animatables ?? {}).length,
2789
+ valuesSize: storeState.values instanceof Map ? storeState.values.size : null,
2790
+ stepHz: status.stepHz ?? null
2791
+ });
2792
+ }, [
2793
+ autostart,
2794
+ driveRuntime,
2795
+ faceId,
2796
+ loopMode,
2797
+ namespace,
2798
+ status.controllers.anims.length,
2799
+ status.controllers.graphs.length,
2800
+ status.loading,
2801
+ status.outputPaths.length,
2802
+ status.ready,
2803
+ status.rootId,
2804
+ status.stepHz,
2805
+ store
2806
+ ]);
2807
+ useEffect(() => {
2808
+ publishRuntimeDebugState();
2809
+ const unsubscribe = store.subscribe(() => {
2810
+ publishRuntimeDebugState();
2811
+ });
2812
+ return () => {
2813
+ unsubscribe();
2814
+ clearRuntimeDebugState(runtimeDebugInstanceIdRef.current);
2815
+ };
2816
+ }, [publishRuntimeDebugState, store]);
1974
2817
  useEffect(() => {
1975
2818
  const rigAsset = assetBundle.rig;
1976
2819
  if (!rigAsset) {
@@ -2066,59 +2909,289 @@ function VizijRuntimeProviderInner({
2066
2909
  });
2067
2910
  return;
2068
2911
  }
2069
- markActivity();
2070
- const namespacedPath = namespaceTypedPath(path, namespaceRef.current);
2071
- if (isRuntimeDebugEnabled() && (namespacedPath.includes("animation/authoring.timeline.main") || namespacedPath.endsWith("/blink"))) {
2072
- console.log("[vizij-runtime] stage input", {
2073
- path,
2074
- namespacedPath,
2075
- value
2076
- });
2912
+ markActivity();
2913
+ const namespacedPath = namespaceTypedPath(path, namespaceRef.current);
2914
+ if (isRuntimeDebugEnabled() && (namespacedPath.includes("animation/authoring.timeline.main") || namespacedPath.endsWith("/blink"))) {
2915
+ console.log("[vizij-runtime] stage input", {
2916
+ path,
2917
+ namespacedPath,
2918
+ value
2919
+ });
2920
+ }
2921
+ stagedInputsRef.current.set(namespacedPath, { value, shape });
2922
+ },
2923
+ [
2924
+ assetBundle.pose?.config?.faceId,
2925
+ faceId,
2926
+ markActivity,
2927
+ poseWeightFallbackMap,
2928
+ useLegacyPoseWeightFallback
2929
+ ]
2930
+ );
2931
+ const reportStatus = useCallback(
2932
+ (updater) => {
2933
+ setStatus((prev) => {
2934
+ const next = updater(prev);
2935
+ onStatusChange?.(next);
2936
+ return next;
2937
+ });
2938
+ },
2939
+ [onStatusChange]
2940
+ );
2941
+ const pushError = useCallback(
2942
+ (error) => {
2943
+ errorsRef.current = [...errorsRef.current, error];
2944
+ reportStatus((prev) => ({
2945
+ ...prev,
2946
+ error,
2947
+ errors: errorsRef.current
2948
+ }));
2949
+ console.warn("[vizij-runtime]", error.message, error.cause);
2950
+ },
2951
+ [reportStatus]
2952
+ );
2953
+ const resetErrors = useCallback(() => {
2954
+ errorsRef.current = [];
2955
+ reportStatus((prev) => ({
2956
+ ...prev,
2957
+ error: null,
2958
+ errors: []
2959
+ }));
2960
+ }, [reportStatus]);
2961
+ useEffect(() => {
2962
+ autostartRef.current = autostart;
2963
+ updateLoopMode();
2964
+ }, [autostart, updateLoopMode]);
2965
+ const pendingWritesRef = useRef(/* @__PURE__ */ new Map());
2966
+ const getDevice = useCallback(
2967
+ () => deviceSlot.current?.device ?? null,
2968
+ [deviceSlot]
2969
+ );
2970
+ const ensureAnimationHost = useCallback(() => {
2971
+ if (!animationModuleRef.current) {
2972
+ return null;
2973
+ }
2974
+ if (!animationHostRef.current) {
2975
+ animationHostRef.current = new AnimationModuleHost(
2976
+ getDevice,
2977
+ (key) => resolveAnimationBridgeOutputPaths(
2978
+ key,
2979
+ faceIdRef.current ?? void 0,
2980
+ rigInputMapRef.current
2981
+ )
2982
+ );
2983
+ }
2984
+ return animationHostRef.current;
2985
+ }, [getDevice]);
2986
+ const applyEngineChangesRef = useRef(() => {
2987
+ });
2988
+ const recomposeDevice = useCallback(() => {
2989
+ const spec = composeGraphSpecs(graphSourcesRef.current);
2990
+ deviceSlot.recompose(spec).then((handle) => {
2991
+ if (pendingWritesRef.current.size > 0) {
2992
+ handle.device.writeValues(
2993
+ Object.fromEntries(pendingWritesRef.current)
2994
+ );
2995
+ pendingWritesRef.current.clear();
2996
+ }
2997
+ }).catch((err) => {
2998
+ pushError({
2999
+ message: "Failed to (re)compose the arora device",
3000
+ cause: err,
3001
+ phase: "engine",
3002
+ timestamp: performance.now()
3003
+ });
3004
+ });
3005
+ }, [deviceSlot, pushError]);
3006
+ const initEngine = useCallback(async () => {
3007
+ await ensureWasmInit();
3008
+ setReady(true);
3009
+ }, []);
3010
+ const hasBundleAnimations = (assetBundle.animations?.length ?? 0) > 0;
3011
+ useEffect(() => {
3012
+ if (!hasBundleAnimations || animationModuleRef.current) {
3013
+ return;
3014
+ }
3015
+ let cancelled = false;
3016
+ const loading = loadAnimationModule().then((module) => {
3017
+ if (cancelled) {
3018
+ return;
3019
+ }
3020
+ animationModuleRef.current = module;
3021
+ deviceSlot.setModules([module]);
3022
+ deviceSlot.onDeviceStarted = (handle) => {
3023
+ animationHostRef.current?.replayInto(handle.device);
3024
+ };
3025
+ ensureAnimationHost();
3026
+ }).catch((err) => {
3027
+ pushError({
3028
+ message: "Failed to load the animation module",
3029
+ cause: err,
3030
+ phase: "engine",
3031
+ timestamp: performance.now()
3032
+ });
3033
+ });
3034
+ deviceSlot.waitForModules(loading);
3035
+ return () => {
3036
+ cancelled = true;
3037
+ };
3038
+ }, [deviceSlot, ensureAnimationHost, hasBundleAnimations, pushError]);
3039
+ const removeGraph = useCallback(
3040
+ (id) => {
3041
+ graphSourcesRef.current = graphSourcesRef.current.filter(
3042
+ (s) => s.sourceId !== id && !s.sourceId.startsWith(`${id}#`)
3043
+ );
3044
+ recomposeDevice();
3045
+ },
3046
+ [recomposeDevice]
3047
+ );
3048
+ const registerGraph = useCallback(
3049
+ (cfg) => {
3050
+ const id = cfg.id ?? `graph-${graphSourcesRef.current.length}`;
3051
+ graphSourcesRef.current = [
3052
+ ...graphSourcesRef.current.filter((s) => s.sourceId !== id),
3053
+ { sourceId: id, spec: cfg.spec ?? {} }
3054
+ ];
3055
+ recomposeDevice();
3056
+ return id;
3057
+ },
3058
+ [recomposeDevice]
3059
+ );
3060
+ const registerMergedGraph = useCallback(
3061
+ (cfg) => {
3062
+ const id = cfg.id ?? `merged-${graphSourcesRef.current.length}`;
3063
+ const members = cfg.graphs.map((graph, index) => ({
3064
+ sourceId: `${id}#${graph.id ?? index}`,
3065
+ spec: graph.spec ?? {}
3066
+ }));
3067
+ graphSourcesRef.current = [
3068
+ ...graphSourcesRef.current.filter(
3069
+ (s) => s.sourceId !== id && !s.sourceId.startsWith(`${id}#`)
3070
+ ),
3071
+ ...members
3072
+ ];
3073
+ recomposeDevice();
3074
+ return id;
3075
+ },
3076
+ [recomposeDevice]
3077
+ );
3078
+ const registerAnimation = useCallback(
3079
+ (cfg) => cfg.id ?? `animation-${registeredAnimationsRef.current.length}`,
3080
+ []
3081
+ );
3082
+ const removeAnimation = useCallback((_id) => {
3083
+ }, []);
3084
+ const setAnimationsSourceRegistered = useCallback(
3085
+ (registered) => {
3086
+ if (registered === animationsSourceRegisteredRef.current) {
3087
+ return;
3088
+ }
3089
+ animationsSourceRegisteredRef.current = registered;
3090
+ if (registered) {
3091
+ graphSourcesRef.current = [
3092
+ ...graphSourcesRef.current.filter(
3093
+ (s) => s.sourceId !== animationsGraphSource().sourceId
3094
+ ),
3095
+ animationsGraphSource()
3096
+ ];
3097
+ } else {
3098
+ graphSourcesRef.current = graphSourcesRef.current.filter(
3099
+ (s) => s.sourceId !== animationsGraphSource().sourceId
3100
+ );
3101
+ }
3102
+ recomposeDevice();
3103
+ },
3104
+ [recomposeDevice]
3105
+ );
3106
+ const listControllers = useCallback(
3107
+ () => ({
3108
+ graphs: [...registeredGraphsRef.current],
3109
+ anims: [...registeredAnimationsRef.current]
3110
+ }),
3111
+ []
3112
+ );
3113
+ const deviceSetInput = useCallback(
3114
+ (path, value, _shape) => {
3115
+ const handle = deviceSlot.current;
3116
+ if (handle) {
3117
+ handle.device.setValue(path, value);
3118
+ } else {
3119
+ pendingWritesRef.current.set(path, value);
2077
3120
  }
2078
- stagedInputsRef.current.set(namespacedPath, { value, shape });
2079
3121
  },
2080
- [
2081
- assetBundle.pose?.config?.faceId,
2082
- faceId,
2083
- markActivity,
2084
- poseWeightFallbackMap,
2085
- useLegacyPoseWeightFallback
2086
- ]
3122
+ [deviceSlot]
2087
3123
  );
2088
- const reportStatus = useCallback(
2089
- (updater) => {
2090
- setStatus((prev) => {
2091
- const next = updater(prev);
2092
- onStatusChange?.(next);
2093
- return next;
3124
+ const getPathSnapshot = useCallback(
3125
+ (path) => {
3126
+ const handle = deviceSlot.current;
3127
+ if (!handle) {
3128
+ return pendingWritesRef.current.get(path);
3129
+ }
3130
+ return handle.device.readValues([path])[path] ?? void 0;
3131
+ },
3132
+ [deviceSlot]
3133
+ );
3134
+ const getStoreSnapshot = useCallback(() => {
3135
+ const handle = deviceSlot.current;
3136
+ if (!handle) {
3137
+ return void 0;
3138
+ }
3139
+ return handle.device.snapshot();
3140
+ }, [deviceSlot]);
3141
+ const stepListenersRef = useRef(/* @__PURE__ */ new Set());
3142
+ const subscribeToStep = useCallback((listener) => {
3143
+ const listeners = stepListenersRef.current;
3144
+ listeners.add(listener);
3145
+ return () => {
3146
+ listeners.delete(listener);
3147
+ };
3148
+ }, []);
3149
+ const stepRuntime = useCallback(
3150
+ (dt) => {
3151
+ const handle = deviceSlot.current;
3152
+ if (!handle) {
3153
+ return;
3154
+ }
3155
+ if (!handle.device.running) {
3156
+ handle.device.step(dt * 1e3);
3157
+ }
3158
+ const changes = handle.device.drainChanges();
3159
+ applyEngineChangesRef.current(changes);
3160
+ if (storeChangeListenersRef.current.size > 0) {
3161
+ const paths = Object.keys(changes);
3162
+ if (paths.length > 0) {
3163
+ storeChangeListenersRef.current.forEach((listener) => {
3164
+ try {
3165
+ listener(changes);
3166
+ } catch (err) {
3167
+ console.error("[vizij-runtime] store-change listener error", err);
3168
+ }
3169
+ });
3170
+ }
3171
+ }
3172
+ stepListenersRef.current.forEach((listener) => {
3173
+ try {
3174
+ listener();
3175
+ } catch (err) {
3176
+ console.error("[vizij-runtime] step listener error", err);
3177
+ }
2094
3178
  });
2095
3179
  },
2096
- [onStatusChange]
3180
+ [deviceSlot]
2097
3181
  );
2098
- const pushError = useCallback(
2099
- (error) => {
2100
- errorsRef.current = [...errorsRef.current, error];
2101
- reportStatus((prev) => ({
2102
- ...prev,
2103
- error,
2104
- errors: errorsRef.current
2105
- }));
2106
- console.warn("[vizij-runtime]", error.message, error.cause);
3182
+ const storeChangeListenersRef = useRef(
3183
+ /* @__PURE__ */ new Set()
3184
+ );
3185
+ const subscribeToStoreChanges = useCallback(
3186
+ (listener) => {
3187
+ const listeners = storeChangeListenersRef.current;
3188
+ listeners.add(listener);
3189
+ return () => {
3190
+ listeners.delete(listener);
3191
+ };
2107
3192
  },
2108
- [reportStatus]
3193
+ []
2109
3194
  );
2110
- const resetErrors = useCallback(() => {
2111
- errorsRef.current = [];
2112
- reportStatus((prev) => ({
2113
- ...prev,
2114
- error: null,
2115
- errors: []
2116
- }));
2117
- }, [reportStatus]);
2118
- useEffect(() => {
2119
- autostartRef.current = autostart;
2120
- updateLoopMode();
2121
- }, [autostart, updateLoopMode]);
2122
3195
  const clearControllers = useCallback(() => {
2123
3196
  const existing = listControllers();
2124
3197
  existing.graphs.forEach((id) => {
@@ -2153,8 +3226,6 @@ function VizijRuntimeProviderInner({
2153
3226
  baseOutputPathsRef.current = /* @__PURE__ */ new Set();
2154
3227
  namespacedOutputPathsRef.current = /* @__PURE__ */ new Set();
2155
3228
  rigPoseControlInputIdsRef.current = /* @__PURE__ */ new Set();
2156
- clipOutputValuesRef.current.clear();
2157
- clipAggregateValuesRef.current.clear();
2158
3229
  }, [listControllers, removeAnimation, removeGraph, pushError]);
2159
3230
  useEffect(() => {
2160
3231
  namespaceRef.current = namespace;
@@ -2165,8 +3236,24 @@ function VizijRuntimeProviderInner({
2165
3236
  }));
2166
3237
  }, [namespace, faceId, reportStatus]);
2167
3238
  useEffect(() => {
2168
- driveOrchestratorRef.current = driveOrchestrator;
2169
- }, [driveOrchestrator]);
3239
+ driveRuntimeRef.current = driveRuntime;
3240
+ }, [driveRuntime]);
3241
+ useEffect(() => {
3242
+ deviceSlot.onRunEnded = (error) => {
3243
+ pushError({
3244
+ message: "The device's run loop ended: stepping failed",
3245
+ cause: error,
3246
+ phase: "engine",
3247
+ timestamp: performance.now()
3248
+ });
3249
+ };
3250
+ if (driveRuntime) {
3251
+ deviceSlot.runPeriodMs = RUN_PERIOD_MS;
3252
+ deviceSlot.startRun();
3253
+ } else {
3254
+ deviceSlot.runPeriodMs = null;
3255
+ }
3256
+ }, [deviceSlot, driveRuntime, pushError]);
2170
3257
  const glbAsset = effectiveAssetBundle.glb;
2171
3258
  const baseBundle = effectiveAssetBundle.bundle ?? null;
2172
3259
  useEffect(() => {
@@ -2279,16 +3366,16 @@ function VizijRuntimeProviderInner({
2279
3366
  ]);
2280
3367
  useEffect(() => {
2281
3368
  if (!ready && autoCreate) {
2282
- createOrchestrator(createOptions).catch((err) => {
3369
+ initEngine().catch((err) => {
2283
3370
  pushError({
2284
- message: "Failed to create orchestrator runtime",
3371
+ message: "Failed to initialize the engine",
2285
3372
  cause: err,
2286
- phase: "orchestrator",
3373
+ phase: "engine",
2287
3374
  timestamp: performance.now()
2288
3375
  });
2289
3376
  });
2290
3377
  }
2291
- }, [ready, autoCreate, createOptions, createOrchestrator, pushError]);
3378
+ }, [ready, autoCreate, initEngine, pushError]);
2292
3379
  const registerControllers = useCallback(async () => {
2293
3380
  clearControllers();
2294
3381
  if (isRuntimeDebugEnabled()) {
@@ -2451,19 +3538,13 @@ function VizijRuntimeProviderInner({
2451
3538
  console.log("[vizij-runtime] registered graph ids", graphIds);
2452
3539
  }
2453
3540
  const animationIds = [];
3541
+ const hostClips = [];
2454
3542
  for (const anim of assetBundle.animations ?? []) {
2455
3543
  try {
2456
- const controllerId = namespaceControllerId(anim.id, namespace, "animation") ?? anim.id;
2457
- const animationPayload = anim.setup?.animation ?? toStoredAnimationClip(anim.id, anim.clip);
2458
- const config = {
2459
- id: controllerId,
2460
- setup: {
2461
- ...anim.setup ?? {},
2462
- animation: animationPayload
2463
- }
2464
- };
2465
- const id = registerAnimation(config);
2466
- animationIds.push(id);
3544
+ const stored = anim.setup?.animation ?? toStoredAnimationClip(anim.id, anim.clip);
3545
+ hostClips.push({ id: anim.id, stored });
3546
+ registerAnimation({ id: anim.id });
3547
+ animationIds.push(anim.id);
2467
3548
  } catch (err) {
2468
3549
  if (isRuntimeDebugEnabled()) {
2469
3550
  console.warn("[vizij-runtime] failed animation registration", {
@@ -2479,6 +3560,7 @@ function VizijRuntimeProviderInner({
2479
3560
  });
2480
3561
  }
2481
3562
  }
3563
+ ensureAnimationHost()?.setClips(hostClips);
2482
3564
  registeredAnimationsRef.current = animationIds;
2483
3565
  if (assetBundle.initialInputs) {
2484
3566
  Object.entries(assetBundle.initialInputs).forEach(([path, value]) => {
@@ -2512,6 +3594,7 @@ function VizijRuntimeProviderInner({
2512
3594
  }, [
2513
3595
  assetBundle,
2514
3596
  clearControllers,
3597
+ ensureAnimationHost,
2515
3598
  listControllers,
2516
3599
  mergeStrategy,
2517
3600
  namespace,
@@ -2543,75 +3626,77 @@ function VizijRuntimeProviderInner({
2543
3626
  });
2544
3627
  }, [ready, status.loading, graphUpdateToken, registerControllers, pushError]);
2545
3628
  useEffect(() => {
2546
- if (!frame) {
2547
- return;
2548
- }
2549
- const writes = frame.merged_writes ?? [];
2550
- if (!writes.length) {
2551
- return;
2552
- }
2553
- const setWorldValues = store.getState().setValues;
2554
- const namespaceValue = status.namespace;
2555
- const currentValues = store.getState().values;
2556
- const rigInputPathMap = rigInputMapRef.current;
2557
- const rigPoseControlInputIds = rigPoseControlInputIdsRef.current;
2558
- const batched = [];
2559
- const namespacedOutputs = namespacedOutputPathsRef.current;
2560
- const baseOutputs = baseOutputPathsRef.current;
2561
- writes.forEach((write) => {
2562
- const path = normalisePath(write.path);
2563
- const basePath = stripNamespace(path, namespaceValue);
2564
- const isTrackedOutput = namespacedOutputs.has(path) || baseOutputs.has(basePath);
2565
- if (!isTrackedOutput) {
2566
- return;
2567
- }
2568
- const raw = valueJSONToRaw(write.value);
2569
- if (raw === void 0) {
3629
+ applyEngineChangesRef.current = (changes) => {
3630
+ const entries = Object.entries(changes);
3631
+ if (!entries.length) {
2570
3632
  return;
2571
3633
  }
2572
- const poseControlMatch = /^rig\/[^/]+\/pose\/control\/(.+)$/.exec(
2573
- basePath
2574
- );
2575
- if (poseControlMatch && typeof raw === "number" && Number.isFinite(raw)) {
2576
- const inputId = (poseControlMatch[1] ?? "").trim();
2577
- const hasNativePoseControlInput = inputId.length > 0 && rigPoseControlInputIds.has(inputId);
2578
- const mappedInputPath = inputId.length === 0 ? void 0 : resolvePoseControlInputPath({
2579
- inputId,
2580
- basePath,
2581
- rigInputPathMap,
2582
- hasNativePoseControlInput
2583
- });
2584
- if (mappedInputPath) {
2585
- const bridgeKey = `${namespaceValue}:${mappedInputPath}`;
2586
- const previousValue = poseControlBridgeValuesRef.current.get(bridgeKey);
2587
- if (previousValue === void 0 || Math.abs(previousValue - raw) > POSE_CONTROL_BRIDGE_EPSILON) {
2588
- poseControlBridgeValuesRef.current.set(bridgeKey, raw);
2589
- setInput(mappedInputPath, { float: raw });
3634
+ const setWorldValues = store.getState().setValues;
3635
+ const namespaceValue = status.namespace;
3636
+ const currentValues = store.getState().values;
3637
+ const rigInputPathMap = rigInputMapRef.current;
3638
+ const rigPoseControlInputIds = rigPoseControlInputIdsRef.current;
3639
+ const batched = [];
3640
+ const namespacedOutputs = namespacedOutputPathsRef.current;
3641
+ const baseOutputs = baseOutputPathsRef.current;
3642
+ entries.forEach(([changedPath, changedValue]) => {
3643
+ if (changedValue === null || isBuiltInPath(changedPath)) {
3644
+ return;
3645
+ }
3646
+ const path = normalisePath(changedPath);
3647
+ const basePath = stripNamespace(path, namespaceValue);
3648
+ const isTrackedOutput = namespacedOutputs.has(path) || baseOutputs.has(basePath);
3649
+ if (!isTrackedOutput) {
3650
+ return;
3651
+ }
3652
+ const raw = valueJSONToRaw(changedValue);
3653
+ if (raw === void 0) {
3654
+ return;
3655
+ }
3656
+ const poseControlMatch = /^rig\/[^/]+\/pose\/control\/(.+)$/.exec(
3657
+ basePath
3658
+ );
3659
+ if (poseControlMatch && typeof raw === "number" && Number.isFinite(raw)) {
3660
+ const inputId = (poseControlMatch[1] ?? "").trim();
3661
+ const hasNativePoseControlInput = inputId.length > 0 && rigPoseControlInputIds.has(inputId);
3662
+ const mappedInputPath = inputId.length === 0 ? void 0 : resolvePoseControlInputPath({
3663
+ inputId,
3664
+ basePath,
3665
+ rigInputPathMap,
3666
+ hasNativePoseControlInput
3667
+ });
3668
+ if (mappedInputPath) {
3669
+ const bridgeKey = `${namespaceValue}:${mappedInputPath}`;
3670
+ const previousValue = poseControlBridgeValuesRef.current.get(bridgeKey);
3671
+ if (previousValue === void 0 || Math.abs(previousValue - raw) > POSE_CONTROL_BRIDGE_EPSILON) {
3672
+ poseControlBridgeValuesRef.current.set(bridgeKey, raw);
3673
+ setInput(mappedInputPath, { float: raw });
3674
+ }
2590
3675
  }
2591
3676
  }
3677
+ const targetPath = baseOutputs.has(basePath) ? basePath : path;
3678
+ const currentValue = currentValues.get(
3679
+ getLookup(namespaceValue, targetPath)
3680
+ );
3681
+ const nextWrite = transformOutputWrite ? transformOutputWrite({
3682
+ id: targetPath,
3683
+ namespace: namespaceValue,
3684
+ value: raw,
3685
+ currentValue
3686
+ }) : {
3687
+ id: targetPath,
3688
+ namespace: namespaceValue,
3689
+ value: raw
3690
+ };
3691
+ if (nextWrite) {
3692
+ batched.push(nextWrite);
3693
+ }
3694
+ });
3695
+ if (batched.length > 0) {
3696
+ setWorldValues(batched);
2592
3697
  }
2593
- const targetPath = baseOutputs.has(basePath) ? basePath : path;
2594
- const currentValue = currentValues.get(
2595
- getLookup(namespaceValue, targetPath)
2596
- );
2597
- const nextWrite = transformOutputWrite ? transformOutputWrite({
2598
- id: targetPath,
2599
- namespace: namespaceValue,
2600
- value: raw,
2601
- currentValue
2602
- }) : {
2603
- id: targetPath,
2604
- namespace: namespaceValue,
2605
- value: raw
2606
- };
2607
- if (nextWrite) {
2608
- batched.push(nextWrite);
2609
- }
2610
- });
2611
- if (batched.length > 0) {
2612
- setWorldValues(batched);
2613
- }
2614
- }, [frame, status.namespace, store, transformOutputWrite]);
3698
+ };
3699
+ }, [status.namespace, store, transformOutputWrite]);
2615
3700
  const stagePoseNeutral = useCallback(
2616
3701
  (force = false) => {
2617
3702
  const neutral = assetBundle.pose?.config?.neutralInputs ?? {};
@@ -2700,180 +3785,34 @@ function VizijRuntimeProviderInner({
2700
3785
  state.completion = completion;
2701
3786
  return completion;
2702
3787
  }, []);
2703
- const setAnimationInput = useCallback(
2704
- (path, value, options) => {
2705
- setInput(path, { float: value });
2706
- if (!options?.immediate) {
2707
- return;
2708
- }
2709
- const namespacedPath = namespaceTypedPath(path, namespaceRef.current);
2710
- const staged = stagedInputsRef.current.get(namespacedPath);
2711
- if (staged) {
2712
- orchestratorSetInput(namespacedPath, staged.value, staged.shape);
2713
- stagedInputsRef.current.delete(namespacedPath);
2714
- return;
2715
- }
2716
- orchestratorSetInput(namespacedPath, { float: value });
2717
- },
2718
- [orchestratorSetInput, setInput]
2719
- );
2720
- const clearAnimationInput = useCallback(
2721
- (path) => {
2722
- const namespacedPath = namespaceTypedPath(path, namespaceRef.current);
2723
- stagedInputsRef.current.delete(namespacedPath);
2724
- removeInput(namespacedPath);
2725
- },
2726
- [removeInput]
2727
- );
2728
- const buildClipOutputValues = useCallback(
2729
- (clip, state) => sampleAnimationClipOutputValues(
2730
- clip.clip,
2731
- state.time,
2732
- state.weight,
2733
- faceId ?? void 0,
2734
- rigInputMapRef.current
2735
- ),
2736
- [faceId]
2737
- );
2738
- const computeClipAggregateValues = useCallback(() => {
2739
- const aggregate = /* @__PURE__ */ new Map();
2740
- clipOutputValuesRef.current.forEach((outputValues) => {
2741
- outputValues.forEach((value, path) => {
2742
- aggregate.set(path, (aggregate.get(path) ?? 0) + value);
2743
- });
2744
- });
2745
- return aggregate;
2746
- }, []);
2747
- const stageClipAggregateValues = useCallback(
2748
- (nextAggregate, options) => {
2749
- diffAnimationAggregateValues(
2750
- clipAggregateValuesRef.current,
2751
- nextAggregate,
2752
- POSE_CONTROL_BRIDGE_EPSILON
2753
- ).forEach((operation) => {
2754
- if (operation.kind === "clear") {
2755
- clearAnimationInput(operation.path);
2756
- return;
2757
- }
2758
- setAnimationInput(operation.path, operation.value, options);
2759
- });
2760
- clipAggregateValuesRef.current = nextAggregate;
2761
- },
2762
- [clearAnimationInput, setAnimationInput]
2763
- );
2764
- const syncClipOutputs = useCallback(
2765
- (options) => {
2766
- stageClipAggregateValues(computeClipAggregateValues(), options);
2767
- },
2768
- [computeClipAggregateValues, stageClipAggregateValues]
2769
- );
2770
- const writeClipOutputs = useCallback(
2771
- (clip, state, options) => {
2772
- if (!animationSystemActiveRef.current) {
2773
- return;
2774
- }
2775
- clipOutputValuesRef.current.set(
2776
- clip.id,
2777
- buildClipOutputValues(clip, state)
2778
- );
2779
- syncClipOutputs(options);
2780
- },
2781
- [buildClipOutputValues, syncClipOutputs]
2782
- );
2783
- const clearClipOutputs = useCallback(
2784
- (clipId, options) => {
2785
- if (!clipOutputValuesRef.current.has(clipId)) {
2786
- return;
3788
+ const ensureClipState = useCallback(
3789
+ (id) => {
3790
+ const clip = resolveClipById(id);
3791
+ if (!clip) {
3792
+ return null;
2787
3793
  }
2788
- clipOutputValuesRef.current.delete(clipId);
2789
- syncClipOutputs(options);
2790
- },
2791
- [syncClipOutputs]
2792
- );
2793
- const createClipPlaybackState = useCallback(
2794
- (clip) => {
2795
3794
  const duration = resolveClipDurationSeconds(
2796
3795
  clip.clip
2797
3796
  );
2798
- return {
2799
- id: clip.id,
2800
- time: 0,
3797
+ const existing = clipPlaybackRef.current.get(id);
3798
+ if (existing) {
3799
+ existing.duration = duration;
3800
+ return { clip, state: existing };
3801
+ }
3802
+ const state = {
3803
+ id,
2801
3804
  duration,
2802
3805
  speed: 1,
2803
- weight: Number.isFinite(clip.weight) ? Number(clip.weight) : 1,
2804
- loop: false,
3806
+ weight: 1,
3807
+ loop: true,
2805
3808
  playing: false,
2806
3809
  resolve: null,
2807
3810
  completion: null
2808
3811
  };
3812
+ clipPlaybackRef.current.set(id, state);
3813
+ return { clip, state };
2809
3814
  },
2810
- []
2811
- );
2812
- const ensureClipPlaybackState = useCallback(
2813
- (id) => {
2814
- const clip = resolveClipById(id);
2815
- if (!clip) {
2816
- return null;
2817
- }
2818
- const existing = clipPlaybackRef.current.get(id);
2819
- if (existing) {
2820
- existing.duration = resolveClipDurationSeconds(
2821
- clip.clip,
2822
- existing.duration
2823
- );
2824
- existing.time = clampAnimationTime(existing.time, existing.duration);
2825
- return { clip, state: existing };
2826
- }
2827
- const next = createClipPlaybackState(clip);
2828
- clipPlaybackRef.current.set(id, next);
2829
- return { clip, state: next };
2830
- },
2831
- [createClipPlaybackState, resolveClipById]
2832
- );
2833
- const advanceClipPlayback = useCallback(
2834
- (dt) => {
2835
- if (clipPlaybackRef.current.size === 0) {
2836
- return;
2837
- }
2838
- const toDelete = [];
2839
- clipPlaybackRef.current.forEach((state, key) => {
2840
- const clip = resolveClipById(state.id);
2841
- if (!clip) {
2842
- toDelete.push(key);
2843
- resolveClipPromise(state);
2844
- return;
2845
- }
2846
- state.duration = resolveClipDurationSeconds(
2847
- clip.clip,
2848
- state.duration
2849
- );
2850
- const { time, completed } = advanceClipTime(
2851
- {
2852
- time: state.time,
2853
- duration: state.duration,
2854
- speed: state.speed,
2855
- loop: state.loop,
2856
- playing: state.playing
2857
- },
2858
- dt
2859
- );
2860
- state.time = clampAnimationTime(time, state.duration);
2861
- if (state.playing || completed) {
2862
- writeClipOutputs(clip, state);
2863
- }
2864
- if (completed) {
2865
- toDelete.push(key);
2866
- resolveClipPromise(state);
2867
- }
2868
- });
2869
- toDelete.forEach((key) => {
2870
- clipPlaybackRef.current.delete(key);
2871
- if (animationSystemActiveRef.current) {
2872
- clearClipOutputs(key);
2873
- }
2874
- });
2875
- },
2876
- [clearClipOutputs, resolveClipById, resolveClipPromise, writeClipOutputs]
3815
+ [resolveClipById]
2877
3816
  );
2878
3817
  const animateValue = useCallback(
2879
3818
  (path, target, options) => {
@@ -2947,42 +3886,111 @@ function VizijRuntimeProviderInner({
2947
3886
  useLegacyPoseWeightFallback
2948
3887
  ]
2949
3888
  );
3889
+ const warnAnimationGap = useCallback((key, message) => {
3890
+ if (animationGapWarnedRef.current.has(key)) {
3891
+ return;
3892
+ }
3893
+ animationGapWarnedRef.current.add(key);
3894
+ console.warn(`[vizij-runtime] ${message}`);
3895
+ }, []);
3896
+ const syncAnimationsSource = useCallback(() => {
3897
+ const host = animationHostRef.current;
3898
+ setAnimationsSourceRegistered(host ? host.hasPlaying() : false);
3899
+ updateLoopMode();
3900
+ }, [setAnimationsSourceRegistered, updateLoopMode]);
3901
+ const readPlayerStates = useCallback(() => {
3902
+ const device = deviceSlot.current?.device;
3903
+ if (!device) {
3904
+ return [];
3905
+ }
3906
+ const raw = device.readValues([ANIMATION_PLAYERS_PATH])[ANIMATION_PLAYERS_PATH];
3907
+ return decodePlayerStates(raw);
3908
+ }, [deviceSlot]);
3909
+ const settleFinishedClips = useCallback(() => {
3910
+ let pending = false;
3911
+ clipPlaybackRef.current.forEach((state) => {
3912
+ if (state.completion && !state.loop) {
3913
+ pending = true;
3914
+ }
3915
+ });
3916
+ const host = animationHostRef.current;
3917
+ if (!pending || !host) {
3918
+ return;
3919
+ }
3920
+ const states = readPlayerStates();
3921
+ if (states.length === 0) {
3922
+ return;
3923
+ }
3924
+ clipPlaybackRef.current.forEach((state, id) => {
3925
+ if (!state.completion || state.loop) {
3926
+ return;
3927
+ }
3928
+ const playerId = host.playerIdOf(id);
3929
+ if (playerId === null) {
3930
+ return;
3931
+ }
3932
+ const feedback = states.find((entry) => entry.player === playerId);
3933
+ if (!feedback) {
3934
+ return;
3935
+ }
3936
+ const duration = feedback.duration > 0 ? feedback.duration : state.duration;
3937
+ const atEnd = duration > 0 && feedback.time >= duration - 1e-3;
3938
+ if (feedback.state === "stopped" || atEnd) {
3939
+ state.playing = false;
3940
+ resolveClipPromise(state);
3941
+ host.pause(id);
3942
+ syncAnimationsSource();
3943
+ }
3944
+ });
3945
+ }, [readPlayerStates, resolveClipPromise, syncAnimationsSource]);
2950
3946
  const playAnimation = useCallback(
2951
3947
  (id, options) => {
2952
- const ensured = ensureClipPlaybackState(id);
3948
+ const ensured = ensureClipState(id);
2953
3949
  if (!ensured) {
2954
3950
  return Promise.reject(
2955
3951
  new Error(`Animation ${id} is not part of the current asset bundle.`)
2956
3952
  );
2957
3953
  }
2958
- const { clip, state } = ensured;
2959
- const shouldReset = options?.reset === true;
2960
- if (shouldReset) {
2961
- resolveClipPromise(state);
2962
- state.time = 0;
2963
- }
2964
- const speed = options?.speed ?? state.speed ?? 1;
2965
- const weight = options?.weight ?? state.weight ?? clip.weight ?? 1;
2966
- state.speed = Number.isFinite(speed) && speed > 0 ? speed : 1;
2967
- state.weight = Number.isFinite(weight) ? Number(weight) : 1;
2968
- state.duration = resolveClipDurationSeconds(
2969
- clip.clip,
2970
- state.duration
2971
- );
2972
- state.time = clampAnimationTime(state.time, state.duration);
3954
+ const { state } = ensured;
3955
+ const host = ensureAnimationHost();
3956
+ state.speed = options?.speed ?? state.speed;
3957
+ state.weight = options?.weight ?? state.weight;
2973
3958
  state.playing = true;
2974
3959
  const completion = ensureClipPromise(state);
2975
3960
  clipPlaybackRef.current.set(id, state);
2976
- writeClipOutputs(clip, state);
3961
+ if (host) {
3962
+ if (options?.reset === true) {
3963
+ host.seek(id, 0);
3964
+ }
3965
+ host.setSpeed(id, state.speed);
3966
+ host.setWeight(id, state.weight);
3967
+ host.setLoop(id, state.loop ? "loop" : "once");
3968
+ void host.play(id).catch((err) => {
3969
+ pushError({
3970
+ message: `Failed to start animation ${id} on the device`,
3971
+ cause: err,
3972
+ phase: "animation",
3973
+ timestamp: performance.now()
3974
+ });
3975
+ });
3976
+ } else {
3977
+ warnAnimationGap(
3978
+ "module-not-ready",
3979
+ "playAnimation called before the animation module finished loading; playback starts once it is ready."
3980
+ );
3981
+ }
3982
+ syncAnimationsSource();
2977
3983
  markActivity();
2978
3984
  return completion;
2979
3985
  },
2980
3986
  [
2981
- ensureClipPlaybackState,
3987
+ ensureAnimationHost,
2982
3988
  ensureClipPromise,
3989
+ ensureClipState,
2983
3990
  markActivity,
2984
- resolveClipPromise,
2985
- writeClipOutputs
3991
+ pushError,
3992
+ syncAnimationsSource,
3993
+ warnAnimationGap
2986
3994
  ]
2987
3995
  );
2988
3996
  const pauseAnimation = useCallback(
@@ -2992,34 +4000,28 @@ function VizijRuntimeProviderInner({
2992
4000
  return;
2993
4001
  }
2994
4002
  state.playing = false;
2995
- updateLoopMode();
2996
- },
2997
- [updateLoopMode]
2998
- );
2999
- const seekAnimation = useCallback(
3000
- (id, timeSeconds) => {
3001
- const ensured = ensureClipPlaybackState(id);
3002
- if (!ensured) {
3003
- return;
3004
- }
3005
- const { clip, state } = ensured;
3006
- state.time = clampAnimationTime(timeSeconds, state.duration);
3007
- clipPlaybackRef.current.set(id, state);
3008
- writeClipOutputs(clip, state, { immediate: true });
4003
+ animationHostRef.current?.pause(id);
4004
+ syncAnimationsSource();
3009
4005
  },
3010
- [ensureClipPlaybackState, writeClipOutputs]
4006
+ [syncAnimationsSource]
3011
4007
  );
4008
+ const seekAnimation = useCallback((id, timeSeconds) => {
4009
+ if (!clipPlaybackRef.current.has(id)) {
4010
+ return;
4011
+ }
4012
+ animationHostRef.current?.seek(id, Math.max(0, timeSeconds));
4013
+ }, []);
3012
4014
  const setAnimationLoop = useCallback(
3013
4015
  (id, enabled) => {
3014
- const ensured = ensureClipPlaybackState(id);
4016
+ const ensured = ensureClipState(id);
3015
4017
  if (!ensured) {
3016
4018
  return;
3017
4019
  }
3018
4020
  ensured.state.loop = Boolean(enabled);
3019
- clipPlaybackRef.current.set(id, ensured.state);
4021
+ animationHostRef.current?.setLoop(id, enabled ? "loop" : "once");
3020
4022
  updateLoopMode();
3021
4023
  },
3022
- [ensureClipPlaybackState, updateLoopMode]
4024
+ [ensureClipState, updateLoopMode]
3023
4025
  );
3024
4026
  const getAnimationState = useCallback(
3025
4027
  (id) => {
@@ -3027,15 +4029,17 @@ function VizijRuntimeProviderInner({
3027
4029
  if (!state) {
3028
4030
  return null;
3029
4031
  }
4032
+ const playerId = animationHostRef.current?.playerIdOf(id) ?? null;
4033
+ const feedback = playerId !== null ? readPlayerStates().find((entry) => entry.player === playerId) : void 0;
3030
4034
  return {
3031
- time: state.time,
3032
- duration: state.duration,
3033
- playing: state.playing,
4035
+ time: feedback?.time ?? 0,
4036
+ duration: feedback && feedback.duration > 0 ? feedback.duration : state.duration,
4037
+ playing: feedback ? feedback.state === "playing" : state.playing,
3034
4038
  loop: state.loop,
3035
- speed: state.speed
4039
+ speed: feedback?.speed ?? state.speed
3036
4040
  };
3037
4041
  },
3038
- []
4042
+ [readPlayerStates]
3039
4043
  );
3040
4044
  const stopAnimation = useCallback(
3041
4045
  (id, options) => {
@@ -3045,12 +4049,19 @@ function VizijRuntimeProviderInner({
3045
4049
  state.playing = false;
3046
4050
  resolveClipPromise(state);
3047
4051
  }
3048
- if (options?.clearOutputs !== false) {
3049
- clearClipOutputs(id);
4052
+ const host = animationHostRef.current;
4053
+ if (options?.clearOutputs === false) {
4054
+ host?.pause(id);
4055
+ syncAnimationsSource();
4056
+ } else {
4057
+ host?.stop(id);
4058
+ const unsubscribe = subscribeToStep(() => {
4059
+ unsubscribe();
4060
+ syncAnimationsSource();
4061
+ });
3050
4062
  }
3051
- updateLoopMode();
3052
4063
  },
3053
- [clearClipOutputs, resolveClipPromise, updateLoopMode]
4064
+ [resolveClipPromise, subscribeToStep, syncAnimationsSource]
3054
4065
  );
3055
4066
  const refreshControllerStatus = useCallback(() => {
3056
4067
  const controllers = listControllers();
@@ -3199,11 +4210,21 @@ function VizijRuntimeProviderInner({
3199
4210
  ]);
3200
4211
  const playProgram = useCallback(
3201
4212
  (id) => {
3202
- if (!resolveProgramById(id)) {
4213
+ const program = resolveProgramById(id);
4214
+ if (!program) {
3203
4215
  throw new Error(
3204
4216
  `Program ${id} is not part of the current asset bundle.`
3205
4217
  );
3206
4218
  }
4219
+ deriveProgramInputSeedValues({
4220
+ program,
4221
+ namespace,
4222
+ inputConstraints: inputConstraintsRef.current,
4223
+ getPathSnapshot,
4224
+ stagedInputs: stagedInputsRef.current
4225
+ }).forEach(({ path, value }) => {
4226
+ setInput(path, value);
4227
+ });
3207
4228
  programPlaybackRef.current.set(id, {
3208
4229
  id,
3209
4230
  state: "playing"
@@ -3211,7 +4232,14 @@ function VizijRuntimeProviderInner({
3211
4232
  syncProgramPlaybackControllers();
3212
4233
  markActivity();
3213
4234
  },
3214
- [markActivity, resolveProgramById, syncProgramPlaybackControllers]
4235
+ [
4236
+ getPathSnapshot,
4237
+ markActivity,
4238
+ namespace,
4239
+ resolveProgramById,
4240
+ setInput,
4241
+ syncProgramPlaybackControllers
4242
+ ]
3215
4243
  );
3216
4244
  const pauseProgram = useCallback(
3217
4245
  (id) => {
@@ -3310,6 +4338,7 @@ function VizijRuntimeProviderInner({
3310
4338
  );
3311
4339
  const registerInputDriver = useCallback(
3312
4340
  (id, factory) => {
4341
+ inputDriverIdsRef.current.add(id);
3313
4342
  const driver = factory({
3314
4343
  setInput,
3315
4344
  setRendererValue,
@@ -3351,6 +4380,8 @@ function VizijRuntimeProviderInner({
3351
4380
  phase: "driver",
3352
4381
  timestamp: performance.now()
3353
4382
  });
4383
+ } finally {
4384
+ inputDriverIdsRef.current.delete(id);
3354
4385
  }
3355
4386
  }
3356
4387
  };
@@ -3358,22 +4389,15 @@ function VizijRuntimeProviderInner({
3358
4389
  },
3359
4390
  [faceId, namespace, pushError, setInput, setRendererValue]
3360
4391
  );
3361
- const advanceAnimations = useCallback(
3362
- (dt) => {
3363
- advanceAnimationTweens(dt);
3364
- advanceClipPlayback(dt);
3365
- },
3366
- [advanceAnimationTweens, advanceClipPlayback]
3367
- );
3368
4392
  const flushStagedInputs = useCallback(() => {
3369
4393
  if (stagedInputsRef.current.size === 0) {
3370
4394
  return;
3371
4395
  }
3372
4396
  stagedInputsRef.current.forEach(({ value, shape }, path) => {
3373
- orchestratorSetInput(path, value, shape);
4397
+ deviceSetInput(path, value, shape);
3374
4398
  });
3375
4399
  stagedInputsRef.current.clear();
3376
- }, [orchestratorSetInput]);
4400
+ }, [deviceSetInput]);
3377
4401
  const step = useCallback(
3378
4402
  (dt, opts) => {
3379
4403
  if (dt > 0 && Number.isFinite(dt)) {
@@ -3381,13 +4405,19 @@ function VizijRuntimeProviderInner({
3381
4405
  const alpha = 0.1;
3382
4406
  avgStepDtRef.current = prev * (1 - alpha) + dt * alpha;
3383
4407
  }
3384
- advanceAnimations(dt);
4408
+ advanceAnimationTweens(dt);
3385
4409
  flushStagedInputs();
3386
- if (driveOrchestratorRef.current || opts?.forceRuntime) {
4410
+ if (driveRuntimeRef.current || opts?.forceRuntime) {
3387
4411
  stepRuntime(dt);
3388
4412
  }
4413
+ settleFinishedClips();
3389
4414
  },
3390
- [advanceAnimations, flushStagedInputs, stepRuntime]
4415
+ [
4416
+ advanceAnimationTweens,
4417
+ flushStagedInputs,
4418
+ settleFinishedClips,
4419
+ stepRuntime
4420
+ ]
3391
4421
  );
3392
4422
  useEffect(() => {
3393
4423
  if (loopMode !== "active") {
@@ -3402,7 +4432,7 @@ function VizijRuntimeProviderInner({
3402
4432
  if (lastTime == null) {
3403
4433
  lastTime = timestamp;
3404
4434
  }
3405
- const dt = Math.max(0, (timestamp - lastTime) / 1e3);
4435
+ const dt = inferImplicitPause(Math.max(0, (timestamp - lastTime) / 1e3));
3406
4436
  lastTime = timestamp;
3407
4437
  step(dt || 0);
3408
4438
  requestLoopMode(computeDesiredLoopMode());
@@ -3433,7 +4463,7 @@ function VizijRuntimeProviderInner({
3433
4463
  return;
3434
4464
  }
3435
4465
  const current = now();
3436
- const dt = Math.max(0, (current - lastTime) / 1e3);
4466
+ const dt = inferImplicitPause(Math.max(0, (current - lastTime) / 1e3));
3437
4467
  lastTime = current;
3438
4468
  step(dt || 0);
3439
4469
  requestLoopMode(computeDesiredLoopMode());
@@ -3449,8 +4479,6 @@ function VizijRuntimeProviderInner({
3449
4479
  clipPlaybackRef.current.clear();
3450
4480
  programPlaybackRef.current.clear();
3451
4481
  programControllerIdsRef.current.clear();
3452
- clipOutputValuesRef.current.clear();
3453
- clipAggregateValuesRef.current.clear();
3454
4482
  };
3455
4483
  }, []);
3456
4484
  useEffect(() => {
@@ -3503,6 +4531,10 @@ function VizijRuntimeProviderInner({
3503
4531
  ...status,
3504
4532
  assetBundle,
3505
4533
  setInput,
4534
+ getValueSnapshot: getPathSnapshot,
4535
+ getStoreSnapshot,
4536
+ subscribeToStoreChanges,
4537
+ subscribeToStep,
3506
4538
  setGraphBundle,
3507
4539
  setValue: setRendererValue,
3508
4540
  stagePoseNeutral,
@@ -3522,13 +4554,16 @@ function VizijRuntimeProviderInner({
3522
4554
  setAnimationActive,
3523
4555
  isAnimationActive,
3524
4556
  step,
3525
- advanceAnimations,
3526
4557
  inputConstraints
3527
4558
  }),
3528
4559
  [
3529
4560
  status,
3530
4561
  assetBundle,
3531
4562
  setInput,
4563
+ getPathSnapshot,
4564
+ getStoreSnapshot,
4565
+ subscribeToStoreChanges,
4566
+ subscribeToStep,
3532
4567
  setGraphBundle,
3533
4568
  setRendererValue,
3534
4569
  stagePoseNeutral,
@@ -3548,7 +4583,6 @@ function VizijRuntimeProviderInner({
3548
4583
  setAnimationActive,
3549
4584
  isAnimationActive,
3550
4585
  step,
3551
- advanceAnimations,
3552
4586
  inputConstraints
3553
4587
  ]
3554
4588
  );
@@ -3560,9 +4594,9 @@ import { memo } from "react";
3560
4594
  import { Vizij } from "@vizij/render";
3561
4595
 
3562
4596
  // src/hooks/useVizijRuntime.ts
3563
- import { useContext as useContext2 } from "react";
4597
+ import { useContext } from "react";
3564
4598
  function useVizijRuntime() {
3565
- const ctx = useContext2(VizijRuntimeContext);
4599
+ const ctx = useContext(VizijRuntimeContext);
3566
4600
  if (!ctx) {
3567
4601
  throw new Error(
3568
4602
  "useVizijRuntime must be used within a VizijRuntimeProvider."
@@ -3593,9 +4627,9 @@ function VizijRuntimeFaceInner({
3593
4627
  var VizijRuntimeFace = memo(VizijRuntimeFaceInner);
3594
4628
 
3595
4629
  // src/hooks/useOptionalVizijRuntime.ts
3596
- import { useContext as useContext3 } from "react";
4630
+ import { useContext as useContext2 } from "react";
3597
4631
  function useOptionalVizijRuntime() {
3598
- return useContext3(VizijRuntimeContext);
4632
+ return useContext2(VizijRuntimeContext);
3599
4633
  }
3600
4634
 
3601
4635
  // src/hooks/useVizijOutputs.ts