@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.cjs CHANGED
@@ -50,16 +50,356 @@ module.exports = __toCommonJS(index_exports);
50
50
  // src/VizijRuntimeProvider.tsx
51
51
  var import_react2 = require("react");
52
52
  var import_render = require("@vizij/render");
53
- var import_orchestrator_react = require("@vizij/orchestrator-react");
54
53
  var import_node_graph_authoring = require("@vizij/node-graph-authoring");
55
54
  var import_value_json2 = require("@vizij/value-json");
56
55
  var import_utils = require("@vizij/utils");
56
+ var import_animation_module = require("@vizij/animation-module");
57
+
58
+ // src/engine/aroraEngine.ts
59
+ var import_runtime = require("@vizij/runtime");
60
+
61
+ // src/engine/graphSpecDiff.ts
62
+ function asRecords(value) {
63
+ return Array.isArray(value) ? value : [];
64
+ }
65
+ function nodeId(node) {
66
+ return typeof node.id === "string" ? node.id : void 0;
67
+ }
68
+ function endpointId(endpoint) {
69
+ const id = endpoint?.node_id;
70
+ return typeof id === "string" ? id : void 0;
71
+ }
72
+ function endpointInput(endpoint) {
73
+ const input = endpoint?.input;
74
+ return typeof input === "string" ? input : void 0;
75
+ }
76
+ function stable(value) {
77
+ return JSON.stringify(sortKeys(value));
78
+ }
79
+ function sortKeys(value) {
80
+ if (Array.isArray(value)) return value.map(sortKeys);
81
+ if (value && typeof value === "object") {
82
+ const out = {};
83
+ for (const key of Object.keys(value).sort()) {
84
+ out[key] = sortKeys(value[key]);
85
+ }
86
+ return out;
87
+ }
88
+ return value;
89
+ }
90
+ function edgeKey(edge) {
91
+ return stable([
92
+ endpointId(edge.from),
93
+ edge.from?.output ?? "out",
94
+ endpointId(edge.to),
95
+ endpointInput(edge.to),
96
+ edge.selector ?? null
97
+ ]);
98
+ }
99
+ function graphSpecDiff(prev, next) {
100
+ const prevNodes = /* @__PURE__ */ new Map();
101
+ for (const node of asRecords(prev.nodes)) {
102
+ const id = nodeId(node);
103
+ if (id) prevNodes.set(id, node);
104
+ }
105
+ const nextNodes = /* @__PURE__ */ new Map();
106
+ for (const node of asRecords(next.nodes)) {
107
+ const id = nodeId(node);
108
+ if (id) nextNodes.set(id, node);
109
+ }
110
+ const prevEdges = asRecords(prev.edges);
111
+ const nextEdges = asRecords(next.edges);
112
+ const remove_nodes = [...prevNodes.keys()].filter((id) => !nextNodes.has(id));
113
+ const upsertIds = /* @__PURE__ */ new Set();
114
+ for (const [id, node] of nextNodes) {
115
+ const before = prevNodes.get(id);
116
+ if (!before || stable(before) !== stable(node)) upsertIds.add(id);
117
+ }
118
+ const prevEdgeKeys = new Set(prevEdges.map(edgeKey));
119
+ const nextEdgeKeys = new Set(nextEdges.map(edgeKey));
120
+ const removedEdges = prevEdges.filter((e) => !nextEdgeKeys.has(edgeKey(e)));
121
+ const changedEdges = [
122
+ ...nextEdges.filter((e) => !prevEdgeKeys.has(edgeKey(e))),
123
+ ...removedEdges
124
+ ];
125
+ for (const edge of changedEdges) {
126
+ for (const id of [endpointId(edge.from), endpointId(edge.to)]) {
127
+ if (id && nextNodes.has(id)) upsertIds.add(id);
128
+ }
129
+ }
130
+ const incidentToUpsert = (edge) => {
131
+ const from = endpointId(edge.from);
132
+ const to = endpointId(edge.to);
133
+ return from !== void 0 && upsertIds.has(from) || to !== void 0 && upsertIds.has(to);
134
+ };
135
+ const upsert_nodes = [...upsertIds].map((id) => nextNodes.get(id));
136
+ const upsert_edges = nextEdges.filter(incidentToUpsert);
137
+ const removeNodeSet = new Set(remove_nodes);
138
+ const remove_edges = removedEdges.flatMap((edge) => {
139
+ const to = endpointId(edge.to);
140
+ const input = endpointInput(edge.to);
141
+ if (!to || !input || removeNodeSet.has(to) || upsertIds.has(to)) return [];
142
+ return [{ node_id: to, input }];
143
+ });
144
+ if (upsert_nodes.length === 0 && remove_nodes.length === 0 && upsert_edges.length === 0 && remove_edges.length === 0) {
145
+ return null;
146
+ }
147
+ return { upsert_nodes, remove_nodes, upsert_edges, remove_edges };
148
+ }
149
+
150
+ // src/engine/aroraEngine.ts
151
+ var BUILT_IN_PREFIX = "arora/";
152
+ function isBuiltInPath(path) {
153
+ return path.startsWith(BUILT_IN_PREFIX);
154
+ }
155
+ var RUN_PERIOD_MS = 1e3 / 60;
156
+ var initPromise = null;
157
+ function ensureWasmInit(input) {
158
+ if (!initPromise) {
159
+ initPromise = (0, import_runtime.init)(input);
160
+ }
161
+ return initPromise;
162
+ }
163
+ var DeviceSlot = class {
164
+ constructor() {
165
+ this.handle = null;
166
+ /** Module loading a boot must wait out before it reads `modules`. */
167
+ this.modulesLoading = null;
168
+ /**
169
+ * When set, a booted device is handed to its own loop (`device.run`) at
170
+ * this period. `null`: the host drives `step()` itself.
171
+ */
172
+ this.runPeriodMs = null;
173
+ /**
174
+ * Serializes device operations. Every `recompose` chains behind the
175
+ * previous op so two never run concurrently — a bundle whose import fans
176
+ * out into several graph registrations recomposes in a burst. Failures are
177
+ * swallowed on the chain so one failed op doesn't wedge later ones; each
178
+ * caller still sees its own rejection through the promise `enqueue`
179
+ * returns.
180
+ */
181
+ this.opChain = Promise.resolve();
182
+ }
183
+ get current() {
184
+ return this.handle;
185
+ }
186
+ /**
187
+ * The modules every subsequent device boot loads. The live device (if any)
188
+ * is not touched: a later `recompose` that sees the list changed rebuilds.
189
+ */
190
+ setModules(modules) {
191
+ this.modules = modules && modules.length > 0 ? modules : void 0;
192
+ }
193
+ /**
194
+ * Announce module loading in flight: boots wait for `loading` to settle
195
+ * before reading the module list, so a device never boots without modules
196
+ * that were already on their way. Pass a promise that cannot reject (the
197
+ * owner handles its own failures) — boots resume either way.
198
+ */
199
+ waitForModules(loading) {
200
+ this.modulesLoading = loading;
201
+ }
202
+ /**
203
+ * Hand the live device to its own loop, if there is one and `runPeriodMs`
204
+ * says so — for a host whose drive mode turns self-paced after boot.
205
+ */
206
+ startRun() {
207
+ if (this.handle) {
208
+ this.maybeRun(this.handle.device);
209
+ }
210
+ }
211
+ maybeRun(device) {
212
+ if (this.runPeriodMs === null || device.running) {
213
+ return;
214
+ }
215
+ device.run(this.runPeriodMs).catch((error) => {
216
+ this.onRunEnded?.(error);
217
+ });
218
+ }
219
+ enqueue(op) {
220
+ const run = this.opChain.then(op, op);
221
+ this.opChain = run.then(
222
+ () => void 0,
223
+ () => void 0
224
+ );
225
+ return run;
226
+ }
227
+ async boot(spec) {
228
+ await ensureWasmInit();
229
+ if (this.modulesLoading) {
230
+ await this.modulesLoading;
231
+ }
232
+ const device = await (0, import_runtime.startRuntime)(spec, void 0, this.modules);
233
+ this.handle = { device, spec, modules: this.modules };
234
+ this.onDeviceStarted?.(this.handle);
235
+ this.maybeRun(device);
236
+ return this.handle;
237
+ }
238
+ /**
239
+ * Rebuild the device because its module set changed since boot — the one
240
+ * change a live device cannot take. Carries the store across (snapshot
241
+ * minus built-in keys) and replays module setup via `onDeviceStarted`. The
242
+ * old device's wrapper is freed; if it was self-paced its loop keeps
243
+ * ticking unreachable — which is why boots wait for announced modules
244
+ * instead of leaning on this path.
245
+ */
246
+ async rebuild(old, spec) {
247
+ const carried = {};
248
+ for (const [path, value] of Object.entries(old.device.snapshot())) {
249
+ if (!isBuiltInPath(path)) {
250
+ carried[path] = value;
251
+ }
252
+ }
253
+ const device = await (0, import_runtime.startRuntime)(spec, void 0, this.modules);
254
+ if (Object.keys(carried).length > 0) {
255
+ device.writeValues(carried);
256
+ }
257
+ this.handle = { device, spec, modules: this.modules };
258
+ old.device.dispose();
259
+ this.onDeviceStarted?.(this.handle);
260
+ this.maybeRun(device);
261
+ return this.handle;
262
+ }
263
+ /**
264
+ * Make the device run `spec`: boot it if none is live, otherwise swap the
265
+ * graph in place (the store is the same store, so nothing is carried).
266
+ * No-op if the spec is identical by reference. Serialized behind any
267
+ * in-flight op (see `opChain`).
268
+ */
269
+ recompose(spec) {
270
+ return this.enqueue(async () => {
271
+ const old = this.handle;
272
+ if (!old) {
273
+ return this.boot(spec);
274
+ }
275
+ if (old.modules !== this.modules) {
276
+ return this.rebuild(old, spec);
277
+ }
278
+ if (old.spec === spec) {
279
+ return old;
280
+ }
281
+ const edits = graphSpecDiff(old.spec, spec);
282
+ if (edits) {
283
+ try {
284
+ await old.device.applyGraphEdits(edits);
285
+ } catch (err) {
286
+ console.warn(
287
+ "[vizij-runtime] applyGraphEdits failed; reloading the whole graph",
288
+ err
289
+ );
290
+ await old.device.loadGraph(spec);
291
+ }
292
+ }
293
+ old.spec = spec;
294
+ return old;
295
+ });
296
+ }
297
+ };
298
+
299
+ // src/utils/composeGraph.ts
300
+ function asArray(value) {
301
+ return Array.isArray(value) ? value.filter((n) => n && typeof n === "object") : [];
302
+ }
303
+ function prefixId(sourceId, id) {
304
+ return typeof id === "string" && id ? `${sourceId}::${id}` : id;
305
+ }
306
+ function prefixEndpoint(sourceId, endpoint) {
307
+ if (!endpoint || typeof endpoint !== "object") {
308
+ return endpoint;
309
+ }
310
+ const record = endpoint;
311
+ if (typeof record.node_id !== "string") {
312
+ return endpoint;
313
+ }
314
+ return { ...record, node_id: prefixId(sourceId, record.node_id) };
315
+ }
316
+ function outputPathsOf(spec) {
317
+ const paths = [];
318
+ for (const node of asArray(spec.nodes)) {
319
+ if (String(node.type ?? "").toLowerCase() !== "output") {
320
+ continue;
321
+ }
322
+ const path = node.params?.path;
323
+ if (typeof path === "string" && path.trim()) {
324
+ paths.push(path.trim());
325
+ }
326
+ }
327
+ return paths;
328
+ }
329
+ function composeGraphSpecs(sources) {
330
+ const nodes = [];
331
+ const edges = [];
332
+ const outputOwners = /* @__PURE__ */ new Map();
333
+ for (const { sourceId, spec } of sources) {
334
+ for (const path of outputPathsOf(spec)) {
335
+ const owner = outputOwners.get(path);
336
+ if (owner && owner !== sourceId) {
337
+ console.warn(
338
+ `[vizij-runtime] output path "${path}" is written by both "${owner}" and "${sourceId}"; last writer wins ("${sourceId}"). There is no combiner node for additive merges yet.`
339
+ );
340
+ }
341
+ outputOwners.set(path, sourceId);
342
+ }
343
+ for (const node of asArray(spec.nodes)) {
344
+ nodes.push({ ...node, id: prefixId(sourceId, node.id) });
345
+ }
346
+ for (const edge of asArray(spec.edges)) {
347
+ edges.push({
348
+ ...edge,
349
+ from: prefixEndpoint(sourceId, edge.from),
350
+ to: prefixEndpoint(sourceId, edge.to)
351
+ });
352
+ }
353
+ }
354
+ return { nodes, edges };
355
+ }
57
356
 
58
357
  // src/context.ts
59
358
  var import_react = require("react");
60
359
  var VizijRuntimeContext = (0, import_react.createContext)(null);
61
360
 
361
+ // src/memoryInvestigation.ts
362
+ function getGlobalMemoryInvestigation() {
363
+ return globalThis;
364
+ }
365
+ function updateRuntimeMap(updater) {
366
+ const globalObj = getGlobalMemoryInvestigation();
367
+ if (!globalObj.__VIZIJ_MEMORY_INVESTIGATION__?.enabled) {
368
+ return;
369
+ }
370
+ if (!globalObj.__vizijMemoryDebugState) {
371
+ globalObj.__vizijMemoryDebugState = {};
372
+ }
373
+ if (!globalObj.__vizijMemoryDebugState.runtimes) {
374
+ globalObj.__vizijMemoryDebugState.runtimes = {};
375
+ }
376
+ updater(globalObj.__vizijMemoryDebugState.runtimes);
377
+ }
378
+ function setRuntimeDebugState(runtimeId, state) {
379
+ updateRuntimeMap((runtimeMap) => {
380
+ runtimeMap[runtimeId] = state;
381
+ });
382
+ }
383
+ function clearRuntimeDebugState(runtimeId) {
384
+ updateRuntimeMap((runtimeMap) => {
385
+ delete runtimeMap[runtimeId];
386
+ });
387
+ }
388
+
62
389
  // src/updatePolicy.ts
