@vizij/runtime-react 0.0.14 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,42 +1,30 @@
1
- "use strict";
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- var __export = (target, all) => {
7
- for (var name in all)
8
- __defProp(target, name, { get: all[name], enumerable: true });
9
- };
10
- var __copyProps = (to, from, except, desc) => {
11
- if (from && typeof from === "object" || typeof from === "function") {
12
- for (let key of __getOwnPropNames(from))
13
- if (!__hasOwnProp.call(to, key) && key !== except)
14
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
- }
16
- return to;
17
- };
18
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
-
20
- // src/index.ts
21
- var index_exports = {};
22
- __export(index_exports, {
23
- VizijRuntimeFace: () => VizijRuntimeFace,
24
- VizijRuntimeProvider: () => VizijRuntimeProvider,
25
- useRigInput: () => useRigInput,
26
- useVizijOutputs: () => useVizijOutputs,
27
- useVizijRuntime: () => useVizijRuntime
28
- });
29
- module.exports = __toCommonJS(index_exports);
30
-
31
1
  // src/VizijRuntimeProvider.tsx
32
- var import_react2 = require("react");
33
- var import_render = require("@vizij/render");
34
- var import_orchestrator_react = require("@vizij/orchestrator-react");
35
- var import_value_json2 = require("@vizij/value-json");
2
+ import {
3
+ useCallback,
4
+ useContext,
5
+ useEffect,
6
+ useMemo,
7
+ useRef,
8
+ useState
9
+ } from "react";
10
+ import {
11
+ VizijContext,
12
+ createVizijStore,
13
+ loadGLTFWithBundle,
14
+ loadGLTFFromBlobWithBundle
15
+ } from "@vizij/render";
16
+ import {
17
+ OrchestratorProvider,
18
+ OrchestratorContext,
19
+ useOrchestrator,
20
+ useOrchFrame
21
+ } from "@vizij/orchestrator-react";
22
+ import { compileIrGraph } from "@vizij/node-graph-authoring";
23
+ import { valueAsNumber as valueAsNumber2 } from "@vizij/value-json";
36
24
 
37
25
  // src/context.ts
38
- var import_react = require("react");
39
- var VizijRuntimeContext = (0, import_react.createContext)(null);
26
+ import { createContext } from "react";
27
+ var VizijRuntimeContext = createContext(null);
40
28
 
41
29
  // src/utils/graph.ts
