@vizij/runtime-react 0.4.1 → 1.0.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/README.md +86 -445
- package/dist/index.cjs +409 -5273
- package/dist/index.d.cts +120 -430
- package/dist/index.d.ts +120 -430
- package/dist/index.js +409 -5269
- package/package.json +13 -15
package/README.md
CHANGED
|
@@ -1,470 +1,110 @@
|
|
|
1
1
|
# @vizij/runtime-react
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
One Vizij face's lifecycle in React, over
|
|
4
|
+
[`@vizij/runtime`](https://www.npmjs.com/package/@vizij/runtime) — the Bevy
|
|
5
|
+
view and each face's Arora device in one wasm module. The provider loads the
|
|
6
|
+
face and hands its children the face's `Runtime`, unwrapped: every device
|
|
7
|
+
operation is called on it. The package adds only what React needs: the
|
|
8
|
+
provider owning the face's one change drain, hooks that subscribe a component
|
|
9
|
+
to keys, and a JS tween.
|
|
4
10
|
|
|
5
|
-
The
|
|
6
|
-
|
|
7
|
-
> Status: experimental. Public API is still moving with the runtime/export pipeline.
|
|
8
|
-
|
|
9
|
-
## What It Handles
|
|
10
|
-
|
|
11
|
-
- load a face from a GLB URL, a `Blob`, or an already loaded world
|
|
12
|
-
- extract the embedded `VIZIJ_bundle` payload when present
|
|
13
|
-
- merge explicit `rig`, `pose`, `animations`, and `programs` with discovered bundle content
|
|
14
|
-
- compose the registered graphs into the device's behavior and drive its step loop
|
|
15
|
-
- expose runtime status, controls, diagnostics, and update hooks through React context
|
|
16
|
-
- render the resolved face with `VizijRuntimeFace`
|
|
11
|
+
The design, with the justification of each export and the mapping of every
|
|
12
|
+
0.4 call, is [`DESIGN-1.0.md`](./DESIGN-1.0.md).
|
|
17
13
|
|
|
18
14
|
## Installation
|
|
19
15
|
|
|
20
16
|
```bash
|
|
21
|
-
pnpm add @vizij/runtime-react
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
If your app also imports lower-level renderer or engine APIs directly, install those packages too:
|
|
25
|
-
|
|
26
|
-
```bash
|
|
27
|
-
pnpm add @vizij/render @vizij/runtime
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
`@vizij/runtime-react`, `@vizij/render`, and `@vizij/runtime` should stay on the same workspace/release line.
|
|
31
|
-
|
|
32
|
-
### Bundler Notes
|
|
33
|
-
|
|
34
|
-
The runtime depends on Vizij wasm packages transitively. Your bundler needs to emit `.wasm` assets and allow async wasm loading.
|
|
35
|
-
|
|
36
|
-
Example `next.config.js`:
|
|
37
|
-
|
|
38
|
-
```js
|
|
39
|
-
module.exports = {
|
|
40
|
-
webpack: (config) => {
|
|
41
|
-
config.experiments = {
|
|
42
|
-
...(config.experiments ?? {}),
|
|
43
|
-
asyncWebAssembly: true,
|
|
44
|
-
};
|
|
45
|
-
config.module.rules.push({
|
|
46
|
-
test: /\.wasm$/,
|
|
47
|
-
type: "asset/resource",
|
|
48
|
-
});
|
|
49
|
-
return config;
|
|
50
|
-
},
|
|
51
|
-
};
|
|
17
|
+
pnpm add @vizij/runtime-react @vizij/render @vizij/runtime react
|
|
52
18
|
```
|
|
53
19
|
|
|
54
|
-
|
|
20
|
+
`@vizij/runtime` and `@vizij/render` are peer dependencies, so that a page
|
|
21
|
+
resolves one copy of each: one wasm module, one page canvas.
|
|
55
22
|
|
|
56
|
-
##
|
|
23
|
+
## Usage
|
|
57
24
|
|
|
58
25
|
```tsx
|
|
26
|
+
import { VizijCanvas } from "@vizij/render";
|
|
59
27
|
import {
|
|
60
|
-
VizijRuntimeFace,
|
|
61
28
|
VizijRuntimeProvider,
|
|
29
|
+
VizijRuntimeFace,
|
|
30
|
+
useAnimateValue,
|
|
62
31
|
useVizijRuntime,
|
|
63
|
-
type VizijAssetBundle,
|
|
64
32
|
} from "@vizij/runtime-react";
|
|
65
33
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
const assetBundle: VizijAssetBundle = {
|
|
69
|
-
namespace: "demo-face",
|
|
70
|
-
glb: {
|
|
71
|
-
kind: "url",
|
|
72
|
-
src: faceAssetUrl,
|
|
73
|
-
aggressiveImport: true,
|
|
74
|
-
},
|
|
75
|
-
pose: {
|
|
76
|
-
stageNeutralFilter: (_id, path) => !path.includes("/color/"),
|
|
77
|
-
},
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
export function App() {
|
|
34
|
+
export function Page() {
|
|
81
35
|
return (
|
|
82
|
-
|
|
83
|
-
<
|
|
84
|
-
|
|
36
|
+
<>
|
|
37
|
+
<VizijCanvas />
|
|
38
|
+
<VizijRuntimeProvider glb="/faces/Quori_Current_Extended.glb">
|
|
39
|
+
<VizijRuntimeFace style={{ width: 480, height: 360 }} />
|
|
40
|
+
<Controls />
|
|
41
|
+
</VizijRuntimeProvider>
|
|
42
|
+
</>
|
|
85
43
|
);
|
|
86
44
|
}
|
|
87
45
|
|
|
88
|
-
function
|
|
89
|
-
const {
|
|
90
|
-
|
|
91
|
-
if (
|
|
92
|
-
if (error) return <p>Runtime failed: {error.message}</p>;
|
|
93
|
-
if (!ready) return <p>Preparing runtime…</p>;
|
|
94
|
-
|
|
46
|
+
function Controls() {
|
|
47
|
+
const { runtime, status } = useVizijRuntime();
|
|
48
|
+
const { animateValue } = useAnimateValue();
|
|
49
|
+
if (!runtime || status !== "ready") return null;
|
|
95
50
|
return (
|
|
96
|
-
|
|
97
|
-
<button
|
|
98
|
-
|
|
99
|
-
|
|
51
|
+
<>
|
|
52
|
+
<button
|
|
53
|
+
onClick={() =>
|
|
54
|
+
animateValue(runtime.path("standard/vizij/expression/happy"), 1)
|
|
55
|
+
}
|
|
56
|
+
>
|
|
57
|
+
happy
|
|
58
|
+
</button>
|
|
59
|
+
<button onClick={() => runtime.invoke("say", { text: "Hello" })}>
|
|
60
|
+
say hello
|
|
61
|
+
</button>
|
|
62
|
+
</>
|
|
100
63
|
);
|
|
101
64
|
}
|
|
102
65
|
```
|
|
103
66
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
`
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
It is an `ExternalFunction` node that steps the **animation module**
|
|
146
|
-
([`@vizij/animation-module`](https://www.npmjs.com/package/@vizij/animation-module))
|
|
147
|
-
every device tick off the golden `arora/dt`. Clips register into the module
|
|
148
|
-
as data (through its call surface), the module samples them **inside the
|
|
149
|
-
device**, and its per-track outputs are routed on to the rig-input store
|
|
150
|
-
paths (VIZ-61 Stage B — the JS clip pipeline no longer samples clips).
|
|
151
|
-
|
|
152
|
-
Sources are namespaced by id (`source::node`) so nodes can't collide;
|
|
153
|
-
**store paths are deliberately shared** — that is the cross-source contract.
|
|
154
|
-
|
|
155
|
-
**How a change reaches the device.** When the composition changes — a program
|
|
156
|
-
starts or stops, the rig or a source is swapped — the runtime diffs the old and
|
|
157
|
-
new composed specs and **patches the running graph in place** through the
|
|
158
|
-
behavior interpreter's `apply(GraphDiff)`: only the nodes and edges the change
|
|
159
|
-
touched are re-installed (VIZ-79). A structurally empty diff is a no-op; if a
|
|
160
|
-
patch ever fails the runtime falls back to a whole-spec `loadGraph`. The Vizij
|
|
161
|
-
node graph is a full behavior interpreter — it accepts both an incremental
|
|
162
|
-
`apply(GraphDiff)` and a whole `load` (see
|
|
163
|
-
[`vizij-arora-behavior/docs/node-graph.md`](https://github.com/vizij-ai/vizij-rs/blob/main/crates/interop/vizij-arora-behavior/docs/node-graph.md)).
|
|
164
|
-
The device, its store, and its loaded module set are untouched across the
|
|
165
|
-
change; the device is rebuilt only when the **module set** itself changes.
|
|
166
|
-
Stateful nodes keep their state too — springs, dampers, and the graph clock
|
|
167
|
-
carry over, and a node the edit didn't touch keeps its integration state, so a
|
|
168
|
-
program starting or stopping (or an unrelated edit) does not reset it.
|
|
169
|
-
|
|
170
|
-
### Animations and the device
|
|
171
|
-
|
|
172
|
-
Playback lives inside the device, end to end. Clips load into the animation
|
|
173
|
-
module with their **final store keys resolved at load** (the rig routing the
|
|
174
|
-
JS pipeline used to apply per tick), the composed animations source steps
|
|
175
|
-
the module off the golden `arora/dt`, and a path-less `output` node applies
|
|
176
|
-
the sampled batch onto those keys — no JS touches the per-tick path.
|
|
177
|
-
|
|
178
|
-
Transport rides the module (0.2.0): `play`/`pause` resume and hold the
|
|
179
|
-
player, `stop` resets its playhead (the next tick emits the clip's t=0
|
|
180
|
-
pose; `stopAnimation({ clearOutputs: false })` holds the pose instead),
|
|
181
|
-
`seekAnimation`, `setAnimationLoop(false)` (one-shot), and
|
|
182
|
-
`playAnimation({ speed, weight })` are real player commands.
|
|
183
|
-
`getAnimationState()` reads the module's `player_states` feedback — a live
|
|
184
|
-
playhead, duration, playing, speed — and `playAnimation()`'s promise
|
|
185
|
-
resolves when a non-looping clip reaches its end.
|
|
186
|
-
|
|
187
|
-
Remaining fidelity gap: authored `cubic` keyframes carry no explicit
|
|
188
|
-
handles in the stored form, so they sample the engine's default ease
|
|
189
|
-
(`linear`/`step` timing rides through as explicit bezier handles).
|
|
190
|
-
|
|
191
|
-
### Asset reloads vs graph re-registration
|
|
192
|
-
|
|
193
|
-
When you swap the `assetBundle` prop, the runtime decides whether it needs to reload assets or only re-register controllers. That behavior is controlled by `updateTier` and is also exposed as `resolveRuntimeUpdatePlan()`.
|
|
194
|
-
|
|
195
|
-
This matters for tooling apps like `vizij-authoring`, where graphs/animations can change without replacing the face asset itself.
|
|
196
|
-
|
|
197
|
-
## `VizijAssetBundle`
|
|
198
|
-
|
|
199
|
-
```ts
|
|
200
|
-
type VizijAssetBundle = {
|
|
201
|
-
namespace?: string;
|
|
202
|
-
faceId?: string;
|
|
203
|
-
glb: VizijGlbAsset;
|
|
204
|
-
rig?: VizijGraphAsset;
|
|
205
|
-
pose?: {
|
|
206
|
-
graph?: VizijGraphAsset;
|
|
207
|
-
config?: PoseRigConfig;
|
|
208
|
-
stageNeutralFilter?: (id: string, path: string) => boolean;
|
|
209
|
-
};
|
|
210
|
-
animations?: VizijAnimationAsset[];
|
|
211
|
-
programs?: VizijProgramAsset[];
|
|
212
|
-
initialInputs?: Record<string, ValueJSON>;
|
|
213
|
-
metadata?: Record<string, unknown>;
|
|
214
|
-
bundle?: VizijBundleExtension | null;
|
|
215
|
-
};
|
|
216
|
-
```
|
|
217
|
-
|
|
218
|
-
### `glb`
|
|
219
|
-
|
|
220
|
-
Required. One of:
|
|
221
|
-
|
|
222
|
-
- `{ kind: "url", src, aggressiveImport?, rootBounds? }`
|
|
223
|
-
- `{ kind: "blob", blob, aggressiveImport?, rootBounds? }`
|
|
224
|
-
- `{ kind: "world", world, animatables, bundle? }`
|
|
225
|
-
|
|
226
|
-
Use `kind: "world"` when your app already loaded the scene and wants runtime-react to wire only the engine/runtime layer.
|
|
227
|
-
|
|
228
|
-
### `rig`
|
|
229
|
-
|
|
230
|
-
Optional `VizijGraphAsset` for the main rig graph. When omitted, the runtime looks for a compatible graph in the embedded bundle.
|
|
231
|
-
|
|
232
|
-
### `pose`
|
|
233
|
-
|
|
234
|
-
Optional pose graph/config surface:
|
|
235
|
-
|
|
236
|
-
- `graph`: pose-driver graph override
|
|
237
|
-
- `config`: `PoseRigConfig` used by pose-aware UIs and pose-blending configuration
|
|
238
|
-
- `stageNeutralFilter`: lets you skip specific neutral writes, for example baked color channels
|
|
239
|
-
|
|
240
|
-
`PoseRigConfig.poseGroups` defines how subsets of poses are grouped for local blend behavior and wider composition. Group labels such as `viseme` or `emotion` are not runtime path segments. Runtime-facing pose writes still use canonical per-pose paths.
|
|
241
|
-
|
|
242
|
-
### `animations`
|
|
243
|
-
|
|
244
|
-
Optional authored clips. These merge with embedded bundle animations and extracted GLTF animation clips.
|
|
245
|
-
|
|
246
|
-
### `programs`
|
|
247
|
-
|
|
248
|
-
Optional procedural programs. These merge with embedded bundle `motiongraph` entries.
|
|
249
|
-
|
|
250
|
-
### `initialInputs`
|
|
251
|
-
|
|
252
|
-
Optional `ValueJSON` map staged before autostart/manual stepping.
|
|
253
|
-
|
|
254
|
-
### `metadata`
|
|
255
|
-
|
|
256
|
-
Arbitrary app metadata. The runtime keeps it attached to the resolved `assetBundle`.
|
|
257
|
-
|
|
258
|
-
### `bundle`
|
|
259
|
-
|
|
260
|
-
Optional pre-parsed `VizijBundleExtension`. Useful when you already decoded bundle metadata yourself.
|
|
261
|
-
|
|
262
|
-
## Provider Props
|
|
263
|
-
|
|
264
|
-
`VizijRuntimeProviderProps`:
|
|
265
|
-
|
|
266
|
-
- `assetBundle`: required runtime bundle
|
|
267
|
-
- `namespace`, `faceId`: override resolved ids without mutating the incoming bundle
|
|
268
|
-
- `updateTier`: `"auto"` (default), `"assets"`, or `"graphs"`
|
|
269
|
-
- `autoCreate`: load the engine wasm and boot the device automatically on mount
|
|
270
|
-
- `autostart`: start the runtime loop automatically after registration
|
|
271
|
-
- `driveRuntime`: whether this runtime instance should call `step()` during its loop
|
|
272
|
-
- `mergeStrategy`: forwarded to graph registration
|
|
273
|
-
- `transformOutputWrite(write)`: intercept or drop output writes before they hit the renderer store
|
|
274
|
-
- `onRegisterControllers(ids)`: receive registered graph/animation ids
|
|
275
|
-
- `onStatusChange(status)`: subscribe to runtime status changes
|
|
276
|
-
|
|
277
|
-
### Important runtime flags
|
|
278
|
-
|
|
279
|
-
- `autostart` controls whether the device begins stepping automatically once ready.
|
|
280
|
-
- `driveRuntime={false}` is useful for faces stepped manually or at a background cadence.
|
|
281
|
-
- `transformOutputWrite` is the hook to remap or suppress specific runtime outputs before they update renderer state.
|
|
282
|
-
|
|
283
|
-
## Runtime Context API
|
|
284
|
-
|
|
285
|
-
Use `useVizijRuntime()` inside the provider tree.
|
|
286
|
-
|
|
287
|
-
### Status and identity
|
|
288
|
-
|
|
289
|
-
- `loading`, `ready`
|
|
290
|
-
- `error`, `errors`
|
|
291
|
-
- `namespace`, `faceId`, `rootId`
|
|
292
|
-
- `controllers.graphs`, `controllers.anims`
|
|
293
|
-
- `outputPaths`
|
|
294
|
-
- `stepHz`
|
|
295
|
-
- `assetBundle`
|
|
296
|
-
- `inputConstraints`
|
|
297
|
-
|
|
298
|
-
`inputConstraints` is built from graph metadata and is the right source for slider defaults/ranges in tooling UIs.
|
|
299
|
-
|
|
300
|
-
### Input and renderer writes
|
|
301
|
-
|
|
302
|
-
- `setInput(path, value, shape?)`
|
|
303
|
-
- `getValueSnapshot(path)` — the current engine-store value of a path (reads your own writes)
|
|
304
|
-
- `setValue(id, namespace, value)`
|
|
305
|
-
- `stagePoseNeutral(force?)`
|
|
306
|
-
|
|
307
|
-
### Runtime graph updates
|
|
308
|
-
|
|
309
|
-
- `setGraphBundle(bundle, options?)`
|
|
310
|
-
|
|
311
|
-
`setGraphBundle()` lets you swap `rig`, `pose`, `animations`, and `programs` at runtime. This is the API that tooling apps use when the face asset stays the same but the authored runtime bundle changes.
|
|
312
|
-
|
|
313
|
-
### Value animation helpers
|
|
314
|
-
|
|
315
|
-
- `animateValue(path, target, options?)`
|
|
316
|
-
- `cancelAnimation(path)`
|
|
317
|
-
- `setAnimationActive(active)`
|
|
318
|
-
- `isAnimationActive()`
|
|
319
|
-
|
|
320
|
-
`animateValue()` is the simple way to tween a single rig input path with built-in easing.
|
|
321
|
-
|
|
322
|
-
### Clip transport
|
|
323
|
-
|
|
324
|
-
- `playAnimation(id, options?)`
|
|
325
|
-
- `pauseAnimation(id)`
|
|
326
|
-
- `seekAnimation(id, timeSeconds)`
|
|
327
|
-
- `setAnimationLoop(id, enabled)`
|
|
328
|
-
- `getAnimationState(id)`
|
|
329
|
-
- `stopAnimation(id, options?)`
|
|
330
|
-
|
|
331
|
-
### Program transport
|
|
332
|
-
|
|
333
|
-
- `playProgram(id)`
|
|
334
|
-
- `pauseProgram(id)`
|
|
335
|
-
- `stopProgram(id, options?)`
|
|
336
|
-
- `getProgramState(id)`
|
|
337
|
-
|
|
338
|
-
### Manual stepping
|
|
339
|
-
|
|
340
|
-
- `step(dt, opts?)`
|
|
341
|
-
- `advanceAnimations(dt)`
|
|
342
|
-
|
|
343
|
-
Use manual stepping when you do not want the provider to own the runtime loop or when hidden/shared faces need low-frequency background stepping.
|
|
344
|
-
|
|
345
|
-
### Driver registration
|
|
346
|
-
|
|
347
|
-
- `registerInputDriver(id, factory)`
|
|
348
|
-
|
|
349
|
-
Custom input drivers receive:
|
|
350
|
-
|
|
351
|
-
- `setInput(path, value, shape?)`
|
|
352
|
-
- `setRendererValue(id, namespace, valueOrUpdater)`
|
|
353
|
-
- `namespace`
|
|
354
|
-
- `faceId`
|
|
355
|
-
|
|
356
|
-
Return `{ start, stop, dispose }`.
|
|
357
|
-
|
|
358
|
-
## Hooks
|
|
359
|
-
|
|
360
|
-
### `useVizijRuntime()`
|
|
361
|
-
|
|
362
|
-
Throws when used outside the provider.
|
|
363
|
-
|
|
364
|
-
### `useOptionalVizijRuntime()`
|
|
365
|
-
|
|
366
|
-
Returns `null` when no provider is present. This is useful for shared components that can operate with or without a runtime.
|
|
367
|
-
|
|
368
|
-
### `useRigInput(path)`
|
|
369
|
-
|
|
370
|
-
Returns `[value, setValue]` for a single runtime input path. The setter writes into the device's store, while the value mirrors the renderer store.
|
|
371
|
-
|
|
372
|
-
### `useVizijOutputs(paths)`
|
|
373
|
-
|
|
374
|
-
Subscribes to renderer output values for the current namespace and returns a path-to-value map.
|
|
375
|
-
|
|
376
|
-
## Components
|
|
377
|
-
|
|
378
|
-
### `VizijRuntimeFace`
|
|
379
|
-
|
|
380
|
-
`VizijRuntimeFace` renders the resolved face using the current runtime namespace and root id.
|
|
381
|
-
|
|
382
|
-
It accepts normal `Vizij` renderer props except `rootId` and `namespace`, which are owned by the runtime. It also supports `namespaceOverride` when you need to inspect another namespace while keeping the current runtime context.
|
|
383
|
-
|
|
384
|
-
## Exported Utilities
|
|
385
|
-
|
|
386
|
-
### Pose path helpers
|
|
387
|
-
|
|
388
|
-
- `buildRigInputPath(faceId, path)`
|
|
389
|
-
- `buildPoseWeightInputPathSegment(poseId)`
|
|
390
|
-
- `buildPoseWeightRelativePath(poseId)`
|
|
391
|
-
- `buildPoseWeightPathMap(poses, faceId)`
|
|
392
|
-
|
|
393
|
-
These are the canonical helpers for pose-weight paths. The current runtime/export contract is:
|
|
394
|
-
|
|
395
|
-
```text
|
|
396
|
-
rig/{faceId}/poses/{poseId}.weight
|
|
397
|
-
```
|
|
398
|
-
|
|
399
|
-
That contract does not change when the pose belongs to a group labeled `viseme` or `emotion`. Group membership exists to control blend behavior, not to build a different input path family.
|
|
400
|
-
|
|
401
|
-
### Pose semantics helpers
|
|
402
|
-
|
|
403
|
-
- `normalizePoseSemanticKey()`
|
|
404
|
-
- `getPoseSemanticKey()`
|
|
405
|
-
- `resolvePoseMembership()`
|
|
406
|
-
- `resolvePoseSemantics()`
|
|
407
|
-
- `filterPosesBySemanticKind()`
|
|
408
|
-
- `buildSemanticPoseWeightPathMap()`
|
|
409
|
-
- constants such as `VISEME_POSE_KEYS`, `EMOTION_POSE_KEYS`, and `EXPRESSIVE_EMOTION_POSE_KEYS`
|
|
410
|
-
|
|
411
|
-
These helpers are convenience utilities for example apps that need to order or match current poses without hard-coding face-specific names. Pose groups themselves still exist to control blending/composition, and these helpers do not introduce paths such as `rig/{faceId}/visemes/...`.
|
|
412
|
-
|
|
413
|
-
### Automatic input-path detection and pose-control bridging
|
|
414
|
-
|
|
415
|
-
Runtime-react also auto-detects the actual rig input paths it needs from the registered rig graph:
|
|
416
|
-
|
|
417
|
-
1. `collectInputPathMap()` scans `input` nodes and records aliases for authored channel ids.
|
|
418
|
-
2. When both direct rig inputs and `pose/control` inputs exist, the direct rig input path is preferred for bare channel ids.
|
|
419
|
-
3. Compiled pose graphs still emit internal pose-control outputs on:
|
|
420
|
-
|
|
421
|
-
```text
|
|
422
|
-
rig/{faceId}/pose/control/{inputId}
|
|
423
|
-
```
|
|
424
|
-
|
|
425
|
-
4. The provider bridges those internal outputs back onto the detected rig input path when possible and falls back to the native `pose/control` path only when necessary.
|
|
426
|
-
|
|
427
|
-
This is why dependent apps should prefer runtime-react helpers and resolved metadata over hard-coded face-specific input paths.
|
|
428
|
-
|
|
429
|
-
### Face control helpers
|
|
430
|
-
|
|
431
|
-
- `resolveFaceControls(assetBundle, runtimeFaceId?, inputConstraints?)`
|
|
432
|
-
- `mapNormalizedControlValue(control, value)`
|
|
433
|
-
- `mapUnitControlValue(control, value)`
|
|
434
|
-
|
|
435
|
-
Use these when you want to build gaze/blink/eyelid controls from runtime metadata rather than hard-coded paths.
|
|
436
|
-
|
|
437
|
-
### Update policy helpers
|
|
438
|
-
|
|
439
|
-
- `resolveRuntimeUpdatePlan(previous, next, tier)`
|
|
440
|
-
|
|
441
|
-
This is the same policy used internally by the provider to decide between:
|
|
442
|
-
|
|
443
|
-
- reloading assets
|
|
444
|
-
- only re-registering graphs/animations/programs
|
|
445
|
-
- doing nothing
|
|
446
|
-
|
|
447
|
-
## Common Patterns
|
|
448
|
-
|
|
449
|
-
### Multiple faces in one app
|
|
450
|
-
|
|
451
|
-
Mount one `VizijRuntimeProvider` per face; each owns its device. Give hidden or non-driver faces `driveRuntime={false}` and step them at a background cadence (see `vizij-showcase`'s `HiddenStepController`).
|
|
452
|
-
|
|
453
|
-
### Bundle-first player
|
|
454
|
-
|
|
455
|
-
See [`apps/demo-vizij-player`](../../../apps/demo-vizij-player/README.md) for the reference “one bundled GLB in, runtime UI out” flow.
|
|
456
|
-
|
|
457
|
-
### Fullscreen face tutorials
|
|
458
|
-
|
|
459
|
-
See:
|
|
460
|
-
|
|
461
|
-
- [`apps/tutorial-fullscreen-face/tutorial.md`](../../../apps/tutorial-fullscreen-face/tutorial.md)
|
|
462
|
-
- [`apps/tutorial-agent-face/tutorial.md`](../../../apps/tutorial-agent-face/tutorial.md)
|
|
463
|
-
- [`apps/vizij-standalone`](../../../apps/vizij-standalone/README.md) — the face page with a voice agent, on `@vizij/runtime` directly
|
|
464
|
-
|
|
465
|
-
### Runtime-truthful authoring
|
|
466
|
-
|
|
467
|
-
See [`apps/vizij-authoring/README.md`](../../../apps/vizij-authoring/README.md) for the `setGraphBundle()` and `transformOutputWrite()` tooling workflow.
|
|
67
|
+
A face is driven through its Arora API — the methods it declares (`invoke`)
|
|
68
|
+
and its store (`setValue`, `writeValues`) — on the keys of the Vizij face
|
|
69
|
+
standard, `runtime.path("standard/vizij/…")`. See
|
|
70
|
+
[`DESIGN-1.0.md`](./DESIGN-1.0.md) section 4 for every intent and its key or
|
|
71
|
+
method, and for the keys a composed mapping or a running program writes over.
|
|
72
|
+
|
|
73
|
+
## API
|
|
74
|
+
|
|
75
|
+
- **`<VizijRuntimeProvider glb vizijId? options? run?>`** — fetches the GLB
|
|
76
|
+
(a URL, a `Blob` or bytes), reads it with `describe`, waits for the page's
|
|
77
|
+
`<VizijCanvas>`, loads the face with `loadVizij(vizijId, bytes, options)` and
|
|
78
|
+
steps its device (`run`, default `true`): once per animation frame while the
|
|
79
|
+
page is visible, on a timer while it is hidden; `false` leaves the page to
|
|
80
|
+
call `runtime.step(dtMs)`. A new `glb` reloads the face in place; unmounting
|
|
81
|
+
unloads it. `options` are `loadVizij`'s, read at each load. It reports the
|
|
82
|
+
face's stages to `@vizij/render`'s `setVizijProgress` — the GLB's bytes as
|
|
83
|
+
they arrive, `loadVizij`, the first frame — which the face's slot shows in
|
|
84
|
+
its loading view.
|
|
85
|
+
- **`useVizijRuntime()`** — the face: `vizijId`, `status` (`loading`,
|
|
86
|
+
`ready`, `error`), `error`, `behaviorError` (the device's standing behavior
|
|
87
|
+
error), `runtime` (the `Runtime`) and `description` (`describe(glb)`: poses,
|
|
88
|
+
rig inputs, programs, animations, metadata, bounds).
|
|
89
|
+
`useOptionalVizijRuntime()` returns `null` outside a provider.
|
|
90
|
+
- **`useVizijValues(paths)`** — the current values of `paths`, re-rendering on
|
|
91
|
+
change.
|
|
92
|
+
- **`useVizijChanges(listener)`** — each batch of store changes, at most once
|
|
93
|
+
per animation frame, the first batch after a (re)load being the whole store;
|
|
94
|
+
for consumers that must not re-render per change.
|
|
95
|
+
- **`useAnimateValue()`** — `animateValue(path, target, options)` and
|
|
96
|
+
`cancelAnimation(path)`: a JS tween, one `setValue` per animation frame, in
|
|
97
|
+
the numeric kind the key holds or rests at; `options` are `duration`
|
|
98
|
+
(seconds) and `easing`. A newer tween on a path replaces the running one;
|
|
99
|
+
unmounting cancels a component's tweens.
|
|
100
|
+
- **`<VizijRuntimeFace …>`** — `@vizij/render`'s `<Vizij>` slot bound to the
|
|
101
|
+
provider's face.
|
|
102
|
+
|
|
103
|
+
The provider is the only caller of `runtime.drainChanges()`, whose cursor is
|
|
104
|
+
one per `Runtime`; read changes through the two hooks. It drains once per
|
|
105
|
+
animation frame while one of them listens, and not at all otherwise: the
|
|
106
|
+
runtime holds what is not drained as the latest value per key, so the next
|
|
107
|
+
batch is the net change since the previous drain.
|
|
468
108
|
|
|
469
109
|
## Development
|
|
470
110
|
|
|
@@ -473,10 +113,11 @@ pnpm --filter "@vizij/runtime-react" build
|
|
|
473
113
|
pnpm --filter "@vizij/runtime-react" test
|
|
474
114
|
pnpm --filter "@vizij/runtime-react" typecheck
|
|
475
115
|
pnpm --filter "@vizij/runtime-react" lint
|
|
476
|
-
pnpm --filter "@vizij/runtime-react" dev
|
|
477
116
|
```
|
|
478
117
|
|
|
479
|
-
|
|
118
|
+
The tests (Vitest, jsdom) run the provider and the hooks against stand-ins of
|
|
119
|
+
`@vizij/runtime` and `@vizij/render`.
|
|
480
120
|
|
|
481
|
-
|
|
482
|
-
|
|
121
|
+
A new export that wraps or duplicates what Arora or `@vizij/runtime` provides
|
|
122
|
+
needs its justification written in [`DESIGN-1.0.md`](./DESIGN-1.0.md) section
|
|
123
|
+
5.1; a capability the apps miss is added to `@vizij/runtime` instead.
|