390
+ var blobIdentityMap = /* @__PURE__ */ new WeakMap();
391
+ var nextBlobIdentity = 0;
392
+ function getBlobIdentity(blob) {
393
+ if (!blob) {
394
+ return "missing";
395
+ }
396
+ let identity = blobIdentityMap.get(blob);
397
+ if (identity === void 0) {
398
+ identity = nextBlobIdentity++;
399
+ blobIdentityMap.set(blob, identity);
400
+ }
401
+ return `${identity}:${blob.size}:${blob.type}`;
402
+ }
63
403
  function applyRuntimeGraphBundle(base, bundle) {
64
404
  const next = {
65
405
  ...base
@@ -75,18 +415,10 @@ function applyRuntimeGraphBundle(base, bundle) {
75
415
  "programs"
76
416
  );
77
417
  if (hasRigOverride) {
78
- if (bundle.rig) {
79
- next.rig = bundle.rig;
80
- } else {
81
- delete next.rig;
82
- }
418
+ next.rig = bundle.rig;
83
419
  }
84
420
  if (hasPoseOverride) {
85
- if (bundle.pose) {
86
- next.pose = bundle.pose;
87
- } else {
88
- delete next.pose;
89
- }
421
+ next.pose = bundle.pose;
90
422
  }
91
423
  if (hasAnimationsOverride) {
92
424
  next.animations = Array.isArray(bundle.animations) ? bundle.animations : void 0;
@@ -108,10 +440,18 @@ function normalizeSpecPayload(value) {
108
440
  }
109
441
  function glbSignature(glb) {
110
442
  if (glb.kind === "url") {
111
- return `url:${glb.src}`;
443
+ const importOptions = normalizeSpecPayload({
444
+ aggressiveImport: glb.aggressiveImport ?? false,
445
+ rootBounds: glb.rootBounds ?? null
446
+ });
447
+ return `url:${glb.src}:${importOptions}`;
112
448
  }
113
449
  if (glb.kind === "blob") {
114
- return `blob:${glb.blob?.size ?? 0}`;
450
+ const importOptions = normalizeSpecPayload({
451
+ aggressiveImport: glb.aggressiveImport ?? false,
452
+ rootBounds: glb.rootBounds ?? null
453
+ });
454
+ return `blob:${getBlobIdentity(glb.blob)}:${importOptions}`;
115
455
  }
116
456
  return `world:${normalizeSpecPayload(glb.world)}`;
117
457
  }
@@ -605,65 +945,20 @@ function resolvePoseControlInputPath({
605
945
  }
606
946
 
607
947
  // src/utils/clipPlayback.ts
608
- var EPSILON = 1e-6;
609
948
  function toFiniteNumber(value, fallback) {
610
949
  const parsed = Number(value);
611
950
  return Number.isFinite(parsed) ? parsed : fallback;
612
951
  }
613
- function normaliseInterpolation(interpolation) {
614
- const mode = typeof interpolation === "string" ? interpolation.trim().toLowerCase() : "linear";
615
- if (mode === "step") {
616
- return "step";
617
- }
618
- if (mode === "cubic" || mode === "cubicspline") {
619
- return "cubic";
620
- }
621
- return "linear";
622
- }
623
- function asNumericKeyframe(keyframe) {
624
- if (!keyframe || typeof keyframe !== "object") {
625
- return null;
626
- }
627
- const time = Number(keyframe.time);
628
- const value = Number(keyframe.value);
629
- if (!Number.isFinite(time) || !Number.isFinite(value)) {
630
- return null;
631
- }
632
- const inTangentRaw = keyframe.inTangent;
633
- const outTangentRaw = keyframe.outTangent;
634
- const inTangent = inTangentRaw == null || Number.isFinite(Number(inTangentRaw)) ? inTangentRaw : void 0;
635
- const outTangent = outTangentRaw == null || Number.isFinite(Number(outTangentRaw)) ? outTangentRaw : void 0;
636
- return {
637
- time,
638
- value,
639
- inTangent,
640
- outTangent
641
- };
642
- }
643
- function getNumericKeyframes(track) {
952
+ function lastKeyframeTime(track) {
644
953
  const keyframes = Array.isArray(track.keyframes) ? track.keyframes : [];
645
- const numeric = keyframes.map((keyframe) => asNumericKeyframe(keyframe)).filter((keyframe) => Boolean(keyframe));
646
- if (numeric.length <= 1) {
647
- return numeric;
648
- }
649
- return [...numeric].sort((a, b) => a.time - b.time);
650
- }
651
- function resolveTangent(value, fallback) {
652
- const parsed = Number(value);
653
- if (Number.isFinite(parsed)) {
654
- return parsed;
655
- }
656
- return fallback;
657
- }
658
- function sampleHermite(startValue, endValue, outTangent, inTangent, factor, duration) {
659
- const t = factor;
660
- const t2 = t * t;
661
- const t3 = t2 * t;
662
- const h00 = 2 * t3 - 3 * t2 + 1;
663
- const h10 = t3 - 2 * t2 + t;
664
- const h01 = -2 * t3 + 3 * t2;
665
- const h11 = t3 - t2;
666
- return h00 * startValue + h10 * outTangent * duration + h01 * endValue + h11 * inTangent * duration;
954
+ let maxTime = 0;
955
+ keyframes.forEach((keyframe) => {
956
+ const time = Number(keyframe?.time);
957
+ if (Number.isFinite(time) && time > maxTime) {
958
+ maxTime = time;
959
+ }
960
+ });
961
+ return maxTime;
667
962
  }
668
963
  function resolveClipDurationSeconds(clip, fallbackDurationSeconds = 0) {
669
964
  const fallback = Math.max(0, toFiniteNumber(fallbackDurationSeconds, 0));
@@ -677,109 +972,13 @@ function resolveClipDurationSeconds(clip, fallbackDurationSeconds = 0) {
677
972
  const tracks = Array.isArray(clip.tracks) ? clip.tracks : [];
678
973
  let maxTime = 0;
679
974
  tracks.forEach((track) => {
680
- const keyframes = getNumericKeyframes(track);
681
- const last = keyframes[keyframes.length - 1];
682
- if (last && last.time > maxTime) {
683
- maxTime = last.time;
975
+ const time = lastKeyframeTime(track);
976
+ if (time > maxTime) {
977
+ maxTime = time;
684
978
  }
685
979
  });
686
980
  return maxTime > 0 ? maxTime : fallback;
687
981
  }
688
- function clampAnimationTime(time, duration) {
689
- if (!Number.isFinite(duration) || duration <= 0) {
690
- return 0;
691
- }
692
- if (!Number.isFinite(time)) {
693
- return 0;
694
- }
695
- if (time <= 0) {
696
- return 0;
697
- }
698
- if (time >= duration) {
699
- return duration;
700
- }
701
- return time;
702
- }
703
- function advanceClipTime(state, dt) {
704
- const duration = Math.max(0, toFiniteNumber(state.duration, 0));
705
- const speed = Number.isFinite(state.speed) && state.speed > 0 ? state.speed : 1;
706
- const currentTime = clampAnimationTime(state.time, duration);
707
- const delta = Number.isFinite(dt) && dt > 0 ? Math.max(0, dt) * speed : 0;
708
- if (!state.playing || delta <= 0) {
709
- return { time: currentTime, completed: false };
710
- }
711
- if (duration <= 0) {
712
- return { time: 0, completed: true };
713
- }
714
- const nextTime = currentTime + delta;
715
- if (state.loop) {
716
- if (nextTime < duration) {
717
- return { time: nextTime, completed: false };
718
- }
719
- const wrapped = (nextTime % duration + duration) % duration;
720
- return { time: wrapped, completed: false };
721
- }
722
- if (nextTime >= duration - EPSILON) {
723
- return { time: duration, completed: true };
724
- }
725
- return { time: nextTime, completed: false };
726
- }
727
- function sampleTrackAtTime(track, timeSeconds) {
728
- const keyframes = getNumericKeyframes(track);
729
- if (keyframes.length === 0) {
730
- return 0;
731
- }
732
- if (keyframes.length === 1) {
733
- return keyframes[0].value;
734
- }
735
- const mode = normaliseInterpolation(track.interpolation);
736
- const time = Number.isFinite(timeSeconds) ? timeSeconds : 0;
737
- const first = keyframes[0];
738
- if (time <= first.time + EPSILON) {
739
- return first.value;
740
- }
741
- const last = keyframes[keyframes.length - 1];
742
- if (time >= last.time - EPSILON) {
743
- return last.value;
744
- }
745
- for (let i = 0; i < keyframes.length - 1; i += 1) {
746
- const current = keyframes[i];
747
- const next = keyframes[i + 1];
748
- const start = current.time;
749
- const end = next.time;
750
- const duration = end - start;
751
- if (duration <= EPSILON) {
752
- if (time <= end + EPSILON) {
753
- return next.value;
754
- }
755
- continue;
756
- }
757
- if (Math.abs(time - end) <= EPSILON) {
758
- return next.value;
759
- }
760
- if (time < end) {
761
- const factor = (time - start) / duration;
762
- if (mode === "step") {
763
- return current.value;
764
- }
765
- if (mode === "cubic") {
766
- const slope = (next.value - current.value) / duration;
767
- const outTangent = resolveTangent(current.outTangent, slope);
768
- const inTangent = resolveTangent(next.inTangent, slope);
769
- return sampleHermite(
770
- current.value,
771
- next.value,
772
- outTangent,
773
- inTangent,
774
- factor,
775
- duration
776
- );
777
- }
778
- return current.value + (next.value - current.value) * factor;
779
- }
780
- }
781
- return last.value;
782
- }
783
982
 
784
983
  // src/utils/animationBridge.ts
785
984
  function resolveAnimationBridgeOutputPaths(channel, faceId, rigInputMap) {
@@ -850,53 +1049,6 @@ function collectAnimationClipOutputPaths(clip, faceId, rigInputMap) {
850
1049
  (left, right) => left.localeCompare(right)
851
1050
  );
852
1051
  }
853
- function sampleAnimationClipOutputValues(clip, timeSeconds, weight = 1, faceId, rigInputMap) {
854
- const appliedWeight = Number.isFinite(weight) && weight >= 0 ? Number(weight) : 1;
855
- const outputValues = /* @__PURE__ */ new Map();
856
- const tracks = Array.isArray(clip.tracks) ? clip.tracks : [];
857
- tracks.forEach((track) => {
858
- const channel = typeof track.channel === "string" ? track.channel.trim() : "";
859
- if (!channel) {
860
- return;
861
- }
862
- const sampledValue = sampleTrackAtTime(
863
- track,
864
- timeSeconds
865
- );
866
- const weightedValue = sampledValue * appliedWeight;
867
- resolveAnimationBridgeOutputPaths(channel, faceId, rigInputMap).forEach(
868
- (path) => {
869
- outputValues.set(path, (outputValues.get(path) ?? 0) + weightedValue);
870
- }
871
- );
872
- });
873
- return outputValues;
874
- }
875
- function diffAnimationAggregateValues(previousAggregate, nextAggregate, epsilon = 1e-6) {
876
- const operations = [];
877
- const changedPaths = /* @__PURE__ */ new Set();
878
- previousAggregate.forEach((previousValue, path) => {
879
- const nextValue = nextAggregate.get(path);
880
- if (nextValue === void 0 || Math.abs(nextValue - previousValue) > epsilon) {
881
- changedPaths.add(path);
882
- }
883
- });
884
- nextAggregate.forEach((nextValue, path) => {
885
- const previousValue = previousAggregate.get(path);
886
- if (previousValue === void 0 || Math.abs(nextValue - previousValue) > epsilon) {
887
- changedPaths.add(path);
888
- }
889
- });
890
- changedPaths.forEach((path) => {
891
- const nextValue = nextAggregate.get(path);
892
- if (nextValue === void 0) {
893
- operations.push({ kind: "clear", path });
894
- return;
895
- }
896
- operations.push({ kind: "set", path, value: nextValue });
897
- });
898
- return operations;
899
- }
900
1052
 
901
1053
  // src/utils/valueConversion.ts
902
1054
  var import_value_json = require("@vizij/value-json");
@@ -944,27 +1096,28 @@ function valueJSONToRaw(value) {
944
1096
  if (Array.isArray(value)) {
945
1097
  return value.map((entry) => valueJSONToRaw(entry));
946
1098
  }
947
- if (typeof value === "object" && !("type" in value)) {
948
- const entries = Object.entries(value).map(([key, entry]) => [
949
- key,
950
- valueJSONToRaw(entry)
951
- ]);
952
- return Object.fromEntries(entries);
953
- }
954
- if (!(0, import_value_json.isNormalizedValue)(value)) {
1099
+ const normalized = (0, import_value_json.isNormalizedValue)(value) ? value : (0, import_value_json.fromAroraValueJSON)(value);
1100
+ if (normalized === void 0) {
1101
+ if (typeof value === "object" && !("type" in value)) {
1102
+ const entries = Object.entries(value).map(([key, entry]) => [
1103
+ key,
1104
+ valueJSONToRaw(entry)
1105
+ ]);
1106
+ return Object.fromEntries(entries);
1107
+ }
955
1108
  return void 0;
956
1109
  }
957
- switch (value.type) {
1110
+ switch (normalized.type) {
958
1111
  case "float": {
959
- const num = (0, import_value_json.valueAsNumber)(value);
1112
+ const num = (0, import_value_json.valueAsNumber)(normalized);
960
1113
  return typeof num === "number" ? num : void 0;
961
1114
  }
962
1115
  case "bool": {
963
- const boolVal = (0, import_value_json.valueAsBool)(value);
1116
+ const boolVal = (0, import_value_json.valueAsBool)(normalized);
964
1117
  return typeof boolVal === "boolean" ? boolVal : void 0;
965
1118
  }
966
1119
  case "text": {
967
- const text = (0, import_value_json.valueAsText)(value);
1120
+ const text = (0, import_value_json.valueAsText)(normalized);
968
1121
  return typeof text === "string" ? text : void 0;
969
1122
  }
970
1123
  case "vec2":
@@ -972,11 +1125,11 @@ function valueJSONToRaw(value) {
972
1125
  case "vec4":
973
1126
  case "quat":
974
1127
  case "vector": {
975
- const vec = (0, import_value_json.valueAsVector)(value);
1128
+ const vec = (0, import_value_json.valueAsVector)(normalized);
976
1129
  return vec ? numericArrayToRaw(vec) : void 0;
977
1130
  }
978
1131
  case "colorrgba": {
979
- const color = (0, import_value_json.valueAsColorRgba)(value);
1132
+ const color = (0, import_value_json.valueAsColorRgba)(normalized);
980
1133
  if (!color) {
981
1134
  return void 0;
982
1135
  }
@@ -984,7 +1137,7 @@ function valueJSONToRaw(value) {
984
1137
  return { r, g, b, a };
985
1138
  }
986
1139
  case "transform": {
987
- const transform = (0, import_value_json.valueAsTransform)(value);
1140
+ const transform = (0, import_value_json.valueAsTransform)(normalized);
988
1141
  if (!transform) {
989
1142
  return void 0;
990
1143
  }
@@ -994,15 +1147,20 @@ function valueJSONToRaw(value) {
994
1147
  scale: numericArrayToRaw(transform.scale)
995
1148
  };
996
1149
  }
1150
+ case "array":
1151
+ case "list":
1152
+ case "tuple":
1153
+ return (normalized.data ?? []).map(
1154
+ (entry) => valueJSONToRaw(entry)
1155
+ );
997
1156
  case "record": {
998
- const entries = Object.entries(value.data ?? {}).map(([key, entry]) => [
999
- key,
1000
- valueJSONToRaw(entry)
1001
- ]);
1157
+ const entries = Object.entries(normalized.data ?? {}).map(
1158
+ ([key, entry]) => [key, valueJSONToRaw(entry)]
1159
+ );
1002
1160
  return Object.fromEntries(entries);
1003
1161
  }
1004
1162
  case "enum": {
1005
- const [tag, inner] = value.data;
1163
+ const [tag, inner] = normalized.data;
1006
1164
  return {
1007
1165
  tag,
1008
1166
  value: valueJSONToRaw(inner)
@@ -1013,17 +1171,608 @@ function valueJSONToRaw(value) {
1013
1171
  }
1014
1172
  }
1015
1173
 
1174
+ // src/engine/animationModule.ts
1175
+ var ANIMATION_MODULE_FN = {
1176
+ loadAnimation: "76697a69-6a00-0000-0f00-000000000001",
1177
+ createPlayer: "76697a69-6a00-0000-0f00-000000000002",
1178
+ addInstance: "76697a69-6a00-0000-0f00-000000000003",
1179
+ step: "76697a69-6a00-0000-0f00-000000000004",
1180
+ play: "76697a69-6a00-0000-0f00-000000000005",
1181
+ pause: "76697a69-6a00-0000-0f00-000000000006",
1182
+ stop: "76697a69-6a00-0000-0f00-000000000007",
1183
+ seek: "76697a69-6a00-0000-0f00-000000000008",
1184
+ setSpeed: "76697a69-6a00-0000-0f00-000000000009",
1185
+ setLoop: "76697a69-6a00-0000-0f00-00000000000a",
1186
+ setWeight: "76697a69-6a00-0000-0f00-00000000000b",
1187
+ removeInstance: "76697a69-6a00-0000-0f00-00000000000c",
1188
+ playerStates: "76697a69-6a00-0000-0f00-00000000000d"
1189
+ };
1190
+ var ANIMATION_MODULE_PARAM = {
1191
+ clip: "76697a69-6a00-0000-0f01-000000000001",
1192
+ playerName: "76697a69-6a00-0000-0f02-000000000001",
1193
+ player: "76697a69-6a00-0000-0f03-000000000001",
1194
+ anim: "76697a69-6a00-0000-0f03-000000000002",
1195
+ dtNs: "76697a69-6a00-0000-0f04-000000000001",
1196
+ playPlayer: "76697a69-6a00-0000-0f05-000000000001",
1197
+ pausePlayer: "76697a69-6a00-0000-0f06-000000000001",
1198
+ stopPlayer: "76697a69-6a00-0000-0f07-000000000001",
1199
+ seekPlayer: "76697a69-6a00-0000-0f08-000000000001",
1200
+ seekTimeNs: "76697a69-6a00-0000-0f08-000000000002",
1201
+ speedPlayer: "76697a69-6a00-0000-0f09-000000000001",
1202
+ speedValue: "76697a69-6a00-0000-0f09-000000000002",
1203
+ loopPlayer: "76697a69-6a00-0000-0f0a-000000000001",
1204
+ loopMode: "76697a69-6a00-0000-0f0a-000000000002",
1205
+ weightPlayer: "76697a69-6a00-0000-0f0b-000000000001",
1206
+ weightInstance: "76697a69-6a00-0000-0f0b-000000000002",
1207
+ weightValue: "76697a69-6a00-0000-0f0b-000000000003",
1208
+ removePlayer: "76697a69-6a00-0000-0f0c-000000000001",
1209
+ removeInstance: "76697a69-6a00-0000-0f0c-000000000002"
1210
+ };
1211
+ var ANIMATION_MODULE_TYPE = {
1212
+ clip: "76697a69-6a00-0000-0000-000000000100",
1213
+ track: "76697a69-6a00-0000-0000-000000000101",
1214
+ keypoint: "76697a69-6a00-0000-0000-000000000102",
1215
+ transitionHandle: "76697a69-6a00-0000-0000-000000000103",
1216
+ trackOutput: "76697a69-6a00-0000-0000-000000000110",
1217
+ playerState: "76697a69-6a00-0000-0000-000000000111"
1218
+ };
1219
+ var ANIMATION_MODULE_FIELD = {
1220
+ clipName: "76697a69-6a00-0000-0100-000000000001",
1221
+ clipDuration: "76697a69-6a00-0000-0100-000000000002",
1222
+ clipTracks: "76697a69-6a00-0000-0100-000000000003",
1223
+ trackId: "76697a69-6a00-0000-0101-000000000001",
1224
+ trackName: "76697a69-6a00-0000-0101-000000000002",
1225
+ trackAnimatable: "76697a69-6a00-0000-0101-000000000003",
1226
+ trackPoints: "76697a69-6a00-0000-0101-000000000004",
1227
+ keypointId: "76697a69-6a00-0000-0102-000000000001",
1228
+ keypointStamp: "76697a69-6a00-0000-0102-000000000002",
1229
+ keypointValue: "76697a69-6a00-0000-0102-000000000003",
1230
+ keypointTransitionsIn: "76697a69-6a00-0000-0102-000000000004",
1231
+ keypointTransitionsOut: "76697a69-6a00-0000-0102-000000000005",
1232
+ handleX: "76697a69-6a00-0000-0103-000000000001",
1233
+ handleY: "76697a69-6a00-0000-0103-000000000002",
1234
+ outputTrackId: "76697a69-6a00-0000-0110-000000000001",
1235
+ outputDefaultKey: "76697a69-6a00-0000-0110-000000000002",
1236
+ outputValue: "76697a69-6a00-0000-0110-000000000003",
1237
+ statePlayer: "76697a69-6a00-0000-0111-000000000001",
1238
+ stateState: "76697a69-6a00-0000-0111-000000000002",
1239
+ stateTimeNs: "76697a69-6a00-0000-0111-000000000003",
1240
+ stateDurationNs: "76697a69-6a00-0000-0111-000000000004",
1241
+ stateSpeed: "76697a69-6a00-0000-0111-000000000005"
1242
+ };
1243
+ var field = (id, value) => ({
1244
+ id,
1245
+ value
1246
+ });
1247
+ function storedClipToModuleValue(clip, resolveKeys) {
1248
+ const name = typeof clip.name === "string" && clip.name.trim().length > 0 ? clip.name.trim() : typeof clip.id === "string" ? clip.id : "";
1249
+ const durationMs = Number(clip.duration);
1250
+ const duration = Number.isFinite(durationMs) && durationMs > 0 ? Math.round(durationMs) : 1;
1251
+ const tracks = (Array.isArray(clip.tracks) ? clip.tracks : []).map((rawTrack, index) => {
1252
+ if (!rawTrack || typeof rawTrack !== "object") {
1253
+ return null;
1254
+ }
1255
+ const animatableId = typeof rawTrack.animatableId === "string" ? rawTrack.animatableId.trim() : "";
1256
+ if (!animatableId) {
1257
+ return null;
1258
+ }
1259
+ const trackId = typeof rawTrack.id === "string" && rawTrack.id.trim().length > 0 ? rawTrack.id.trim() : `track-${index}`;
1260
+ const trackName = typeof rawTrack.name === "string" && rawTrack.name.trim().length > 0 ? rawTrack.name.trim() : trackId;
1261
+ const points = (Array.isArray(rawTrack.points) ? rawTrack.points : []).map((rawPoint, pointIndex) => {
1262
+ if (!rawPoint || typeof rawPoint !== "object") {
1263
+ return null;
1264
+ }
1265
+ const stamp = Number(rawPoint.stamp);
1266
+ const value = Number(rawPoint.value);
1267
+ if (!Number.isFinite(stamp) || !Number.isFinite(value)) {
1268
+ return null;
1269
+ }
1270
+ const pointId = typeof rawPoint.id === "string" && rawPoint.id.trim().length > 0 ? rawPoint.id.trim() : `${trackId}:point-${pointIndex}`;
1271
+ const handles = (handle) => ({
1272
+ structs: {
1273
+ id: ANIMATION_MODULE_TYPE.transitionHandle,
1274
+ elements: handle ? [
1275
+ {
1276
+ fields: [
1277
+ field(ANIMATION_MODULE_FIELD.handleX, {
1278
+ f32: handle.x
1279
+ }),
1280
+ field(ANIMATION_MODULE_FIELD.handleY, {
1281
+ f32: handle.y
1282
+ })
1283
+ ]
1284
+ }
1285
+ ] : []
1286
+ }
1287
+ });
1288
+ return {
1289
+ fields: [
1290
+ field(ANIMATION_MODULE_FIELD.keypointId, { str: pointId }),
1291
+ field(ANIMATION_MODULE_FIELD.keypointStamp, {
1292
+ f32: Math.max(0, Math.min(1, stamp))
1293
+ }),
1294
+ field(ANIMATION_MODULE_FIELD.keypointValue, { f32: value }),
1295
+ field(
1296
+ ANIMATION_MODULE_FIELD.keypointTransitionsIn,
1297
+ handles(rawPoint.transitions?.in)
1298
+ ),
1299
+ field(
1300
+ ANIMATION_MODULE_FIELD.keypointTransitionsOut,
1301
+ handles(rawPoint.transitions?.out)
1302
+ )
1303
+ ]
1304
+ };
1305
+ }).filter(Boolean);
1306
+ if (points.length === 0) {
1307
+ return null;
1308
+ }
1309
+ const targets = resolveKeys?.(animatableId) ?? [animatableId];
1310
+ return targets.map((target, targetIndex) => ({
1311
+ fields: [
1312
+ field(ANIMATION_MODULE_FIELD.trackId, {
1313
+ str: targetIndex === 0 ? trackId : `${trackId}~${targetIndex}`
1314
+ }),
1315
+ field(ANIMATION_MODULE_FIELD.trackName, { str: trackName }),
1316
+ field(ANIMATION_MODULE_FIELD.trackAnimatable, { str: target }),
1317
+ field(ANIMATION_MODULE_FIELD.trackPoints, {
1318
+ structs: {
1319
+ id: ANIMATION_MODULE_TYPE.keypoint,
1320
+ elements: points
1321
+ }
1322
+ })
1323
+ ]
1324
+ }));
1325
+ }).filter(Boolean).flat();
1326
+ return {
1327
+ struct: {
1328
+ id: ANIMATION_MODULE_TYPE.clip,
1329
+ fields: [
1330
+ field(ANIMATION_MODULE_FIELD.clipName, { str: name }),
1331
+ field(ANIMATION_MODULE_FIELD.clipDuration, { u32: duration }),
1332
+ field(ANIMATION_MODULE_FIELD.clipTracks, {
1333
+ structs: {
1334
+ id: ANIMATION_MODULE_TYPE.track,
1335
+ elements: tracks
1336
+ }
1337
+ })
1338
+ ]
1339
+ }
1340
+ };
1341
+ }
1342
+ function loadAnimationCall(clipValue) {
1343
+ return {
1344
+ id: ANIMATION_MODULE_FN.loadAnimation,
1345
+ args: [field(ANIMATION_MODULE_PARAM.clip, clipValue)]
1346
+ };
1347
+ }
1348
+ function createPlayerCall(name) {
1349
+ return {
1350
+ id: ANIMATION_MODULE_FN.createPlayer,
1351
+ args: [field(ANIMATION_MODULE_PARAM.playerName, { str: name })]
1352
+ };
1353
+ }
1354
+ function addInstanceCall(player, anim) {
1355
+ return {
1356
+ id: ANIMATION_MODULE_FN.addInstance,
1357
+ args: [
1358
+ field(ANIMATION_MODULE_PARAM.player, { u32: player }),
1359
+ field(ANIMATION_MODULE_PARAM.anim, { u32: anim })
1360
+ ]
1361
+ };
1362
+ }
1363
+ function playCall(player) {
1364
+ return {
1365
+ id: ANIMATION_MODULE_FN.play,
1366
+ args: [field(ANIMATION_MODULE_PARAM.playPlayer, { u32: player })]
1367
+ };
1368
+ }
1369
+ function pauseCall(player) {
1370
+ return {
1371
+ id: ANIMATION_MODULE_FN.pause,
1372
+ args: [field(ANIMATION_MODULE_PARAM.pausePlayer, { u32: player })]
1373
+ };
1374
+ }
1375
+ function stopCall(player) {
1376
+ return {
1377
+ id: ANIMATION_MODULE_FN.stop,
1378
+ args: [field(ANIMATION_MODULE_PARAM.stopPlayer, { u32: player })]
1379
+ };
1380
+ }
1381
+ function seekCall(player, timeNs) {
1382
+ return {
1383
+ id: ANIMATION_MODULE_FN.seek,
1384
+ args: [
1385
+ field(ANIMATION_MODULE_PARAM.seekPlayer, { u32: player }),
1386
+ field(ANIMATION_MODULE_PARAM.seekTimeNs, {
1387
+ u64: Math.max(0, Math.round(timeNs))
1388
+ })
1389
+ ]
1390
+ };
1391
+ }
1392
+ function setSpeedCall(player, speed) {
1393
+ return {
1394
+ id: ANIMATION_MODULE_FN.setSpeed,
1395
+ args: [
1396
+ field(ANIMATION_MODULE_PARAM.speedPlayer, { u32: player }),
1397
+ field(ANIMATION_MODULE_PARAM.speedValue, { f32: speed })
1398
+ ]
1399
+ };
1400
+ }
1401
+ function setLoopCall(player, mode) {
1402
+ return {
1403
+ id: ANIMATION_MODULE_FN.setLoop,
1404
+ args: [
1405
+ field(ANIMATION_MODULE_PARAM.loopPlayer, { u32: player }),
1406
+ field(ANIMATION_MODULE_PARAM.loopMode, { str: mode })
1407
+ ]
1408
+ };
1409
+ }
1410
+ function setWeightCall(player, instance, weight) {
1411
+ return {
1412
+ id: ANIMATION_MODULE_FN.setWeight,
1413
+ args: [
1414
+ field(ANIMATION_MODULE_PARAM.weightPlayer, { u32: player }),
1415
+ field(ANIMATION_MODULE_PARAM.weightInstance, { u32: instance }),
1416
+ field(ANIMATION_MODULE_PARAM.weightValue, { f32: weight })
1417
+ ]
1418
+ };
1419
+ }
1420
+ function callResultU32(result) {
1421
+ const ret = result.ret;
1422
+ if (ret && typeof ret === "object" && "u32" in ret) {
1423
+ const value = Number(ret.u32);
1424
+ return Number.isFinite(value) ? value : null;
1425
+ }
1426
+ return null;
1427
+ }
1428
+ var ANIMATION_PLAYERS_PATH = "vizij/animations/players";
1429
+ var ANIMATIONS_SOURCE_ID = "animations";
1430
+ function animationsGraphSource() {
1431
+ return {
1432
+ sourceId: ANIMATIONS_SOURCE_ID,
1433
+ spec: {
1434
+ nodes: [
1435
+ { id: "dt", type: "input", params: { path: "arora/dt" } },
1436
+ {
1437
+ id: "step",
1438
+ type: "externalfunction",
1439
+ params: {
1440
+ function: ANIMATION_MODULE_FN.step,
1441
+ param_ids: [ANIMATION_MODULE_PARAM.dtNs]
1442
+ }
1443
+ },
1444
+ {
1445
+ id: "apply",
1446
+ type: "output",
1447
+ params: {
1448
+ key_field: ANIMATION_MODULE_FIELD.outputDefaultKey,
1449
+ value_field: ANIMATION_MODULE_FIELD.outputValue
1450
+ }
1451
+ },
1452
+ {
1453
+ id: "states",
1454
+ type: "externalfunction",
1455
+ params: { function: ANIMATION_MODULE_FN.playerStates, param_ids: [] }
1456
+ },
1457
+ {
1458
+ id: "states-out",
1459
+ type: "output",
1460
+ params: { path: ANIMATION_PLAYERS_PATH }
1461
+ }
1462
+ ],
1463
+ edges: [
1464
+ { from: { node_id: "dt" }, to: { node_id: "step", input: "args_0" } },
1465
+ { from: { node_id: "step" }, to: { node_id: "apply", input: "in" } },
1466
+ {
1467
+ from: { node_id: "states" },
1468
+ to: { node_id: "states-out", input: "in" }
1469
+ }
1470
+ ]
1471
+ }
1472
+ };
1473
+ }
1474
+ function fieldNumber(value) {
1475
+ if (!value || typeof value !== "object") {
1476
+ return null;
1477
+ }
1478
+ for (const key of ["u64", "u32", "f32", "f64"]) {
1479
+ if (key in value) {
1480
+ const parsed = Number(value[key]);
1481
+ return Number.isFinite(parsed) ? parsed : null;
1482
+ }
1483
+ }
1484
+ return null;
1485
+ }
1486
+ function decodePlayerStates(raw) {
1487
+ if (!raw || typeof raw !== "object" || !("structs" in raw)) {
1488
+ return [];
1489
+ }
1490
+ const structs = raw.structs;
1491
+ const elements = Array.isArray(structs?.elements) ? structs.elements : [];
1492
+ const states = [];
1493
+ for (const element of elements) {
1494
+ const fields = element?.fields;
1495
+ if (!Array.isArray(fields)) {
1496
+ continue;
1497
+ }
1498
+ const byId = /* @__PURE__ */ new Map();
1499
+ for (const entry of fields) {
1500
+ const id = entry?.id;
1501
+ const value = entry?.value;
1502
+ if (typeof id === "string" && value && typeof value === "object") {
1503
+ byId.set(id, value);
1504
+ }
1505
+ }
1506
+ const player = fieldNumber(byId.get(ANIMATION_MODULE_FIELD.statePlayer));
1507
+ if (player === null) {
1508
+ continue;
1509
+ }
1510
+ const stateValue = byId.get(ANIMATION_MODULE_FIELD.stateState);
1511
+ states.push({
1512
+ player,
1513
+ state: stateValue && "str" in stateValue ? String(stateValue.str) : "",
1514
+ time: (fieldNumber(byId.get(ANIMATION_MODULE_FIELD.stateTimeNs)) ?? 0) / 1e9,
1515
+ duration: (fieldNumber(byId.get(ANIMATION_MODULE_FIELD.stateDurationNs)) ?? 0) / 1e9,
1516
+ speed: fieldNumber(byId.get(ANIMATION_MODULE_FIELD.stateSpeed)) ?? 1
1517
+ });
1518
+ }
1519
+ return states;
1520
+ }
1521
+
1522
+ // src/engine/animationModuleHost.ts
1523
+ var AnimationModuleHost = class {
1524
+ constructor(getDevice, resolveKeys = (key) => [key]) {
1525
+ this.getDevice = getDevice;
1526
+ this.resolveKeys = resolveKeys;
1527
+ this.clips = /* @__PURE__ */ new Map();
1528
+ }
1529
+ /** Whether any clip is currently marked playing (drives source lifecycle). */
1530
+ hasPlaying() {
1531
+ for (const entry of this.clips.values()) {
1532
+ if (entry.playing) {
1533
+ return true;
1534
+ }
1535
+ }
1536
+ return false;
1537
+ }
1538
+ /** Whether a clip id is known to the host. */
1539
+ has(clipId) {
1540
+ return this.clips.has(clipId);
1541
+ }
1542
+ /** The clip's module `PlayerId` on the live device (feedback correlation). */
1543
+ playerIdOf(clipId) {
1544
+ return this.clips.get(clipId)?.playerId ?? null;
1545
+ }
1546
+ /** The clip's transport state as last commanded. */
1547
+ transportOf(clipId) {
1548
+ const entry = this.clips.get(clipId);
1549
+ if (!entry) {
1550
+ return null;
1551
+ }
1552
+ return {
1553
+ playing: entry.playing,
1554
+ speed: entry.speed,
1555
+ loop: entry.loop,
1556
+ weight: entry.weight
1557
+ };
1558
+ }
1559
+ /**
1560
+ * Declare the clips the bundle carries, as data. Keeps playing/transport
1561
+ * state for clips that survive; clips no longer present are dropped. Does
1562
+ * not touch the device — loading is lazy (on first play), so muted/unused
1563
+ * clips never enter the module, and conversion happens at load with the
1564
+ * key resolver's state at that moment.
1565
+ */
1566
+ setClips(clips) {
1567
+ const nextIds = new Set(clips.map((clip) => clip.id));
1568
+ for (const id of Array.from(this.clips.keys())) {
1569
+ if (!nextIds.has(id)) {
1570
+ this.clips.delete(id);
1571
+ }
1572
+ }
1573
+ for (const clip of clips) {
1574
+ const existing = this.clips.get(clip.id);
1575
+ if (existing) {
1576
+ existing.stored = clip.stored;
1577
+ existing.animId = null;
1578
+ existing.playerId = null;
1579
+ existing.instId = null;
1580
+ } else {
1581
+ this.clips.set(clip.id, {
1582
+ stored: clip.stored,
1583
+ playing: false,
1584
+ speed: 1,
1585
+ loop: "loop",
1586
+ weight: 1,
1587
+ animId: null,
1588
+ playerId: null,
1589
+ instId: null
1590
+ });
1591
+ }
1592
+ }
1593
+ }
1594
+ /**
1595
+ * Mark a clip playing, ensure it is loaded into the live device with a
1596
+ * player + instance, and issue `play` (a resume after `pause` needs it; on
1597
+ * a fresh player it is a no-op). Idempotent. Resolves once the calls have
1598
+ * dispatched (or immediately when there is no live device yet — the
1599
+ * provider registers the source, which boots the device and replays via
1600
+ * `replayInto`).
1601
+ */
1602
+ async play(clipId) {
1603
+ const entry = this.clips.get(clipId);
1604
+ if (!entry) {
1605
+ return;
1606
+ }
1607
+ entry.playing = true;
1608
+ const device = this.getDevice();
1609
+ if (!device) {
1610
+ return;
1611
+ }
1612
+ await this.ensureLoaded(device, entry);
1613
+ if (entry.playerId !== null) {
1614
+ await device.call(playCall(entry.playerId));
1615
+ }
1616
+ }
1617
+ /** Hold the clip's playhead (`pause` on its player). */
1618
+ pause(clipId) {
1619
+ const entry = this.clips.get(clipId);
1620
+ if (!entry) {
1621
+ return;
1622
+ }
1623
+ entry.playing = false;
1624
+ this.dispatch(
1625
+ (device) => entry.playerId !== null ? device.call(pauseCall(entry.playerId)) : null
1626
+ );
1627
+ }
1628
+ /**
1629
+ * Stop a clip: not playing, playhead reset to the window start (`stop` on
1630
+ * its player — the next tick emits the clip's t=0 pose). The clip stays
1631
+ * loaded for a cheap replay.
1632
+ */
1633
+ stop(clipId) {
1634
+ const entry = this.clips.get(clipId);
1635
+ if (!entry) {
1636
+ return;
1637
+ }
1638
+ entry.playing = false;
1639
+ this.dispatch(
1640
+ (device) => entry.playerId !== null ? device.call(stopCall(entry.playerId)) : null
1641
+ );
1642
+ }
1643
+ /** Move the clip's playhead to `seconds`. */
1644
+ seek(clipId, seconds) {
1645
+ const entry = this.clips.get(clipId);
1646
+ if (!entry) {
1647
+ return;
1648
+ }
1649
+ this.dispatch(
1650
+ (device) => entry.playerId !== null ? device.call(seekCall(entry.playerId, seconds * 1e9)) : null
1651
+ );
1652
+ }
1653
+ /** Set the clip's playback speed multiplier. */
1654
+ setSpeed(clipId, speed) {
1655
+ const entry = this.clips.get(clipId);
1656
+ if (!entry) {
1657
+ return;
1658
+ }
1659
+ entry.speed = speed;
1660
+ this.dispatch(
1661
+ (device) => entry.playerId !== null ? device.call(setSpeedCall(entry.playerId, speed)) : null
1662
+ );
1663
+ }
1664
+ /** Set how the clip's player time maps into clip time. */
1665
+ setLoop(clipId, mode) {
1666
+ const entry = this.clips.get(clipId);
1667
+ if (!entry) {
1668
+ return;
1669
+ }
1670
+ entry.loop = mode;
1671
+ this.dispatch(
1672
+ (device) => entry.playerId !== null ? device.call(setLoopCall(entry.playerId, mode)) : null
1673
+ );
1674
+ }
1675
+ /** Set the clip instance's blend weight. */
1676
+ setWeight(clipId, weight) {
1677
+ const entry = this.clips.get(clipId);
1678
+ if (!entry) {
1679
+ return;
1680
+ }
1681
+ entry.weight = weight;
1682
+ this.dispatch(
1683
+ (device) => entry.playerId !== null && entry.instId !== null ? device.call(setWeightCall(entry.playerId, entry.instId, weight)) : null
1684
+ );
1685
+ }
1686
+ /**
1687
+ * Re-issue setup and transport for every playing clip against a freshly
1688
+ * started device (module guest state did not survive the rebuild). Voids
1689
+ * stale ids first. Called from `DeviceSlot.onDeviceStarted`.
1690
+ */
1691
+ replayInto(device) {
1692
+ for (const entry of this.clips.values()) {
1693
+ entry.animId = null;
1694
+ entry.playerId = null;
1695
+ entry.instId = null;
1696
+ if (entry.playing) {
1697
+ void this.ensureLoaded(device, entry);
1698
+ }
1699
+ }
1700
+ }
1701
+ /** Fire-and-forget a transport call against the live device, if any. */
1702
+ dispatch(issue) {
1703
+ const device = this.getDevice();
1704
+ if (!device) {
1705
+ return;
1706
+ }
1707
+ void issue(device)?.catch(() => {
1708
+ });
1709
+ }
1710
+ async ensureLoaded(device, entry) {
1711
+ if (entry.instId !== null) {
1712
+ return;
1713
+ }
1714
+ if (entry.animId === null) {
1715
+ const anim = await device.call(
1716
+ loadAnimationCall(
1717
+ storedClipToModuleValue(entry.stored, this.resolveKeys)
1718
+ )
1719
+ );
1720
+ entry.animId = callResultU32(anim);
1721
+ }
1722
+ if (entry.playerId === null) {
1723
+ const player = await device.call(createPlayerCall(""));
1724
+ entry.playerId = callResultU32(player);
1725
+ }
1726
+ if (entry.instId === null && entry.animId !== null && entry.playerId !== null) {
1727
+ const inst = await device.call(
1728
+ addInstanceCall(entry.playerId, entry.animId)
1729
+ );
1730
+ entry.instId = callResultU32(inst);
1731
+ }
1732
+ const followups = [];
1733
+ if (entry.playerId !== null) {
1734
+ if (entry.speed !== 1) {
1735
+ followups.push(setSpeedCall(entry.playerId, entry.speed));
1736
+ }
1737
+ if (entry.loop !== "loop") {
1738
+ followups.push(setLoopCall(entry.playerId, entry.loop));
1739
+ }
1740
+ if (entry.instId !== null && entry.weight !== 1) {
1741
+ followups.push(
1742
+ setWeightCall(entry.playerId, entry.instId, entry.weight)
1743
+ );
1744
+ }
1745
+ }
1746
+ for (const call of followups) {
1747
+ await device.call(call);
1748
+ }
1749
+ }
1750
+ };
1751
+
1016
1752
  // src/VizijRuntimeProvider.tsx
1017
1753
  var import_jsx_runtime = require("react/jsx-runtime");
1018
1754
  var ACTIVE_GRACE_MS = 250;
1019
1755
  var VISIBLE_IDLE_FPS = 30;
1020
1756
  var HIDDEN_IDLE_FPS = 1;
1757
+ var IMPLICIT_PAUSE_GAP_S = 5;
1758
+ var inferImplicitPause = (dt) => {
1759
+ if (dt <= IMPLICIT_PAUSE_GAP_S) {
1760
+ return dt;
1761
+ }
1762
+ console.warn(
1763
+ `[vizij-runtime] step loop woke after a ${dt.toFixed(
1764
+ 1
1765
+ )}s wall-clock gap; treating it as a suspension (dt = 0)`
1766
+ );
1767
+ return 0;
1768
+ };
1021
1769
  var DEFAULT_MERGE = {
1022
1770
  outputs: "add",
1023
1771
  intermediate: "add"
1024
1772
  };
1025
1773
  var DEFAULT_DURATION = 0.35;
1026
1774
  var POSE_CONTROL_BRIDGE_EPSILON = 1e-6;
1775
+ var runtimeDebugInstanceSequence = 0;
1027
1776
  var DEV_MODE = (() => {
1028
1777
  const nodeEnv = globalThis.process?.env?.NODE_ENV;
1029
1778
  return typeof nodeEnv === "string" && nodeEnv === "development";
@@ -1514,12 +2263,15 @@ function pickExtractedAnimations(asset) {
1514
2263
  }
1515
2264
  return animations;
1516
2265
  }
1517
- function mergeAnimationLists(explicit, fromBundle) {
1518
- if (!explicit?.length && fromBundle.length === 0) {
2266
+ function mergeAnimationLists(explicit, hasExplicitOverride, fromBundle) {
2267
+ if (!hasExplicitOverride) {
2268
+ return fromBundle.length > 0 ? fromBundle : void 0;
2269
+ }
2270
+ if (!Array.isArray(explicit)) {
1519
2271
  return void 0;
1520
2272
  }
1521
- if (!explicit?.length) {
1522
- return fromBundle.length > 0 ? fromBundle : void 0;
2273
+ if (explicit.length === 0) {
2274
+ return [];
1523
2275
  }
1524
2276
  if (fromBundle.length === 0) {
1525
2277
  return explicit;
@@ -1570,13 +2322,68 @@ function convertBundlePrograms(entries) {
1570
2322
  };
1571
2323
  }).filter(Boolean);
1572
2324
  }
1573
- function mergeProgramLists(explicit, fromBundle) {
1574
- if (!explicit?.length && fromBundle.length === 0) {
2325
+ function resolveConstraintDefault(path, namespace, inputConstraints) {
2326
+ const trimmed = path.trim();
2327
+ if (!trimmed) {
1575
2328
  return void 0;
1576
2329
  }
1577
- if (!explicit?.length) {
2330
+ const stripped = stripRigFacePrefix(trimmed);
2331
+ const relativePath = stripped ? `/${stripped}` : "";
2332
+ const candidates = [
2333
+ namespaceTypedPath(trimmed, namespace),
2334
+ trimmed,
2335
+ stripped ? namespaceTypedPath(stripped, namespace) : void 0,
2336
+ stripped || void 0,
2337
+ relativePath || void 0
2338
+ ];
2339
+ for (const candidate of candidates) {
2340
+ if (!candidate) {
2341
+ continue;
2342
+ }
2343
+ const defaultValue = inputConstraints[candidate]?.defaultValue;
2344
+ if (Number.isFinite(defaultValue)) {
2345
+ return Number(defaultValue);
2346
+ }
2347
+ }
2348
+ return void 0;
2349
+ }
2350
+ function deriveProgramInputSeedValues(args) {
2351
+ const graphSpec = resolveGraphSpec(
2352
+ args.program.graph,
2353
+ `${args.program.id ?? "program"} graph (seed defaults)`
2354
+ );
2355
+ if (!graphSpec) {
2356
+ return [];
2357
+ }
2358
+ return collectInputPaths(graphSpec).map((path) => path.trim()).filter((path) => path.length > 0).flatMap((path) => {
2359
+ const namespacedPath = namespaceTypedPath(path, args.namespace);
2360
+ if (args.stagedInputs.has(namespacedPath)) {
2361
+ return [];
2362
+ }
2363
+ if (args.getPathSnapshot(namespacedPath) !== void 0) {
2364
+ return [];
2365
+ }
2366
+ const defaultValue = resolveConstraintDefault(
2367
+ path,
2368
+ args.namespace,
2369
+ args.inputConstraints
2370
+ );
2371
+ if (!Number.isFinite(defaultValue)) {
2372
+ return [];
2373
+ }
2374
+ return [{ path, value: { float: Number(defaultValue) } }];
2375
+ });
2376
+ }
2377
+ function mergeProgramLists(explicit, hasExplicitOverride, fromBundle) {
2378
+ if (!hasExplicitOverride) {
1578
2379
  return fromBundle.length > 0 ? fromBundle : void 0;
1579
2380
  }
2381
+ if (!Array.isArray(explicit)) {
2382
+ return void 0;
2383
+ }
2384
+ if (explicit.length === 0) {
2385
+ return [];
2386
+ }
1580
2387
  if (fromBundle.length === 0) {
1581
2388
  return explicit;
1582
2389
  }
@@ -1595,10 +2402,23 @@ function mergeProgramLists(explicit, fromBundle) {
1595
2402
  }
1596
2403
  function mergeAssetBundle(base, extracted, extractedAnimations) {
1597
2404
  const resolvedBundle = base.bundle ?? extracted ?? null;
2405
+ const hasBaseRigOverride = Object.prototype.hasOwnProperty.call(base, "rig");
2406
+ const hasBasePoseOverride = Object.prototype.hasOwnProperty.call(
2407
+ base,
2408
+ "pose"
2409
+ );
2410
+ const hasBaseAnimationsOverride = Object.prototype.hasOwnProperty.call(
2411
+ base,
2412
+ "animations"
2413
+ );
2414
+ const hasBaseProgramsOverride = Object.prototype.hasOwnProperty.call(
2415
+ base,
2416
+ "programs"
2417
+ );
1598
2418
  const rigFromBundle = convertBundleGraph(
1599
2419
  pickBundleGraph(resolvedBundle, ["rig"])
1600
2420
  );
1601
- const resolvedRig = base.rig ?? rigFromBundle ?? void 0;
2421
+ const resolvedRig = hasBaseRigOverride ? base.rig : base.rig ?? rigFromBundle ?? void 0;
1602
2422
  const basePose = base.pose;
1603
2423
  const hasBasePoseGraphOverride = Boolean(
1604
2424
  basePose && Object.prototype.hasOwnProperty.call(basePose, "graph")
@@ -1607,13 +2427,15 @@ function mergeAssetBundle(base, extracted, extractedAnimations) {
1607
2427
  basePose && Object.prototype.hasOwnProperty.call(basePose, "config")
1608
2428
  );
1609
2429
  const poseStageFilter = basePose?.stageNeutralFilter;
1610
- const poseGraphFromBundle = hasBasePoseGraphOverride ? null : convertBundleGraph(
2430
+ const poseGraphFromBundle = hasBasePoseOverride && !basePose ? null : hasBasePoseGraphOverride ? null : convertBundleGraph(
1611
2431
  pickBundleGraph(resolvedBundle, ["pose-driver", "pose"])
1612
2432
  );
1613
2433
  const resolvedPoseGraph = hasBasePoseGraphOverride ? basePose?.graph : basePose?.graph ?? poseGraphFromBundle ?? void 0;
1614
2434
  const resolvedPoseConfig = hasBasePoseConfigOverride ? basePose?.config : basePose?.config ?? resolvedBundle?.poses?.config ?? void 0;
1615
2435
  let resolvedPose = basePose;
1616
- if (basePose) {
2436
+ if (hasBasePoseOverride && !basePose) {
2437
+ resolvedPose = void 0;
2438
+ } else if (basePose) {
1617
2439
  const nextPose = { ...basePose };
1618
2440
  let changed = false;
1619
2441
  if (resolvedPoseGraph && basePose.graph !== resolvedPoseGraph) {
@@ -1641,17 +2463,20 @@ function mergeAssetBundle(base, extracted, extractedAnimations) {
1641
2463
  );
1642
2464
  let resolvedAnimations = mergeAnimationLists(
1643
2465
  base.animations,
2466
+ hasBaseAnimationsOverride,
1644
2467
  animationsFromBundle
1645
2468
  );
1646
2469
  const animationsFromAsset = extractedAnimations && extractedAnimations.length > 0 ? extractedAnimations : [];
1647
2470
  if (animationsFromAsset.length > 0) {
1648
2471
  resolvedAnimations = mergeAnimationLists(
1649
2472
  resolvedAnimations,
2473
+ true,
1650
2474
  animationsFromAsset
1651
2475
  );
1652
2476
  }
1653
2477
  const programsFromBundle = mergeProgramLists(
1654
2478
  base.programs,
2479
+ hasBaseProgramsOverride,
1655
2480
  convertBundlePrograms(resolvedBundle?.graphs)
1656
2481
  );
1657
2482
  const merged = {
@@ -1765,28 +2590,18 @@ function VizijRuntimeProvider({
1765
2590
  faceId: faceIdProp,
1766
2591
  updateTier = "auto",
1767
2592
  autoCreate = true,
1768
- createOptions,
1769
2593
  autostart = false,
1770
- driveOrchestrator = true,
2594
+ driveRuntime = true,
1771
2595
  mergeStrategy,
1772
2596
  onRegisterControllers,
1773
2597
  onStatusChange,
1774
- transformOutputWrite,
1775
- orchestratorScope = "auto"
2598
+ transformOutputWrite
1776
2599
  }) {
1777
2600
  const storeRef = (0, import_react2.useRef)(null);
1778
2601
  if (!storeRef.current) {
1779
2602
  storeRef.current = (0, import_render.createVizijStore)();
1780
2603
  }
1781
- const parentOrchestrator = (0, import_react2.useContext)(import_orchestrator_react.OrchestratorContext);
1782
- const hasParentOrchestrator = Boolean(parentOrchestrator);
1783
- const shouldProvideOrchestrator = orchestratorScope === "isolated" || !hasParentOrchestrator && orchestratorScope !== "shared";
1784
- if (orchestratorScope === "shared" && !hasParentOrchestrator) {
1785
- console.warn(
1786
- '[vizij-runtime] orchestratorScope="shared" requires an OrchestratorProvider higher in the tree; falling back to an isolated provider.'
1787
- );
1788
- }
1789
- const runtimeTree = /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_render.VizijContext.Provider, { value: storeRef.current, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2604
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_render.VizijContext.Provider, { value: storeRef.current, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1790
2605
  VizijRuntimeProviderInner,
1791
2606
  {
1792
2607
  assetBundle,
@@ -1795,8 +2610,7 @@ function VizijRuntimeProvider({
1795
2610
  updateTier,
1796
2611
  autoCreate,
1797
2612
  autostart,
1798
- driveOrchestrator,
1799
- createOptions,
2613
+ driveRuntime,
1800
2614
  mergeStrategy,
1801
2615
  onRegisterControllers,
1802
2616
  onStatusChange,
@@ -1805,18 +2619,6 @@ function VizijRuntimeProvider({
1805
2619
  children
1806
2620
  }
1807
2621
  ) });
1808
- if (!shouldProvideOrchestrator) {
1809
- return runtimeTree;
1810
- }
1811
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1812
- import_orchestrator_react.OrchestratorProvider,
1813
- {
1814
- autoCreate,
1815
- createOptions,
1816
- autostart: false,
1817
- children: runtimeTree
1818
- }
1819
- );
1820
2622
  }
1821
2623
  function VizijRuntimeProviderInner({
1822
2624
  assetBundle: initialAssetBundle,
@@ -1831,8 +2633,7 @@ function VizijRuntimeProviderInner({
1831
2633
  children,
1832
2634
  autoCreate,
1833
2635
  autostart,
1834
- createOptions,
1835
- driveOrchestrator
2636
+ driveRuntime
1836
2637
  }) {
1837
2638
  const [assetBundleOverride, setAssetBundleOverride] = (0, import_react2.useState)(null);
1838
2639
  const [graphUpdateToken, setGraphUpdateToken] = (0, import_react2.useState)(0);
@@ -1894,23 +2695,17 @@ function VizijRuntimeProviderInner({
1894
2695
  setGraphUpdateToken((prev) => prev + 1);
1895
2696
  }
1896
2697
  }, [effectiveAssetBundle]);
1897
- const {
1898
- ready,
1899
- createOrchestrator,
1900
- registerGraph,
1901
- registerMergedGraph,
1902
- registerAnimation,
1903
- removeGraph,
1904
- removeAnimation,
1905
- removeInput,
1906
- listControllers,
1907
- setInput: orchestratorSetInput,
1908
- getPathSnapshot,
1909
- step: stepRuntime
1910
- } = (0, import_orchestrator_react.useOrchestrator)();
1911
- const frame = (0, import_orchestrator_react.useOrchFrame)();
2698
+ const deviceSlotRef = (0, import_react2.useRef)(null);
2699
+ if (!deviceSlotRef.current) {
2700
+ deviceSlotRef.current = new DeviceSlot();
2701
+ }
2702
+ const deviceSlot = deviceSlotRef.current;
2703
+ const [ready, setReady] = (0, import_react2.useState)(false);
2704
+ const graphSourcesRef = (0, import_react2.useRef)([]);
1912
2705
  const namespace = namespaceProp ?? assetBundle.namespace ?? "default";
1913
2706
  const faceId = faceIdProp ?? assetBundle.faceId ?? assetBundle.pose?.config?.faceId ?? assetBundle.pose?.config?.faceId ?? void 0;
2707
+ const faceIdRef = (0, import_react2.useRef)(faceId);
2708
+ faceIdRef.current = faceId;
1914
2709
  const [status, setStatus] = (0, import_react2.useState)({
1915
2710
  loading: true,
1916
2711
  ready: false,
@@ -1923,12 +2718,15 @@ function VizijRuntimeProviderInner({
1923
2718
  stepHz: void 0,
1924
2719
  controllers: { graphs: [], anims: [] }
1925
2720
  });
2721
+ const runtimeDebugInstanceIdRef = (0, import_react2.useRef)(
2722
+ `vizij-runtime:${runtimeDebugInstanceSequence++}`
2723
+ );
1926
2724
  const errorsRef = (0, import_react2.useRef)([]);
1927
2725
  const outputPathsRef = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
1928
2726
  const baseOutputPathsRef = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
1929
2727
  const namespacedOutputPathsRef = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
1930
2728
  const namespaceRef = (0, import_react2.useRef)(namespace);
1931
- const driveOrchestratorRef = (0, import_react2.useRef)(driveOrchestrator);
2729
+ const driveRuntimeRef = (0, import_react2.useRef)(driveRuntime);
1932
2730
  const rigInputMapRef = (0, import_react2.useRef)({});
1933
2731
  const rigPoseControlInputIdsRef = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
1934
2732
  const registeredGraphsRef = (0, import_react2.useRef)([]);
@@ -1970,16 +2768,17 @@ function VizijRuntimeProviderInner({
1970
2768
  const [inputConstraints, setInputConstraints] = (0, import_react2.useState)({});
1971
2769
  const inputConstraintsRef = (0, import_react2.useRef)({});
1972
2770
  const avgStepDtRef = (0, import_react2.useRef)(null);
2771
+ const inputDriverIdsRef = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
1973
2772
  const animationTweensRef = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
1974
2773
  const clipPlaybackRef = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
1975
2774
  const programPlaybackRef = (0, import_react2.useRef)(
1976
2775
  /* @__PURE__ */ new Map()
1977
2776
  );
1978
2777
  const programControllerIdsRef = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
1979
- const clipOutputValuesRef = (0, import_react2.useRef)(
1980
- /* @__PURE__ */ new Map()
1981
- );
1982
- const clipAggregateValuesRef = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
2778
+ const animationModuleRef = (0, import_react2.useRef)(null);
2779
+ const animationHostRef = (0, import_react2.useRef)(null);
2780
+ const animationsSourceRegisteredRef = (0, import_react2.useRef)(false);
2781
+ const animationGapWarnedRef = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
1983
2782
  const animationSystemActiveRef = (0, import_react2.useRef)(true);
1984
2783
  const stagedInputsRef = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
1985
2784
  const autostartRef = (0, import_react2.useRef)(autostart);
@@ -1995,6 +2794,58 @@ function VizijRuntimeProviderInner({
1995
2794
  runtimeMountedRef.current = false;
1996
2795
  };
1997
2796
  }, []);
2797
+ const publishRuntimeDebugState = (0, import_react2.useCallback)(() => {
2798
+ const storeState = store.getState();
2799
+ setRuntimeDebugState(runtimeDebugInstanceIdRef.current, {
2800
+ namespace,
2801
+ faceId: faceId ?? null,
2802
+ rootId: status.rootId,
2803
+ ready: status.ready,
2804
+ loading: status.loading,
2805
+ autostart,
2806
+ driveRuntime,
2807
+ loopMode,
2808
+ outputCount: status.outputPaths.length,
2809
+ graphControllerCount: status.controllers.graphs.length,
2810
+ animationControllerCount: status.controllers.anims.length,
2811
+ registeredGraphCount: registeredGraphsRef.current.length,
2812
+ registeredAnimationCount: registeredAnimationsRef.current.length,
2813
+ programControllerCount: programControllerIdsRef.current.size,
2814
+ animationTweenCount: animationTweensRef.current.size,
2815
+ clipPlaybackCount: clipPlaybackRef.current.size,
2816
+ programPlaybackCount: programPlaybackRef.current.size,
2817
+ stagedInputCount: stagedInputsRef.current.size,
2818
+ activeDriverCount: inputDriverIdsRef.current.size,
2819
+ worldEntryCount: Object.keys(storeState.world ?? {}).length,
2820
+ animatableCount: Object.keys(storeState.animatables ?? {}).length,
2821
+ valuesSize: storeState.values instanceof Map ? storeState.values.size : null,
2822
+ stepHz: status.stepHz ?? null
2823
+ });
2824
+ }, [
2825
+ autostart,
2826
+ driveRuntime,
2827
+ faceId,
2828
+ loopMode,
2829
+ namespace,
2830
+ status.controllers.anims.length,
2831
+ status.controllers.graphs.length,
2832
+ status.loading,
2833
+ status.outputPaths.length,
2834
+ status.ready,
2835
+ status.rootId,
2836
+ status.stepHz,
2837
+ store
2838
+ ]);
2839
+ (0, import_react2.useEffect)(() => {
2840
+ publishRuntimeDebugState();
2841
+ const unsubscribe = store.subscribe(() => {
2842
+ publishRuntimeDebugState();
2843
+ });
2844
+ return () => {
2845
+ unsubscribe();
2846
+ clearRuntimeDebugState(runtimeDebugInstanceIdRef.current);
2847
+ };
2848
+ }, [publishRuntimeDebugState, store]);
1998
2849
  (0, import_react2.useEffect)(() => {
1999
2850
  const rigAsset = assetBundle.rig;
2000
2851
  if (!rigAsset) {
@@ -2090,59 +2941,289 @@ function VizijRuntimeProviderInner({
2090
2941
  });
2091
2942
  return;
2092
2943
  }
2093
- markActivity();
2094
- const namespacedPath = namespaceTypedPath(path, namespaceRef.current);
2095
- if (isRuntimeDebugEnabled() && (namespacedPath.includes("animation/authoring.timeline.main") || namespacedPath.endsWith("/blink"))) {
2096
- console.log("[vizij-runtime] stage input", {
2097
- path,
2098
- namespacedPath,
2099
- value
2100
- });
2944
+ markActivity();
2945
+ const namespacedPath = namespaceTypedPath(path, namespaceRef.current);
2946
+ if (isRuntimeDebugEnabled() && (namespacedPath.includes("animation/authoring.timeline.main") || namespacedPath.endsWith("/blink"))) {
2947
+ console.log("[vizij-runtime] stage input", {
2948
+ path,
2949
+ namespacedPath,
2950
+ value
2951
+ });
2952
+ }
2953
+ stagedInputsRef.current.set(namespacedPath, { value, shape });
2954
+ },
2955
+ [
2956
+ assetBundle.pose?.config?.faceId,
2957
+ faceId,
2958
+ markActivity,
2959
+ poseWeightFallbackMap,
2960
+ useLegacyPoseWeightFallback
2961
+ ]
2962
+ );
2963
+ const reportStatus = (0, import_react2.useCallback)(
2964
+ (updater) => {
2965
+ setStatus((prev) => {
2966
+ const next = updater(prev);
2967
+ onStatusChange?.(next);
2968
+ return next;
2969
+ });
2970
+ },
2971
+ [onStatusChange]
2972
+ );
2973
+ const pushError = (0, import_react2.useCallback)(
2974
+ (error) => {
2975
+ errorsRef.current = [...errorsRef.current, error];
2976
+ reportStatus((prev) => ({
2977
+ ...prev,
2978
+ error,
2979
+ errors: errorsRef.current
2980
+ }));
2981
+ console.warn("[vizij-runtime]", error.message, error.cause);
2982
+ },
2983
+ [reportStatus]
2984
+ );
2985
+ const resetErrors = (0, import_react2.useCallback)(() => {
2986
+ errorsRef.current = [];
2987
+ reportStatus((prev) => ({
2988
+ ...prev,
2989
+ error: null,
2990
+ errors: []
2991
+ }));
2992
+ }, [reportStatus]);
2993
+ (0, import_react2.useEffect)(() => {
2994
+ autostartRef.current = autostart;
2995
+ updateLoopMode();
2996
+ }, [autostart, updateLoopMode]);
2997
+ const pendingWritesRef = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
2998
+ const getDevice = (0, import_react2.useCallback)(
2999
+ () => deviceSlot.current?.device ?? null,
3000
+ [deviceSlot]
3001
+ );
3002
+ const ensureAnimationHost = (0, import_react2.useCallback)(() => {
3003
+ if (!animationModuleRef.current) {
3004
+ return null;
3005
+ }
3006
+ if (!animationHostRef.current) {
3007
+ animationHostRef.current = new AnimationModuleHost(
3008
+ getDevice,
3009
+ (key) => resolveAnimationBridgeOutputPaths(
3010
+ key,
3011
+ faceIdRef.current ?? void 0,
3012
+ rigInputMapRef.current
3013
+ )
3014
+ );
3015
+ }
3016
+ return animationHostRef.current;
3017
+ }, [getDevice]);
3018
+ const applyEngineChangesRef = (0, import_react2.useRef)(() => {
3019
+ });
3020
+ const recomposeDevice = (0, import_react2.useCallback)(() => {
3021
+ const spec = composeGraphSpecs(graphSourcesRef.current);
3022
+ deviceSlot.recompose(spec).then((handle) => {
3023
+ if (pendingWritesRef.current.size > 0) {
3024
+ handle.device.writeValues(
3025
+ Object.fromEntries(pendingWritesRef.current)
3026
+ );
3027
+ pendingWritesRef.current.clear();
3028
+ }
3029
+ }).catch((err) => {
3030
+ pushError({
3031
+ message: "Failed to (re)compose the arora device",
3032
+ cause: err,
3033
+ phase: "engine",
3034
+ timestamp: performance.now()
3035
+ });
3036
+ });
3037
+ }, [deviceSlot, pushError]);
3038
+ const initEngine = (0, import_react2.useCallback)(async () => {
3039
+ await ensureWasmInit();
3040
+ setReady(true);
3041
+ }, []);
3042
+ const hasBundleAnimations = (assetBundle.animations?.length ?? 0) > 0;
3043
+ (0, import_react2.useEffect)(() => {
3044
+ if (!hasBundleAnimations || animationModuleRef.current) {
3045
+ return;
3046
+ }
3047
+ let cancelled = false;
3048
+ const loading = (0, import_animation_module.loadAnimationModule)().then((module2) => {
3049
+ if (cancelled) {
3050
+ return;
3051
+ }
3052
+ animationModuleRef.current = module2;
3053
+ deviceSlot.setModules([module2]);
3054
+ deviceSlot.onDeviceStarted = (handle) => {
3055
+ animationHostRef.current?.replayInto(handle.device);
3056
+ };
3057
+ ensureAnimationHost();
3058
+ }).catch((err) => {
3059
+ pushError({
3060
+ message: "Failed to load the animation module",
3061
+ cause: err,
3062
+ phase: "engine",
3063
+ timestamp: performance.now()
3064
+ });
3065
+ });
3066
+ deviceSlot.waitForModules(loading);
3067
+ return () => {
3068
+ cancelled = true;
3069
+ };
3070
+ }, [deviceSlot, ensureAnimationHost, hasBundleAnimations, pushError]);
3071
+ const removeGraph = (0, import_react2.useCallback)(
3072
+ (id) => {
3073
+ graphSourcesRef.current = graphSourcesRef.current.filter(
3074
+ (s) => s.sourceId !== id && !s.sourceId.startsWith(`${id}#`)
3075
+ );
3076
+ recomposeDevice();
3077
+ },
3078
+ [recomposeDevice]
3079
+ );
3080
+ const registerGraph = (0, import_react2.useCallback)(
3081
+ (cfg) => {
3082
+ const id = cfg.id ?? `graph-${graphSourcesRef.current.length}`;
3083
+ graphSourcesRef.current = [
3084
+ ...graphSourcesRef.current.filter((s) => s.sourceId !== id),
3085
+ { sourceId: id, spec: cfg.spec ?? {} }
3086
+ ];
3087
+ recomposeDevice();
3088
+ return id;
3089
+ },
3090
+ [recomposeDevice]
3091
+ );
3092
+ const registerMergedGraph = (0, import_react2.useCallback)(
3093
+ (cfg) => {
3094
+ const id = cfg.id ?? `merged-${graphSourcesRef.current.length}`;
3095
+ const members = cfg.graphs.map((graph, index) => ({
3096
+ sourceId: `${id}#${graph.id ?? index}`,
3097
+ spec: graph.spec ?? {}
3098
+ }));
3099
+ graphSourcesRef.current = [
3100
+ ...graphSourcesRef.current.filter(
3101
+ (s) => s.sourceId !== id && !s.sourceId.startsWith(`${id}#`)
3102
+ ),
3103
+ ...members
3104
+ ];
3105
+ recomposeDevice();
3106
+ return id;
3107
+ },
3108
+ [recomposeDevice]
3109
+ );
3110
+ const registerAnimation = (0, import_react2.useCallback)(
3111
+ (cfg) => cfg.id ?? `animation-${registeredAnimationsRef.current.length}`,
3112
+ []
3113
+ );
3114
+ const removeAnimation = (0, import_react2.useCallback)((_id) => {
3115
+ }, []);
3116
+ const setAnimationsSourceRegistered = (0, import_react2.useCallback)(
3117
+ (registered) => {
3118
+ if (registered === animationsSourceRegisteredRef.current) {
3119
+ return;
3120
+ }
3121
+ animationsSourceRegisteredRef.current = registered;
3122
+ if (registered) {
3123
+ graphSourcesRef.current = [
3124
+ ...graphSourcesRef.current.filter(
3125
+ (s) => s.sourceId !== animationsGraphSource().sourceId
3126
+ ),
3127
+ animationsGraphSource()
3128
+ ];
3129
+ } else {
3130
+ graphSourcesRef.current = graphSourcesRef.current.filter(
3131
+ (s) => s.sourceId !== animationsGraphSource().sourceId
3132
+ );
2101
3133
  }
2102
- stagedInputsRef.current.set(namespacedPath, { value, shape });
3134
+ recomposeDevice();
2103
3135
  },
2104
- [
2105
- assetBundle.pose?.config?.faceId,
2106
- faceId,
2107
- markActivity,
2108
- poseWeightFallbackMap,
2109
- useLegacyPoseWeightFallback
2110
- ]
3136
+ [recomposeDevice]
2111
3137
  );
2112
- const reportStatus = (0, import_react2.useCallback)(
2113
- (updater) => {
2114
- setStatus((prev) => {
2115
- const next = updater(prev);
2116
- onStatusChange?.(next);
2117
- return next;
3138
+ const listControllers = (0, import_react2.useCallback)(
3139
+ () => ({
3140
+ graphs: [...registeredGraphsRef.current],
3141
+ anims: [...registeredAnimationsRef.current]
3142
+ }),
3143
+ []
3144
+ );
3145
+ const deviceSetInput = (0, import_react2.useCallback)(
3146
+ (path, value, _shape) => {
3147
+ const handle = deviceSlot.current;
3148
+ if (handle) {
3149
+ handle.device.setValue(path, value);
3150
+ } else {
3151
+ pendingWritesRef.current.set(path, value);
3152
+ }
3153
+ },
3154
+ [deviceSlot]
3155
+ );
3156
+ const getPathSnapshot = (0, import_react2.useCallback)(
3157
+ (path) => {
3158
+ const handle = deviceSlot.current;
3159
+ if (!handle) {
3160
+ return pendingWritesRef.current.get(path);
3161
+ }
3162
+ return handle.device.readValues([path])[path] ?? void 0;
3163
+ },
3164
+ [deviceSlot]
3165
+ );
3166
+ const getStoreSnapshot = (0, import_react2.useCallback)(() => {
3167
+ const handle = deviceSlot.current;
3168
+ if (!handle) {
3169
+ return void 0;
3170
+ }
3171
+ return handle.device.snapshot();
3172
+ }, [deviceSlot]);
3173
+ const stepListenersRef = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
3174
+ const subscribeToStep = (0, import_react2.useCallback)((listener) => {
3175
+ const listeners = stepListenersRef.current;
3176
+ listeners.add(listener);
3177
+ return () => {
3178
+ listeners.delete(listener);
3179
+ };
3180
+ }, []);
3181
+ const stepRuntime = (0, import_react2.useCallback)(
3182
+ (dt) => {
3183
+ const handle = deviceSlot.current;
3184
+ if (!handle) {
3185
+ return;
3186
+ }
3187
+ if (!handle.device.running) {
3188
+ handle.device.step(dt * 1e3);
3189
+ }
3190
+ const changes = handle.device.drainChanges();
3191
+ applyEngineChangesRef.current(changes);
3192
+ if (storeChangeListenersRef.current.size > 0) {
3193
+ const paths = Object.keys(changes);
3194
+ if (paths.length > 0) {
3195
+ storeChangeListenersRef.current.forEach((listener) => {
3196
+ try {
3197
+ listener(changes);
3198
+ } catch (err) {
3199
+ console.error("[vizij-runtime] store-change listener error", err);
3200
+ }
3201
+ });
3202
+ }
3203
+ }
3204
+ stepListenersRef.current.forEach((listener) => {
3205
+ try {
3206
+ listener();
3207
+ } catch (err) {
3208
+ console.error("[vizij-runtime] step listener error", err);
3209
+ }
2118
3210
  });
2119
3211
  },
2120
- [onStatusChange]
3212
+ [deviceSlot]
2121
3213
  );
2122
- const pushError = (0, import_react2.useCallback)(
2123
- (error) => {
2124
- errorsRef.current = [...errorsRef.current, error];
2125
- reportStatus((prev) => ({
2126
- ...prev,
2127
- error,
2128
- errors: errorsRef.current
2129
- }));
2130
- console.warn("[vizij-runtime]", error.message, error.cause);
3214
+ const storeChangeListenersRef = (0, import_react2.useRef)(
3215
+ /* @__PURE__ */ new Set()
3216
+ );
3217
+ const subscribeToStoreChanges = (0, import_react2.useCallback)(
3218
+ (listener) => {
3219
+ const listeners = storeChangeListenersRef.current;
3220
+ listeners.add(listener);
3221
+ return () => {
3222
+ listeners.delete(listener);
3223
+ };
2131
3224
  },
2132
- [reportStatus]
3225
+ []
2133
3226
  );
2134
- const resetErrors = (0, import_react2.useCallback)(() => {
2135
- errorsRef.current = [];
2136
- reportStatus((prev) => ({
2137
- ...prev,
2138
- error: null,
2139
- errors: []
2140
- }));
2141
- }, [reportStatus]);
2142
- (0, import_react2.useEffect)(() => {
2143
- autostartRef.current = autostart;
2144
- updateLoopMode();
2145
- }, [autostart, updateLoopMode]);
2146
3227
  const clearControllers = (0, import_react2.useCallback)(() => {
2147
3228
  const existing = listControllers();
2148
3229
  existing.graphs.forEach((id) => {
@@ -2177,8 +3258,6 @@ function VizijRuntimeProviderInner({
2177
3258
  baseOutputPathsRef.current = /* @__PURE__ */ new Set();
2178
3259
  namespacedOutputPathsRef.current = /* @__PURE__ */ new Set();
2179
3260
  rigPoseControlInputIdsRef.current = /* @__PURE__ */ new Set();
2180
- clipOutputValuesRef.current.clear();
2181
- clipAggregateValuesRef.current.clear();
2182
3261
  }, [listControllers, removeAnimation, removeGraph, pushError]);
2183
3262
  (0, import_react2.useEffect)(() => {
2184
3263
  namespaceRef.current = namespace;
@@ -2189,8 +3268,24 @@ function VizijRuntimeProviderInner({
2189
3268
  }));
2190
3269
  }, [namespace, faceId, reportStatus]);
2191
3270
  (0, import_react2.useEffect)(() => {
2192
- driveOrchestratorRef.current = driveOrchestrator;
2193
- }, [driveOrchestrator]);
3271
+ driveRuntimeRef.current = driveRuntime;
3272
+ }, [driveRuntime]);
3273
+ (0, import_react2.useEffect)(() => {
3274
+ deviceSlot.onRunEnded = (error) => {
3275
+ pushError({
3276
+ message: "The device's run loop ended: stepping failed",
3277
+ cause: error,
3278
+ phase: "engine",
3279
+ timestamp: performance.now()
3280
+ });
3281
+ };
3282
+ if (driveRuntime) {
3283
+ deviceSlot.runPeriodMs = RUN_PERIOD_MS;
3284
+ deviceSlot.startRun();
3285
+ } else {
3286
+ deviceSlot.runPeriodMs = null;
3287
+ }
3288
+ }, [deviceSlot, driveRuntime, pushError]);
2194
3289
  const glbAsset = effectiveAssetBundle.glb;
2195
3290
  const baseBundle = effectiveAssetBundle.bundle ?? null;
2196
3291
  (0, import_react2.useEffect)(() => {
@@ -2303,16 +3398,16 @@ function VizijRuntimeProviderInner({
2303
3398
  ]);
2304
3399
  (0, import_react2.useEffect)(() => {
2305
3400
  if (!ready && autoCreate) {
2306
- createOrchestrator(createOptions).catch((err) => {
3401
+ initEngine().catch((err) => {
2307
3402
  pushError({
2308
- message: "Failed to create orchestrator runtime",
3403
+ message: "Failed to initialize the engine",
2309
3404
  cause: err,
2310
- phase: "orchestrator",
3405
+ phase: "engine",
2311
3406
  timestamp: performance.now()
2312
3407
  });
2313
3408
  });
2314
3409
  }
2315
- }, [ready, autoCreate, createOptions, createOrchestrator, pushError]);
3410
+ }, [ready, autoCreate, initEngine, pushError]);
2316
3411
  const registerControllers = (0, import_react2.useCallback)(async () => {
2317
3412
  clearControllers();
2318
3413
  if (isRuntimeDebugEnabled()) {
@@ -2475,19 +3570,13 @@ function VizijRuntimeProviderInner({
2475
3570
  console.log("[vizij-runtime] registered graph ids", graphIds);
2476
3571
  }
2477
3572
  const animationIds = [];
3573
+ const hostClips = [];
2478
3574
  for (const anim of assetBundle.animations ?? []) {
2479
3575
  try {
2480
- const controllerId = namespaceControllerId(anim.id, namespace, "animation") ?? anim.id;
2481
- const animationPayload = anim.setup?.animation ?? toStoredAnimationClip(anim.id, anim.clip);
2482
- const config = {
2483
- id: controllerId,
2484
- setup: {
2485
- ...anim.setup ?? {},
2486
- animation: animationPayload
2487
- }
2488
- };
2489
- const id = registerAnimation(config);
2490
- animationIds.push(id);
3576
+ const stored = anim.setup?.animation ?? toStoredAnimationClip(anim.id, anim.clip);
3577
+ hostClips.push({ id: anim.id, stored });
3578
+ registerAnimation({ id: anim.id });
3579
+ animationIds.push(anim.id);
2491
3580
  } catch (err) {
2492
3581
  if (isRuntimeDebugEnabled()) {
2493
3582
  console.warn("[vizij-runtime] failed animation registration", {
@@ -2503,6 +3592,7 @@ function VizijRuntimeProviderInner({
2503
3592
  });
2504
3593
  }
2505
3594
  }
3595
+ ensureAnimationHost()?.setClips(hostClips);
2506
3596
  registeredAnimationsRef.current = animationIds;
2507
3597
  if (assetBundle.initialInputs) {
2508
3598
  Object.entries(assetBundle.initialInputs).forEach(([path, value]) => {
@@ -2536,6 +3626,7 @@ function VizijRuntimeProviderInner({
2536
3626
  }, [
2537
3627
  assetBundle,
2538
3628
  clearControllers,
3629
+ ensureAnimationHost,
2539
3630
  listControllers,
2540
3631
  mergeStrategy,
2541
3632
  namespace,
@@ -2567,75 +3658,77 @@ function VizijRuntimeProviderInner({
2567
3658
  });
2568
3659
  }, [ready, status.loading, graphUpdateToken, registerControllers, pushError]);
2569
3660
  (0, import_react2.useEffect)(() => {
2570
- if (!frame) {
2571
- return;
2572
- }
2573
- const writes = frame.merged_writes ?? [];
2574
- if (!writes.length) {
2575
- return;
2576
- }
2577
- const setWorldValues = store.getState().setValues;
2578
- const namespaceValue = status.namespace;
2579
- const currentValues = store.getState().values;
2580
- const rigInputPathMap = rigInputMapRef.current;
2581
- const rigPoseControlInputIds = rigPoseControlInputIdsRef.current;
2582
- const batched = [];
2583
- const namespacedOutputs = namespacedOutputPathsRef.current;
2584
- const baseOutputs = baseOutputPathsRef.current;
2585
- writes.forEach((write) => {
2586
- const path = normalisePath(write.path);
2587
- const basePath = stripNamespace(path, namespaceValue);
2588
- const isTrackedOutput = namespacedOutputs.has(path) || baseOutputs.has(basePath);
2589
- if (!isTrackedOutput) {
2590
- return;
2591
- }
2592
- const raw = valueJSONToRaw(write.value);
2593
- if (raw === void 0) {
3661
+ applyEngineChangesRef.current = (changes) => {
3662
+ const entries = Object.entries(changes);
3663
+ if (!entries.length) {
2594
3664
  return;
2595
3665
  }
2596
- const poseControlMatch = /^rig\/[^/]+\/pose\/control\/(.+)$/.exec(
2597
- basePath
2598
- );
2599
- if (poseControlMatch && typeof raw === "number" && Number.isFinite(raw)) {
2600
- const inputId = (poseControlMatch[1] ?? "").trim();
2601
- const hasNativePoseControlInput = inputId.length > 0 && rigPoseControlInputIds.has(inputId);
2602
- const mappedInputPath = inputId.length === 0 ? void 0 : resolvePoseControlInputPath({
2603
- inputId,
2604
- basePath,
2605
- rigInputPathMap,
2606
- hasNativePoseControlInput
2607
- });
2608
- if (mappedInputPath) {
2609
- const bridgeKey = `${namespaceValue}:${mappedInputPath}`;
2610
- const previousValue = poseControlBridgeValuesRef.current.get(bridgeKey);
2611
- if (previousValue === void 0 || Math.abs(previousValue - raw) > POSE_CONTROL_BRIDGE_EPSILON) {
2612
- poseControlBridgeValuesRef.current.set(bridgeKey, raw);
2613
- setInput(mappedInputPath, { float: raw });
3666
+ const setWorldValues = store.getState().setValues;
3667
+ const namespaceValue = status.namespace;
3668
+ const currentValues = store.getState().values;
3669
+ const rigInputPathMap = rigInputMapRef.current;
3670
+ const rigPoseControlInputIds = rigPoseControlInputIdsRef.current;
3671
+ const batched = [];
3672
+ const namespacedOutputs = namespacedOutputPathsRef.current;
3673
+ const baseOutputs = baseOutputPathsRef.current;
3674
+ entries.forEach(([changedPath, changedValue]) => {
3675
+ if (changedValue === null || isBuiltInPath(changedPath)) {
3676
+ return;
3677
+ }
3678
+ const path = normalisePath(changedPath);
3679
+ const basePath = stripNamespace(path, namespaceValue);
3680
+ const isTrackedOutput = namespacedOutputs.has(path) || baseOutputs.has(basePath);
3681
+ if (!isTrackedOutput) {
3682
+ return;
3683
+ }
3684
+ const raw = valueJSONToRaw(changedValue);
3685
+ if (raw === void 0) {
3686
+ return;
3687
+ }
3688
+ const poseControlMatch = /^rig\/[^/]+\/pose\/control\/(.+)$/.exec(
3689
+ basePath
3690
+ );
3691
+ if (poseControlMatch && typeof raw === "number" && Number.isFinite(raw)) {
3692
+ const inputId = (poseControlMatch[1] ?? "").trim();
3693
+ const hasNativePoseControlInput = inputId.length > 0 && rigPoseControlInputIds.has(inputId);
3694
+ const mappedInputPath = inputId.length === 0 ? void 0 : resolvePoseControlInputPath({
3695
+ inputId,
3696
+ basePath,
3697
+ rigInputPathMap,
3698
+ hasNativePoseControlInput
3699
+ });
3700
+ if (mappedInputPath) {
3701
+ const bridgeKey = `${namespaceValue}:${mappedInputPath}`;
3702
+ const previousValue = poseControlBridgeValuesRef.current.get(bridgeKey);
3703
+ if (previousValue === void 0 || Math.abs(previousValue - raw) > POSE_CONTROL_BRIDGE_EPSILON) {
3704
+ poseControlBridgeValuesRef.current.set(bridgeKey, raw);
3705
+ setInput(mappedInputPath, { float: raw });
3706
+ }
2614
3707
  }
2615
3708
  }
3709
+ const targetPath = baseOutputs.has(basePath) ? basePath : path;
3710
+ const currentValue = currentValues.get(
3711
+ (0, import_utils.getLookup)(namespaceValue, targetPath)
3712
+ );
3713
+ const nextWrite = transformOutputWrite ? transformOutputWrite({
3714
+ id: targetPath,
3715
+ namespace: namespaceValue,
3716
+ value: raw,
3717
+ currentValue
3718
+ }) : {
3719
+ id: targetPath,
3720
+ namespace: namespaceValue,
3721
+ value: raw
3722
+ };
3723
+ if (nextWrite) {
3724
+ batched.push(nextWrite);
3725
+ }
3726
+ });
3727
+ if (batched.length > 0) {
3728
+ setWorldValues(batched);
2616
3729
  }
2617
- const targetPath = baseOutputs.has(basePath) ? basePath : path;
2618
- const currentValue = currentValues.get(
2619
- (0, import_utils.getLookup)(namespaceValue, targetPath)
2620
- );
2621
- const nextWrite = transformOutputWrite ? transformOutputWrite({
2622
- id: targetPath,
2623
- namespace: namespaceValue,
2624
- value: raw,
2625
- currentValue
2626
- }) : {
2627
- id: targetPath,
2628
- namespace: namespaceValue,
2629
- value: raw
2630
- };
2631
- if (nextWrite) {
2632
- batched.push(nextWrite);
2633
- }
2634
- });
2635
- if (batched.length > 0) {
2636
- setWorldValues(batched);
2637
- }
2638
- }, [frame, status.namespace, store, transformOutputWrite]);
3730
+ };
3731
+ }, [status.namespace, store, transformOutputWrite]);
2639
3732
  const stagePoseNeutral = (0, import_react2.useCallback)(
2640
3733
  (force = false) => {
2641
3734
  const neutral = assetBundle.pose?.config?.neutralInputs ?? {};
@@ -2724,180 +3817,34 @@ function VizijRuntimeProviderInner({
2724
3817
  state.completion = completion;
2725
3818
  return completion;
2726
3819
  }, []);
2727
- const setAnimationInput = (0, import_react2.useCallback)(
2728
- (path, value, options) => {
2729
- setInput(path, { float: value });
2730
- if (!options?.immediate) {
2731
- return;
2732
- }
2733
- const namespacedPath = namespaceTypedPath(path, namespaceRef.current);
2734
- const staged = stagedInputsRef.current.get(namespacedPath);
2735
- if (staged) {
2736
- orchestratorSetInput(namespacedPath, staged.value, staged.shape);
2737
- stagedInputsRef.current.delete(namespacedPath);
2738
- return;
2739
- }
2740
- orchestratorSetInput(namespacedPath, { float: value });
2741
- },
2742
- [orchestratorSetInput, setInput]
2743
- );
2744
- const clearAnimationInput = (0, import_react2.useCallback)(
2745
- (path) => {
2746
- const namespacedPath = namespaceTypedPath(path, namespaceRef.current);
2747
- stagedInputsRef.current.delete(namespacedPath);
2748
- removeInput(namespacedPath);
2749
- },
2750
- [removeInput]
2751
- );
2752
- const buildClipOutputValues = (0, import_react2.useCallback)(
2753
- (clip, state) => sampleAnimationClipOutputValues(
2754
- clip.clip,
2755
- state.time,
2756
- state.weight,
2757
- faceId ?? void 0,
2758
- rigInputMapRef.current
2759
- ),
2760
- [faceId]
2761
- );
2762
- const computeClipAggregateValues = (0, import_react2.useCallback)(() => {
2763
- const aggregate = /* @__PURE__ */ new Map();
2764
- clipOutputValuesRef.current.forEach((outputValues) => {
2765
- outputValues.forEach((value, path) => {
2766
- aggregate.set(path, (aggregate.get(path) ?? 0) + value);
2767
- });
2768
- });
2769
- return aggregate;
2770
- }, []);
2771
- const stageClipAggregateValues = (0, import_react2.useCallback)(
2772
- (nextAggregate, options) => {
2773
- diffAnimationAggregateValues(
2774
- clipAggregateValuesRef.current,
2775
- nextAggregate,
2776
- POSE_CONTROL_BRIDGE_EPSILON
2777
- ).forEach((operation) => {
2778
- if (operation.kind === "clear") {
2779
- clearAnimationInput(operation.path);
2780
- return;
2781
- }
2782
- setAnimationInput(operation.path, operation.value, options);
2783
- });
2784
- clipAggregateValuesRef.current = nextAggregate;
2785
- },
2786
- [clearAnimationInput, setAnimationInput]
2787
- );
2788
- const syncClipOutputs = (0, import_react2.useCallback)(
2789
- (options) => {
2790
- stageClipAggregateValues(computeClipAggregateValues(), options);
2791
- },
2792
- [computeClipAggregateValues, stageClipAggregateValues]
2793
- );
2794
- const writeClipOutputs = (0, import_react2.useCallback)(
2795
- (clip, state, options) => {
2796
- if (!animationSystemActiveRef.current) {
2797
- return;
2798
- }
2799
- clipOutputValuesRef.current.set(
2800
- clip.id,
2801
- buildClipOutputValues(clip, state)
2802
- );
2803
- syncClipOutputs(options);
2804
- },
2805
- [buildClipOutputValues, syncClipOutputs]
2806
- );
2807
- const clearClipOutputs = (0, import_react2.useCallback)(
2808
- (clipId, options) => {
2809
- if (!clipOutputValuesRef.current.has(clipId)) {
2810
- return;
3820
+ const ensureClipState = (0, import_react2.useCallback)(
3821
+ (id) => {
3822
+ const clip = resolveClipById(id);
3823
+ if (!clip) {
3824
+ return null;
2811
3825
  }
2812
- clipOutputValuesRef.current.delete(clipId);
2813
- syncClipOutputs(options);
2814
- },
2815
- [syncClipOutputs]
2816
- );
2817
- const createClipPlaybackState = (0, import_react2.useCallback)(
2818
- (clip) => {
2819
3826
  const duration = resolveClipDurationSeconds(
2820
3827
  clip.clip
2821
3828
  );
2822
- return {
2823
- id: clip.id,
2824
- time: 0,
3829
+ const existing = clipPlaybackRef.current.get(id);
3830
+ if (existing) {
3831
+ existing.duration = duration;
3832
+ return { clip, state: existing };
3833
+ }
3834
+ const state = {
3835
+ id,
2825
3836
  duration,
2826
3837
  speed: 1,
2827
- weight: Number.isFinite(clip.weight) ? Number(clip.weight) : 1,
2828
- loop: false,
3838
+ weight: 1,
3839
+ loop: true,
2829
3840
  playing: false,
2830
3841
  resolve: null,
2831
3842
  completion: null
2832
3843
  };
3844
+ clipPlaybackRef.current.set(id, state);
3845
+ return { clip, state };
2833
3846
  },
2834
- []
2835
- );
2836
- const ensureClipPlaybackState = (0, import_react2.useCallback)(
2837
- (id) => {
2838
- const clip = resolveClipById(id);
2839
- if (!clip) {
2840
- return null;
2841
- }
2842
- const existing = clipPlaybackRef.current.get(id);
2843
- if (existing) {
2844
- existing.duration = resolveClipDurationSeconds(
2845
- clip.clip,
2846
- existing.duration
2847
- );
2848
- existing.time = clampAnimationTime(existing.time, existing.duration);
2849
- return { clip, state: existing };
2850
- }
2851
- const next = createClipPlaybackState(clip);
2852
- clipPlaybackRef.current.set(id, next);
2853
- return { clip, state: next };
2854
- },
2855
- [createClipPlaybackState, resolveClipById]
2856
- );
2857
- const advanceClipPlayback = (0, import_react2.useCallback)(
2858
- (dt) => {
2859
- if (clipPlaybackRef.current.size === 0) {
2860
- return;
2861
- }
2862
- const toDelete = [];
2863
- clipPlaybackRef.current.forEach((state, key) => {
2864
- const clip = resolveClipById(state.id);
2865
- if (!clip) {
2866
- toDelete.push(key);
2867
- resolveClipPromise(state);
2868
- return;
2869
- }
2870
- state.duration = resolveClipDurationSeconds(
2871
- clip.clip,
2872
- state.duration
2873
- );
2874
- const { time, completed } = advanceClipTime(
2875
- {
2876
- time: state.time,
2877
- duration: state.duration,
2878
- speed: state.speed,
2879
- loop: state.loop,
2880
- playing: state.playing
2881
- },
2882
- dt
2883
- );
2884
- state.time = clampAnimationTime(time, state.duration);
2885
- if (state.playing || completed) {
2886
- writeClipOutputs(clip, state);
2887
- }
2888
- if (completed) {
2889
- toDelete.push(key);
2890
- resolveClipPromise(state);
2891
- }
2892
- });
2893
- toDelete.forEach((key) => {
2894
- clipPlaybackRef.current.delete(key);
2895
- if (animationSystemActiveRef.current) {
2896
- clearClipOutputs(key);
2897
- }
2898
- });
2899
- },
2900
- [clearClipOutputs, resolveClipById, resolveClipPromise, writeClipOutputs]
3847
+ [resolveClipById]
2901
3848
  );
2902
3849
  const animateValue = (0, import_react2.useCallback)(
2903
3850
  (path, target, options) => {
@@ -2971,42 +3918,111 @@ function VizijRuntimeProviderInner({
2971
3918
  useLegacyPoseWeightFallback
2972
3919
  ]
2973
3920
  );
3921
+ const warnAnimationGap = (0, import_react2.useCallback)((key, message) => {
3922
+ if (animationGapWarnedRef.current.has(key)) {
3923
+ return;
3924
+ }
3925
+ animationGapWarnedRef.current.add(key);
3926
+ console.warn(`[vizij-runtime] ${message}`);
3927
+ }, []);
3928
+ const syncAnimationsSource = (0, import_react2.useCallback)(() => {
3929
+ const host = animationHostRef.current;
3930
+ setAnimationsSourceRegistered(host ? host.hasPlaying() : false);
3931
+ updateLoopMode();
3932
+ }, [setAnimationsSourceRegistered, updateLoopMode]);
3933
+ const readPlayerStates = (0, import_react2.useCallback)(() => {
3934
+ const device = deviceSlot.current?.device;
3935
+ if (!device) {
3936
+ return [];
3937
+ }
3938
+ const raw = device.readValues([ANIMATION_PLAYERS_PATH])[ANIMATION_PLAYERS_PATH];
3939
+ return decodePlayerStates(raw);
3940
+ }, [deviceSlot]);
3941
+ const settleFinishedClips = (0, import_react2.useCallback)(() => {
3942
+ let pending = false;
3943
+ clipPlaybackRef.current.forEach((state) => {
3944
+ if (state.completion && !state.loop) {
3945
+ pending = true;
3946
+ }
3947
+ });
3948
+ const host = animationHostRef.current;
3949
+ if (!pending || !host) {
3950
+ return;
3951
+ }
3952
+ const states = readPlayerStates();
3953
+ if (states.length === 0) {
3954
+ return;
3955
+ }
3956
+ clipPlaybackRef.current.forEach((state, id) => {
3957
+ if (!state.completion || state.loop) {
3958
+ return;
3959
+ }
3960
+ const playerId = host.playerIdOf(id);
3961
+ if (playerId === null) {
3962
+ return;
3963
+ }
3964
+ const feedback = states.find((entry) => entry.player === playerId);
3965
+ if (!feedback) {
3966
+ return;
3967
+ }
3968
+ const duration = feedback.duration > 0 ? feedback.duration : state.duration;
3969
+ const atEnd = duration > 0 && feedback.time >= duration - 1e-3;
3970
+ if (feedback.state === "stopped" || atEnd) {
3971
+ state.playing = false;
3972
+ resolveClipPromise(state);
3973
+ host.pause(id);
3974
+ syncAnimationsSource();
3975
+ }
3976
+ });
3977
+ }, [readPlayerStates, resolveClipPromise, syncAnimationsSource]);
2974
3978
  const playAnimation = (0, import_react2.useCallback)(
2975
3979
  (id, options) => {
2976
- const ensured = ensureClipPlaybackState(id);
3980
+ const ensured = ensureClipState(id);
2977
3981
  if (!ensured) {
2978
3982
  return Promise.reject(
2979
3983
  new Error(`Animation ${id} is not part of the current asset bundle.`)
2980
3984
  );
2981
3985
  }
2982
- const { clip, state } = ensured;
2983
- const shouldReset = options?.reset === true;
2984
- if (shouldReset) {
2985
- resolveClipPromise(state);
2986
- state.time = 0;
2987
- }
2988
- const speed = options?.speed ?? state.speed ?? 1;
2989
- const weight = options?.weight ?? state.weight ?? clip.weight ?? 1;
2990
- state.speed = Number.isFinite(speed) && speed > 0 ? speed : 1;
2991
- state.weight = Number.isFinite(weight) ? Number(weight) : 1;
2992
- state.duration = resolveClipDurationSeconds(
2993
- clip.clip,
2994
- state.duration
2995
- );
2996
- state.time = clampAnimationTime(state.time, state.duration);
3986
+ const { state } = ensured;
3987
+ const host = ensureAnimationHost();
3988
+ state.speed = options?.speed ?? state.speed;
3989
+ state.weight = options?.weight ?? state.weight;
2997
3990
  state.playing = true;
2998
3991
  const completion = ensureClipPromise(state);
2999
3992
  clipPlaybackRef.current.set(id, state);
3000
- writeClipOutputs(clip, state);
3993
+ if (host) {
3994
+ if (options?.reset === true) {
3995
+ host.seek(id, 0);
3996
+ }
3997
+ host.setSpeed(id, state.speed);
3998
+ host.setWeight(id, state.weight);
3999
+ host.setLoop(id, state.loop ? "loop" : "once");
4000
+ void host.play(id).catch((err) => {
4001
+ pushError({
4002
+ message: `Failed to start animation ${id} on the device`,
4003
+ cause: err,
4004
+ phase: "animation",
4005
+ timestamp: performance.now()
4006
+ });
4007
+ });
4008
+ } else {
4009
+ warnAnimationGap(
4010
+ "module-not-ready",
4011
+ "playAnimation called before the animation module finished loading; playback starts once it is ready."
4012
+ );
4013
+ }
4014
+ syncAnimationsSource();
3001
4015
  markActivity();
3002
4016
  return completion;
3003
4017
  },
3004
4018
  [
3005
- ensureClipPlaybackState,
4019
+ ensureAnimationHost,
3006
4020
  ensureClipPromise,
4021
+ ensureClipState,
3007
4022
  markActivity,
3008
- resolveClipPromise,
3009
- writeClipOutputs
4023
+ pushError,
4024
+ syncAnimationsSource,
4025
+ warnAnimationGap
3010
4026
  ]
3011
4027
  );
3012
4028
  const pauseAnimation = (0, import_react2.useCallback)(
@@ -3016,34 +4032,28 @@ function VizijRuntimeProviderInner({
3016
4032
  return;
3017
4033
  }
3018
4034
  state.playing = false;
3019
- updateLoopMode();
3020
- },
3021
- [updateLoopMode]
3022
- );
3023
- const seekAnimation = (0, import_react2.useCallback)(
3024
- (id, timeSeconds) => {
3025
- const ensured = ensureClipPlaybackState(id);
3026
- if (!ensured) {
3027
- return;
3028
- }
3029
- const { clip, state } = ensured;
3030
- state.time = clampAnimationTime(timeSeconds, state.duration);
3031
- clipPlaybackRef.current.set(id, state);
3032
- writeClipOutputs(clip, state, { immediate: true });
4035
+ animationHostRef.current?.pause(id);
4036
+ syncAnimationsSource();
3033
4037
  },
3034
- [ensureClipPlaybackState, writeClipOutputs]
4038
+ [syncAnimationsSource]
3035
4039
  );
4040
+ const seekAnimation = (0, import_react2.useCallback)((id, timeSeconds) => {
4041
+ if (!clipPlaybackRef.current.has(id)) {
4042
+ return;
4043
+ }
4044
+ animationHostRef.current?.seek(id, Math.max(0, timeSeconds));
4045
+ }, []);
3036
4046
  const setAnimationLoop = (0, import_react2.useCallback)(
3037
4047
  (id, enabled) => {
3038
- const ensured = ensureClipPlaybackState(id);
4048
+ const ensured = ensureClipState(id);
3039
4049
  if (!ensured) {
3040
4050
  return;
3041
4051
  }
3042
4052
  ensured.state.loop = Boolean(enabled);
3043
- clipPlaybackRef.current.set(id, ensured.state);
4053
+ animationHostRef.current?.setLoop(id, enabled ? "loop" : "once");
3044
4054
  updateLoopMode();
3045
4055
  },
3046
- [ensureClipPlaybackState, updateLoopMode]
4056
+ [ensureClipState, updateLoopMode]
3047
4057
  );
3048
4058
  const getAnimationState = (0, import_react2.useCallback)(
3049
4059
  (id) => {
@@ -3051,15 +4061,17 @@ function VizijRuntimeProviderInner({
3051
4061
  if (!state) {
3052
4062
  return null;
3053
4063
  }
4064
+ const playerId = animationHostRef.current?.playerIdOf(id) ?? null;
4065
+ const feedback = playerId !== null ? readPlayerStates().find((entry) => entry.player === playerId) : void 0;
3054
4066
  return {
3055
- time: state.time,
3056
- duration: state.duration,
3057
- playing: state.playing,
4067
+ time: feedback?.time ?? 0,
4068
+ duration: feedback && feedback.duration > 0 ? feedback.duration : state.duration,
4069
+ playing: feedback ? feedback.state === "playing" : state.playing,
3058
4070
  loop: state.loop,
3059
- speed: state.speed
4071
+ speed: feedback?.speed ?? state.speed
3060
4072
  };
3061
4073
  },
3062
- []
4074
+ [readPlayerStates]
3063
4075
  );
3064
4076
  const stopAnimation = (0, import_react2.useCallback)(
3065
4077
  (id, options) => {
@@ -3069,12 +4081,19 @@ function VizijRuntimeProviderInner({
3069
4081
  state.playing = false;
3070
4082
  resolveClipPromise(state);
3071
4083
  }
3072
- if (options?.clearOutputs !== false) {
3073
- clearClipOutputs(id);
4084
+ const host = animationHostRef.current;
4085
+ if (options?.clearOutputs === false) {
4086
+ host?.pause(id);
4087
+ syncAnimationsSource();
4088
+ } else {
4089
+ host?.stop(id);
4090
+ const unsubscribe = subscribeToStep(() => {
4091
+ unsubscribe();
4092
+ syncAnimationsSource();
4093
+ });
3074
4094
  }
3075
- updateLoopMode();
3076
4095
  },
3077
- [clearClipOutputs, resolveClipPromise, updateLoopMode]
4096
+ [resolveClipPromise, subscribeToStep, syncAnimationsSource]
3078
4097
  );
3079
4098
  const refreshControllerStatus = (0, import_react2.useCallback)(() => {
3080
4099
  const controllers = listControllers();
@@ -3223,11 +4242,21 @@ function VizijRuntimeProviderInner({
3223
4242
  ]);
3224
4243
  const playProgram = (0, import_react2.useCallback)(
3225
4244
  (id) => {
3226
- if (!resolveProgramById(id)) {
4245
+ const program = resolveProgramById(id);
4246
+ if (!program) {
3227
4247
  throw new Error(
3228
4248
  `Program ${id} is not part of the current asset bundle.`
3229
4249
  );
3230
4250
  }
4251
+ deriveProgramInputSeedValues({
4252
+ program,
4253
+ namespace,
4254
+ inputConstraints: inputConstraintsRef.current,
4255
+ getPathSnapshot,
4256
+ stagedInputs: stagedInputsRef.current
4257
+ }).forEach(({ path, value }) => {
4258
+ setInput(path, value);
4259
+ });
3231
4260
  programPlaybackRef.current.set(id, {
3232
4261
  id,
3233
4262
  state: "playing"
@@ -3235,7 +4264,14 @@ function VizijRuntimeProviderInner({
3235
4264
  syncProgramPlaybackControllers();
3236
4265
  markActivity();
3237
4266
  },
3238
- [markActivity, resolveProgramById, syncProgramPlaybackControllers]
4267
+ [
4268
+ getPathSnapshot,
4269
+ markActivity,
4270
+ namespace,
4271
+ resolveProgramById,
4272
+ setInput,
4273
+ syncProgramPlaybackControllers
4274
+ ]
3239
4275
  );
3240
4276
  const pauseProgram = (0, import_react2.useCallback)(
3241
4277
  (id) => {
@@ -3334,6 +4370,7 @@ function VizijRuntimeProviderInner({
3334
4370
  );
3335
4371
  const registerInputDriver = (0, import_react2.useCallback)(
3336
4372
  (id, factory) => {
4373
+ inputDriverIdsRef.current.add(id);
3337
4374
  const driver = factory({
3338
4375
  setInput,
3339
4376
  setRendererValue,
@@ -3375,6 +4412,8 @@ function VizijRuntimeProviderInner({
3375
4412
  phase: "driver",
3376
4413
  timestamp: performance.now()
3377
4414
  });
4415
+ } finally {
4416
+ inputDriverIdsRef.current.delete(id);
3378
4417
  }
3379
4418
  }
3380
4419
  };
@@ -3382,22 +4421,15 @@ function VizijRuntimeProviderInner({
3382
4421
  },
3383
4422
  [faceId, namespace, pushError, setInput, setRendererValue]
3384
4423
  );
3385
- const advanceAnimations = (0, import_react2.useCallback)(
3386
- (dt) => {
3387
- advanceAnimationTweens(dt);
3388
- advanceClipPlayback(dt);
3389
- },
3390
- [advanceAnimationTweens, advanceClipPlayback]
3391
- );
3392
4424
  const flushStagedInputs = (0, import_react2.useCallback)(() => {
3393
4425
  if (stagedInputsRef.current.size === 0) {
3394
4426
  return;
3395
4427
  }
3396
4428
  stagedInputsRef.current.forEach(({ value, shape }, path) => {
3397
- orchestratorSetInput(path, value, shape);
4429
+ deviceSetInput(path, value, shape);
3398
4430
  });
3399
4431
  stagedInputsRef.current.clear();
3400
- }, [orchestratorSetInput]);
4432
+ }, [deviceSetInput]);
3401
4433
  const step = (0, import_react2.useCallback)(
3402
4434
  (dt, opts) => {
3403
4435
  if (dt > 0 && Number.isFinite(dt)) {
@@ -3405,13 +4437,19 @@ function VizijRuntimeProviderInner({
3405
4437
  const alpha = 0.1;
3406
4438
  avgStepDtRef.current = prev * (1 - alpha) + dt * alpha;
3407
4439
  }
3408
- advanceAnimations(dt);
4440
+ advanceAnimationTweens(dt);
3409
4441
  flushStagedInputs();
3410
- if (driveOrchestratorRef.current || opts?.forceRuntime) {
4442
+ if (driveRuntimeRef.current || opts?.forceRuntime) {
3411
4443
  stepRuntime(dt);
3412
4444
  }
4445
+ settleFinishedClips();
3413
4446
  },
3414
- [advanceAnimations, flushStagedInputs, stepRuntime]
4447
+ [
4448
+ advanceAnimationTweens,
4449
+ flushStagedInputs,
4450
+ settleFinishedClips,
4451
+ stepRuntime
4452
+ ]
3415
4453
  );
3416
4454
  (0, import_react2.useEffect)(() => {
3417
4455
  if (loopMode !== "active") {
@@ -3426,7 +4464,7 @@ function VizijRuntimeProviderInner({
3426
4464
  if (lastTime == null) {
3427
4465
  lastTime = timestamp;
3428
4466
  }
3429
- const dt = Math.max(0, (timestamp - lastTime) / 1e3);
4467
+ const dt = inferImplicitPause(Math.max(0, (timestamp - lastTime) / 1e3));
3430
4468
  lastTime = timestamp;
3431
4469
  step(dt || 0);
3432
4470
  requestLoopMode(computeDesiredLoopMode());
@@ -3457,7 +4495,7 @@ function VizijRuntimeProviderInner({
3457
4495
  return;
3458
4496
  }
3459
4497
  const current = now();
3460
- const dt = Math.max(0, (current - lastTime) / 1e3);
4498
+ const dt = inferImplicitPause(Math.max(0, (current - lastTime) / 1e3));
3461
4499
  lastTime = current;
3462
4500
  step(dt || 0);
3463
4501
  requestLoopMode(computeDesiredLoopMode());
@@ -3473,8 +4511,6 @@ function VizijRuntimeProviderInner({
3473
4511
  clipPlaybackRef.current.clear();
3474
4512
  programPlaybackRef.current.clear();
3475
4513
  programControllerIdsRef.current.clear();
3476
- clipOutputValuesRef.current.clear();
3477
- clipAggregateValuesRef.current.clear();
3478
4514
  };
3479
4515
  }, []);
3480
4516
  (0, import_react2.useEffect)(() => {
@@ -3527,6 +4563,10 @@ function VizijRuntimeProviderInner({
3527
4563
  ...status,
3528
4564
  assetBundle,
3529
4565
  setInput,
4566
+ getValueSnapshot: getPathSnapshot,
4567
+ getStoreSnapshot,
4568
+ subscribeToStoreChanges,
4569
+ subscribeToStep,
3530
4570
  setGraphBundle,
3531
4571
  setValue: setRendererValue,
3532
4572
  stagePoseNeutral,
@@ -3546,13 +4586,16 @@ function VizijRuntimeProviderInner({
3546
4586
  setAnimationActive,
3547
4587
  isAnimationActive,
3548
4588
  step,
3549
- advanceAnimations,
3550
4589
  inputConstraints
3551
4590
  }),
3552
4591
  [
3553
4592
  status,
3554
4593
  assetBundle,
3555
4594
  setInput,
4595
+ getPathSnapshot,
4596
+ getStoreSnapshot,
4597
+ subscribeToStoreChanges,
4598
+ subscribeToStep,
3556
4599
  setGraphBundle,
3557
4600
  setRendererValue,
3558
4601
  stagePoseNeutral,
@@ -3572,7 +4615,6 @@ function VizijRuntimeProviderInner({
3572
4615
  setAnimationActive,
3573
4616
  isAnimationActive,
3574
4617
  step,
3575
- advanceAnimations,
3576
4618
  inputConstraints
3577
4619
  ]
3578
4620
  );