42
30
  function getNodes(spec) {
@@ -99,7 +87,15 @@ function collectInputPathMap(spec) {
99
87
  }
100
88
 
101
89
  // src/utils/valueConversion.ts
102
- var import_value_json = require("@vizij/value-json");
90
+ import {
91
+ isNormalizedValue,
92
+ valueAsBool,
93
+ valueAsColorRgba,
94
+ valueAsNumber,
95
+ valueAsText,
96
+ valueAsTransform,
97
+ valueAsVector
98
+ } from "@vizij/value-json";
103
99
  function numericArrayToRaw(arr) {
104
100
  const normalised = arr.map((entry) => Number(entry ?? 0));
105
101
  switch (normalised.length) {
@@ -151,20 +147,20 @@ function valueJSONToRaw(value) {
151
147
  ]);
152
148
  return Object.fromEntries(entries);
153
149
  }
154
- if (!(0, import_value_json.isNormalizedValue)(value)) {
150
+ if (!isNormalizedValue(value)) {
155
151
  return void 0;
156
152
  }
157
153
  switch (value.type) {
158
154
  case "float": {
159
- const num = (0, import_value_json.valueAsNumber)(value);
155
+ const num = valueAsNumber(value);
160
156
  return typeof num === "number" ? num : void 0;
161
157
  }
162
158
  case "bool": {
163
- const boolVal = (0, import_value_json.valueAsBool)(value);
159
+ const boolVal = valueAsBool(value);
164
160
  return typeof boolVal === "boolean" ? boolVal : void 0;
165
161
  }
166
162
  case "text": {
167
- const text = (0, import_value_json.valueAsText)(value);
163
+ const text = valueAsText(value);
168
164
  return typeof text === "string" ? text : void 0;
169
165
  }
170
166
  case "vec2":
@@ -172,11 +168,11 @@ function valueJSONToRaw(value) {
172
168
  case "vec4":
173
169
  case "quat":
174
170
  case "vector": {
175
- const vec = (0, import_value_json.valueAsVector)(value);
171
+ const vec = valueAsVector(value);
176
172
  return vec ? numericArrayToRaw(vec) : void 0;
177
173
  }
178
174
  case "colorrgba": {
179
- const color = (0, import_value_json.valueAsColorRgba)(value);
175
+ const color = valueAsColorRgba(value);
180
176
  if (!color) {
181
177
  return void 0;
182
178
  }
@@ -184,7 +180,7 @@ function valueJSONToRaw(value) {
184
180
  return { r, g, b, a };
185
181
  }
186
182
  case "transform": {
187
- const transform = (0, import_value_json.valueAsTransform)(value);
183
+ const transform = valueAsTransform(value);
188
184
  if (!transform) {
189
185
  return void 0;
190
186
  }
@@ -214,7 +210,10 @@ function valueJSONToRaw(value) {
214
210
  }
215
211
 
216
212
  // src/VizijRuntimeProvider.tsx
217
- var import_jsx_runtime = require("react/jsx-runtime");
213
+ import { jsx } from "react/jsx-runtime";
214
+ var ACTIVE_GRACE_MS = 250;
215
+ var VISIBLE_IDLE_FPS = 30;
216
+ var HIDDEN_IDLE_FPS = 1;
218
217
  var DEFAULT_MERGE = {
219
218
  outputs: "add",
220
219
  intermediate: "add"
@@ -252,6 +251,166 @@ function normalisePath(path) {
252
251
  function normaliseBundleKind(kind) {
253
252
  return typeof kind === "string" ? kind.toLowerCase() : "";
254
253
  }
254
+ function addConstraintVariant(map, key, constraint) {
255
+ if (!key) return;
256
+ if (!map[key]) {
257
+ map[key] = constraint;
258
+ }
259
+ }
260
+ function stripRigFacePrefix(path) {
261
+ const trimmed = path.startsWith("/") ? path.slice(1) : path;
262
+ const match = /^rig\/[^/]+\/(.+)$/.exec(trimmed);
263
+ if (match && match[1]) {
264
+ return match[1];
265
+ }
266
+ if (trimmed.startsWith("rig/")) {
267
+ return trimmed.slice("rig/".length);
268
+ }
269
+ return trimmed;
270
+ }
271
+ function extractInputConstraints(spec, extraInputs, namespace) {
272
+ if (!spec || typeof spec !== "object") {
273
+ return {};
274
+ }
275
+ const inputs = [];
276
+ if (Array.isArray(extraInputs)) {
277
+ inputs.push(...extraInputs);
278
+ }
279
+ const entries = spec.metadata?.vizij?.inputs;
280
+ if (Array.isArray(entries)) {
281
+ entries.forEach((entry) => {
282
+ if (entry && typeof entry === "object") {
283
+ inputs.push(entry);
284
+ }
285
+ });
286
+ }
287
+ if (inputs.length === 0) {
288
+ return {};
289
+ }
290
+ const map = {};
291
+ inputs.forEach((entry) => {
292
+ const path = entry.path;
293
+ if (typeof path !== "string") return;
294
+ const namespaced = namespaceTypedPath(path, namespace);
295
+ const stripped = stripRigFacePrefix(path);
296
+ const strippedNamespaced = stripped ? namespaceTypedPath(stripped, namespace) : stripped;
297
+ const min = entry.range?.min;
298
+ const max = entry.range?.max;
299
+ const defaultValue = entry.defaultValue;
300
+ const constraint = {
301
+ ...Number.isFinite(Number(min)) ? { min: Number(min) } : {},
302
+ ...Number.isFinite(Number(max)) ? { max: Number(max) } : {},
303
+ ...Number.isFinite(Number(defaultValue)) ? { defaultValue: Number(defaultValue) } : {}
304
+ };
305
+ addConstraintVariant(map, namespaced, constraint);
306
+ addConstraintVariant(map, path, constraint);
307
+ if (stripped) {
308
+ addConstraintVariant(map, stripped, constraint);
309
+ }
310
+ if (strippedNamespaced) {
311
+ addConstraintVariant(map, strippedNamespaced, constraint);
312
+ }
313
+ });
314
+ return map;
315
+ }
316
+ function namespaceTypedPath(path, namespace) {
317
+ const trimmed = typeof path === "string" ? path.trim() : "";
318
+ if (!trimmed) {
319
+ return trimmed;
320
+ }
321
+ const prefix = `${namespace}/`;
322
+ if (trimmed.startsWith(prefix)) {
323
+ return trimmed;
324
+ }
325
+ if (trimmed.startsWith("debug/")) {
326
+ const rest = trimmed.slice("debug/".length);
327
+ const namespacedRest = namespaceTypedPath(rest, namespace);
328
+ return namespacedRest.startsWith("debug/") ? namespacedRest : `debug/${namespacedRest}`;
329
+ }
330
+ return `${prefix}${trimmed}`;
331
+ }
332
+ function stripNamespace(path, namespace) {
333
+ const prefix = `${namespace}/`;
334
+ if (path.startsWith(prefix)) {
335
+ return path.slice(prefix.length);
336
+ }
337
+ const debugPrefix = `debug/${prefix}`;
338
+ if (path.startsWith(debugPrefix)) {
339
+ return path.slice(debugPrefix.length);
340
+ }
341
+ if (path.startsWith("debug/")) {
342
+ return path.slice("debug/".length);
343
+ }
344
+ return path;
345
+ }
346
+ function namespaceControllerId(id, namespace, kind = "graph") {
347
+ if (!id) {
348
+ return void 0;
349
+ }
350
+ const trimmed = id.trim();
351
+ if (!trimmed) {
352
+ return void 0;
353
+ }
354
+ const prefix = `${namespace}/${kind}/`;
355
+ if (trimmed.startsWith(prefix)) {
356
+ return trimmed;
357
+ }
358
+ return `${prefix}${trimmed}`;
359
+ }
360
+ function namespaceSubscriptions(subs, namespace) {
361
+ if (!subs) {
362
+ return void 0;
363
+ }
364
+ const inputs = Array.isArray(subs.inputs) ? subs.inputs.map((path) => namespaceTypedPath(path, namespace)) : void 0;
365
+ const outputs = Array.isArray(subs.outputs) ? subs.outputs.map((path) => namespaceTypedPath(path, namespace)) : void 0;
366
+ if (!inputs && !outputs) {
367
+ return subs;
368
+ }
369
+ return {
370
+ ...subs,
371
+ ...inputs ? { inputs } : {},
372
+ ...outputs ? { outputs } : {}
373
+ };
374
+ }
375
+ function namespaceGraphSpec(spec, namespace) {
376
+ if (!spec || typeof spec !== "object") {
377
+ return spec;
378
+ }
379
+ const nodes = spec.nodes;
380
+ if (!Array.isArray(nodes)) {
381
+ return spec;
382
+ }
383
+ let changed = false;
384
+ const nextNodes = nodes.map((node) => {
385
+ if (!node || typeof node !== "object") {
386
+ return node;
387
+ }
388
+ const path = node.params?.path;
389
+ if (typeof path !== "string") {
390
+ return node;
391
+ }
392
+ const namespacedPath = namespaceTypedPath(path, namespace);
393
+ if (namespacedPath === path) {
394
+ return node;
395
+ }
396
+ changed = true;
397
+ return {
398
+ ...node,
399
+ params: {
400
+ ...node.params ?? {},
401
+ path: namespacedPath
402
+ }
403
+ };
404
+ });
405
+ if (!changed) {
406
+ return spec;
407
+ }
408
+ return {
409
+ ...spec,
410
+ nodes: nextNodes
411
+ };
412
+ }
413
+ var now = () => typeof performance !== "undefined" ? performance.now() : Date.now();
255
414
  function pickBundleGraph(bundle, preferredKinds) {
256
415
  if (!bundle?.graphs || bundle.graphs.length === 0) {
257
416
  return null;
@@ -271,14 +430,91 @@ function pickBundleGraph(bundle, preferredKinds) {
271
430
  }
272
431
  return null;
273
432
  }
433
+ function extractIrGraph(payload) {
434
+ if (!payload || typeof payload !== "object") {
435
+ return void 0;
436
+ }
437
+ return payload;
438
+ }
274
439
  function convertBundleGraph(entry) {
275
- if (!entry || !entry.id || !entry.spec) {
440
+ if (!entry || !entry.id) {
441
+ return null;
442
+ }
443
+ const rawSpec = entry.spec;
444
+ const inputMetadata = extractVizijInputMetadata(
445
+ rawSpec
446
+ );
447
+ const spec = rawSpec ? stripVizijMetadata(rawSpec) : void 0;
448
+ const ir = extractIrGraph(entry.ir);
449
+ if (!spec && !ir) {
276
450
  return null;
277
451
  }
278
452
  return {
279
453
  id: entry.id,
280
- spec: entry.spec
454
+ spec,
455
+ ir: ir ?? null,
456
+ inputMetadata
457
+ };
458
+ }
459
+ function resolveGraphSpec(asset, context) {
460
+ if (asset.spec) {
461
+ return stripVizijMetadata(asset.spec);
462
+ }
463
+ if (asset.ir) {
464
+ try {
465
+ const compiled = compileIrGraph(asset.ir, { preferLegacySpec: false });
466
+ if (compiled.issues && compiled.issues.length > 0) {
467
+ console.warn(
468
+ `[vizij-runtime] IR compile for graph "${context}" reported issues`,
469
+ compiled.issues
470
+ );
471
+ }
472
+ return stripVizijMetadata(compiled.spec);
473
+ } catch (error) {
474
+ console.warn(
475
+ `[vizij-runtime] Failed to compile IR graph "${context}"`,
476
+ error
477
+ );
478
+ }
479
+ }
480
+ return null;
481
+ }
482
+ function stripVizijMetadata(spec) {
483
+ if (!spec || typeof spec !== "object") {
484
+ return spec;
485
+ }
486
+ const cloned = {
487
+ ...spec,
488
+ nodes: spec.nodes ? spec.nodes.map((node) => ({ ...node })) : spec.nodes,
489
+ edges: spec.edges ? spec.edges.map((edge) => ({ ...edge })) : spec.edges
281
490
  };
491
+ if (cloned.metadata && typeof cloned.metadata === "object") {
492
+ const metadata = { ...cloned.metadata };
493
+ if ("vizij" in metadata) {
494
+ delete metadata.vizij;
495
+ }
496
+ if (Object.keys(metadata).length === 0) {
497
+ delete cloned.metadata;
498
+ } else {
499
+ cloned.metadata = metadata;
500
+ }
501
+ }
502
+ return cloned;
503
+ }
504
+ function extractVizijInputMetadata(spec) {
505
+ if (!spec || typeof spec !== "object") {
506
+ return [];
507
+ }
508
+ const inputs = spec.metadata?.vizij?.inputs;
509
+ if (!Array.isArray(inputs)) {
510
+ return [];
511
+ }
512
+ return inputs.map((entry) => {
513
+ if (!entry || typeof entry !== "object") {
514
+ return null;
515
+ }
516
+ return entry;
517
+ }).filter(Boolean);
282
518
  }
283
519
  function convertBundleAnimations(entries) {
284
520
  if (!Array.isArray(entries) || entries.length === 0) {
@@ -502,36 +738,53 @@ function VizijRuntimeProvider({
502
738
  autoCreate = true,
503
739
  createOptions,
504
740
  autostart = false,
741
+ driveOrchestrator = true,
505
742
  mergeStrategy,
506
743
  onRegisterControllers,
507
- onStatusChange
744
+ onStatusChange,
745
+ orchestratorScope = "auto"
508
746
  }) {
509
- const storeRef = (0, import_react2.useRef)();
747
+ const storeRef = useRef(null);
510
748
  if (!storeRef.current) {
511
- storeRef.current = (0, import_render.createVizijStore)();
749
+ storeRef.current = createVizijStore();
512
750
  }
513
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_render.VizijContext.Provider, { value: storeRef.current, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
514
- import_orchestrator_react.OrchestratorProvider,
751
+ const parentOrchestrator = useContext(OrchestratorContext);
752
+ const hasParentOrchestrator = Boolean(parentOrchestrator);
753
+ const shouldProvideOrchestrator = orchestratorScope === "isolated" || !hasParentOrchestrator && orchestratorScope !== "shared";
754
+ if (orchestratorScope === "shared" && !hasParentOrchestrator) {
755
+ console.warn(
756
+ '[vizij-runtime] orchestratorScope="shared" requires an OrchestratorProvider higher in the tree; falling back to an isolated provider.'
757
+ );
758
+ }
759
+ const runtimeTree = /* @__PURE__ */ jsx(VizijContext.Provider, { value: storeRef.current, children: /* @__PURE__ */ jsx(
760
+ VizijRuntimeProviderInner,
515
761
  {
762
+ assetBundle,
763
+ namespace: namespaceProp,
764
+ faceId: faceIdProp,
516
765
  autoCreate,
517
- createOptions,
518
766
  autostart,
519
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
520
- VizijRuntimeProviderInner,
521
- {
522
- assetBundle,
523
- namespace: namespaceProp,
524
- faceId: faceIdProp,
525
- autoCreate,
526
- mergeStrategy,
527
- onRegisterControllers,
528
- onStatusChange,
529
- store: storeRef.current,
530
- children
531
- }
532
- )
767
+ driveOrchestrator,
768
+ createOptions,
769
+ mergeStrategy,
770
+ onRegisterControllers,
771
+ onStatusChange,
772
+ store: storeRef.current,
773
+ children
533
774
  }
534
775
  ) });
776
+ if (!shouldProvideOrchestrator) {
777
+ return runtimeTree;
778
+ }
779
+ return /* @__PURE__ */ jsx(
780
+ OrchestratorProvider,
781
+ {
782
+ autoCreate,
783
+ createOptions,
784
+ autostart: false,
785
+ children: runtimeTree
786
+ }
787
+ );
535
788
  }
536
789
  function VizijRuntimeProviderInner({
537
790
  assetBundle: initialAssetBundle,
@@ -542,9 +795,12 @@ function VizijRuntimeProviderInner({
542
795
  onStatusChange,
543
796
  store,
544
797
  children,
545
- autoCreate
798
+ autoCreate,
799
+ autostart,
800
+ createOptions,
801
+ driveOrchestrator
546
802
  }) {
547
- const [extractedBundle, setExtractedBundle] = (0, import_react2.useState)(() => {
803
+ const [extractedBundle, setExtractedBundle] = useState(() => {
548
804
  if (initialAssetBundle.bundle) {
549
805
  return initialAssetBundle.bundle;
550
806
  }
@@ -553,8 +809,8 @@ function VizijRuntimeProviderInner({
553
809
  }
554
810
  return null;
555
811
  });
556
- const [extractedAnimations, setExtractedAnimations] = (0, import_react2.useState)([]);
557
- (0, import_react2.useEffect)(() => {
812
+ const [extractedAnimations, setExtractedAnimations] = useState([]);
813
+ useEffect(() => {
558
814
  if (initialAssetBundle.bundle) {
559
815
  setExtractedBundle(initialAssetBundle.bundle);
560
816
  return;
@@ -565,7 +821,7 @@ function VizijRuntimeProviderInner({
565
821
  setExtractedBundle(null);
566
822
  }
567
823
  }, [initialAssetBundle]);
568
- const assetBundle = (0, import_react2.useMemo)(
824
+ const assetBundle = useMemo(
569
825
  () => mergeAssetBundle(
570
826
  initialAssetBundle,
571
827
  extractedBundle,
@@ -582,14 +838,14 @@ function VizijRuntimeProviderInner({
582
838
  removeGraph,
583
839
  removeAnimation,
584
840
  listControllers,
585
- setInput,
841
+ setInput: orchestratorSetInput,
586
842
  getPathSnapshot,
587
843
  step: stepRuntime
588
- } = (0, import_orchestrator_react.useOrchestrator)();
589
- const frame = (0, import_orchestrator_react.useOrchFrame)();
844
+ } = useOrchestrator();
845
+ const frame = useOrchFrame();
590
846
  const namespace = namespaceProp ?? assetBundle.namespace ?? "default";
591
847
  const faceId = faceIdProp ?? assetBundle.faceId ?? assetBundle.pose?.config?.faceId ?? assetBundle.pose?.config?.faceId ?? void 0;
592
- const [status, setStatus] = (0, import_react2.useState)({
848
+ const [status, setStatus] = useState({
593
849
  loading: true,
594
850
  ready: false,
595
851
  error: null,
@@ -598,19 +854,98 @@ function VizijRuntimeProviderInner({
598
854
  faceId,
599
855
  rootId: null,
600
856
  outputPaths: [],
857
+ stepHz: void 0,
601
858
  controllers: { graphs: [], anims: [] }
602
859
  });
603
- const errorsRef = (0, import_react2.useRef)([]);
604
- const outputPathsRef = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
605
- const rigInputMapRef = (0, import_react2.useRef)({});
606
- const registeredGraphsRef = (0, import_react2.useRef)([]);
607
- const registeredAnimationsRef = (0, import_react2.useRef)([]);
608
- const mergedGraphRef = (0, import_react2.useRef)(null);
609
- const animationTweensRef = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
610
- const clipPlaybackRef = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
611
- const rafHandleRef = (0, import_react2.useRef)(null);
612
- const lastFrameTimeRef = (0, import_react2.useRef)(null);
613
- const reportStatus = (0, import_react2.useCallback)(
860
+ const errorsRef = useRef([]);
861
+ const outputPathsRef = useRef(/* @__PURE__ */ new Set());
862
+ const baseOutputPathsRef = useRef(/* @__PURE__ */ new Set());
863
+ const namespacedOutputPathsRef = useRef(/* @__PURE__ */ new Set());
864
+ const namespaceRef = useRef(namespace);
865
+ const driveOrchestratorRef = useRef(driveOrchestrator);
866
+ const rigInputMapRef = useRef({});
867
+ const registeredGraphsRef = useRef([]);
868
+ const registeredAnimationsRef = useRef([]);
869
+ const mergedGraphRef = useRef(null);
870
+ const [inputConstraints, setInputConstraints] = useState({});
871
+ const avgStepDtRef = useRef(null);
872
+ const animationTweensRef = useRef(/* @__PURE__ */ new Map());
873
+ const clipPlaybackRef = useRef(/* @__PURE__ */ new Map());
874
+ const stagedInputsRef = useRef(/* @__PURE__ */ new Map());
875
+ const autostartRef = useRef(autostart);
876
+ const lastActivityTimeRef = useRef(now());
877
+ const [loopMode, setLoopMode] = useState("stopped");
878
+ const loopModeRef = useRef("stopped");
879
+ useEffect(() => {
880
+ loopModeRef.current = loopMode;
881
+ }, [loopMode]);
882
+ const runtimeMountedRef = useRef(true);
883
+ useEffect(() => {
884
+ return () => {
885
+ runtimeMountedRef.current = false;
886
+ };
887
+ }, []);
888
+ useEffect(() => {
889
+ const rigAsset = assetBundle.rig;
890
+ if (!rigAsset) {
891
+ setInputConstraints({});
892
+ return;
893
+ }
894
+ const rigSpec = resolveGraphSpec(
895
+ rigAsset,
896
+ `${rigAsset.id ?? "rig"} graph (constraints)`
897
+ );
898
+ const constraints = extractInputConstraints(
899
+ rigSpec,
900
+ rigAsset.inputMetadata,
901
+ namespace
902
+ );
903
+ setInputConstraints(constraints);
904
+ const isDevEnv = typeof globalThis !== "undefined" && Boolean(globalThis?.process?.env?.NODE_ENV !== "production");
905
+ if (isDevEnv) {
906
+ const size = Object.keys(constraints).length;
907
+ console.log("[vizij-runtime] input constraints computed", size, {
908
+ namespace,
909
+ rigId: rigAsset.id
910
+ });
911
+ }
912
+ }, [assetBundle.rig, namespace]);
913
+ const requestLoopMode = useCallback((mode) => {
914
+ if (!runtimeMountedRef.current) {
915
+ return;
916
+ }
917
+ setLoopMode((prev) => prev === mode ? prev : mode);
918
+ }, []);
919
+ const hasActiveAnimations = useCallback(() => {
920
+ return animationTweensRef.current.size > 0 || clipPlaybackRef.current.size > 0;
921
+ }, []);
922
+ const computeDesiredLoopMode = useCallback(() => {
923
+ const hasAnimations = hasActiveAnimations();
924
+ const recentlyActive = now() - lastActivityTimeRef.current <= ACTIVE_GRACE_MS;
925
+ if (autostartRef.current && (hasAnimations || recentlyActive)) {
926
+ return "active";
927
+ }
928
+ if (autostartRef.current) {
929
+ return "idle-visible";
930
+ }
931
+ return "idle-hidden";
932
+ }, [hasActiveAnimations]);
933
+ const updateLoopMode = useCallback(() => {
934
+ requestLoopMode(computeDesiredLoopMode());
935
+ }, [computeDesiredLoopMode, requestLoopMode]);
936
+ const markActivity = useCallback(() => {
937
+ lastActivityTimeRef.current = now();
938
+ updateLoopMode();
939
+ }, [updateLoopMode]);
940
+ const setInput = useCallback(
941
+ (path, value, shape) => {
942
+ markActivity();
943
+ const namespacedPath = namespaceTypedPath(path, namespaceRef.current);
944
+ stagedInputsRef.current.set(namespacedPath, { value, shape });
945
+ },
946
+ [markActivity]
947
+ );
948
+ const reportStatus = useCallback(
614
949
  (updater) => {
615
950
  setStatus((prev) => {
616
951
  const next = updater(prev);
@@ -620,7 +955,7 @@ function VizijRuntimeProviderInner({
620
955
  },
621
956
  [onStatusChange]
622
957
  );
623
- const pushError = (0, import_react2.useCallback)(
958
+ const pushError = useCallback(
624
959
  (error) => {
625
960
  errorsRef.current = [...errorsRef.current, error];
626
961
  reportStatus((prev) => ({
@@ -632,7 +967,7 @@ function VizijRuntimeProviderInner({
632
967
  },
633
968
  [reportStatus]
634
969
  );
635
- const resetErrors = (0, import_react2.useCallback)(() => {
970
+ const resetErrors = useCallback(() => {
636
971
  errorsRef.current = [];
637
972
  reportStatus((prev) => ({
638
973
  ...prev,
@@ -640,7 +975,11 @@ function VizijRuntimeProviderInner({
640
975
  errors: []
641
976
  }));
642
977
  }, [reportStatus]);
643
- const clearControllers = (0, import_react2.useCallback)(() => {
978
+ useEffect(() => {
979
+ autostartRef.current = autostart;
980
+ updateLoopMode();
981
+ }, [autostart, updateLoopMode]);
982
+ const clearControllers = useCallback(() => {
644
983
  const existing = listControllers();
645
984
  existing.graphs.forEach((id) => {
646
985
  try {
@@ -669,17 +1008,24 @@ function VizijRuntimeProviderInner({
669
1008
  registeredGraphsRef.current = [];
670
1009
  registeredAnimationsRef.current = [];
671
1010
  mergedGraphRef.current = null;
1011
+ outputPathsRef.current = /* @__PURE__ */ new Set();
1012
+ baseOutputPathsRef.current = /* @__PURE__ */ new Set();
1013
+ namespacedOutputPathsRef.current = /* @__PURE__ */ new Set();
672
1014
  }, [listControllers, removeAnimation, removeGraph, pushError]);
673
- (0, import_react2.useEffect)(() => {
1015
+ useEffect(() => {
1016
+ namespaceRef.current = namespace;
674
1017
  reportStatus((prev) => ({
675
1018
  ...prev,
676
1019
  namespace,
677
1020
  faceId
678
1021
  }));
679
1022
  }, [namespace, faceId, reportStatus]);
1023
+ useEffect(() => {
1024
+ driveOrchestratorRef.current = driveOrchestrator;
1025
+ }, [driveOrchestrator]);
680
1026
  const glbAsset = initialAssetBundle.glb;
681
1027
  const baseBundle = initialAssetBundle.bundle ?? null;
682
- (0, import_react2.useEffect)(() => {
1028
+ useEffect(() => {
683
1029
  let cancelled = false;
684
1030
  resetErrors();
685
1031
  reportStatus((prev) => ({
@@ -698,7 +1044,7 @@ function VizijRuntimeProviderInner({
698
1044
  let bundle = baseBundle;
699
1045
  let gltfAnimations;
700
1046
  if (glbAsset.kind === "url") {
701
- const loaded = await (0, import_render.loadGLTFWithBundle)(
1047
+ const loaded = await loadGLTFWithBundle(
702
1048
  glbAsset.src,
703
1049
  [namespace],
704
1050
  glbAsset.aggressiveImport ?? false,
@@ -709,7 +1055,7 @@ function VizijRuntimeProviderInner({
709
1055
  bundle = loaded.bundle ?? bundle;
710
1056
  gltfAnimations = pickExtractedAnimations(loaded);
711
1057
  } else if (glbAsset.kind === "blob") {
712
- const loaded = await (0, import_render.loadGLTFFromBlobWithBundle)(
1058
+ const loaded = await loadGLTFFromBlobWithBundle(
713
1059
  glbAsset.blob,
714
1060
  [namespace],
715
1061
  glbAsset.aggressiveImport ?? false,
@@ -771,9 +1117,9 @@ function VizijRuntimeProviderInner({
771
1117
  setExtractedBundle,
772
1118
  setExtractedAnimations
773
1119
  ]);
774
- (0, import_react2.useEffect)(() => {
1120
+ useEffect(() => {
775
1121
  if (!ready && autoCreate) {
776
- createOrchestrator().catch((err) => {
1122
+ createOrchestrator(createOptions).catch((err) => {
777
1123
  pushError({
778
1124
  message: "Failed to create orchestrator runtime",
779
1125
  cause: err,
@@ -782,10 +1128,20 @@ function VizijRuntimeProviderInner({
782
1128
  });
783
1129
  });
784
1130
  }
785
- }, [ready, autoCreate, createOrchestrator, pushError]);
786
- const registerControllers = (0, import_react2.useCallback)(async () => {
787
- const normalize = (spec) => spec;
1131
+ }, [ready, autoCreate, createOptions, createOrchestrator, pushError]);
1132
+ const registerControllers = useCallback(async () => {
788
1133
  clearControllers();
1134
+ const baseOutputPaths = /* @__PURE__ */ new Set();
1135
+ const namespacedOutputPaths = /* @__PURE__ */ new Set();
1136
+ const recordOutputs = (paths) => {
1137
+ paths.forEach((path) => {
1138
+ const trimmed = path.trim();
1139
+ if (!trimmed) return;
1140
+ const basePath = stripNamespace(trimmed, namespace);
1141
+ baseOutputPaths.add(basePath);
1142
+ namespacedOutputPaths.add(namespaceTypedPath(trimmed, namespace));
1143
+ });
1144
+ };
789
1145
  const rigAsset = assetBundle.rig;
790
1146
  if (!rigAsset) {
791
1147
  pushError({
@@ -795,37 +1151,67 @@ function VizijRuntimeProviderInner({
795
1151
  });
796
1152
  return;
797
1153
  }
798
- const rigSpec = normalize(rigAsset.spec);
1154
+ const rigSpec = resolveGraphSpec(rigAsset, `${rigAsset.id ?? "rig"} graph`);
1155
+ if (!rigSpec) {
1156
+ pushError({
1157
+ message: "Rig graph is missing a usable spec or IR payload.",
1158
+ phase: "registration",
1159
+ timestamp: performance.now()
1160
+ });
1161
+ return;
1162
+ }
799
1163
  const rigOutputs = collectOutputPaths(rigSpec);
800
1164
  const rigInputs = collectInputPaths(rigSpec);
801
1165
  rigInputMapRef.current = collectInputPathMap(rigSpec);
802
- outputPathsRef.current = new Set(rigOutputs);
803
- const rigConfig = {
804
- id: rigAsset.id,
805
- spec: rigSpec,
806
- subs: rigAsset.subscriptions ?? {
807
- inputs: rigInputs,
808
- outputs: rigOutputs
809
- }
1166
+ recordOutputs(rigOutputs);
1167
+ const rigSubs = rigAsset.subscriptions ?? {
1168
+ inputs: rigInputs,
1169
+ outputs: rigOutputs
810
1170
  };
811
- const graphConfigs = [rigConfig];
812
- if (assetBundle.pose?.graph) {
813
- const poseSpec = normalize(assetBundle.pose.graph.spec);
814
- const poseOutputs = collectOutputPaths(poseSpec);
815
- const poseInputs = collectInputPaths(poseSpec);
816
- graphConfigs.push({
817
- id: assetBundle.pose.graph.id,
818
- spec: poseSpec,
819
- subs: assetBundle.pose.graph.subscriptions ?? {
1171
+ const graphConfigs = [
1172
+ {
1173
+ id: namespaceControllerId(rigAsset.id, namespace, "graph"),
1174
+ spec: namespaceGraphSpec(rigSpec, namespace),
1175
+ subs: namespaceSubscriptions(rigSubs, namespace)
1176
+ }
1177
+ ];
1178
+ const poseGraphAsset = assetBundle.pose?.graph;
1179
+ if (poseGraphAsset) {
1180
+ const poseSpec = resolveGraphSpec(
1181
+ poseGraphAsset,
1182
+ `${poseGraphAsset.id ?? "pose"} graph`
1183
+ );
1184
+ if (poseSpec) {
1185
+ const poseOutputs = collectOutputPaths(poseSpec);
1186
+ const poseInputs = collectInputPaths(poseSpec);
1187
+ recordOutputs(poseOutputs);
1188
+ const poseSubs = poseGraphAsset.subscriptions ?? {
820
1189
  inputs: poseInputs,
821
1190
  outputs: poseOutputs
822
- }
823
- });
1191
+ };
1192
+ graphConfigs.push({
1193
+ id: namespaceControllerId(poseGraphAsset.id, namespace, "graph"),
1194
+ spec: namespaceGraphSpec(poseSpec, namespace),
1195
+ subs: namespaceSubscriptions(poseSubs, namespace)
1196
+ });
1197
+ } else {
1198
+ console.warn(
1199
+ "[vizij-runtime] Pose graph is missing a usable spec or IR payload; skipping registration."
1200
+ );
1201
+ }
824
1202
  }
1203
+ outputPathsRef.current = namespacedOutputPaths;
1204
+ baseOutputPathsRef.current = baseOutputPaths;
1205
+ namespacedOutputPathsRef.current = namespacedOutputPaths;
825
1206
  const graphIds = [];
826
1207
  try {
827
1208
  if (graphConfigs.length > 1) {
828
1209
  const mergedId = registerMergedGraph({
1210
+ id: namespaceControllerId(
1211
+ mergedGraphRef.current ?? `merged-${namespace}`,
1212
+ namespace,
1213
+ "merged"
1214
+ ) ?? void 0,
829
1215
  graphs: graphConfigs,
830
1216
  strategy: mergeStrategy ?? DEFAULT_MERGE
831
1217
  });
@@ -849,8 +1235,9 @@ function VizijRuntimeProviderInner({
849
1235
  const animationIds = [];
850
1236
  for (const anim of assetBundle.animations ?? []) {
851
1237
  try {
1238
+ const controllerId = namespaceControllerId(anim.id, namespace, "animation") ?? anim.id;
852
1239
  const config = {
853
- id: anim.id,
1240
+ id: controllerId,
854
1241
  setup: {
855
1242
  animation: anim.clip,
856
1243
  ...anim.setup ?? {}
@@ -895,6 +1282,7 @@ function VizijRuntimeProviderInner({
895
1282
  clearControllers,
896
1283
  listControllers,
897
1284
  mergeStrategy,
1285
+ namespace,
898
1286
  onRegisterControllers,
899
1287
  pushError,
900
1288
  registerAnimation,
@@ -903,7 +1291,7 @@ function VizijRuntimeProviderInner({
903
1291
  reportStatus,
904
1292
  setInput
905
1293
  ]);
906
- (0, import_react2.useEffect)(() => {
1294
+ useEffect(() => {
907
1295
  if (!ready || status.loading) {
908
1296
  return;
909
1297
  }
@@ -916,7 +1304,7 @@ function VizijRuntimeProviderInner({
916
1304
  });
917
1305
  });
918
1306
  }, [ready, status.loading, registerControllers, pushError]);
919
- (0, import_react2.useEffect)(() => {
1307
+ useEffect(() => {
920
1308
  if (!frame) {
921
1309
  return;
922
1310
  }
@@ -924,21 +1312,29 @@ function VizijRuntimeProviderInner({
924
1312
  if (!writes.length) {
925
1313
  return;
926
1314
  }
927
- const setWorldValue = store.getState().setValue;
1315
+ const setWorldValues = store.getState().setValues;
928
1316
  const namespaceValue = status.namespace;
1317
+ const batched = [];
1318
+ const namespacedOutputs = namespacedOutputPathsRef.current;
1319
+ const baseOutputs = baseOutputPathsRef.current;
929
1320
  writes.forEach((write) => {
930
1321
  const path = normalisePath(write.path);
931
- if (!outputPathsRef.current.has(path)) {
1322
+ if (!namespacedOutputs.has(path)) {
932
1323
  return;
933
1324
  }
934
1325
  const raw = valueJSONToRaw(write.value);
935
1326
  if (raw === void 0) {
936
1327
  return;
937
1328
  }
938
- setWorldValue(path, namespaceValue, raw);
1329
+ const basePath = stripNamespace(path, namespaceValue);
1330
+ const targetPath = baseOutputs.has(basePath) ? basePath : path;
1331
+ batched.push({ id: targetPath, namespace: namespaceValue, value: raw });
939
1332
  });
1333
+ if (batched.length > 0) {
1334
+ setWorldValues(batched);
1335
+ }
940
1336
  }, [frame, status.namespace, store]);
941
- const stagePoseNeutral = (0, import_react2.useCallback)(
1337
+ const stagePoseNeutral = useCallback(
942
1338
  (force = false) => {
943
1339
  const neutral = assetBundle.pose?.config?.neutralInputs ?? {};
944
1340
  const rigMap = rigInputMapRef.current;
@@ -970,41 +1366,20 @@ function VizijRuntimeProviderInner({
970
1366
  },
971
1367
  [assetBundle.pose?.config?.neutralInputs, setInput]
972
1368
  );
973
- const setRendererValue = (0, import_react2.useCallback)(
1369
+ const setRendererValue = useCallback(
974
1370
  (id, ns, value) => {
975
1371
  store.getState().setValue(id, ns, value);
976
1372
  },
977
1373
  [store]
978
1374
  );
979
- const cancelAnimation = (0, import_react2.useCallback)((path) => {
1375
+ const cancelAnimation = useCallback((path) => {
980
1376
  if (animationTweensRef.current.has(path)) {
981
1377
  const entry = animationTweensRef.current.get(path);
982
1378
  animationTweensRef.current.delete(path);
983
1379
  entry?.resolve();
984
1380
  }
985
1381
  }, []);
986
- const scheduleLoop = (0, import_react2.useCallback)(() => {
987
- if (rafHandleRef.current !== null) {
988
- return;
989
- }
990
- const tick = (timestamp) => {
991
- if (lastFrameTimeRef.current == null) {
992
- lastFrameTimeRef.current = timestamp;
993
- }
994
- const dt = Math.max(0, (timestamp - lastFrameTimeRef.current) / 1e3);
995
- lastFrameTimeRef.current = timestamp;
996
- advanceAnimationTweens(dt);
997
- advanceClipPlayback(dt);
998
- if (animationTweensRef.current.size > 0 || clipPlaybackRef.current.size > 0) {
999
- rafHandleRef.current = requestAnimationFrame(tick);
1000
- } else {
1001
- rafHandleRef.current = null;
1002
- lastFrameTimeRef.current = null;
1003
- }
1004
- };
1005
- rafHandleRef.current = requestAnimationFrame(tick);
1006
- }, []);
1007
- const advanceAnimationTweens = (0, import_react2.useCallback)(
1382
+ const advanceAnimationTweens = useCallback(
1008
1383
  (dt) => {
1009
1384
  if (animationTweensRef.current.size === 0) {
1010
1385
  return;
@@ -1026,7 +1401,7 @@ function VizijRuntimeProviderInner({
1026
1401
  },
1027
1402
  [setInput]
1028
1403
  );
1029
- const sampleTrack = (0, import_react2.useCallback)(
1404
+ const sampleTrack = useCallback(
1030
1405
  (track, time) => {
1031
1406
  const keyframes = Array.isArray(track.keyframes) ? track.keyframes : [];
1032
1407
  if (!keyframes.length) {
@@ -1057,7 +1432,7 @@ function VizijRuntimeProviderInner({
1057
1432
  },
1058
1433
  []
1059
1434
  );
1060
- const advanceClipPlayback = (0, import_react2.useCallback)(
1435
+ const advanceClipPlayback = useCallback(
1061
1436
  (dt) => {
1062
1437
  if (clipPlaybackRef.current.size === 0) {
1063
1438
  return;
@@ -1106,21 +1481,22 @@ function VizijRuntimeProviderInner({
1106
1481
  },
1107
1482
  [assetBundle.animations, sampleTrack, setInput]
1108
1483
  );
1109
- const animateValue = (0, import_react2.useCallback)(
1484
+ const animateValue = useCallback(
1110
1485
  (path, target, options) => {
1111
1486
  const easing = resolveEasing(options?.easing);
1112
1487
  const duration = Math.max(0, options?.duration ?? DEFAULT_DURATION);
1113
1488
  cancelAnimation(path);
1114
- const current = getPathSnapshot(path);
1115
- const fromValue = (0, import_value_json2.valueAsNumber)(current);
1116
- const toValue = (0, import_value_json2.valueAsNumber)(target);
1489
+ const namespacedPath = namespaceTypedPath(path, namespaceRef.current);
1490
+ const current = getPathSnapshot(namespacedPath);
1491
+ const fromValue = valueAsNumber2(current);
1492
+ const toValue = valueAsNumber2(target);
1117
1493
  if (fromValue == null || toValue == null || duration === 0) {
1118
1494
  setInput(path, target);
1119
1495
  return Promise.resolve();
1120
1496
  }
1121
1497
  return new Promise((resolve) => {
1122
1498
  animationTweensRef.current.set(path, {
1123
- path,
1499
+ path: namespacedPath,
1124
1500
  from: fromValue,
1125
1501
  to: toValue,
1126
1502
  duration,
@@ -1128,12 +1504,12 @@ function VizijRuntimeProviderInner({
1128
1504
  easing,
1129
1505
  resolve
1130
1506
  });
1131
- scheduleLoop();
1507
+ markActivity();
1132
1508
  });
1133
1509
  },
1134
- [cancelAnimation, getPathSnapshot, scheduleLoop, setInput]
1510
+ [cancelAnimation, getPathSnapshot, markActivity, setInput]
1135
1511
  );
1136
- const playAnimation = (0, import_react2.useCallback)(
1512
+ const playAnimation = useCallback(
1137
1513
  (id, options) => {
1138
1514
  const clip = assetBundle.animations?.find((anim) => anim.id === id);
1139
1515
  if (!clip) {
@@ -1156,12 +1532,12 @@ function VizijRuntimeProviderInner({
1156
1532
  weight,
1157
1533
  resolve
1158
1534
  });
1159
- scheduleLoop();
1535
+ markActivity();
1160
1536
  });
1161
1537
  },
1162
- [assetBundle.animations, scheduleLoop]
1538
+ [assetBundle.animations, markActivity]
1163
1539
  );
1164
- const stopAnimation = (0, import_react2.useCallback)(
1540
+ const stopAnimation = useCallback(
1165
1541
  (id) => {
1166
1542
  const clip = assetBundle.animations?.find((anim) => anim.id === id);
1167
1543
  const state = clipPlaybackRef.current.get(id);
@@ -1180,7 +1556,7 @@ function VizijRuntimeProviderInner({
1180
1556
  },
1181
1557
  [assetBundle.animations, setInput]
1182
1558
  );
1183
- const registerInputDriver = (0, import_react2.useCallback)(
1559
+ const registerInputDriver = useCallback(
1184
1560
  (id, factory) => {
1185
1561
  const driver = factory({
1186
1562
  setInput,
@@ -1230,32 +1606,111 @@ function VizijRuntimeProviderInner({
1230
1606
  },
1231
1607
  [faceId, namespace, pushError, setInput, setRendererValue]
1232
1608
  );
1233
- const advanceAnimations = (0, import_react2.useCallback)(
1609
+ const advanceAnimations = useCallback(
1234
1610
  (dt) => {
1235
1611
  advanceAnimationTweens(dt);
1236
1612
  advanceClipPlayback(dt);
1237
1613
  },
1238
1614
  [advanceAnimationTweens, advanceClipPlayback]
1239
1615
  );
1240
- const step = (0, import_react2.useCallback)(
1241
- (dt) => {
1616
+ const flushStagedInputs = useCallback(() => {
1617
+ if (stagedInputsRef.current.size === 0) {
1618
+ return;
1619
+ }
1620
+ stagedInputsRef.current.forEach(({ value, shape }, path) => {
1621
+ orchestratorSetInput(path, value, shape);
1622
+ });
1623
+ stagedInputsRef.current.clear();
1624
+ }, [orchestratorSetInput]);
1625
+ const step = useCallback(
1626
+ (dt, opts) => {
1627
+ if (dt > 0 && Number.isFinite(dt)) {
1628
+ const prev = avgStepDtRef.current ?? dt;
1629
+ const alpha = 0.1;
1630
+ avgStepDtRef.current = prev * (1 - alpha) + dt * alpha;
1631
+ }
1242
1632
  advanceAnimations(dt);
1243
- stepRuntime(dt);
1633
+ flushStagedInputs();
1634
+ if (driveOrchestratorRef.current || opts?.forceRuntime) {
1635
+ stepRuntime(dt);
1636
+ }
1244
1637
  },
1245
- [advanceAnimations, stepRuntime]
1638
+ [advanceAnimations, flushStagedInputs, stepRuntime]
1246
1639
  );
1247
- (0, import_react2.useEffect)(() => {
1640
+ useEffect(() => {
1641
+ if (loopMode !== "active") {
1642
+ return;
1643
+ }
1644
+ let rafId = null;
1645
+ let lastTime = null;
1646
+ const tick = (timestamp) => {
1647
+ if (loopModeRef.current !== "active") {
1648
+ return;
1649
+ }
1650
+ if (lastTime == null) {
1651
+ lastTime = timestamp;
1652
+ }
1653
+ const dt = Math.max(0, (timestamp - lastTime) / 1e3);
1654
+ lastTime = timestamp;
1655
+ step(dt || 0);
1656
+ requestLoopMode(computeDesiredLoopMode());
1657
+ rafId = requestAnimationFrame(tick);
1658
+ };
1659
+ rafId = requestAnimationFrame(tick);
1248
1660
  return () => {
1249
- if (rafHandleRef.current !== null) {
1250
- cancelAnimationFrame(rafHandleRef.current);
1251
- rafHandleRef.current = null;
1661
+ if (rafId !== null) {
1662
+ cancelAnimationFrame(rafId);
1663
+ }
1664
+ };
1665
+ }, [loopMode, computeDesiredLoopMode, requestLoopMode, step]);
1666
+ useEffect(() => {
1667
+ if (loopMode !== "idle-visible" && loopMode !== "idle-hidden") {
1668
+ return;
1669
+ }
1670
+ if (typeof window === "undefined") {
1671
+ return;
1672
+ }
1673
+ const fps = loopMode === "idle-visible" ? VISIBLE_IDLE_FPS : HIDDEN_IDLE_FPS;
1674
+ if (fps <= 0) {
1675
+ return;
1676
+ }
1677
+ let lastTime = now();
1678
+ const interval = 1e3 / fps;
1679
+ const tick = () => {
1680
+ if (loopModeRef.current !== "idle-visible" && loopModeRef.current !== "idle-hidden") {
1681
+ return;
1252
1682
  }
1253
- lastFrameTimeRef.current = null;
1683
+ const current = now();
1684
+ const dt = Math.max(0, (current - lastTime) / 1e3);
1685
+ lastTime = current;
1686
+ step(dt || 0);
1687
+ requestLoopMode(computeDesiredLoopMode());
1688
+ };
1689
+ const intervalId = window.setInterval(tick, interval);
1690
+ return () => {
1691
+ window.clearInterval(intervalId);
1692
+ };
1693
+ }, [loopMode, computeDesiredLoopMode, requestLoopMode, step]);
1694
+ useEffect(() => {
1695
+ return () => {
1254
1696
  animationTweensRef.current.clear();
1255
1697
  clipPlaybackRef.current.clear();
1256
1698
  };
1257
1699
  }, []);
1258
- const contextValue = (0, import_react2.useMemo)(
1700
+ useEffect(() => {
1701
+ if (typeof window === "undefined") {
1702
+ return;
1703
+ }
1704
+ const id = window.setInterval(() => {
1705
+ const avg = avgStepDtRef.current;
1706
+ const stepHz = avg && Number.isFinite(avg) && avg > 0 ? 1 / avg : void 0;
1707
+ reportStatus(
1708
+ (prev) => prev.stepHz === stepHz ? prev : { ...prev, stepHz }
1709
+ );
1710
+ }, 500);
1711
+ return () => window.clearInterval(id);
1712
+ }, [reportStatus]);
1713
+ const contextValue = useMemo(
1259
1714
  () => ({
1260
1715
  ...status,
1261
1716
  assetBundle,
@@ -1268,7 +1723,8 @@ function VizijRuntimeProviderInner({
1268
1723
  playAnimation,
1269
1724
  stopAnimation,
1270
1725
  step,
1271
- advanceAnimations
1726
+ advanceAnimations,
1727
+ inputConstraints
1272
1728
  }),
1273
1729
  [
1274
1730
  status,
@@ -1282,20 +1738,21 @@ function VizijRuntimeProviderInner({
1282
1738
  playAnimation,
1283
1739
  stopAnimation,
1284
1740
  step,
1285
- advanceAnimations
1741
+ advanceAnimations,
1742
+ inputConstraints
1286
1743
  ]
1287
1744
  );
1288
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(VizijRuntimeContext.Provider, { value: contextValue, children });
1745
+ return /* @__PURE__ */ jsx(VizijRuntimeContext.Provider, { value: contextValue, children });
1289
1746
  }
1290
1747
 
1291
1748
  // src/VizijRuntimeFace.tsx
1292
- var import_react4 = require("react");
1293
- var import_render2 = require("@vizij/render");
1749
+ import { memo } from "react";
1750
+ import { Vizij } from "@vizij/render";
1294
1751
 
1295
1752
  // src/hooks/useVizijRuntime.ts
1296
- var import_react3 = require("react");
1753
+ import { useContext as useContext2 } from "react";
1297
1754
  function useVizijRuntime() {
1298
- const ctx = (0, import_react3.useContext)(VizijRuntimeContext);
1755
+ const ctx = useContext2(VizijRuntimeContext);
1299
1756
  if (!ctx) {
1300
1757
  throw new Error(
1301
1758
  "useVizijRuntime must be used within a VizijRuntimeProvider."
@@ -1305,7 +1762,7 @@ function useVizijRuntime() {
1305
1762
  }
1306
1763
 
1307
1764
  // src/VizijRuntimeFace.tsx
1308
- var import_jsx_runtime2 = require("react/jsx-runtime");
1765
+ import { jsx as jsx2 } from "react/jsx-runtime";
1309
1766
  function VizijRuntimeFaceInner({
1310
1767
  namespaceOverride,
1311
1768
  ...props
@@ -1314,8 +1771,8 @@ function VizijRuntimeFaceInner({
1314
1771
  if (!rootId) {
1315
1772
  return null;
1316
1773
  }
1317
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1318
- import_render2.Vizij,
1774
+ return /* @__PURE__ */ jsx2(
1775
+ Vizij,
1319
1776
  {
1320
1777
  ...props,
1321
1778
  rootId,
@@ -1323,17 +1780,19 @@ function VizijRuntimeFaceInner({
1323
1780
  }
1324
1781
  );
1325
1782
  }
1326
- var VizijRuntimeFace = (0, import_react4.memo)(VizijRuntimeFaceInner);
1783
+ var VizijRuntimeFace = memo(VizijRuntimeFaceInner);
1327
1784
 
1328
1785
  // src/hooks/useVizijOutputs.ts
1329
- var import_render3 = require("@vizij/render");
1330
- var import_utils = require("@vizij/utils");
1786
+ import {
1787
+ useVizijStore
1788
+ } from "@vizij/render";
1789
+ import { getLookup } from "@vizij/utils";
1331
1790
  function useVizijOutputs(paths) {
1332
1791
  const { namespace } = useVizijRuntime();
1333
- return (0, import_render3.useVizijStore)((state) => {
1792
+ return useVizijStore((state) => {
1334
1793
  const result = {};
1335
1794
  paths.forEach((path) => {
1336
- const lookup = (0, import_utils.getLookup)(namespace, path);
1795
+ const lookup = getLookup(namespace, path);
1337
1796
  result[path] = state.values.get(lookup);
1338
1797
  });
1339
1798
  return result;
@@ -1341,15 +1800,17 @@ function useVizijOutputs(paths) {
1341
1800
  }
1342
1801
 
1343
1802
  // src/hooks/useRigInput.ts
1344
- var import_react5 = require("react");
1345
- var import_render4 = require("@vizij/render");
1346
- var import_utils2 = require("@vizij/utils");
1803
+ import { useCallback as useCallback2 } from "react";
1804
+ import {
1805
+ useVizijStore as useVizijStore2
1806
+ } from "@vizij/render";
1807
+ import { getLookup as getLookup2 } from "@vizij/utils";
1347
1808
  function useRigInput(path) {
1348
1809
  const { namespace, setInput } = useVizijRuntime();
1349
- const value = (0, import_render4.useVizijStore)((state) => {
1350
- return state.values.get((0, import_utils2.getLookup)(namespace, path));
1810
+ const value = useVizijStore2((state) => {
1811
+ return state.values.get(getLookup2(namespace, path));
1351
1812
  });
1352
- const setter = (0, import_react5.useCallback)(
1813
+ const setter = useCallback2(
1353
1814
  (next, shape) => {
1354
1815
  setInput(path, next, shape);
1355
1816
  },
@@ -1357,11 +1818,10 @@ function useRigInput(path) {
1357
1818
  );
1358
1819
  return [value, setter];
1359
1820
  }
1360
- // Annotate the CommonJS export names for ESM import in node:
1361
- 0 && (module.exports = {
1821
+ export {
1362
1822
  VizijRuntimeFace,
1363
1823
  VizijRuntimeProvider,
1364
1824
  useRigInput,
1365
1825
  useVizijOutputs,
1366
1826
  useVizijRuntime
1367
- });
1827
+ };