@lovelace_lol/polymer 0.0.7 → 0.0.8

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.
Files changed (95) hide show
  1. package/README.md +434 -12
  2. package/dist/index.d.ts +1945 -244
  3. package/dist/index.js +1686 -1017
  4. package/dist/vision/face-worker.js +296 -0
  5. package/dist/vision/face_landmarker.task +0 -0
  6. package/dist/vision/manifest.edn +1 -0
  7. package/dist/vision/vision_bundle.js +2 -0
  8. package/dist/vision/wasm/vision_wasm_internal.js +8841 -0
  9. package/dist/vision/wasm/vision_wasm_internal.wasm +0 -0
  10. package/dist/vision/wasm/vision_wasm_module_internal.js +8837 -0
  11. package/dist/vision/wasm/vision_wasm_module_internal.wasm +0 -0
  12. package/dist/vision/wasm/vision_wasm_nosimd_internal.js +8832 -0
  13. package/dist/vision/wasm/vision_wasm_nosimd_internal.wasm +0 -0
  14. package/dist/worker/agency-worker.js +1276 -962
  15. package/dist/worker/manifest.edn +1 -1
  16. package/package.json +33 -20
  17. package/dist/test.cjs +0 -1519
  18. package/shadow-cljs.edn +0 -173
  19. package/src/polymer/animation/agency.cljs +0 -84
  20. package/src/polymer/animation/domain.cljs +0 -110
  21. package/src/polymer/animation/planner.cljs +0 -88
  22. package/src/polymer/animation/runtime.cljs +0 -137
  23. package/src/polymer/animation/scheduler.cljs +0 -178
  24. package/src/polymer/animation/service.cljs +0 -981
  25. package/src/polymer/animation/state.cljs +0 -112
  26. package/src/polymer/blink/agency.cljs +0 -131
  27. package/src/polymer/blink/planner.cljs +0 -115
  28. package/src/polymer/blink/scheduler.cljs +0 -90
  29. package/src/polymer/blink/snippet.cljs +0 -50
  30. package/src/polymer/blink/state.cljs +0 -102
  31. package/src/polymer/camera_context/agency.cljs +0 -132
  32. package/src/polymer/camera_context/domain.cljs +0 -186
  33. package/src/polymer/camera_context/planner.cljs +0 -50
  34. package/src/polymer/camera_context/scheduler.cljs +0 -113
  35. package/src/polymer/camera_context/state.cljs +0 -121
  36. package/src/polymer/character.cljs +0 -403
  37. package/src/polymer/conversation/agency.cljs +0 -83
  38. package/src/polymer/conversation/domain.cljs +0 -35
  39. package/src/polymer/conversation/effector.cljs +0 -130
  40. package/src/polymer/conversation/planner.cljs +0 -178
  41. package/src/polymer/conversation/scheduler.cljs +0 -202
  42. package/src/polymer/conversation/service.cljs +0 -329
  43. package/src/polymer/conversation/state.cljs +0 -186
  44. package/src/polymer/core.cljs +0 -172
  45. package/src/polymer/eye_head/agency.cljs +0 -75
  46. package/src/polymer/eye_head/domain.cljs +0 -133
  47. package/src/polymer/eye_head/goap.cljs +0 -106
  48. package/src/polymer/eye_head/scheduler.cljs +0 -176
  49. package/src/polymer/eye_head/service.cljs +0 -259
  50. package/src/polymer/eye_head/snippet.cljs +0 -92
  51. package/src/polymer/eye_head/state.cljs +0 -167
  52. package/src/polymer/gaze/agency.cljs +0 -85
  53. package/src/polymer/gaze/domain.cljs +0 -211
  54. package/src/polymer/gaze/goap.cljs +0 -208
  55. package/src/polymer/gaze/scheduler.cljs +0 -184
  56. package/src/polymer/gaze/state.cljs +0 -202
  57. package/src/polymer/gesture/agency.cljs +0 -185
  58. package/src/polymer/gesture/goap.cljs +0 -461
  59. package/src/polymer/gesture/scheduler.cljs +0 -122
  60. package/src/polymer/gesture/snippet.cljs +0 -198
  61. package/src/polymer/gesture/state.cljs +0 -273
  62. package/src/polymer/lipsync/agency.cljs +0 -593
  63. package/src/polymer/lipsync/articulation/jaw.cljs +0 -258
  64. package/src/polymer/lipsync/articulation/snippet.cljs +0 -605
  65. package/src/polymer/lipsync/articulation/tongue.cljs +0 -418
  66. package/src/polymer/lipsync/articulation/visemes.cljs +0 -429
  67. package/src/polymer/lipsync/goap.cljs +0 -132
  68. package/src/polymer/lipsync/pronunciation/effector.cljs +0 -51
  69. package/src/polymer/lipsync/scheduler.cljs +0 -302
  70. package/src/polymer/lipsync/state.cljs +0 -202
  71. package/src/polymer/nlp/phonemes.cljs +0 -164
  72. package/src/polymer/prosodic/agency.cljs +0 -196
  73. package/src/polymer/prosodic/goap.cljs +0 -122
  74. package/src/polymer/prosodic/scheduler.cljs +0 -99
  75. package/src/polymer/prosodic/snippet.cljs +0 -106
  76. package/src/polymer/prosodic/state.cljs +0 -139
  77. package/src/polymer/runtime/embody.cljs +0 -117
  78. package/src/polymer/runtime/embody_wasm.cljs +0 -177
  79. package/src/polymer/runtime/rust_embody_host.cljs +0 -647
  80. package/src/polymer/society.cljs +0 -253
  81. package/src/polymer/stream.cljs +0 -130
  82. package/src/polymer/transcription/agency.cljs +0 -69
  83. package/src/polymer/transcription/domain.cljs +0 -68
  84. package/src/polymer/transcription/effector.cljs +0 -611
  85. package/src/polymer/transcription/planner.cljs +0 -203
  86. package/src/polymer/transcription/scheduler.cljs +0 -225
  87. package/src/polymer/transcription/service.cljs +0 -209
  88. package/src/polymer/transcription/state.cljs +0 -239
  89. package/src/polymer/tts/agency.cljs +0 -503
  90. package/src/polymer/tts/azure.cljs +0 -290
  91. package/src/polymer/tts/planner.cljs +0 -178
  92. package/src/polymer/tts/runtime.cljs +0 -624
  93. package/src/polymer/tts/scheduler.cljs +0 -162
  94. package/src/polymer/tts/state.cljs +0 -191
  95. package/src/polymer/worker.cljs +0 -81
package/dist/index.d.ts CHANGED
@@ -1,14 +1,523 @@
1
+ // This hand-authored declaration file is package source. `pnpm build` copies it
2
+ // into dist/ beside the generated ClojureScript artifacts.
1
3
  import type {
2
- Embody,
3
- EmbodyRuntime,
4
- HairAppearanceState,
5
- HairColorAppearance,
6
- HairPhysicsRuntimeConfig,
7
- HairPhysicsRuntimeConfigUpdate,
8
- MeshInfo,
9
- Profile,
10
- } from '@lovelace_lol/embody';
11
- import type { Object3D } from 'three';
4
+ AnimationClip,
5
+ Camera,
6
+ ColorRepresentation,
7
+ Mesh,
8
+ Object3D,
9
+ PerspectiveCamera,
10
+ Scene,
11
+ Texture,
12
+ Vector3,
13
+ WebGLRenderer,
14
+ } from 'three';
15
+ import type { GLTF } from 'three/examples/jsm/loaders/GLTFLoader.js';
16
+ import type { ThreeModelReferencePose as EmbodyModelReferencePose } from '@lovelace_lol/embody/three';
17
+ export type ThreeModelReferencePose = EmbodyModelReferencePose;
18
+ import type {
19
+ CharacterSceneRenderer,
20
+ CharacterSceneBackend,
21
+ DefaultCharacterSceneAsyncOptions,
22
+ ReadyDefaultCharacterScene,
23
+ DefaultCharacterSceneRuntime,
24
+ DefaultCharacterSceneRuntimeOptions,
25
+ CharacterSceneRenderingController,
26
+ } from '@lovelace_lol/embody/three';
27
+ export type {
28
+ CharacterSceneRendererPreference,
29
+ CharacterSceneRenderer,
30
+ CharacterSceneBackend,
31
+ DefaultCharacterSceneAsyncOptions,
32
+ ReadyDefaultCharacterScene,
33
+ DefaultCharacterSceneRuntime,
34
+ DefaultCharacterSceneRuntimeOptions,
35
+ CharacterSceneRenderingSettings,
36
+ CharacterSceneRenderingStatus,
37
+ CharacterSceneRenderingSnapshot,
38
+ CharacterSceneRenderingController,
39
+ CharacterSceneRendererLease,
40
+ } from '@lovelace_lol/embody/three';
41
+
42
+
43
+ /** Capture before playback or profile application; retain for engine recreation. */
44
+ export declare function captureModelReferencePose(model: Object3D): ThreeModelReferencePose;
45
+
46
+ export type PresetType = 'cc4' | 'fish' | string;
47
+ export type BoneAxisKey = 'pitch' | 'yaw' | 'roll';
48
+ export type BoneAxisDirection = 'negative' | 'positive';
49
+ export type BoneAxisDirectionScale = -1 | 1;
50
+ export type RotationChannel = 'rx' | 'ry' | 'rz';
51
+ export type BilateralBoneAxisScope = 'shared' | 'left' | 'right';
52
+ export type BoneControlFamily = 'Horizontal Gaze' | 'Vertical Gaze';
53
+ export type BoneControlScope = 'Both Eyes' | 'Left Eye Only' | 'Right Eye Only';
54
+ export type HumanoidSkeletonTemplateVec3 = readonly [number, number, number];
55
+ export type HumanoidSkeletonTemplateBounds = readonly [number, number, number, number, number, number];
56
+ export type MorphTargetRef = string | number;
57
+ export type MeshCategory = 'body' | 'eye' | 'eyeOcclusion' | 'tearLine' | 'teeth' | 'tongue' | 'hair' | 'eyebrow' | 'cornea' | 'eyelash';
58
+ export type BlendingMode = 'Normal' | 'Additive' | 'Subtractive' | 'Multiply' | 'None';
59
+
60
+ export interface MorphTargetsBySide {
61
+ left: MorphTargetRef[];
62
+ right: MorphTargetRef[];
63
+ center: MorphTargetRef[];
64
+ }
65
+
66
+ export interface BoneBinding {
67
+ node: string;
68
+ channel: 'rx' | 'ry' | 'rz' | 'tx' | 'ty' | 'tz';
69
+ scale: BoneAxisDirectionScale;
70
+ maxDegrees?: number;
71
+ maxUnits?: number;
72
+ side?: 'left' | 'right';
73
+ }
74
+
75
+ export interface AUInfo {
76
+ id: string;
77
+ name: string;
78
+ muscularBasis?: string;
79
+ links?: string[];
80
+ faceArea?: 'Upper' | 'Lower' | 'Body';
81
+ facePart?: string;
82
+ }
83
+
84
+ export interface RotationAxis {
85
+ aus: number[];
86
+ axis: RotationChannel;
87
+ negative?: number | number[];
88
+ positive?: number | number[];
89
+ }
90
+
91
+ export interface CompositeRotation {
92
+ node: string;
93
+ pitch: RotationAxis | null;
94
+ yaw: RotationAxis | null;
95
+ roll: RotationAxis | null;
96
+ }
97
+
98
+ export interface VisemeSlotFeatures {
99
+ jawOpen?: number;
100
+ lipClosed?: number;
101
+ lipRound?: number;
102
+ lipSpread?: number;
103
+ tongueTip?: number;
104
+ fricative?: number;
105
+ nasal?: number;
106
+ }
107
+
108
+ export interface VisemeSlot {
109
+ id: string;
110
+ label: string;
111
+ order?: number;
112
+ providerIds?: Record<string, Array<string | number>>;
113
+ phonemes?: string[];
114
+ matchers?: string[];
115
+ features?: VisemeSlotFeatures;
116
+ defaultJawAmount?: number;
117
+ }
118
+
119
+ export type VisemeTongueTarget = Record<string, number>;
120
+
121
+ export interface VisemeBindingTarget {
122
+ morph: MorphTargetRef;
123
+ weight?: number;
124
+ }
125
+
126
+ export interface VisemeBinding {
127
+ targets?: VisemeBindingTarget[];
128
+ morph?: MorphTargetRef;
129
+ jawAmount?: number;
130
+ note?: string;
131
+ sharedWith?: string[];
132
+ }
133
+
134
+ export interface ResolvedVisemeSlot extends Omit<VisemeSlot, 'label'> {
135
+ label?: string;
136
+ [extension: string]: unknown;
137
+ }
138
+ /** Model-independent authored resolution. Array indices equal Embody runtime slot indices. */
139
+ export interface ResolvedVisemeProfile {
140
+ routingMode: 'explicit' | 'legacy';
141
+ /** Stock canonical sound index to runtime slot index, including reordered stock profiles. */
142
+ canonicalSlotIndices?: number[];
143
+ visemeSlots: ResolvedVisemeSlot[];
144
+ visemeBindingTargets: Array<Array<Required<VisemeBindingTarget>>>;
145
+ visemeJawAmounts: number[];
146
+ visemeTongueTargets: VisemeTongueTarget[];
147
+ visemeMeshNames: string[];
148
+ visemeMeshCategory: string;
149
+ }
150
+ /** Requires initEmbodyCore(); does not verify target existence on a loaded model. */
151
+ export declare function resolveVisemeProfile(profile: Partial<Profile>): ResolvedVisemeProfile;
152
+
153
+ export interface MappingEditorSection {
154
+ id: string;
155
+ label: string;
156
+ kind: 'au' | 'viseme' | 'lipSync' | 'hair' | 'unmapped' | 'custom';
157
+ order: number;
158
+ meshCategory?: string;
159
+ facePart?: string;
160
+ }
161
+
162
+ export interface MeshMaterialSettings {
163
+ renderOrder?: number;
164
+ transparent?: boolean;
165
+ opacity?: number;
166
+ depthWrite?: boolean;
167
+ depthTest?: boolean;
168
+ blending?: BlendingMode;
169
+ }
170
+
171
+ export interface MeshInfo {
172
+ name?: string;
173
+ visible?: boolean;
174
+ category: MeshCategory;
175
+ morphCount: number;
176
+ material?: MeshMaterialSettings;
177
+ }
178
+
179
+ export type HairMorphAxis = 'yaw' | 'pitch' | 'roll';
180
+ export interface HairMorphTargetMapping { key: string; axis: HairMorphAxis; }
181
+ export interface HairMorphTargetValueMapping { value: number; axis: HairMorphAxis; }
182
+ export interface HairPhysicsProfileConfig {
183
+ enabled?: boolean;
184
+ stiffness?: number;
185
+ damping?: number;
186
+ inertia?: number;
187
+ gravity?: number;
188
+ responseScale?: number;
189
+ idleSwayAmount?: number;
190
+ idleSwaySpeed?: number;
191
+ windStrength?: number;
192
+ windDirectionX?: number;
193
+ windDirectionZ?: number;
194
+ windTurbulence?: number;
195
+ windFrequency?: number;
196
+ idleClipDuration?: number;
197
+ impulseClipDuration?: number;
198
+ direction?: { yawSign?: 1 | -1; pitchSign?: 1 | -1 };
199
+ morphTargets?: {
200
+ swayLeft?: HairMorphTargetMapping;
201
+ swayRight?: HairMorphTargetMapping;
202
+ swayFront?: HairMorphTargetMapping;
203
+ fluffRight?: HairMorphTargetMapping;
204
+ fluffBottom?: HairMorphTargetMapping;
205
+ headUp?: Record<string, HairMorphTargetValueMapping>;
206
+ headDown?: Record<string, HairMorphTargetValueMapping>;
207
+ };
208
+ }
209
+
210
+ export type VRMHumanoidRole =
211
+ | 'hips' | 'spine' | 'chest' | 'upperChest' | 'neck' | 'head' | 'leftEye' | 'rightEye' | 'jaw'
212
+ | 'leftShoulder' | 'leftUpperArm' | 'leftLowerArm' | 'leftHand'
213
+ | 'rightShoulder' | 'rightUpperArm' | 'rightLowerArm' | 'rightHand'
214
+ | 'leftUpperLeg' | 'leftLowerLeg' | 'leftFoot' | 'leftToes'
215
+ | 'rightUpperLeg' | 'rightLowerLeg' | 'rightFoot' | 'rightToes'
216
+ | 'leftThumbMetacarpal' | 'leftThumbProximal' | 'leftThumbDistal'
217
+ | 'rightThumbMetacarpal' | 'rightThumbProximal' | 'rightThumbDistal'
218
+ | 'leftIndexProximal' | 'leftIndexIntermediate' | 'leftIndexDistal'
219
+ | 'rightIndexProximal' | 'rightIndexIntermediate' | 'rightIndexDistal'
220
+ | 'leftMiddleProximal' | 'leftMiddleIntermediate' | 'leftMiddleDistal'
221
+ | 'rightMiddleProximal' | 'rightMiddleIntermediate' | 'rightMiddleDistal'
222
+ | 'leftRingProximal' | 'leftRingIntermediate' | 'leftRingDistal'
223
+ | 'rightRingProximal' | 'rightRingIntermediate' | 'rightRingDistal'
224
+ | 'leftLittleProximal' | 'leftLittleIntermediate' | 'leftLittleDistal'
225
+ | 'rightLittleProximal' | 'rightLittleIntermediate' | 'rightLittleDistal';
226
+
227
+ /** A standard bone role; motion axes/ranges belong to the authored rig profile. */
228
+ export interface HumanoidBoneDefinition {
229
+ role: VRMHumanoidRole;
230
+ label: string;
231
+ section: 'Torso' | 'Head' | 'Arms' | 'Hands' | 'Legs' | 'Feet';
232
+ parent: VRMHumanoidRole | null;
233
+ required: boolean;
234
+ /** This optional parent must be mapped when the child is mapped. */
235
+ requiresParent: boolean;
236
+ }
237
+
238
+ export interface HumanoidSpecification {
239
+ standard: 'VRMC_vrm-1.0';
240
+ bones: HumanoidBoneDefinition[];
241
+ }
242
+
243
+ export interface HumanoidRoleBinding {
244
+ nodeKey: string;
245
+ /** Literal model bone selected by the role editor, before preset prefix lookup. */
246
+ exactBoneName?: string;
247
+ source?: string;
248
+ confidence?: number;
249
+ }
250
+
251
+ export interface HumanoidCharacterization {
252
+ schemaVersion: 1;
253
+ standard: 'VRMC_vrm-1.0';
254
+ status: 'verified-vrm' | 'characterized' | 'incomplete';
255
+ /** Explicitly edited role catalogs are authoritative across preset upgrades. */
256
+ authored?: boolean;
257
+ roles: Partial<Record<VRMHumanoidRole, HumanoidRoleBinding>>;
258
+ }
259
+
260
+ export interface HumanoidCharacterizationResolution {
261
+ schemaVersion: number | null;
262
+ standard: string | null;
263
+ status: string | null;
264
+ roles: Record<string, { nodeKey: string; boneName: string; source: string | null; confidence: number | null }>;
265
+ missingRequiredRoles: string[];
266
+ errors: string[];
267
+ warnings: string[];
268
+ valid: boolean;
269
+ }
270
+
271
+ /** Semantic body actions reuse the AU bone/morph evaluator. VRM names identify targets. */
272
+ export interface BodyControlDefinition {
273
+ label: string;
274
+ section: string;
275
+ auId: number;
276
+ negativeAuId?: number | null;
277
+ bilateral: boolean;
278
+ roles: VRMHumanoidRole[];
279
+ order?: number;
280
+ }
281
+
282
+ export interface BodyControlDescriptor {
283
+ id: string;
284
+ label: string;
285
+ section: string;
286
+ auId: number;
287
+ negativeAuId: number | null;
288
+ auIds: number[];
289
+ bilateral: boolean;
290
+ roles: VRMHumanoidRole[];
291
+ order: number;
292
+ boneBindings: Array<BoneBinding & { auId: number; boneName: string }>;
293
+ morphBindings: MorphTargetsBySide;
294
+ meshNames: string[];
295
+ hasBones: boolean;
296
+ hasMorphs: boolean;
297
+ isMixed: boolean;
298
+ available: boolean;
299
+ diagnostics: string[];
300
+ }
301
+
302
+ /** Pose values are normalized authored action amounts, never world Euler angles. */
303
+ export interface SemanticPoseChannel {
304
+ intensity: number;
305
+ /** -1 = character's left, 0 = both, +1 = character's right. */
306
+ balance?: number;
307
+ /** Morph contribution only; bone activation remains intensity. Defaults to profile. */
308
+ morphStrength?: number;
309
+ }
310
+ export interface SemanticPose {
311
+ version: 1;
312
+ controls: Array<{ controlId: string; positive?: SemanticPoseChannel; negative?: SemanticPoseChannel }>;
313
+ }
314
+ export interface SemanticPoseDirection {
315
+ auId: number; label: string; available: boolean; availableSides?: Array<'left' | 'right' | 'center'>; hasBones: boolean; hasMorphs: boolean; morphStrength: number;
316
+ }
317
+ export interface SemanticPoseControl {
318
+ id: string; label: string; section: string; roles: string[]; bilateral: boolean;
319
+ boneNames: string[]; morphNames: string[];
320
+ positive: SemanticPoseDirection; negative?: SemanticPoseDirection;
321
+ }
322
+ export interface SemanticAnimation {
323
+ version: 1;
324
+ durationSeconds: number;
325
+ tracks: Array<{ controlId: string; direction: 'positive' | 'negative'; balance?: number; morphStrength?: number;
326
+ keyframes: Array<{ time: number; intensity: number }> }>;
327
+ }
328
+ export interface SemanticAnimationOptions extends Record<string, unknown> {
329
+ faceCurves?: Record<string, Array<{ time: number; intensity: number; inherit?: boolean }>>;
330
+ }
331
+
332
+ export interface Profile {
333
+ name?: string;
334
+ animalType?: string;
335
+ emoji?: string;
336
+ auToMorphs: Record<number, MorphTargetsBySide>;
337
+ auToBones: Record<number, BoneBinding[]>;
338
+ boneNodes: Record<string, string>;
339
+ bonePrefix?: string;
340
+ boneSuffix?: string;
341
+ morphPrefix?: string;
342
+ morphSuffix?: string;
343
+ suffixPattern?: string;
344
+ leftMorphSuffixes?: string[];
345
+ rightMorphSuffixes?: string[];
346
+ morphToMesh: Record<string, string[]>;
347
+ auFacePartToMeshCategory?: Record<string, string>;
348
+ mappingSections?: MappingEditorSection[];
349
+ visemeKeys: MorphTargetRef[];
350
+ visemeSystemId?: string;
351
+ visemeSlots?: VisemeSlot[];
352
+ visemeBindings?: Record<string, VisemeBinding>;
353
+ visemeMeshCategory?: string;
354
+ visemeJawAmounts?: number[];
355
+ /** Per-viseme tongue AU targets in canonical slot order. */
356
+ visemeTongueTargets?: VisemeTongueTarget[];
357
+ auMixDefaults?: Record<number, number>;
358
+ auInfo?: Record<string, AUInfo>;
359
+ eyeMeshNodes?: { LEFT: string; RIGHT: string };
360
+ compositeRotations?: CompositeRotation[];
361
+ meshes?: Record<string, MeshInfo>;
362
+ continuumPairs?: Record<number, {
363
+ pairId: number;
364
+ isNegative: boolean;
365
+ axis: BoneAxisKey;
366
+ node: string;
367
+ }>;
368
+ continuumLabels?: Record<string, string>;
369
+ annotationRegions?: Region[];
370
+ disabledRegions?: string[];
371
+ hairPhysics?: HairPhysicsProfileConfig;
372
+ gazeCalibration?: {
373
+ head?: { opticalAxis?: PolymerVector3 };
374
+ leftEye?: { opticalAxis?: PolymerVector3 };
375
+ rightEye?: { opticalAxis?: PolymerVector3 };
376
+ /** Authored model coordinate units per physical meter, before root scaling. */
377
+ modelUnitsPerMeter?: number;
378
+ };
379
+ humanoidCharacterization?: HumanoidCharacterization;
380
+ /** Authored overrides may patch individual fields; null removes a preset control. */
381
+ bodyControls?: Record<string, Partial<BodyControlDefinition> | null>;
382
+ }
383
+
384
+ export interface ProfileRuntimeConfig extends Partial<Profile> {
385
+ profilePresetId?: PresetType;
386
+ presetId?: PresetType;
387
+ baseProfileId?: PresetType;
388
+ auPresetType?: PresetType;
389
+ profile?: Partial<Profile>;
390
+ defaultRegion?: string;
391
+ regions?: Region[];
392
+ markerStyle?: MarkerStyle;
393
+ playIntroOnLoad?: boolean;
394
+ modelOffset?: { x?: number; y?: number; z?: number };
395
+ modelRotation?: { x?: number; y?: number; z?: number };
396
+ modelScale?: number;
397
+ modelGroundClearance?: number;
398
+ deletedBakedAnimationClips?: string[];
399
+ markerGroups?: MarkerGroup[];
400
+ lineDefaults?: LineConfig;
401
+ markerDefaults?: Partial<MarkerStyleOverrides>;
402
+ }
403
+
404
+ export interface CharacterProfile extends ProfileRuntimeConfig {
405
+ characterId?: string;
406
+ characterName?: string;
407
+ modelPath?: string;
408
+ }
409
+
410
+ export type CharacterConfig = CharacterProfile & {
411
+ characterId: string;
412
+ characterName: string;
413
+ modelPath: string;
414
+ regions: Region[];
415
+ };
416
+
417
+ export type LineStyle = 'solid' | 'dashed' | 'dotted';
418
+ export type LineCurve = 'straight' | 'bezier' | 'arc';
419
+ export type NamedDirection = 'radial' | 'camera' | 'up' | 'down' | 'left' | 'right' | 'forward' | 'backward';
420
+ export interface LineConfig { style?: LineStyle; curve?: LineCurve; arrowHead?: boolean; thickness?: number; length?: number; }
421
+ export interface MarkerStyleOverrides {
422
+ markerColor?: number;
423
+ markerRadius?: number;
424
+ lineColor?: number;
425
+ labelColor?: string;
426
+ labelBackground?: string;
427
+ labelFontSize?: number;
428
+ opacity?: number;
429
+ lineDirection?: NamedDirection | { x: number; y: number; z: number };
430
+ line?: LineConfig;
431
+ }
432
+ export type ExpandAnimation = 'outward' | 'staggered';
433
+ export interface ExpandedRegionState { regionName: string; isExpanded: boolean; children: string[]; }
434
+ export interface FallbackConfig { fallbackMarker?: string; clickBehavior?: 'fit-all' | 'rotate'; }
435
+ export interface MarkerGroup { groupId: string; regions: string[]; fallback?: FallbackConfig; }
436
+ export interface Region {
437
+ name: string;
438
+ bones?: string[];
439
+ meshes?: string[];
440
+ objects?: string[];
441
+ paddingFactor?: number;
442
+ cameraAngle?: number;
443
+ cameraOffset?: { x?: number; y?: number; z?: number };
444
+ parent?: string;
445
+ children?: string[];
446
+ expandAnimation?: ExpandAnimation;
447
+ style?: MarkerStyleOverrides;
448
+ groupId?: string;
449
+ isFallback?: boolean;
450
+ customPosition?: { x: number; y: number; z: number };
451
+ }
452
+ export type MarkerStyle = 'html' | '3d';
453
+
454
+ export interface HairColorAppearance { name: string; baseColor: string; emissive: string; emissiveIntensity: number; }
455
+ export interface HairAppearanceState { hairColor: HairColorAppearance; eyebrowColor: HairColorAppearance; showOutline: boolean; outlineColor: string; outlineOpacity: number; }
456
+ export interface HairPhysicsRuntimeConfig { enabled: boolean; [key: string]: any; }
457
+ export type HairPhysicsRuntimeConfigUpdate = Partial<HairPhysicsRuntimeConfig>;
458
+ export interface TransitionHandle { promise: Promise<void>; pause(): void; resume(): void; cancel(): void; }
459
+
460
+ export interface BoneAxisBindingUpdate {
461
+ channel?: RotationChannel;
462
+ positiveAuId?: number | null;
463
+ negativeAuId?: number | null;
464
+ positiveMaxDegrees?: number | null;
465
+ negativeMaxDegrees?: number | null;
466
+ positiveScale?: BoneAxisDirectionScale | null;
467
+ negativeScale?: BoneAxisDirectionScale | null;
468
+ positiveLeftScale?: BoneAxisDirectionScale | null;
469
+ positiveRightScale?: BoneAxisDirectionScale | null;
470
+ negativeLeftScale?: BoneAxisDirectionScale | null;
471
+ negativeRightScale?: BoneAxisDirectionScale | null;
472
+ }
473
+ export interface BoneAxisBindingState { axis: BoneAxisKey; channel: RotationChannel; nodeKey: string; positiveAuId: number | null; negativeAuId: number | null; positiveMaxDegrees: number | null; negativeMaxDegrees: number | null; positiveScale: BoneAxisDirectionScale | null; negativeScale: BoneAxisDirectionScale | null; }
474
+ export interface AUBoneBindingState { auId: number; boneName: string | null; nodeKey: string | null; axis: BoneAxisKey; direction: BoneAxisDirection; channel: RotationChannel | null; maxDegrees: number | null; oppositeAuId: number | null; oppositeMaxDegrees: number | null; hasBinding: boolean; hasMultipleBindings: boolean; }
475
+ export interface BilateralAxisDirectionScaleState { left: BoneAxisDirectionScale | null; right: BoneAxisDirectionScale | null; }
476
+ export interface BilateralAxisScopeBindingState { scope: BilateralBoneAxisScope; label: string; negativeAuId: number | null; positiveAuId: number | null; negativeMaxDegrees: number | null; positiveMaxDegrees: number | null; negativeScale: BilateralAxisDirectionScaleState; positiveScale: BilateralAxisDirectionScaleState; }
477
+ export interface BilateralAxisBindingState { axis: BoneAxisKey; familyLabel: string; leftBoneName: string; leftNodeKey: string; rightBoneName: string; rightNodeKey: string; selectedSide: 'left' | 'right'; shared: BilateralAxisScopeBindingState; left: BilateralAxisScopeBindingState; right: BilateralAxisScopeBindingState; }
478
+ export interface ChiralBoneNamePair { leftBoneName: string; rightBoneName: string; selectedSide: 'left' | 'right'; familyLabel: string; }
479
+ export interface CreatedBoneAxisAu { auId: number; profile: Profile; }
480
+
481
+ export interface BoneInfo { name: string; parent: string | null; children: string[]; worldPosition: { x: number; y: number; z: number }; depth: number; }
482
+ export interface TrackInfo { name: string; targetName: string; property: string; type: string; keyframeCount: number; valueRange?: { min: number[]; max: number[] }; }
483
+ export interface AnimationInfo { name: string; duration: number; tracks: TrackInfo[]; animatedBones: string[]; animatedMorphs: string[]; }
484
+ export interface ModelData { bones: BoneInfo[]; morphs: Array<{ name: string; meshName: string; index: number }>; meshes: Array<{ name: string; hasMorphTargets: boolean; morphCount: number }>; animations: AnimationInfo[]; boneNames: string[]; morphNames: string[]; meshNames: string[]; }
485
+ export interface MappingCorrection { type: 'bone' | 'morph' | 'viseme' | 'mesh'; source: string; target: string; confidence: number; reason: string; applied: boolean; auId?: number; key?: string; }
486
+ export interface ValidationResult { valid: boolean; score: number; missingMorphs: string[]; missingBones: string[]; foundMorphs: string[]; foundBones: string[]; unmappedMorphs: string[]; unmappedBones: string[]; missingMeshes: string[]; foundMeshes: string[]; unmappedMeshes: string[]; warnings: string[]; suggestedConfig?: Profile; corrections?: MappingCorrection[]; unresolved?: MappingCorrection[]; }
487
+ export interface ModelAnalysisReport { model: ModelData; validation?: ValidationResult; animations: { count: number; hasIdleCandidate: boolean; clips: unknown[] }; overallScore: number; summary: string; }
488
+
489
+ export interface AnnotationLaterality { leftSideX: 1 | -1; confidence: number; evidence: string[]; }
490
+ export interface BoneResolutionProfile { bonePrefix?: string; boneSuffix?: string; boneNodes?: Record<string, string>; suffixPattern?: string; }
491
+ export interface CameraRelativeGazeOffset { x: number; y: number; }
492
+ export interface CameraRelativeGazeOptions { yawWeight?: number; pitchWeight?: number; epsilon?: number; }
493
+ export interface MarkerEndpointSeparationOptions { starts: ArrayLike<number>; ends: ArrayLike<number>; modelCenter: { x: number; y: number; z: number }; modelHeight: number; }
494
+
495
+ export interface HumanoidSkeletonTemplateBone { readonly name: string; readonly parent: string | null; readonly translation: HumanoidSkeletonTemplateVec3; }
496
+ export interface HumanoidSkeletonTemplate { readonly id: string; readonly sourceCharacterId: string; readonly sourceAsset: string; readonly sourceSkinName: string; readonly bones: readonly HumanoidSkeletonTemplateBone[]; }
497
+
498
+ export interface CharacterModelLoadOptions { onProgress?: (progress: number) => void; castShadows?: boolean; dracoDecoderPath?: string; }
499
+ export interface CharacterModelLoadResult { model: Object3D; meshes: Mesh[]; animations: AnimationClip[]; gltf: GLTF | null; }
500
+ export interface CharacterModelTransform { modelOffset?: { x?: number; y?: number; z?: number }; modelRotation?: { x?: number; y?: number; z?: number }; modelScale?: number; modelGroundClearance?: number; }
501
+ export interface DefaultCharacterLightingSettings { envMapEnabled: boolean; environmentIntensity: number; environmentBlur: number; exposure: number; ambientIntensity: number; keyIntensity: number; fillIntensity: number; rimIntensity: number; shadowOpacity: number; }
502
+ export type DefaultCharacterLightingPresetId = 'cleanStudio' | 'softFill' | 'inspection' | 'contrast';
503
+ export type CharacterSceneTypeId = 'studio' | 'showcase' | 'inspection' | 'void';
504
+ export interface CharacterSceneTypeDefinition { id: CharacterSceneTypeId; label: string; description: string; background: number | null; lightingPreset: DefaultCharacterLightingPresetId; shadowPlane: boolean; }
505
+ export interface DefaultCharacterLightingController { getSettings(): DefaultCharacterLightingSettings; getEnvironmentTexture(): Texture | null; setSettings(patch: Partial<DefaultCharacterLightingSettings>): DefaultCharacterLightingSettings; setPreset(id: DefaultCharacterLightingPresetId): DefaultCharacterLightingSettings; subscribe(listener: (settings: DefaultCharacterLightingSettings) => void): () => void; dispose(): void; }
506
+ export interface DefaultCharacterSceneOptions { type?: CharacterSceneTypeId; cameraFov?: number; pixelRatioCap?: number; shadows?: boolean; shadowPlane?: boolean; background?: ColorRepresentation | null; lighting?: Partial<DefaultCharacterLightingSettings>; lightingPreset?: DefaultCharacterLightingPresetId; manageResize?: boolean; }
507
+ export interface DefaultCharacterScene { container: HTMLElement; scene: Scene; renderer: WebGLRenderer; camera: PerspectiveCamera; lighting: DefaultCharacterLightingController; shadowPlane: Mesh | null; sceneType: CharacterSceneTypeId; ownsScene: true; resize(): void; dispose(): void; }
508
+ export interface CharacterHostCharacterOptions extends CharacterModelTransform { modelUrl: string; presetType?: PresetType; profile?: Profile; }
509
+ export interface CharacterHostExternalScene { scene: Scene; renderer: CharacterSceneRenderer; backend?: CharacterSceneBackend; rendering?: CharacterSceneRenderingController; camera: Camera; mountCanvas?: boolean; manageResize?: boolean; }
510
+ /** Cancellation applies only until creation resolves; dispose the resolved host explicitly. */
511
+ export interface CharacterHostOptions { signal?: AbortSignal; container: HTMLElement; character: CharacterHostCharacterOptions; embody?: RustEmbodyHostConfig; scene?: Omit<DefaultCharacterSceneAsyncOptions, 'signal'>; external?: CharacterHostExternalScene; loader?: CharacterModelLoadOptions; autoStart?: boolean; renderLoop?: boolean; autoFrame?: boolean; }
512
+ export interface CharacterHost { engine: Embody; scene: Scene; readonly renderer: CharacterSceneRenderer; readonly backend: CharacterSceneBackend | null; readonly rendering: CharacterSceneRenderingController | null; camera: Camera | PerspectiveCamera; model: Object3D; meshes: Mesh[]; animations: AnimationClip[]; lighting: DefaultCharacterLightingController | null; ownsScene: boolean; container: HTMLElement; setCharacter(character: CharacterHostCharacterOptions, loader?: CharacterModelLoadOptions): Promise<void>; resize(): void; dispose(): Promise<void>; }
513
+
514
+ export interface CharacterRegistry { characters: CharacterConfig[]; defaultCharacter?: string; }
515
+ export type AnimationSource = 'baked' | 'clip' | 'snippet';
516
+ export type AnimationBlendMode = 'replace' | 'additive';
517
+ export type BakedClipChannel = 'face' | 'body' | 'scene';
518
+ export interface AnimationClipInfo { name: string; duration: number; trackCount: number; source?: AnimationSource; channels?: BakedClipChannelInfo[]; }
519
+ export interface ThreeMaterialConfig extends MeshMaterialSettings {}
520
+ export interface ThreeResolvedMaterialConfig { renderOrder: number; transparent: boolean; opacity: number; depthWrite: boolean; depthTest: boolean; blending: BlendingMode; }
12
521
 
13
522
  /** Direct Embody Wasm core accessors (via @lovelace_lol/embody/wasm). */
14
523
  export declare function initEmbodyCore(): Promise<EmbodyCoreWasmModule>;
@@ -94,11 +603,14 @@ export interface WasmRuntimeCore {
94
603
  set_au(id: number, value: number, balance: number): void;
95
604
  set_au_signed(id: number, value: number, balance: number): void;
96
605
  get_au(id: number): number;
606
+ get_au_balance(id: number): number;
97
607
  set_continuum(negAu: number, posAu: number, value: number, balance: number): void;
98
608
  get_continuum(negAu: number, posAu: number): number;
99
609
  set_au_mix_weight(id: number, weight: number): void;
100
610
  set_viseme(index: number, value: number): void;
101
611
  set_viseme_jaw_scale(index: number, jawScale: number): void;
612
+ get_viseme(index: number): number;
613
+ get_viseme_jaw_scale(index: number): number;
102
614
  set_viseme_slot_count(count: number): void;
103
615
  transition_au(id: number, to: number, durationMs: number, balance: number): void;
104
616
  transition_viseme(index: number, to: number, durationMs: number, jawScale: number): void;
@@ -142,6 +654,19 @@ export interface EmbodyCoreWasmModule {
142
654
  manualScale: number,
143
655
  manualTranslation: Float32Array
144
656
  ): Float32Array;
657
+ solve_profile_screen_space_gaze(
658
+ profileJson: string,
659
+ screenTarget: Float32Array,
660
+ cameraPosition: Float32Array,
661
+ cameraQuaternion: Float32Array,
662
+ gazeOrigin: Float32Array,
663
+ modelQuaternion: Float32Array,
664
+ verticalFovDegrees: number,
665
+ aspect: number,
666
+ eyesEnabled: boolean,
667
+ headEnabled: boolean,
668
+ headFollowFraction: number
669
+ ): Float32Array;
145
670
  default_hair_physics_config_values(): Float32Array;
146
671
  list_presets(): string[];
147
672
  has_preset(presetId: string): boolean;
@@ -161,13 +686,18 @@ export type RustEmbodyHostConfig =
161
686
  presetType?: string;
162
687
  profile?: Partial<Profile>;
163
688
  meshes?: unknown[];
689
+ /** Reference for the first model bound to this host. */
690
+ referencePose?: ThreeModelReferencePose;
164
691
  }
165
692
  | {
166
693
  profileMode: 'profile';
167
- profile: Profile;
694
+ /** Exact profile; an empty object renders an unmapped model for authoring. */
695
+ profile: Partial<Profile>;
168
696
  presetId?: never;
169
697
  presetType?: never;
170
698
  meshes?: unknown[];
699
+ /** Reference for the first model bound to this host. */
700
+ referencePose?: ThreeModelReferencePose;
171
701
  };
172
702
 
173
703
  /**
@@ -181,8 +711,12 @@ export interface PolymerCharacterRuntime {
181
711
  readonly meshes: unknown[];
182
712
  readonly meshByName: Map<string, unknown>;
183
713
  readonly bones: Record<string, unknown>;
184
- onReady(options: { model: unknown; meshes?: unknown[] }): void;
714
+ /** First bind captures the model reference unless an explicit capture is supplied. */
715
+ onReady(options: { model: unknown; meshes?: unknown[]; animations?: unknown[]; referencePose?: ThreeModelReferencePose }): void;
716
+ /** Rebind using the original reference; incompatible hierarchy edits throw. */
185
717
  bindModel(meshes?: unknown[]): void;
718
+ /** Explicitly include appended nodes without recapturing existing posed nodes. */
719
+ extendReferencePose(): ThreeModelReferencePose;
186
720
  setProfile(profile: Profile): void;
187
721
  getProfile(): Profile;
188
722
  getProfileMode(): RustEmbodyProfileMode;
@@ -190,24 +724,44 @@ export interface PolymerCharacterRuntime {
190
724
  supportsPreset(presetId?: string): boolean;
191
725
  setAU(id: number, value: number, balance?: number): void;
192
726
  getAU(id: number): number;
727
+ /** Current procedural left/right balance (-1..1), or zero after clear. */
728
+ getAUBalance(id: number): number;
729
+ /** Resolve body controls from the configured Rust profile/model; no scene traversal. */
730
+ getBodyControls(): BodyControlDescriptor[];
731
+ getSemanticPoseCatalog(): SemanticPoseControl[];
732
+ captureSemanticPose(): SemanticPose;
733
+ validateSemanticPose(pose: SemanticPose): SemanticPose;
734
+ applySemanticPose(pose: SemanticPose): void;
735
+ validateSemanticAnimation(animation: SemanticAnimation, options?: SemanticAnimationOptions): void;
736
+ semanticAnimationToClip(name: string, animation: SemanticAnimation, options?: SemanticAnimationOptions): AnimationPlaybackHandle;
737
+ /** Reset configured body actions atomically and apply one procedural frame. */
738
+ resetBodyControls(): void;
193
739
  setContinuum(negAU: number, posAU: number, value: number, balance?: number): void;
194
740
  getContinuum(negAU: number, posAU: number): number;
195
- transitionAU(id: number, to: number, durationMs?: number, balance?: number): void;
741
+ /** Mixer-timed live control; defaults to 200 ms. getAU reports its destination immediately. */
742
+ transitionAU(id: number, to: number, durationMs?: number, balance?: number): TransitionHandle;
196
743
  setViseme(index: number, value: number, jawScale?: number): void;
744
+ /** Canonical direct viseme value; unset, cleared or out-of-range slots return 0. Does not sample mixer output. */
745
+ getViseme(index: number): number;
746
+ /** Canonical direct jaw scale; unset, cleared or out-of-range slots return 1. */
747
+ getVisemeJawScale(index: number): number;
197
748
  setVisemeById(slotId: string, value: number, jawScale?: number): void;
198
- transitionViseme(index: number, to: number, durationMs?: number, jawScale?: number): void;
199
- transitionVisemeById(slotId: string, to: number, durationMs?: number, jawScale?: number): void;
749
+ /** Mixer-timed live control; defaults to 80 ms. getViseme reports the sampled direct value. */
750
+ transitionViseme(index: number, to: number, durationMs?: number, jawScale?: number): TransitionHandle;
751
+ transitionVisemeById(slotId: string, to: number, durationMs?: number, jawScale?: number): TransitionHandle;
200
752
  setAUMixWeight(id: number, weight: number): void;
201
753
  getAUMixWeight(id: number): number;
202
754
  hasLeftRightBones(id: number): boolean;
203
755
  setMorph(key: string | number, value: number, meshNames?: string[]): void;
204
756
  setMorphOnMeshes(meshNames: string[], key: string | number, value: number): void;
757
+ /** Relinquish a direct morph override so current AU/viseme/clip output can resume. */
758
+ releaseMorph(key: string | number, meshNames?: string[]): void;
205
759
  transitionMorph(
206
760
  key: string | number,
207
761
  value: number,
208
762
  durationMs?: number,
209
763
  meshNames?: string[]
210
- ): ReturnType<Embody['transitionMorph']>;
764
+ ): TransitionHandle;
211
765
  getMorphTargets(): Record<string, string[]>;
212
766
  getMorphTargetIndices(): Record<string, Array<{ index: number; name: string }>>;
213
767
  getHairMorphTargets(meshName: string): string[];
@@ -216,10 +770,10 @@ export interface PolymerCharacterRuntime {
216
770
  getBones(): Record<string, unknown>;
217
771
  setMeshVisible(meshName: string, visible: boolean): void;
218
772
  highlightMesh(meshName: string | null, color?: number, intensity?: number): void;
219
- getMeshMaterialConfig(meshName: string): ReturnType<Embody['getMeshMaterialConfig']>;
773
+ getMeshMaterialConfig(meshName: string): ThreeResolvedMaterialConfig | null;
220
774
  setMeshMaterialConfig(
221
775
  meshName: string,
222
- config: Parameters<Embody['setMeshMaterialConfig']>[1]
776
+ config: ThreeMaterialConfig
223
777
  ): void;
224
778
  registerHairObjects(
225
779
  objects: Object3D[]
@@ -240,28 +794,42 @@ export interface PolymerCharacterRuntime {
240
794
  validateHairMorphTargets(): string[];
241
795
  updateHairPhysics(dtSeconds: number): void;
242
796
  loadAnimationClips(clips: unknown[]): void;
243
- getAnimationClips(): ReturnType<Embody['getAnimationClips']>;
797
+ /**
798
+ * Register additional baked sources on a bound model without starting clips
799
+ * or interrupting playback. Identical sources are ignored; a replacement
800
+ * under the same name takes effect on the next play. Sources survive rebinds.
801
+ */
802
+ registerAnimationClips(clips: unknown[]): void;
803
+ /** Remove a source and its active action, including the source retained for rebinding. */
804
+ removeAnimationClip(clipName: string): boolean;
805
+ getAnimationClips(): AnimationClipInfo[];
244
806
  playAnimation(
245
807
  clipName: string,
246
- options?: Parameters<Embody['playAnimation']>[1]
247
- ): ReturnType<Embody['playAnimation']>;
808
+ options?: Record<string, unknown>
809
+ ): AnimationPlaybackHandle;
248
810
  stopAnimation(clipName: string): void;
249
811
  stopAllAnimations(): void;
250
812
  playTypedSnippet(
251
- snippet: Parameters<Embody['playTypedSnippet']>[0],
252
- options?: Parameters<Embody['playTypedSnippet']>[1]
253
- ): ReturnType<Embody['playTypedSnippet']>;
813
+ snippet: Record<string, unknown>,
814
+ options?: Record<string, unknown>
815
+ ): AnimationPlaybackHandle;
254
816
  buildTypedClip(
255
817
  clipName: string,
256
- channels: Parameters<Embody['buildTypedClip']>[1],
257
- options?: Parameters<Embody['buildTypedClip']>[2]
258
- ): ReturnType<Embody['buildTypedClip']>;
818
+ channels: unknown[],
819
+ options?: Record<string, unknown>
820
+ ): AnimationPlaybackHandle;
259
821
  buildClip(
260
822
  clipName: string,
261
- curves: Parameters<Embody['buildClip']>[1],
262
- options?: Parameters<Embody['buildClip']>[2]
263
- ): ReturnType<Embody['buildClip']>;
823
+ curves: Record<string, unknown>,
824
+ options?: Record<string, unknown>
825
+ ): AnimationPlaybackHandle;
264
826
  cleanupSnippet(name: string): void;
827
+ /** Read-only seed for gaze motor controls in the current evaluated pose. */
828
+ getGazeControlState(options?: { worldTarget?: PolymerVector3; headIntensity?: number; eyeIntensity?: number }): GazeControlState | null;
829
+ /** Last evaluated optical-ray diagnostics; null without active focus controls. */
830
+ getGazeFocusDiagnostics(): GazeFocusDiagnostics | null;
831
+ /** Fresh immutable copy of the last positive-dt pose update; polling does not renew its stamp. */
832
+ getGazeObservation(): GazeObservation | null;
265
833
  update(dtSeconds: number): void;
266
834
  start(): void;
267
835
  stop(): void;
@@ -270,6 +838,24 @@ export interface PolymerCharacterRuntime {
270
838
  dispose(): void;
271
839
  }
272
840
 
841
+ export interface AnimationPlaybackHandle extends TransitionHandle {
842
+ name: string;
843
+ actionId?: string;
844
+ play(): AnimationPlaybackHandle;
845
+ stop(): AnimationPlaybackHandle;
846
+ setWeight(value: number): AnimationPlaybackHandle;
847
+ setSpeed(value: number): AnimationPlaybackHandle;
848
+ setTime(value: number): AnimationPlaybackHandle;
849
+ subscribe(listener: ((event: unknown) => void) | { next(event: unknown): void }): () => void;
850
+ }
851
+
852
+ export interface EmbodyRuntime extends PolymerCharacterRuntime {}
853
+ export declare class Embody {
854
+ constructor(config?: RustEmbodyHostConfig);
855
+ static create(config?: RustEmbodyHostConfig): Promise<Embody>;
856
+ [key: string]: any;
857
+ }
858
+
273
859
  export declare function createRustEmbodyHost(
274
860
  model: unknown,
275
861
  config?: RustEmbodyHostConfig
@@ -281,172 +867,102 @@ export declare function createRustEmbodyHostUnbound(
281
867
 
282
868
  export declare function supportsRustEmbodyPreset(presetId?: string): boolean;
283
869
 
284
- export {
285
- Embody,
286
- BLENDING_MODES,
287
- THREE_BLENDING_MODES,
288
- collectMorphMeshes,
289
- analyzeModel,
290
- extractModelData,
291
- getPreset,
292
- validateMappings,
293
- extendCharacterConfigWithPreset,
294
- extractProfileOverrides,
295
- mergeCharacterRegionsByName,
296
- applyAUBoneBindingUpdate,
297
- applyBilateralAxisBindingUpdate,
298
- applyBoneAxisBindingUpdate,
299
- buildBoneAuOptions,
300
- classifyAuAsJointControl,
301
- createBilateralBoneAxisAu,
302
- createBoneAxisAu,
303
- DEFAULT_AXIS_TO_CHANNEL,
304
- DEFAULT_BONE_MAX_DEGREES,
305
- ensureBilateralBoneNodeKeys,
306
- ensureBoneNodeKey,
307
- findNodeKeyForBone,
308
- formatAxisDirectionLabel,
309
- formatAxisLabel,
310
- getAUBoneBindingState,
311
- getAxisFromChannel,
312
- getBilateralAxisBindingState,
313
- getBoneAxisBindingState,
314
- inferChiralBoneNamePair,
315
- inferEyeControlFamily,
316
- inferEyeControlScope,
317
- isJointControlAuInfo,
318
- isMaxDegreesOnlyAxisBindingUpdate,
319
- JOINT_CONTROL_SECTION,
320
- resolveBoneAxisChannel,
321
- resolveBoneNameForNodeKey,
322
- resolveContinuumDisplayLabel,
323
- stripConfiguredBoneAffixes,
324
- computeCameraRelativeGazeOffset,
325
- detectAnnotationLaterality,
326
- fuzzyNameMatch,
327
- getDefaultAnnotationLaterality,
328
- getMeshNamesForAUProfile,
329
- getModelLocalOrbitAngle,
330
- getSemanticHorizontalSign,
331
- getSemanticHorizontalSignForSide,
332
- getWorldDirectionForCameraAngle,
333
- hasLeftRightMorphs,
334
- isMixedAU,
335
- passesMarkerCameraAngleGate,
336
- resolveBoneNames,
337
- resolveFaceCenter,
338
- resolveRegionCameraAngle,
339
- resolveRegionVisibilityCameraAngle,
340
- toWorldDirection,
341
- RustAnnotationCameraCore,
342
- createRustAnnotationCameraCore,
343
- CC4_BONES,
344
- VISEME_KEYS,
345
- HAIR_COLOR_PRESETS,
346
- DEFAULT_HAIR_COLOR_APPEARANCE,
347
- normalizeHairColorAppearance,
348
- HUMANOID_SKELETON_TEMPLATES,
349
- JONATHAN_HUMANOID_SKELETON_TEMPLATE,
350
- getHumanoidSkeletonTemplate,
351
- getHumanoidSkeletonTemplateBoneNames,
352
- createValidationSkeletonFromHumanoidTemplate,
353
- computeHumanoidSkeletonTemplateRestBounds,
354
- extractHumanoidSkeletonTemplateFromModel,
355
- createCharacterHost,
356
- createDefaultCharacterScene,
357
- createDefaultCharacterLighting,
358
- createShadowPlane,
359
- loadCharacterModel,
360
- parseCharacterModel,
361
- applyCharacterModelTransform,
362
- disposeCharacterModel,
363
- DEFAULT_CHARACTER_LIGHTING_PRESET_ID,
364
- DEFAULT_CHARACTER_LIGHTING_PRESET_IDS,
365
- DEFAULT_CHARACTER_LIGHTING_PRESETS,
366
- DEFAULT_CHARACTER_LIGHTING_SETTINGS,
367
- normalizeDefaultCharacterLightingSettings,
368
- CHARACTER_SCENE_TYPES,
369
- CHARACTER_SCENE_TYPE_IDS,
370
- DEFAULT_CHARACTER_SCENE_TYPE_ID,
371
- } from '@lovelace_lol/embody';
372
-
373
- export type {
374
- CharacterHost,
375
- CharacterHostCharacterOptions,
376
- CharacterHostExternalScene,
377
- CharacterHostOptions,
378
- CharacterModelLoadOptions,
379
- CharacterModelLoadResult,
380
- CharacterModelTransform,
381
- CharacterSceneTypeDefinition,
382
- CharacterSceneTypeId,
383
- DefaultCharacterLightingController,
384
- DefaultCharacterLightingPresetId,
385
- DefaultCharacterLightingSettings,
386
- DefaultCharacterScene,
387
- DefaultCharacterSceneOptions,
388
- HairAppearanceState,
389
- HairColorAppearance,
390
- HairPhysicsRuntimeConfig,
391
- HairPhysicsRuntimeConfigUpdate,
392
- MarkerEndpointSeparationOptions,
393
- } from '@lovelace_lol/embody';
394
-
395
- export type {
396
- AUBoneBindingState,
397
- AUInfo,
398
- AnimationClipInfo,
399
- AnimationInfo,
400
- AnnotationLaterality,
401
- BilateralAxisBindingState,
402
- BilateralAxisDirectionScaleState,
403
- BilateralAxisScopeBindingState,
404
- BilateralBoneAxisScope,
405
- BoneAxisBindingState,
406
- BoneAxisBindingUpdate,
407
- BoneAxisDirection,
408
- BoneAxisDirectionScale,
409
- BoneAxisKey,
410
- BoneBinding,
411
- BoneControlFamily,
412
- BoneControlScope,
413
- BoneInfo,
414
- CameraRelativeGazeOffset,
415
- CameraRelativeGazeOptions,
416
- CharacterConfig,
417
- CharacterRegistry,
418
- ChiralBoneNamePair,
419
- CompositeRotation,
420
- CreatedBoneAxisAu,
421
- ExpandAnimation,
422
- ExpandedRegionState,
423
- FallbackConfig,
424
- LineConfig,
425
- LineCurve,
426
- LineStyle,
427
- EmbodyRuntime,
428
- MarkerGroup,
429
- MarkerStyle,
430
- MarkerStyleOverrides,
431
- MeshInfo,
432
- ModelAnalysisReport,
433
- ModelData,
434
- MorphTargetsBySide,
435
- NamedDirection,
436
- PresetType,
437
- Profile,
438
- Region,
439
- RotationAxis,
440
- RotationChannel,
441
- TrackInfo,
442
- TransitionHandle,
443
- ValidationResult,
444
- VisemeSlot,
445
- HumanoidSkeletonTemplate,
446
- HumanoidSkeletonTemplateBone,
447
- HumanoidSkeletonTemplateBounds,
448
- HumanoidSkeletonTemplateVec3,
449
- } from '@lovelace_lol/embody';
870
+ export declare const BLENDING_MODES: Record<BlendingMode, number>;
871
+ export declare const THREE_BLENDING_MODES: Record<BlendingMode, number>;
872
+ export declare function collectMorphMeshes(root: Object3D): Mesh[];
873
+ export declare function analyzeModel(options: {
874
+ source: { type: 'gltf'; gltf: GLTF } | { type: 'runtime'; model: Object3D; meshes: Mesh[]; animations: AnimationClip[] };
875
+ preset?: Profile;
876
+ suggestCorrections?: boolean;
877
+ }): Promise<ModelAnalysisReport>;
878
+ export declare function extractModelData(model: Object3D, meshes?: Mesh[], animations?: AnimationClip[]): ModelData;
879
+ export declare function getPreset(presetId: PresetType): Profile;
880
+ export declare function validateMappings(meshes: unknown[], skeleton: unknown, profile: Profile, options?: Record<string, unknown>): ValidationResult;
881
+ export declare function validateHumanoidCharacterization(model: Object3D, profile: Profile): HumanoidCharacterizationResolution;
882
+ export declare function extendCharacterConfigWithPreset(config: CharacterConfig): CharacterConfig;
883
+ export declare function extractProfileOverrides(config: CharacterConfig): Partial<Profile>;
884
+ export declare function mergeCharacterRegionsByName(base?: Region[], override?: Region[]): Region[];
885
+ export declare function applyAUBoneBindingUpdate(profile: Profile, auId: number, update: { boneName: string | null; axis: BoneAxisKey; direction: BoneAxisDirection; maxDegrees: number | null }): Profile;
886
+ export declare function applyBilateralAxisBindingUpdate(profile: Profile, boneName: string, axis: BoneAxisKey, scope: BilateralBoneAxisScope, update: BoneAxisBindingUpdate): Profile;
887
+ export declare function applyBoneAxisBindingUpdate(profile: Profile, boneName: string, axis: BoneAxisKey, update: BoneAxisBindingUpdate): Profile;
888
+ export declare function buildBoneAuOptions(profile: Profile): Array<{ value: string; label: string; detail?: string }>;
889
+ export declare function classifyAuAsJointControl(profile: Profile, auId: number, options?: { boneName?: string | null; nodeKey?: string | null }): Profile;
890
+ export declare function createBilateralBoneAxisAu(profile: Profile, boneName: string, axis: BoneAxisKey, scope: BilateralBoneAxisScope, direction: BoneAxisDirection, options?: { name?: string }): CreatedBoneAxisAu;
891
+ export declare function createBoneAxisAu(profile: Profile, boneName: string, axis: BoneAxisKey, direction: BoneAxisDirection, options?: { name?: string }): CreatedBoneAxisAu;
892
+ export declare const DEFAULT_AXIS_TO_CHANNEL: Record<BoneAxisKey, RotationChannel>;
893
+ export declare const DEFAULT_BONE_MAX_DEGREES: number;
894
+ export declare function ensureBilateralBoneNodeKeys(profile: Profile, leftBoneName: string, rightBoneName: string): Profile;
895
+ export declare function ensureBoneNodeKey(profile: Profile, boneName: string): { nodeKey: string; boneNodes: Record<string, string> };
896
+ export declare function findNodeKeyForBone(boneName: string, profile: Profile | null): string | null;
897
+ export declare function formatAxisDirectionLabel(axis: BoneAxisKey, direction: BoneAxisDirection): string;
898
+ export declare function formatAxisLabel(axis: BoneAxisKey): string;
899
+ export declare function getAUBoneBindingState(profile: Profile, auId: number): AUBoneBindingState;
900
+ export declare function getAxisFromChannel(channel?: string): BoneAxisKey | null;
901
+ export declare function getBilateralAxisBindingState(profile: Profile, boneName: string, axis: BoneAxisKey): BilateralAxisBindingState | null;
902
+ export declare function getBoneAxisBindingState(profile: Profile, boneName: string, axis: BoneAxisKey): BoneAxisBindingState | null;
903
+ export declare function inferChiralBoneNamePair(boneNames: string[], selectedBoneName: string): ChiralBoneNamePair | null;
904
+ export declare function inferEyeControlFamily(binding: Pick<BoneAxisBindingState, 'nodeKey' | 'axis'>): BoneControlFamily | null;
905
+ export declare function inferEyeControlScope(profile: Pick<Profile, 'auToBones'>, binding: Pick<BoneAxisBindingState, 'nodeKey' | 'negativeAuId' | 'positiveAuId'>): BoneControlScope | null;
906
+ export declare function isJointControlAuInfo(info?: Pick<AUInfo, 'facePart'> | null): boolean;
907
+ export declare function isMaxDegreesOnlyAxisBindingUpdate(update: BoneAxisBindingUpdate): boolean;
908
+ export declare const JOINT_CONTROL_SECTION: string;
909
+ export declare function resolveBoneAxisChannel(profile: Profile, boneName: string, axis: BoneAxisKey): RotationChannel;
910
+ export declare function resolveBoneNameForNodeKey(profile: Profile, nodeKey: string): string | null;
911
+ export declare function resolveContinuumDisplayLabel(profile: Pick<Profile, 'continuumPairs' | 'continuumLabels' | 'auInfo' | 'boneNodes' | 'bonePrefix' | 'boneSuffix'>, negativeAuId: number, positiveAuId: number): string;
912
+ export declare function stripConfiguredBoneAffixes(profile: { bonePrefix?: string; boneSuffix?: string } | null | undefined, boneName: string): string;
913
+ export declare function computeCameraRelativeGazeOffset(model: Object3D | null, cameraPosition: { x: number; y: number; z: number }, targetPosition: { x: number; y: number; z: number }, options?: CameraRelativeGazeOptions): CameraRelativeGazeOffset;
914
+ export declare function solveScreenSpaceGaze(options: ScreenSpaceGazeOptions): ScreenSpaceGazeSolution;
915
+ export declare function createEmbodyGazeRuntime(
916
+ profileProvider: Profile | (() => Profile),
917
+ host?: Pick<PolymerCharacterRuntime, 'getGazeControlState'>
918
+ ): EmbodyGazeRuntime;
919
+ export declare function fuzzyNameMatch(objectName: string, targetName: string, suffixPattern?: string): boolean;
920
+ export declare function getMeshNamesForAUProfile(profile: Profile, auId: number): string[];
921
+ export declare function hasLeftRightMorphs(auId: number): boolean;
922
+ export declare function hasLeftRightMorphs(profile: Profile, auId: number): boolean;
923
+ export declare function isMixedAU(auId: number): boolean;
924
+ export declare function isMixedAU(profile: Profile, auId: number): boolean;
925
+ export declare function resolveBoneNames(names: string[] | undefined, profile?: BoneResolutionProfile): string[];
926
+ /** All 55 VRM 1.0 roles, including required flags and optional-parent rules. */
927
+ export declare function getHumanoidSpecification(): HumanoidSpecification;
928
+ /** Assign an actual model bone or clear a role; returns a new authoritative profile. */
929
+ export declare function setHumanoidRoleBinding(profile: Profile, role: VRMHumanoidRole, boneName: string | null): Profile;
930
+ /** Replace one action side's targets, preserving other actions sharing those morphs. */
931
+ export declare function setAUMorphTargets(profile: Profile, auId: number, side: 'left' | 'right' | 'center', targets: Array<string | number>): Profile;
932
+ export declare function resolveHumanoidCharacterization(profile: Profile): HumanoidCharacterizationResolution;
933
+ /** With a model, reports missing bone/morph targets against the actual scene. */
934
+ export declare function getBodyControls(profile: Profile, model?: Object3D): BodyControlDescriptor[];
935
+ export declare function resolveCharacterGazeOrigin(model: Object3D, profile?: Profile | null): Vector3 | null;
936
+ export declare const CC4_BONES: Record<string, string>;
937
+ export declare const VISEME_KEYS: string[];
938
+ export declare const HAIR_COLOR_PRESETS: Record<string, HairColorAppearance>;
939
+ export declare const DEFAULT_HAIR_COLOR_APPEARANCE: HairColorAppearance;
940
+ export declare function normalizeHairColorAppearance(value: HairColorAppearance | string, fallback?: HairColorAppearance): HairColorAppearance;
941
+ export declare const HUMANOID_SKELETON_TEMPLATES: readonly HumanoidSkeletonTemplate[];
942
+ export declare const CC4_HUMANOID_SKELETON_TEMPLATE: HumanoidSkeletonTemplate;
943
+ export declare function getHumanoidSkeletonTemplate(id: string): HumanoidSkeletonTemplate | null;
944
+ export declare function getHumanoidSkeletonTemplateBoneNames(template?: HumanoidSkeletonTemplate): string[];
945
+ export declare function createValidationSkeletonFromHumanoidTemplate(template?: HumanoidSkeletonTemplate): { bones: Array<{ name: string }> };
946
+ export declare function computeHumanoidSkeletonTemplateRestBounds(template: HumanoidSkeletonTemplate): HumanoidSkeletonTemplateBounds;
947
+ export declare function extractHumanoidSkeletonTemplateFromModel(model: Object3D, options: { id: string; sourceCharacterId: string; sourceAsset?: string; sourceSkinName?: string }): HumanoidSkeletonTemplate;
948
+ export declare function createCharacterHost(options: CharacterHostOptions): Promise<CharacterHost>;
949
+ export declare function createDefaultCharacterScene(container: HTMLElement, options?: DefaultCharacterSceneOptions): DefaultCharacterScene;
950
+ export declare function createDefaultCharacterSceneAsync(container: HTMLElement, options?: DefaultCharacterSceneAsyncOptions): Promise<ReadyDefaultCharacterScene>;
951
+ export declare function createDefaultCharacterSceneRuntime(container: HTMLElement, options?: DefaultCharacterSceneRuntimeOptions): DefaultCharacterSceneRuntime;
952
+ export declare function createDefaultCharacterLighting(scene: Scene, renderer: WebGLRenderer, settings?: Partial<DefaultCharacterLightingSettings>): DefaultCharacterLightingController;
953
+ export declare function createShadowPlane(scene: Scene, options?: { size?: number; opacity?: number; yPosition?: number }): Mesh;
954
+ export declare function loadCharacterModel(url: string, options?: CharacterModelLoadOptions): Promise<CharacterModelLoadResult>;
955
+ export declare function parseCharacterModel(data: ArrayBuffer, resourcePath?: string, options?: CharacterModelLoadOptions): Promise<CharacterModelLoadResult>;
956
+ export declare function applyCharacterModelTransform(model: Object3D, value?: CharacterModelTransform): void;
957
+ export declare function disposeCharacterModel(scene: Scene | null, model: Object3D): void;
958
+ export declare const DEFAULT_CHARACTER_LIGHTING_PRESET_ID: DefaultCharacterLightingPresetId;
959
+ export declare const DEFAULT_CHARACTER_LIGHTING_PRESET_IDS: readonly DefaultCharacterLightingPresetId[];
960
+ export declare const DEFAULT_CHARACTER_LIGHTING_PRESETS: Record<DefaultCharacterLightingPresetId, { id: DefaultCharacterLightingPresetId; label: string; settings: DefaultCharacterLightingSettings }>;
961
+ export declare const DEFAULT_CHARACTER_LIGHTING_SETTINGS: DefaultCharacterLightingSettings;
962
+ export declare function normalizeDefaultCharacterLightingSettings(value: unknown): DefaultCharacterLightingSettings | null;
963
+ export declare const CHARACTER_SCENE_TYPES: Record<CharacterSceneTypeId, CharacterSceneTypeDefinition>;
964
+ export declare const CHARACTER_SCENE_TYPE_IDS: readonly CharacterSceneTypeId[];
965
+ export declare const DEFAULT_CHARACTER_SCENE_TYPE_ID: CharacterSceneTypeId;
450
966
 
451
967
  export interface BlinkState {
452
968
  agency: 'blink';
@@ -474,21 +990,176 @@ export interface GazeTarget {
474
990
  z?: number;
475
991
  }
476
992
 
993
+ export interface GazeControlState {
994
+ headSeedLimited?: boolean;
995
+ eyeSeedLimited?: boolean;
996
+ target?: Required<GazeTarget>;
997
+ eyeTarget: Required<GazeTarget>;
998
+ headTarget: Required<GazeTarget>;
999
+ headRoll: number;
1000
+ }
1001
+
1002
+ export interface GazeFocusRequest {
1003
+ worldTarget: PolymerVector3;
1004
+ target: Required<GazeTarget>;
1005
+ headTarget?: Required<GazeTarget>;
1006
+ eyesEnabled: boolean;
1007
+ headEnabled: boolean;
1008
+ /** Tracking contribution in [0, 1]; older values above 1 normalize to 1. Live changes use scheduled motion. */
1009
+ eyeIntensity?: number;
1010
+ /** Tracking contribution in [0, 1], composed over authored head motion. Live changes use scheduled motion. */
1011
+ headIntensity?: number;
1012
+ initialState?: GazeControlState;
1013
+ }
1014
+
1015
+ export interface GazeFocusDiagnostics {
1016
+ target: PolymerVector3;
1017
+ eyes: Array<{ name: string; origin: PolymerVector3; direction: PolymerVector3; errorDegrees: number; limited: boolean }>;
1018
+ headLimited: boolean;
1019
+ missingHead: boolean;
1020
+ missingEyes: boolean;
1021
+ }
1022
+
1023
+ export interface GazeObservation {
1024
+ sampleSequence: number;
1025
+ observedAt: number;
1026
+ bindingGeneration: number;
1027
+ requestId: string | null;
1028
+ diagnostics: GazeFocusDiagnostics;
1029
+ }
1030
+
1031
+ export interface GazeAttentionOutcome {
1032
+ status: 'moving' | 'settled' | 'unavailable' | 'failed';
1033
+ reason: string;
1034
+ angularErrorDeg?: number;
1035
+ sampleSequence?: number;
1036
+ observedAt?: number;
1037
+ bindingGeneration?: number;
1038
+ }
1039
+ export interface GazeRetainedCandidate {
1040
+ id: string;
1041
+ source: string;
1042
+ provenance: 'synthetic' | 'peer';
1043
+ target: GazeTarget;
1044
+ targetSpace: string;
1045
+ confidence: number;
1046
+ salience: number;
1047
+ influence: number;
1048
+ /** Actual accepted Society credit for peers, null for synthetic fixtures. */
1049
+ routeCredit: number | null;
1050
+ edgeId: string | null;
1051
+ score: number;
1052
+ observedAt: number;
1053
+ expiresAt: number;
1054
+ eligible: boolean;
1055
+ reason: string;
1056
+ lastOutcome?: GazeAttentionOutcome;
1057
+ }
1058
+ export interface GazeAttentionState {
1059
+ /** The POC accepts static Conversation facts; live perception arbitration is deferred. */
1060
+ lastRejectedInput?: { source: string; reason: 'moving-peer-arbitration-deferred' };
1061
+ active: boolean;
1062
+ sessionId: string | null;
1063
+ scenario: GazeAttentionScenario | null;
1064
+ phase: 'inactive' | 'waiting' | 'moving' | 'settled' | 'unavailable' | 'failed';
1065
+ reason: string;
1066
+ selectedId: string | null;
1067
+ requestId: string | null;
1068
+ configurationRevision: number;
1069
+ candidates: GazeRetainedCandidate[];
1070
+ feedback: GazeAttentionOutcome | null;
1071
+ decisionCount: number;
1072
+ updatedAt?: number;
1073
+ }
1074
+ export type GazeAttentionScenario = 'competition' | 'equal-score' | 'expiry' | 'unreachable';
1075
+
1076
+ export interface ScreenSpaceGazeSolution {
1077
+ kind: 'gaze.screen-space' | 'gaze.viewer-space';
1078
+ focus?: GazeFocusRequest;
1079
+ target: Required<GazeTarget>;
1080
+ eyeTarget: Required<GazeTarget>;
1081
+ headTarget: Required<GazeTarget>;
1082
+ angles: {
1083
+ yawDegrees: number;
1084
+ pitchDegrees: number;
1085
+ cameraYawDegrees: number;
1086
+ cameraPitchDegrees: number;
1087
+ };
1088
+ /** World-space distance from the character's binocular eye origin to the render camera. */
1089
+ distance: number;
1090
+ viewerTarget: PolymerVector3;
1091
+ screenTarget: Required<GazeTarget>;
1092
+ }
1093
+
1094
+ export interface ScreenSpaceGazeOptions {
1095
+ profile?: Profile;
1096
+ screenTarget: GazeTarget;
1097
+ cameraPosition: PolymerVector3;
1098
+ cameraQuaternion: PolymerQuaternion;
1099
+ gazeOrigin: PolymerVector3;
1100
+ modelQuaternion: PolymerQuaternion;
1101
+ verticalFovDegrees: number;
1102
+ aspect: number;
1103
+ eyesEnabled?: boolean;
1104
+ headEnabled?: boolean;
1105
+ /** Defaults to 1: full target bearing, bounded by the profile's head AU limits. */
1106
+ headFollowFraction?: number;
1107
+ }
1108
+
1109
+ export interface CameraGazeGeometry {
1110
+ cameraPosition: PolymerVector3;
1111
+ cameraQuaternion: PolymerQuaternion;
1112
+ gazeOrigin: PolymerVector3;
1113
+ modelQuaternion: PolymerQuaternion;
1114
+ verticalFovDegrees: number;
1115
+ aspect: number;
1116
+ gazeGeometryComplete: boolean;
1117
+ worldUnitsPerMeter?: number;
1118
+ }
1119
+
1120
+ export interface EmbodyGazeRuntime {
1121
+ solveTarget(
1122
+ screenTarget: GazeTarget,
1123
+ cameraFact: CameraGazeGeometry & Record<string, unknown>,
1124
+ options: GazeApplyOptions & { eyesEnabled: boolean; headEnabled: boolean; headFollowEyes: boolean; headIntensity?: number }
1125
+ ): ScreenSpaceGazeSolution;
1126
+ }
1127
+
477
1128
  export interface GazeConfig {
1129
+ /** Explicitly synthetic lab:A and lab:B receiver influences, not graph-edge weights. */
1130
+ attentionInfluenceA?: number;
1131
+ attentionInfluenceB?: number;
1132
+ attentionDwellMs?: number;
1133
+ attentionSwitchMargin?: number;
1134
+ attentionToleranceDeg?: number;
1135
+ attentionTimeoutMs?: number;
478
1136
  enabled?: boolean;
479
1137
  eyesEnabled?: boolean;
480
1138
  headEnabled?: boolean;
1139
+ /** Default true: follow the full target turn from reference neutral within head AU limits. False prefers neutral, with eye-limit overflow assistance. */
481
1140
  headFollowEyes?: boolean;
1141
+ headFollowDelay?: number;
482
1142
  mirrored?: boolean;
483
1143
  smoothFactor?: number;
1144
+ predictionEnabled?: boolean;
1145
+ predictionFactor?: number;
1146
+ maxPredictionDelta?: number;
484
1147
  minDelta?: number;
485
1148
  transitionDurationMs?: number;
1149
+ /** Tracking contribution in [0, 1]; older values above 1 normalize to 1. With geometry-backed focus, live changes move directly toward the latest value without angular reversal braking. */
486
1150
  eyeIntensity?: number;
1151
+ /** Tracking contribution in [0, 1], composed over authored head motion. With geometry-backed focus, live changes move directly toward the latest value without angular reversal braking. */
487
1152
  headIntensity?: number;
1153
+ trackingSpeed?: number;
1154
+ /** Braking interval for angular direction changes; geometry-backed strength changes bypass it. */
1155
+ courseChangeDurationMs?: number;
488
1156
  coalesceMs?: number;
489
1157
  }
490
1158
 
491
1159
  export interface GazeState {
1160
+ attention: GazeAttentionState;
1161
+ configuration: AgencyConfigurationState;
1162
+ lastDecision: GazeDecision | null;
492
1163
  agency: 'gaze';
493
1164
  status: string;
494
1165
  mode: string;
@@ -496,8 +1167,12 @@ export interface GazeState {
496
1167
  rawTarget: Required<GazeTarget>;
497
1168
  target: Required<GazeTarget>;
498
1169
  baseTarget: Required<GazeTarget>;
1170
+ baseOptions: GazeApplyOptions;
1171
+ cameraInvalidated: boolean;
1172
+ sourceAvailable: boolean;
499
1173
  cameraRelativeOffset: Required<GazeTarget>;
500
1174
  lastCameraFact: null | Record<string, unknown>;
1175
+ lastGazeSolution: ScreenSpaceGazeSolution | null;
501
1176
  lastRequestedTarget: Required<GazeTarget>;
502
1177
  pendingRequest: GazeLookRequest | null;
503
1178
  lastRequest: GazeLookRequest | GazeResetRequest | null;
@@ -517,11 +1192,22 @@ export interface GazeApplyOptions {
517
1192
  eyeEnabled?: boolean;
518
1193
  headEnabled?: boolean;
519
1194
  headFollowEyes?: boolean;
1195
+ headFollowDelay?: number;
1196
+ /** Fraction of total target bearing assigned to the head from reference neutral. Defaults to 1; head AU limits still apply. */
1197
+ headFollowFraction?: number;
1198
+ viewerTarget?: PolymerVector3;
1199
+ viewerTargetSpace?: 'webcam.viewer';
1200
+ viewerVerticalFovDegrees?: number;
1201
+ viewerAspect?: number;
1202
+ rawViewerTarget?: PolymerVector3;
1203
+ /** Default false. True explicitly prefers the camera bearing instead of reference-neutral target following. */
1204
+ lockHeadToCamera?: boolean;
520
1205
  force?: boolean;
521
1206
  }
522
1207
 
523
1208
  export interface GazeAttentionCandidate {
524
1209
  target?: GazeTarget;
1210
+ screenTarget?: GazeTarget;
525
1211
  gazeTarget?: GazeTarget;
526
1212
  lookTarget?: GazeTarget;
527
1213
  x?: number;
@@ -536,19 +1222,25 @@ export interface GazeAttentionCandidate {
536
1222
  }
537
1223
 
538
1224
  export type GazeDispatch =
1225
+ | { type: 'attention.start' | 'attention.stop'; sessionId: string }
1226
+ | { type: 'attention.scenario'; sessionId: string; scenario: GazeAttentionScenario }
539
1227
  | { type: 'configure'; config: GazeConfig }
540
1228
  | { type: 'setMode' | 'set-mode'; mode: string }
541
1229
  | { type: 'setActive' | 'set-active'; active: boolean }
542
1230
  | { type: 'enable' }
543
1231
  | { type: 'disable' }
544
1232
  | { type: 'setTarget' | 'set-target' | 'focusTarget'; target: GazeTarget; options?: GazeApplyOptions }
545
- | { type: 'attention.fact'; targets?: GazeAttentionCandidate[]; target?: GazeTarget; source?: string }
546
- | { type: 'camera.fact'; relativeOffset: GazeTarget; source?: string }
1233
+ | { type: 'attention.fact'; targets?: GazeAttentionCandidate[]; target?: GazeTarget; screenTarget?: GazeTarget; targetSpace?: string; source?: string }
1234
+ | ({ type: 'camera.fact'; relativeOffset: GazeTarget; source?: string } & Partial<CameraGazeGeometry>)
547
1235
  | { type: 'camera.stale' | 'clearCameraOffset'; reason?: string; source?: string }
1236
+ | Extract<PolymerDomainEvent, { type: 'userPerception.faceLost' | 'userPerception.status' }>
548
1237
  | { type: 'reset'; durationMs?: number; eyes?: boolean; head?: boolean }
549
1238
  | { type: 'cancel'; reason?: string };
550
1239
 
551
1240
  export interface GazeLookRequest {
1241
+ attentionFeedback?: { sessionId: string; requestId: string; worldTarget: PolymerVector3 | null; timeoutMs: number; toleranceDeg: number } | null;
1242
+ configurationRevision?: number;
1243
+ coalesceMs?: number;
552
1244
  type: 'eyeHeadTracking.requestGaze';
553
1245
  agency: 'gaze';
554
1246
  targetAgency: 'eyeHeadTracking';
@@ -557,7 +1249,12 @@ export interface GazeLookRequest {
557
1249
  label?: string;
558
1250
  mode: string;
559
1251
  target: Required<GazeTarget>;
1252
+ eyeTarget: Required<GazeTarget>;
1253
+ headTarget: Required<GazeTarget>;
1254
+ gazeSolution?: ScreenSpaceGazeSolution;
560
1255
  rawTarget: Required<GazeTarget>;
1256
+ predictedTarget: Required<GazeTarget>;
1257
+ predictive: boolean;
561
1258
  previousTarget: Required<GazeTarget>;
562
1259
  eyeEnabled: boolean;
563
1260
  headEnabled: boolean;
@@ -605,9 +1302,14 @@ export interface EyeHeadTrackingConfig {
605
1302
  lookAtSpeaker?: boolean;
606
1303
  idleVariation?: boolean;
607
1304
  idleVariationInterval?: number;
1305
+ /** Tracking contribution in [0, 1]; older values above 1 normalize to 1. With geometry-backed focus, live changes move toward the latest value without angular reversal braking. */
608
1306
  eyeIntensity?: number;
609
1307
  eyeBlendWeight?: number;
1308
+ /** Tracking contribution in [0, 1], composed over authored head motion. With geometry-backed focus, live changes move toward the latest value without angular reversal braking. */
610
1309
  headIntensity?: number;
1310
+ trackingSpeed?: number;
1311
+ /** Angular braking interval; geometry-backed strength changes bypass it. */
1312
+ courseChangeDurationMs?: number;
611
1313
  headBlendWeight?: number;
612
1314
  headRoll?: number;
613
1315
  eyePriority?: number;
@@ -656,12 +1358,19 @@ export interface EyeHeadGazeRequest {
656
1358
  sourceAgency?: string;
657
1359
  requestId: string;
658
1360
  target: Required<GazeTarget>;
1361
+ eyeTarget?: Required<GazeTarget>;
1362
+ headTarget?: Required<GazeTarget>;
1363
+ gazeSolution?: ScreenSpaceGazeSolution;
1364
+ gazeFocus?: GazeFocusRequest;
659
1365
  rawTarget?: Required<GazeTarget>;
660
1366
  mode?: string;
661
1367
  eyeEnabled?: boolean;
662
1368
  headEnabled?: boolean;
663
1369
  headFollowEyes?: boolean;
1370
+ headFollowDelay?: number;
1371
+ /** Tracking contribution in [0, 1]; older values above 1 normalize to 1. Live changes use scheduled motion. */
664
1372
  eyeIntensity?: number;
1373
+ /** Tracking contribution in [0, 1], composed over authored head motion. Live changes use scheduled motion. */
665
1374
  headIntensity?: number;
666
1375
  headRoll?: number;
667
1376
  eyeDurationMs?: number;
@@ -709,11 +1418,37 @@ export interface AnimationScheduledSnippet {
709
1418
  snippetCategory?: string;
710
1419
  snippetPriority?: number;
711
1420
  maxTime?: number;
1421
+ cleanupMaxTime?: number;
712
1422
  loop: boolean;
713
1423
  autoPlay: boolean;
714
1424
  requestedAt: number;
715
1425
  }
716
1426
 
1427
+ export interface AnimationBakedClipInfo {
1428
+ name: string;
1429
+ duration: number;
1430
+ channels: unknown[];
1431
+ }
1432
+
1433
+ export interface AnimationBakedUIState {
1434
+ name: string;
1435
+ source: 'baked';
1436
+ time: number;
1437
+ currentTime: number;
1438
+ duration: number;
1439
+ speed: number;
1440
+ playbackRate: number;
1441
+ intensityScale: number;
1442
+ isPlaying: boolean;
1443
+ isPaused: boolean;
1444
+ loop: boolean;
1445
+ loopMode: string;
1446
+ reverse: boolean;
1447
+ category: 'baked';
1448
+ channels: unknown[];
1449
+ [key: string]: unknown;
1450
+ }
1451
+
717
1452
  export interface AnimationState {
718
1453
  agency: 'animation';
719
1454
  scheduled: Record<string, AnimationScheduledSnippet>;
@@ -722,18 +1457,30 @@ export interface AnimationState {
722
1457
  seekCount: number;
723
1458
  updatedCount: number;
724
1459
  removedCount: number;
1460
+ bakedClips: AnimationBakedClipInfo[];
1461
+ bakedByName: Record<string, AnimationBakedUIState>;
1462
+ bakedPlaying: AnimationBakedUIState[];
725
1463
  lastEvent: null | {
726
1464
  type:
727
1465
  | 'animationSnippetScheduled'
728
1466
  | 'animationSnippetStarted'
729
1467
  | 'animationSnippetSeeked'
730
1468
  | 'animationSnippetUpdated'
731
- | 'animationSnippetRemoved';
732
- name: string;
733
- sourceAgency: string;
1469
+ | 'animationSnippetRemoved'
1470
+ | 'animationBakedClipsLoaded'
1471
+ | 'animationBakedStarted'
1472
+ | 'animationBakedStopped'
1473
+ | 'animationBakedPaused'
1474
+ | 'animationBakedResumed'
1475
+ | 'animationBakedSeeked'
1476
+ | 'animationBakedUpdated';
1477
+ name?: string;
1478
+ clipName?: string;
1479
+ sourceAgency?: string;
734
1480
  reason?: string;
735
1481
  offsetSec?: number;
736
1482
  params?: Record<string, unknown>;
1483
+ count?: number;
737
1484
  at: number;
738
1485
  };
739
1486
  }
@@ -754,7 +1501,15 @@ export type AnimationDispatch =
754
1501
  | { type: 'seekSnippet'; sourceAgency?: string; name: string; offsetSec: number }
755
1502
  | { type: 'updateSnippet'; sourceAgency?: string; name: string; params: Record<string, unknown> }
756
1503
  | { type: 'removeSnippet'; sourceAgency?: string; name: string }
757
- | { type: 'clear'; sourceAgency?: string };
1504
+ | { type: 'clear'; sourceAgency?: string }
1505
+ | { type: 'loadBakedClips'; sourceAgency?: string; clips?: AnimationBakedClipInfo[] }
1506
+ | { type: 'playBaked'; sourceAgency?: string; name: string; options?: Record<string, unknown> }
1507
+ | { type: 'stopBaked'; sourceAgency?: string; name: string }
1508
+ | { type: 'pauseBaked'; sourceAgency?: string; name: string }
1509
+ | { type: 'resumeBaked'; sourceAgency?: string; name: string }
1510
+ | { type: 'seekBaked'; sourceAgency?: string; name: string; offsetSec: number }
1511
+ | { type: 'updateBakedParams'; sourceAgency?: string; name: string; params: Record<string, unknown> }
1512
+ | { type: 'stopAllBaked'; sourceAgency?: string };
758
1513
 
759
1514
  export interface PolymerSnippetKeyframe {
760
1515
  time: number;
@@ -792,6 +1547,8 @@ export interface PolymerAnimationHandle {
792
1547
  }
793
1548
 
794
1549
  export interface PolymerAnimationRuntime {
1550
+ /** Optional capability. Unsupported engines never produce confirmed Gaze alignment. */
1551
+ getGazeObservation?: () => GazeObservation | null;
795
1552
  buildClip?: (
796
1553
  name: string,
797
1554
  curves: Record<string, PolymerSnippetKeyframe[]>,
@@ -825,27 +1582,60 @@ export interface PolymerAnimationSnippet {
825
1582
  curves?: Record<string, PolymerSnippetKeyframe[]>;
826
1583
  channels?: PolymerSnippetChannel[];
827
1584
  maxTime: number;
1585
+ /** Optional visual curve end used for cleanup when it outlives the sync clock. */
1586
+ cleanupMaxTime?: number;
828
1587
  loop: boolean;
829
1588
  snippetCategory?: string;
830
1589
  snippetPriority: number;
831
1590
  snippetPlaybackRate: number;
832
1591
  snippetIntensityScale: number;
1592
+ snippetBlendMode?: 'replace' | 'additive';
1593
+ /** Neutral reference sample for converting a clip to additive rotation. */
1594
+ additiveReferenceTimeSec?: number;
1595
+ /** Stable order for zero-fade, once-only additive controls, even after other clips are removed. */
1596
+ additiveCompositionOrder?: number;
833
1597
  snippetJawScale?: number;
834
1598
  autoVisemeJaw?: boolean;
835
1599
  metadata?: Record<string, unknown>;
836
1600
  }
837
1601
 
838
1602
  export interface LipSyncConfig {
1603
+ /**
1604
+ * Resolve from the active runtime profile once per profile change. Explicitly configuring
1605
+ * this field rebuilds an active clip even when equal (setProfile may clear runtime actions).
1606
+ * Null retains legacy canonical routing. Never configure it every frame.
1607
+ */
1608
+ visemeProfile?: ResolvedVisemeProfile | null;
839
1609
  intensity?: number;
840
1610
  speechRate?: number;
841
1611
  jawScale?: number;
842
1612
  /** Scale for independently planned tongue AU curves. Set 0 to disable tongue motion. */
843
1613
  tongueScale?: number;
844
- rampMs?: number;
845
- holdMs?: number;
846
1614
  priority?: number;
1615
+ /**
1616
+ * Scales the shared viseme-to-viseme handoff window. Higher values produce a
1617
+ * longer pose blend without returning the mouth to neutral. Default 1.75.
1618
+ */
1619
+ envelopeTimeScale?: number;
847
1620
  visualLeadMs?: number;
1621
+ /** Mixer fade-in for the scheduled utterance clip (seconds). Default 0.15. */
1622
+ fadeInSec?: number;
1623
+ /** Mixer fade-out when the utterance is stopped or replaced (seconds). Default 0.12. */
1624
+ fadeOutSec?: number;
1625
+ /** Mixer blend mode: "replace" (default) or "additive". */
1626
+ blendMode?: 'replace' | 'additive' | string;
1627
+ /**
1628
+ * Keyframe easing applied by the Embody host when densifying scalar tracks.
1629
+ * Default "easeInOutCubic".
1630
+ */
1631
+ easing?: 'linear' | 'easeIn' | 'easeOut' | 'easeInOut' | 'easeInOutCubic' | string;
1632
+ webSpeechDriftThresholdSec?: number;
1633
+ azureDriftThresholdSec?: number;
848
1634
  wordDriftThresholdSec?: number;
1635
+ /** Per-viseme jaw opening amounts, usually from the character profile. */
1636
+ visemeJawAmounts?: number[];
1637
+ /** Per-viseme tongue AU targets, supplied by the resolved character preset/profile. */
1638
+ visemeTongueTargets?: VisemeTongueTarget[];
849
1639
  }
850
1640
 
851
1641
  export interface LipSyncRuntime {
@@ -858,13 +1648,11 @@ export interface LipSyncRuntime {
858
1648
  }
859
1649
 
860
1650
  export interface LipSyncVisemeEvent {
1651
+ /** Canonical articulation ID, independent of the destination model slot. */
861
1652
  visemeId: number;
862
- /**
863
- * Optional independent jaw-axis activation for this event. When omitted,
864
- * Polymer derives a default from the canonical viseme slot. Set to 0 for a
865
- * lip-only viseme, or scale with LipSyncConfig.jawScale for JALI-style control.
866
- */
867
- jawActivation?: number;
1653
+ /** Optional explicit provider identity; unknown/ambiguous routes remain neutral. */
1654
+ provider?: string;
1655
+ providerId?: string | number;
868
1656
  phoneme?: string;
869
1657
  /**
870
1658
  * Primary visual-speech class for planner rules, e.g. vowel, bilabial,
@@ -908,6 +1696,14 @@ export interface LipSyncTimeline {
908
1696
  durationSec?: number;
909
1697
  }
910
1698
 
1699
+ /** Voice facts TTS relays with an utterance. Only speechRate is acted on today. */
1700
+ export interface LipSyncVoiceFacts {
1701
+ speechRate?: number;
1702
+ volume?: number;
1703
+ pitch?: number;
1704
+ style?: string;
1705
+ }
1706
+
911
1707
  export interface LipSyncState {
912
1708
  agency: 'lipSync';
913
1709
  speaking: boolean;
@@ -916,6 +1712,7 @@ export interface LipSyncState {
916
1712
  snippetName: string | null;
917
1713
  source: string | null;
918
1714
  text: string | null;
1715
+ voice: LipSyncVoiceFacts | null;
919
1716
  startTime: number | null;
920
1717
  audioStartedAt: number | null;
921
1718
  audioTimeSec: number | null;
@@ -925,7 +1722,12 @@ export interface LipSyncState {
925
1722
  scheduledCount: number;
926
1723
  stoppedCount: number;
927
1724
  syncCorrectionCount: number;
928
- config: Required<LipSyncConfig>;
1725
+ rebuildCount: number;
1726
+ config: Required<Omit<LipSyncConfig, 'visemeJawAmounts' | 'visemeTongueTargets' | 'visemeProfile'>> & {
1727
+ visemeProfile: ResolvedVisemeProfile | null;
1728
+ visemeJawAmounts: number[] | null;
1729
+ visemeTongueTargets: VisemeTongueTarget[] | null;
1730
+ };
929
1731
  lastEvent: null | Record<string, unknown>;
930
1732
  }
931
1733
 
@@ -939,6 +1741,10 @@ export type LipSyncDispatch =
939
1741
  wordTimings?: LipSyncWordTiming[];
940
1742
  durationSec?: number;
941
1743
  totalDurationMs?: number;
1744
+ speechRate?: number;
1745
+ volume?: number;
1746
+ pitch?: number;
1747
+ style?: string;
942
1748
  }
943
1749
  | { type: 'startTimeline'; timeline: LipSyncTimeline }
944
1750
  | {
@@ -949,6 +1755,10 @@ export type LipSyncDispatch =
949
1755
  text?: string;
950
1756
  source?: string;
951
1757
  wordTimings?: LipSyncWordTiming[];
1758
+ speechRate?: number;
1759
+ volume?: number;
1760
+ pitch?: number;
1761
+ style?: string;
952
1762
  options?: { wordTimings?: LipSyncWordTiming[]; visualLeadMs?: number; [key: string]: unknown };
953
1763
  }
954
1764
  | { type: 'wordBoundary'; word: string; wordIndex?: number; observedElapsedSec?: number; hostElapsedSec?: number }
@@ -965,6 +1775,8 @@ export interface TTSVoice {
965
1775
  lang?: string;
966
1776
  gender?: string;
967
1777
  styles?: string[];
1778
+ /** False for Google/network Web Speech voices that omit onboundary events. */
1779
+ localService?: boolean;
968
1780
  provider?: 'webSpeech' | 'azure' | string;
969
1781
  }
970
1782
 
@@ -975,16 +1787,10 @@ export interface TTSConfig {
975
1787
  azureVoiceName?: string;
976
1788
  azureStyle?: string;
977
1789
  azureStyleDegree?: string | number | null;
1790
+ /** Voice facts. Relayed to LipSync on each utterance; rate is live today. */
978
1791
  rate?: number;
979
1792
  pitch?: number;
980
1793
  volume?: number;
981
- visualLeadMs?: number;
982
- lipsyncIntensity?: number;
983
- jawScale?: number;
984
- /** Scale for LipSync tongue AU planning forwarded by TTS before each speech session. */
985
- tongueScale?: number;
986
- webSpeechDriftThresholdSec?: number;
987
- azureDriftThresholdSec?: number;
988
1794
  azureCacheLimit?: number;
989
1795
  debug?: boolean;
990
1796
  /** Experimental Web Speech barge-in reference: none | displayMedia */
@@ -1018,6 +1824,9 @@ export type TTSDispatch =
1018
1824
  | {
1019
1825
  type: 'speak';
1020
1826
  text: string;
1827
+ turnId?: string;
1828
+ requestId?: string;
1829
+ speechId?: string;
1021
1830
  engine?: 'webSpeech' | 'azure' | 'livekit' | string;
1022
1831
  name?: string;
1023
1832
  backendUrl?: string;
@@ -1026,17 +1835,161 @@ export type TTSDispatch =
1026
1835
  rate?: number;
1027
1836
  pitch?: number;
1028
1837
  volume?: number;
1029
- visualLeadMs?: number;
1030
1838
  }
1031
- | { type: 'stop' }
1839
+ | { type: 'releasePlaybackReference' }
1840
+ | { type: 'stop'; reason?: string; turnId?: string; requestId?: string; speechId?: string }
1032
1841
  | { type: 'reset' };
1033
1842
 
1843
+ export type ProsodicProfileSnippet = Record<string, unknown> & {
1844
+ name: string;
1845
+ };
1846
+
1847
+ export type ProsodicProfileSnippetInput =
1848
+ | ProsodicProfileSnippet
1849
+ | ({ snippet: ProsodicProfileSnippet } & Record<string, unknown>);
1850
+
1034
1851
  export interface ProsodicConfig {
1035
1852
  enabled?: boolean;
1036
1853
  intensity?: number;
1037
1854
  priority?: number;
1038
1855
  speechGestureEvery?: number;
1856
+ browPulseEvery?: number;
1857
+ headPulseEvery?: number;
1858
+ browIntensity?: number;
1859
+ headIntensity?: number;
1860
+ openingMovement?: boolean;
1039
1861
  blinkFastCooldownMs?: number;
1862
+ /** Saved speaking brow snippet selected by the active character profile. */
1863
+ browSnippet?: ProsodicProfileSnippetInput;
1864
+ /** Saved speaking head snippet selected by the active character profile. */
1865
+ headSnippet?: ProsodicProfileSnippetInput;
1866
+ }
1867
+
1868
+ /** Supported portable live settings; asset bodies and private clocks are not authored here. */
1869
+ export type ProsodicEditableConfiguration = Pick<ProsodicConfig,
1870
+ 'headIntensity' | 'browIntensity' | 'headPulseEvery' | 'browPulseEvery' | 'openingMovement'>;
1871
+
1872
+ /** Scalar policy fields supported by an agency's revisioned apply API. */
1873
+ export interface AgencyParameter<Key extends string = string> {
1874
+ key: Key;
1875
+ label: string;
1876
+ type: 'number' | 'boolean';
1877
+ default: number | boolean;
1878
+ minimum?: number;
1879
+ maximum?: number;
1880
+ step?: number;
1881
+ unit?: string;
1882
+ description: string;
1883
+ applyBoundary: 'next-decision';
1884
+ }
1885
+
1886
+ export interface AgencyConfigurationReceipt {
1887
+ status: 'applied' | 'rejected';
1888
+ requestedRevision: number;
1889
+ appliedRevision: number;
1890
+ reason?: string;
1891
+ }
1892
+
1893
+ export interface AgencyConfigurationState {
1894
+ appliedRevision: number;
1895
+ applyBoundary: 'next-decision';
1896
+ }
1897
+
1898
+ export interface ProsodicParameter extends AgencyParameter<keyof ProsodicEditableConfiguration> {}
1899
+
1900
+ export type GestureEditableConfiguration = Pick<GestureConfig,
1901
+ 'intensity' | 'priority' | 'defaultDurationMs' | 'cooldownMs' | 'rampRatio'
1902
+ | 'holdRatio' | 'returnToBase' | 'replaceActive' | 'maxActive'>;
1903
+ export type GazeEditableConfiguration = Pick<GazeConfig,
1904
+ 'headFollowDelay' | 'predictionEnabled' | 'trackingSpeed' | 'coalesceMs'
1905
+ | 'attentionInfluenceA' | 'attentionInfluenceB' | 'attentionDwellMs'
1906
+ | 'attentionSwitchMargin' | 'attentionToleranceDeg' | 'attentionTimeoutMs'>;
1907
+ export type CameraContextEditableConfiguration = Pick<CameraContextConfig,
1908
+ 'coalesceMs' | 'staleAfterMs' | 'yawWeight' | 'pitchWeight'>;
1909
+
1910
+ export interface GestureDefinition {
1911
+ id: 'gesture';
1912
+ family: 'action';
1913
+ definitionVersion: 1;
1914
+ parameters: AgencyParameter<keyof GestureEditableConfiguration>[];
1915
+ }
1916
+ export interface GazeDefinition {
1917
+ id: 'gaze';
1918
+ family: 'cognition';
1919
+ definitionVersion: 2;
1920
+ parameters: AgencyParameter<keyof GazeEditableConfiguration>[];
1921
+ }
1922
+ export interface CameraContextDefinition {
1923
+ id: 'cameraContext';
1924
+ family: 'perception';
1925
+ definitionVersion: 1;
1926
+ parameters: AgencyParameter<keyof CameraContextEditableConfiguration>[];
1927
+ }
1928
+
1929
+ /** Safe decision projection, not an execution receipt or replay record. */
1930
+ export interface GazeDecision {
1931
+ trigger: string;
1932
+ configurationRevision: number;
1933
+ source: string;
1934
+ mode: string;
1935
+ predictive: boolean;
1936
+ outcome: 'requested' | 'reset-requested' | 'cancel-requested' | 'ignored' | 'rejected' | 'state-updated';
1937
+ reason: string | null;
1938
+ confirmation: 'unobserved';
1939
+ at: number;
1940
+ }
1941
+
1942
+ export interface GestureDecision {
1943
+ sequence: number;
1944
+ trigger: string;
1945
+ configurationRevision: number;
1946
+ at: number;
1947
+ status: 'requested' | 'not-requested';
1948
+ reason: string | null;
1949
+ activeRequestCount: number;
1950
+ }
1951
+
1952
+ export interface CameraContextDecision {
1953
+ sequence: number;
1954
+ configurationRevision: number;
1955
+ outcome: 'requested' | 'published';
1956
+ observedAt: number;
1957
+ publishedAt?: number;
1958
+ gazeGeometryComplete: boolean;
1959
+ }
1960
+
1961
+ export interface ProsodicDefinition {
1962
+ id: 'prosodic';
1963
+ family: 'action';
1964
+ definitionVersion: 1;
1965
+ parameters: ProsodicParameter[];
1966
+ }
1967
+
1968
+ export interface ProsodicOptions extends ProsodicConfig {
1969
+ /** Private constructor-only millisecond clock for synthetic fixtures. Never serialized. */
1970
+ now?: () => number;
1971
+ }
1972
+
1973
+ export interface ProsodicConfigurationReceipt extends AgencyConfigurationReceipt {}
1974
+
1975
+ /** A compact inspection projection, not a complete captured-session replay record. */
1976
+ export interface ProsodicDecision {
1977
+ sequence: number;
1978
+ trigger: string;
1979
+ configurationRevision: number;
1980
+ at: number;
1981
+ outcomes: Array<
1982
+ | {
1983
+ status: 'selected';
1984
+ gesture: string;
1985
+ name: string;
1986
+ effectivePeak: number;
1987
+ /** Request emitted, not a rendered-frame acknowledgment. */
1988
+ outcome: 'requested';
1989
+ }
1990
+ | { status: 'missing-asset'; gesture: string; reason: string }
1991
+ | { status: 'suppressed'; reason: string }
1992
+ >;
1040
1993
  }
1041
1994
 
1042
1995
  export interface ProsodicState {
@@ -1049,7 +2002,23 @@ export interface ProsodicState {
1049
2002
  removedCount: number;
1050
2003
  lastGesture: string | null;
1051
2004
  lastBlinkFastCueAt: number;
1052
- config: Required<ProsodicConfig>;
2005
+ loadedSnippetNames: string[];
2006
+ config: Required<Omit<ProsodicConfig, 'browSnippet' | 'headSnippet'>>;
2007
+ configuration: { appliedRevision: number; applyBoundary: 'next-decision' };
2008
+ lifecycle: 'active' | 'quiescing' | 'disabled' | 'disposed';
2009
+ routingEnabled: boolean;
2010
+ cleanup: {
2011
+ status: 'not-requested' | 'requested';
2012
+ requestedNames: string[];
2013
+ requestedAt?: number;
2014
+ reason?: string;
2015
+ /** Prosodic does not receive runtime cleanup acknowledgments in this pilot. */
2016
+ confirmation: 'unobserved';
2017
+ };
2018
+ scheduler: { retained: number; limit: number; nextSequence: number; dropped: number };
2019
+ lastDecision: ProsodicDecision | null;
2020
+ rejectedCommandCount: number;
2021
+ lastCommandRejection: { commandType: string; reason: string } | null;
1053
2022
  lastEvent: null | Record<string, unknown>;
1054
2023
  }
1055
2024
 
@@ -1073,19 +2042,67 @@ export interface ConversationConfig {
1073
2042
  autoRespond?: boolean;
1074
2043
  maxHistory?: number;
1075
2044
  responseSource?: string;
2045
+ /** Text replies complete when accepted. Speech (default) waits for TTS playout. */
2046
+ responseDelivery?: 'speech' | 'text';
1076
2047
  ttsAgency?: string;
1077
2048
  interruptionMode?: string;
2049
+ /** Snapshot of module speech settings applied to each utterance. */
2050
+ speechConfig?: TTSConfig;
2051
+ transcriptionConfig?: TranscriptionConfig;
1078
2052
  autoListen?: boolean;
1079
2053
  detectInterruptions?: boolean;
2054
+ /** Minimum playback time for transcript fallback; sustained audio is accepted immediately. */
1080
2055
  minSpeakTime?: number;
1081
2056
  allowTranscriptInterruptionFallback?: boolean;
1082
2057
  eyeHeadTracking?: EyeHeadTrackingService | unknown;
1083
2058
  prosodicService?: unknown;
1084
2059
  }
1085
2060
 
2061
+ /** Context identities are scoped to one Conversation instance. Revisions strictly
2062
+ * increase for replacement and removal. Reset clears the bounded registry. */
2063
+ export interface ConversationContextRef {
2064
+ namespace: string;
2065
+ sourceId: string;
2066
+ revision: number;
2067
+ }
2068
+ export interface ConversationContext extends ConversationContextRef {
2069
+ schemaVersion: 1;
2070
+ /** Plain JSON, depth <= 8 and serialized length <= 8192 UTF-16 code units. */
2071
+ content: unknown;
2072
+ }
2073
+ export type ConversationTurnPurpose = 'explain' | 'ask' | 'clarify' | 'feedback';
2074
+ export interface ConversationRequestedTurn {
2075
+ /** Unique per session; duplicate requests are ignored, including after outcome. */
2076
+ requestId: string;
2077
+ purpose: ConversationTurnPurpose;
2078
+ objective: string;
2079
+ }
2080
+ export interface ConversationSpeechIdentity {
2081
+ turnId: string;
2082
+ requestId?: string;
2083
+ speechId: string;
2084
+ }
2085
+ export interface ConversationTurnOutcome {
2086
+ type: 'conversation.turnOutcome';
2087
+ agency: 'conversation';
2088
+ requestId: string;
2089
+ turnId: string;
2090
+ purpose: ConversationTurnPurpose;
2091
+ contextRefs: ConversationContextRef[];
2092
+ outcome: 'completed' | 'interrupted' | 'cancelled' | 'failed';
2093
+ /** True when playout began; only completed guarantees the whole text played. */
2094
+ spoken: boolean;
2095
+ reason: string;
2096
+ at: number;
2097
+ }
2098
+
1086
2099
  export interface ConversationProviderRequest {
1087
2100
  requestId: string;
1088
- text: string;
2101
+ /** Null for agency-requested dialogue; never fabricated as a user utterance. */
2102
+ text: string | null;
2103
+ requestedTurn?: ConversationRequestedTurn | null;
2104
+ contextRefs?: ConversationContextRef[];
2105
+ contexts?: ConversationContext[];
1089
2106
  source: string;
1090
2107
  turnId?: string;
1091
2108
  history: Array<Record<string, unknown>>;
@@ -1124,6 +2141,9 @@ export interface ConversationHistoryEntry {
1124
2141
  source?: string;
1125
2142
  at?: number;
1126
2143
  turnId?: string | null;
2144
+ requestId?: string | null;
2145
+ contextRefs?: ConversationContextRef[];
2146
+ deliveryStatus?: 'requested' | 'pending' | 'speaking' | 'delivered' | 'interrupted' | 'cancelled' | 'failed';
1127
2147
  }
1128
2148
 
1129
2149
  export interface ConversationState {
@@ -1133,7 +2153,15 @@ export interface ConversationState {
1133
2153
  turnId: string | null;
1134
2154
  history: ConversationHistoryEntry[];
1135
2155
  context: Record<string, unknown>;
2156
+ contexts: Record<string, ConversationContext>;
2157
+ contextVersions: Record<string, number>;
2158
+ activeTurn: (Partial<ConversationRequestedTurn> & { turnId: string; contextRefs: ConversationContextRef[]; speechId?: string; responseDelivery: 'speech' | 'text'; spoken: boolean }) | null;
2159
+ seenRequests: string[];
1136
2160
  pendingResponse: Record<string, unknown> | null;
2161
+ activeSpeechId: string | null;
2162
+ /** Conversation has requested speech resources that still require release. */
2163
+ speechResourcesRequested: boolean;
2164
+ playbackStartedAt: number | null;
1137
2165
  lastProviderError: string | null;
1138
2166
  lastUserText: string | null;
1139
2167
  lastAgentText: string | null;
@@ -1154,10 +2182,20 @@ export type ConversationDispatch =
1154
2182
  | { type: 'start' }
1155
2183
  | { type: 'stop'; reason?: string }
1156
2184
  | { type: 'reset' }
1157
- | { type: 'cancel' | 'interrupt'; reason?: string }
2185
+ | { type: 'cancel' | 'interrupt'; reason?: string; requestId?: string }
2186
+ | ({ type: 'context.upsert' } & ConversationContext)
2187
+ | ({ type: 'context.remove' } & ConversationContextRef)
2188
+ | ({ type: 'requestTurn'; contextRefs: ConversationContextRef[] } & ConversationRequestedTurn)
2189
+ | ({ type: 'ttsSpeechStarted' | 'ttsSpeechStopped' | 'ttsSpeechEnded' | 'ttsSpeechFailed'; reason?: string; message?: string } & ConversationSpeechIdentity)
1158
2190
  | { type: 'transcript.final' | 'transcriptFinal' | 'userUtterance'; text?: string; transcript?: string; utterance?: string; responseText?: string; agentText?: string; source?: string }
1159
2191
  | { type: 'agentUtterance' | 'responseReady'; text?: string; utterance?: string; responseText?: string; agentText?: string; requestId?: string; turnId?: string; source?: string }
1160
2192
  | { type: 'providerError'; requestId?: string; turnId?: string; message?: string }
2193
+ | { type: 'listen' | 'pauseListening' | 'flowStart' }
2194
+ | { type: 'audioInterruption' | 'transcriptInterruption'; event?: InterruptionEvent; text?: string }
2195
+ | { type: 'transcript.partial'; text: string }
2196
+ | { type: 'ttsSpeechStarted' | 'ttsSpeechStopped' | 'ttsSpeechEnded'; name?: string }
2197
+ | { type: 'transcriptionStatus'; status: string; reason?: string }
2198
+ | { type: 'ttsError'; message: string }
1161
2199
  | { type: 'tts.status'; status?: string };
1162
2200
 
1163
2201
  export interface TranscriptionConfig {
@@ -1207,6 +2245,7 @@ export interface TranscriptionState {
1207
2245
  export type TranscriptionDispatch =
1208
2246
  | { type: 'configure'; config: TranscriptionConfig }
1209
2247
  | { type: 'start' }
2248
+ | { type: 'release' }
1210
2249
  | { type: 'stop' | 'cancel'; reason?: string }
1211
2250
  | { type: 'reset' }
1212
2251
  | { type: 'providerPartial' | 'partialTranscript' | 'providerFinal' | 'finalTranscript'; text?: string; transcript?: string; confidence?: number; source?: string; speaker?: string }
@@ -1217,6 +2256,7 @@ export type TranscriptionDispatch =
1217
2256
 
1218
2257
  export interface CameraContextConfig {
1219
2258
  coalesceMs?: number;
2259
+ /** Expiry for streaming owners; defaults to 0 (valid until explicitly invalidated). */
1220
2260
  staleAfterMs?: number;
1221
2261
  epsilon?: number;
1222
2262
  yawWeight?: number;
@@ -1237,11 +2277,22 @@ export interface PolymerQuaternion {
1237
2277
  }
1238
2278
 
1239
2279
  export interface CameraContextState {
2280
+ configuration: AgencyConfigurationState;
2281
+ family: 'perception';
2282
+ lastDecision: CameraContextDecision | null;
2283
+ /** Safe bounded inspection projection; full operational snapshots are not redacted. */
2284
+ summary: Pick<CameraContextState,
2285
+ 'status' | 'stale' | 'updateCount' | 'publishedCount' | 'lastUpdatedAt' | 'lastPublishedAt' | 'lastDecision'>;
1240
2286
  agency: 'cameraContext';
1241
2287
  status: string;
1242
2288
  cameraPosition: PolymerVector3 | null;
2289
+ cameraQuaternion: PolymerQuaternion;
1243
2290
  targetPosition: PolymerVector3 | null;
2291
+ gazeOrigin: PolymerVector3 | null;
1244
2292
  modelQuaternion: PolymerQuaternion;
2293
+ verticalFovDegrees: number;
2294
+ aspect: number;
2295
+ gazeGeometryComplete: boolean;
1245
2296
  relativeOffset: Required<GazeTarget>;
1246
2297
  lastUpdatedAt: number | null;
1247
2298
  lastPublishedAt: number | null;
@@ -1261,14 +2312,145 @@ export type CameraContextDispatch =
1261
2312
  type: 'updateCamera';
1262
2313
  facts?: Record<string, unknown>;
1263
2314
  cameraPosition?: PolymerVector3;
2315
+ cameraQuaternion?: PolymerQuaternion;
1264
2316
  targetPosition?: PolymerVector3;
2317
+ gazeOrigin?: PolymerVector3;
2318
+ characterEyePosition?: PolymerVector3;
1265
2319
  modelQuaternion?: PolymerQuaternion;
2320
+ verticalFovDegrees?: number;
2321
+ aspect?: number;
1266
2322
  source?: string;
1267
2323
  }
1268
2324
  | { type: 'publishCameraFacts' }
1269
2325
  | { type: 'invalidateStale'; reason?: string }
1270
2326
  | { type: 'reset' };
1271
2327
 
2328
+ export interface UserPerceptionConfig {
2329
+ enabled?: boolean;
2330
+ /** Package asset by default; may be replaced by a host-controlled URL. */
2331
+ modelAssetPath?: string;
2332
+ /** Package MediaPipe WASM directory by default; may be replaced by a URL. */
2333
+ wasmRoot?: string;
2334
+ /** Package inference worker by default; may be replaced by a worker URL. */
2335
+ workerUrl?: string;
2336
+ delegate?: 'CPU' | 'GPU';
2337
+ maxFaces?: number;
2338
+ maxInferenceFps?: number;
2339
+ minDetectionConfidence?: number;
2340
+ minTrackingConfidence?: number;
2341
+ minPresenceConfidence?: number;
2342
+ /** Retained history over 50ms, from 0 (immediate) through 0.95. Applied to eye-midpoint XY using elapsed time. */
2343
+ smoothing?: number;
2344
+ /** Whether inference pixels are already mirrored. Default false reflects raw webcam X into display-right NDC; a CSS preview does not affect this. */
2345
+ mirrored?: boolean;
2346
+ /** Default true: use the first accepted face as this session's eye-contact origin. False retains absolute webcam-centered coordinates. */
2347
+ centerOnFirstFace?: boolean;
2348
+ faceLossGraceMs?: number;
2349
+ maxRetries?: number;
2350
+ retryBackoffMs?: number;
2351
+ candidatePriority?: number;
2352
+ diagnosticIntervalMs?: number;
2353
+ videoWidth?: number;
2354
+ videoHeight?: number;
2355
+ /** Assumed/calibrated webcam vertical field of view. Default 50 degrees. */
2356
+ viewerVerticalFovDegrees?: number;
2357
+ /** Physical eye-center spacing used only for diagnostic depth estimation. Default 0.063m. */
2358
+ viewerInterpupillaryDistanceMeters?: number;
2359
+ /** Fixed projection depth shared by webcam and mouse tracking. Default 0.8m. */
2360
+ viewerFallbackDepthMeters?: number;
2361
+ /** Lower bound for diagnostic depth only. */
2362
+ viewerMinDepthMeters?: number;
2363
+ /** Upper bound for diagnostic depth only. */
2364
+ viewerMaxDepthMeters?: number;
2365
+ }
2366
+
2367
+ export interface UserPerceptionCameraMetadata {
2368
+ width: number;
2369
+ height: number;
2370
+ }
2371
+
2372
+ export interface UserPerceptionFaceObservation {
2373
+ id: string;
2374
+ confidence?: number;
2375
+ score?: number;
2376
+ bounds: {
2377
+ left: number;
2378
+ top: number;
2379
+ right: number;
2380
+ bottom: number;
2381
+ width: number;
2382
+ height: number;
2383
+ area: number;
2384
+ center: PolymerVector3;
2385
+ };
2386
+ /** Binocular midpoint of the anatomical eye-corner centers, independent of pupil motion. */
2387
+ eyeMidpoint: PolymerVector3;
2388
+ /** Compact separation; z uses MediaPipe's approximate normalized-X depth scale. */
2389
+ interocularDistance?: { x: number; y: number; z?: number; normalized?: number };
2390
+ /** Observational iris offset, not calibrated gaze direction. */
2391
+ irisDirection?: { x: number; y: number } | null;
2392
+ headPose: { yaw: number; pitch: number; roll: number };
2393
+ }
2394
+
2395
+ export interface UserPerceptionState {
2396
+ agency: 'userPerception';
2397
+ status: 'idle' | 'requestingPermission' | 'loadingModel' | 'ready' | 'observing' | 'faceLost' | 'backingOff' | 'stopped' | 'error' | string;
2398
+ active: boolean;
2399
+ visible: boolean;
2400
+ permission: 'unknown' | 'prompt' | 'granted' | 'denied' | string;
2401
+ cameraStatus: string;
2402
+ /** Negotiated/captured dimensions, retained after stop until reset or acquisition. */
2403
+ cameraMetadata: UserPerceptionCameraMetadata | null;
2404
+ modelStatus: string;
2405
+ faceDetected: boolean;
2406
+ selectedFaceConfidence: number | null;
2407
+ target: Required<GazeTarget>;
2408
+ /** Fixed display-right/up normalized eye-midpoint offset for this activation; never persisted across sessions. */
2409
+ eyeContactReference: { x: number; y: number } | null;
2410
+ lastObservationAt: number | null;
2411
+ lastAcceptedAt: number | null;
2412
+ lastDiagnosticAt: number | null;
2413
+ lastCameraFact: Record<string, unknown> | null;
2414
+ lastObservation: Record<string, unknown> | null;
2415
+ lastError: { message: string; phase: string; retrying: boolean; at: number } | null;
2416
+ lastPlan: Record<string, unknown> | null;
2417
+ lastEvent: Record<string, unknown> | null;
2418
+ retryCount: number;
2419
+ capturedFrameCount: number;
2420
+ inferenceCount: number;
2421
+ acceptedCount: number;
2422
+ rejectedCount: number;
2423
+ droppedFrameCount: number;
2424
+ faceLostCount: number;
2425
+ config: Required<UserPerceptionConfig>;
2426
+ }
2427
+
2428
+ export type UserPerceptionDispatch =
2429
+ | { type: 'configure' | 'userPerception.configure'; config: UserPerceptionConfig }
2430
+ | { type: 'start' | 'userPerception.start' }
2431
+ | { type: 'stop' | 'userPerception.stop'; reason?: string }
2432
+ | { type: 'reset' | 'userPerception.reset' }
2433
+ | { type: 'recenter' | 'userPerception.recenter' }
2434
+ | Extract<PolymerDomainEvent, { type: 'camera.fact' | 'camera.stale' }>;
2435
+
2436
+ /** Injectable browser boundary for deterministic tests and alternate hosts. */
2437
+ export interface UserPerceptionRuntime {
2438
+ acquireCamera?(config: Required<UserPerceptionConfig>): unknown | Promise<unknown>;
2439
+ releaseCamera?(handle: unknown): void;
2440
+ createWorker?(url: string): Worker;
2441
+ loadModel?(config: Required<UserPerceptionConfig>): unknown | Promise<unknown>;
2442
+ observe?(config: Required<UserPerceptionConfig>, observedAt: number, requestId: string): {
2443
+ observedAt: number;
2444
+ faces: UserPerceptionFaceObservation[];
2445
+ cameraMetadata?: UserPerceptionCameraMetadata;
2446
+ } | Promise<{
2447
+ observedAt: number;
2448
+ faces: UserPerceptionFaceObservation[];
2449
+ cameraMetadata?: UserPerceptionCameraMetadata;
2450
+ }>;
2451
+ isDocumentVisible?(): boolean;
2452
+ }
2453
+
1272
2454
  export type LipSyncSchedulerQueueEntry =
1273
2455
  | {
1274
2456
  type: 'scheduleAnimation';
@@ -1328,7 +2510,7 @@ export type ProsodicSchedulerQueueEntry =
1328
2510
 
1329
2511
  export type TTSSchedulerQueueEntry =
1330
2512
  | {
1331
- type: 'webSpeechStartFallback';
2513
+ type: 'webSpeechStartFallback' | 'webSpeechBoundaryWatchdog' | 'ttsCompletionWatchdog';
1332
2514
  agency: 'tts';
1333
2515
  sessionId: number;
1334
2516
  delayMs: number;
@@ -1428,6 +2610,11 @@ export interface GestureConfig {
1428
2610
  gestureEmojiMappings?: Record<string, string>;
1429
2611
  }
1430
2612
 
2613
+ export interface GestureOptions extends GestureConfig {
2614
+ /** Private constructor-only clock; never authored in society data. */
2615
+ now?: () => number;
2616
+ }
2617
+
1431
2618
  export interface GestureGoal {
1432
2619
  gestureId?: string;
1433
2620
  id?: string;
@@ -1468,6 +2655,10 @@ export interface GestureActiveSnippet {
1468
2655
  }
1469
2656
 
1470
2657
  export interface GestureState {
2658
+ configuration: AgencyConfigurationState;
2659
+ lastDecision: GestureDecision | null;
2660
+ lifecycle: 'active' | 'disposed';
2661
+ rejectedCommandCount: number;
1471
2662
  agency: 'gesture';
1472
2663
  gestures: string[];
1473
2664
  gestureCount: number;
@@ -1506,6 +2697,14 @@ export interface PolymerInputStream<TCommand> extends PolymerStream<{ type: 'com
1506
2697
 
1507
2698
 
1508
2699
  export type PolymerDomainEvent =
2700
+ | { type: 'gaze.attention'; agency: 'gaze'; attention: GazeAttentionState }
2701
+ | { type: 'animation.requestObserveGaze'; agency: 'eyeHeadTracking'; requestId: string; sessionId: string; worldTarget: PolymerVector3 | null; timeoutMs: number; toleranceDeg: number }
2702
+ | { type: 'animation.requestCancelGazeObservation'; agency: 'eyeHeadTracking'; requestId: string | null }
2703
+ | { type: 'animation.gazeFeedback'; agency: 'animation'; requestId: string; status: 'observed' | 'unavailable'; reason: string; angularErrorDeg?: number; sampleSequence?: number; observedAt?: number; bindingGeneration?: number }
2704
+ | { type: 'gaze.configurationChanged'; agency: 'gaze'; configuration: AgencyConfigurationState; configure: GazeEditableConfiguration }
2705
+ | { type: 'gaze.decision'; agency: 'gaze'; decision: GazeDecision }
2706
+ | { type: 'gestureDecision'; agency: 'gesture'; decision: GestureDecision }
2707
+ | { type: 'gestureCommandRejected'; agency: 'gesture'; commandType: string; reason: 'revision-exhausted' }
1509
2708
  | {
1510
2709
  /** Diagnostic telemetry emitted after a graph route dispatch succeeds. */
1511
2710
  type: 'society.routeActivated';
@@ -1623,18 +2822,24 @@ export type PolymerDomainEvent =
1623
2822
  | GazeLookRequest
1624
2823
  | GazeResetRequest
1625
2824
  | GazeCancelRequest
1626
- | { type: 'camera.status'; agency: 'cameraContext'; status: string; stale: boolean; reason?: string; at: number }
2825
+ | { type: 'camera.status'; agency: 'cameraContext'; status: string; stale: boolean; reason?: string; at: number; configurationRevision: number }
1627
2826
  | {
1628
2827
  type: 'camera.fact';
1629
2828
  agency: 'cameraContext';
2829
+ configurationRevision: number;
1630
2830
  kind: 'camera.relative';
1631
2831
  sequence: number;
1632
2832
  source: string;
1633
2833
  observedAt: number;
1634
2834
  publishedAt: number;
1635
2835
  cameraPosition: Record<string, number>;
2836
+ cameraQuaternion: Record<string, number>;
1636
2837
  targetPosition: Record<string, number>;
2838
+ gazeOrigin: Record<string, number>;
1637
2839
  modelQuaternion: Record<string, number>;
2840
+ verticalFovDegrees: number;
2841
+ aspect: number;
2842
+ gazeGeometryComplete: boolean;
1638
2843
  relativeOffset: Required<GazeTarget>;
1639
2844
  stale: false;
1640
2845
  }
@@ -1648,6 +2853,55 @@ export type PolymerDomainEvent =
1648
2853
  lastObservedAt?: number;
1649
2854
  lastRelativeOffset?: Required<GazeTarget>;
1650
2855
  }
2856
+ | {
2857
+ type: 'userPerception.status';
2858
+ agency: 'userPerception';
2859
+ status: string;
2860
+ active: boolean;
2861
+ visible: boolean;
2862
+ permission: string;
2863
+ cameraStatus: string;
2864
+ cameraMetadata?: UserPerceptionCameraMetadata | null;
2865
+ modelStatus: string;
2866
+ faceDetected: boolean;
2867
+ reason?: string;
2868
+ at: number;
2869
+ }
2870
+ | {
2871
+ type: 'attention.fact';
2872
+ agency: 'userPerception';
2873
+ targetAgency: 'gaze';
2874
+ source: 'userPerception';
2875
+ label: string;
2876
+ observedAt: number;
2877
+ confidence?: number | null;
2878
+ priority: number;
2879
+ target: Required<GazeTarget>;
2880
+ screenTarget: Required<GazeTarget>;
2881
+ targetSpace: 'screen.ndc';
2882
+ viewerTarget: Required<GazeTarget>;
2883
+ viewerTargetSpace: 'webcam.viewer';
2884
+ viewerVerticalFovDegrees: number;
2885
+ viewerAspect: number;
2886
+ cameraMetadata?: UserPerceptionCameraMetadata | null;
2887
+ rawViewerTarget: Required<GazeTarget>;
2888
+ options?: GazeApplyOptions;
2889
+ rawTarget: Required<GazeTarget>;
2890
+ face: Pick<UserPerceptionFaceObservation, 'id' | 'bounds' | 'eyeMidpoint' | 'interocularDistance' | 'headPose' | 'irisDirection'>;
2891
+ }
2892
+ | {
2893
+ type: 'userPerception.observation';
2894
+ agency: 'userPerception';
2895
+ observedAt: number;
2896
+ confidence?: number | null;
2897
+ target: Required<GazeTarget>;
2898
+ viewerTarget: Required<GazeTarget>;
2899
+ cameraMetadata?: UserPerceptionCameraMetadata | null;
2900
+ face: Record<string, unknown>;
2901
+ at: number;
2902
+ }
2903
+ | { type: 'userPerception.faceLost'; agency: 'userPerception'; reason: string; lastSeenAt?: number; at: number }
2904
+ | { type: 'userPerception.ignored'; agency: 'userPerception'; reason: string; commandType?: string; at: number }
1651
2905
  | {
1652
2906
  type: 'eyeHeadTracking.status';
1653
2907
  agency: 'eyeHeadTracking';
@@ -1711,9 +2965,10 @@ export type PolymerDomainEvent =
1711
2965
  status?: 'checking' | 'ready' | 'error' | string;
1712
2966
  message?: string;
1713
2967
  }
1714
- | { type: 'ttsSpeechStarted'; agency: 'tts'; engine: string; name: string; startedAt: number }
1715
- | { type: 'ttsSpeechStopped'; agency: 'tts'; reason: string; stoppedAt: number }
1716
- | { type: 'ttsSpeechEnded'; agency: 'tts'; endedAt: number }
2968
+ | ({ type: 'ttsSpeechStarted'; agency: 'tts'; engine: string; name: string; text?: string; startedAt: number } & Partial<ConversationSpeechIdentity>)
2969
+ | ({ type: 'ttsSpeechStopped'; agency: 'tts'; reason: string; name?: string | null; stoppedAt: number } & Partial<ConversationSpeechIdentity>)
2970
+ | ({ type: 'ttsSpeechEnded'; agency: 'tts'; name?: string | null; endedAt: number } & Partial<ConversationSpeechIdentity>)
2971
+ | ({ type: 'ttsSpeechFailed'; agency: 'tts'; name?: string | null; message: string } & Partial<ConversationSpeechIdentity>)
1717
2972
  | { type: 'ttsPlaybackReferenceChanged'; agency: 'tts'; available: boolean; at: number }
1718
2973
  | {
1719
2974
  type: 'ttsWordBoundary';
@@ -1724,11 +2979,17 @@ export type PolymerDomainEvent =
1724
2979
  hostElapsedSec?: number;
1725
2980
  }
1726
2981
  | { type: 'lipSync.command'; agency: 'tts'; command: LipSyncDispatch }
2982
+ | { type: 'conversation.ttsReleaseReferenceRequested'; agency: 'conversation'; command: TTSDispatch }
2983
+ | { type: 'conversation.ttsConfigureRequested'; agency: 'conversation'; command: TTSDispatch }
2984
+ | { type: 'conversation.transcriptionRequested'; agency: 'conversation'; command: TranscriptionDispatch }
2985
+ | { type: 'conversation.transcript'; agency: 'conversation'; text: string; isFinal: boolean; isInterruption: boolean; at: number }
1727
2986
  | { type: 'conversation.status'; agency: 'conversation'; status: string; turnId?: string; reason?: string; at: number }
1728
2987
  | { type: 'conversation.userUtterance'; agency: 'conversation'; text: string; source: string; turnId?: string; at: number }
1729
- | { type: 'conversation.requestResponse'; agency: 'conversation'; targetAgency: string; requestId: string; text: string; source: string; turnId?: string; history: Array<Record<string, unknown>>; requestedAt: number }
2988
+ | ({ type: 'conversation.requestResponse'; agency: 'conversation'; targetAgency: string } & ConversationProviderRequest)
2989
+ | ({ type: 'conversation.turnRequested'; agency: 'conversation'; turnId: string; contextRefs: ConversationContextRef[]; spoken: false; at: number } & ConversationRequestedTurn)
2990
+ | ConversationTurnOutcome
1730
2991
  | { type: 'conversation.providerError'; agency: 'conversation'; requestId: string; turnId?: string; message: string; at: number }
1731
- | { type: 'conversation.agentUtterance'; agency: 'conversation'; text: string; at: number }
2992
+ | { type: 'conversation.agentUtterance'; agency: 'conversation'; text: string; source: string; turnId: string; at: number }
1732
2993
  | { type: 'conversation.cancelRequested'; agency: 'conversation'; targetAgency: string; reason: string; turnId?: string; at: number }
1733
2994
  | { type: 'conversation.ignored'; agency: 'conversation'; reason: string; requestId?: string; turnId?: string; at: number }
1734
2995
  | { type: 'tts.requestSpeak'; agency: 'conversation'; targetAgency: string; requestId: string; text: string; source: string; turnId?: string; command: TTSDispatch; requestedAt: number }
@@ -1758,6 +3019,8 @@ export type PolymerDomainEvent =
1758
3019
  | { type: 'gestureRemoved'; agency: 'gesture'; name: string; reason: string; removedAt: number }
1759
3020
  | { type: 'prosodicPlanCreated'; agency: 'prosodic'; plan: Record<string, unknown> }
1760
3021
  | { type: 'prosodicConfigChanged'; agency: 'prosodic'; state: ProsodicState }
3022
+ | { type: 'prosodicDecision'; agency: 'prosodic'; decision: ProsodicDecision }
3023
+ | { type: 'prosodicCommandRejected'; agency: 'prosodic'; rejection: { commandType: string; reason: string } }
1761
3024
  | { type: 'prosodicSpeechStarted'; agency: 'prosodic'; name?: string; startedAt: number }
1762
3025
  | { type: 'prosodicWordBoundary'; agency: 'prosodic'; word?: string; wordIndex: number; observedAt: number }
1763
3026
  | {
@@ -1770,7 +3033,7 @@ export type PolymerDomainEvent =
1770
3033
  scheduledAt: number;
1771
3034
  }
1772
3035
  | { type: 'prosodicStopped'; agency: 'prosodic'; reason: string; stoppedAt: number }
1773
- | { type: string; agency: 'conversation' | 'transcription' | 'cameraContext'; [key: string]: unknown }
3036
+ | { type: string; agency: 'conversation' | 'transcription' | 'cameraContext' | 'userPerception'; [key: string]: unknown }
1774
3037
  | {
1775
3038
  type: 'ready';
1776
3039
  agency:
@@ -1785,7 +3048,8 @@ export type PolymerDomainEvent =
1785
3048
  | 'prosodic'
1786
3049
  | 'conversation'
1787
3050
  | 'transcription'
1788
- | 'cameraContext';
3051
+ | 'cameraContext'
3052
+ | 'userPerception';
1789
3053
  }
1790
3054
  | { type: 'error'; agency: string; message: string };
1791
3055
 
@@ -1838,6 +3102,8 @@ export interface AnimationAgency {
1838
3102
  }
1839
3103
 
1840
3104
  export interface GazeAgency {
3105
+ /** Applies between decisions; pending requests retain their accepted policy. */
3106
+ applyConfiguration(update: { revision: number; configure: GazeEditableConfiguration }): AgencyConfigurationReceipt;
1841
3107
  input: PolymerInputStream<GazeDispatch>;
1842
3108
  events: PolymerStream<PolymerDomainEvent>;
1843
3109
  dispatch(command: GazeDispatch): void;
@@ -1934,6 +3200,8 @@ export interface TTSAgency {
1934
3200
  }
1935
3201
 
1936
3202
  export interface GestureAgency {
3203
+ /** Applies between decisions; already scheduled snippets are unchanged. */
3204
+ applyConfiguration(update: { revision: number; configure: GestureEditableConfiguration }): AgencyConfigurationReceipt;
1937
3205
  input: PolymerInputStream<GestureDispatch>;
1938
3206
  events: PolymerStream<PolymerDomainEvent>;
1939
3207
  dispatch(command: GestureDispatch): void;
@@ -1969,6 +3237,14 @@ export interface ProsodicAgency {
1969
3237
  /** Compatibility alias for events. Prefer subscribeEvents. */
1970
3238
  subscribeStatus(listener: (event: PolymerStatusEvent) => void): () => void;
1971
3239
  configure(config: ProsodicConfig): void;
3240
+ /** Atomic between decisions. Invalid/stale updates do not consume a revision.
3241
+ * Existing scheduled work retains its original policy. Reentrant calls during
3242
+ * a decision or configuration notification are rejected; retry at a boundary.
3243
+ */
3244
+ applyConfiguration(update: {
3245
+ revision: number;
3246
+ configure: ProsodicEditableConfiguration;
3247
+ }): ProsodicConfigurationReceipt;
1972
3248
  speechStarted(name?: string): void;
1973
3249
  wordBoundary(word: string, wordIndex?: number): void;
1974
3250
  blinkFast(): void;
@@ -1986,6 +3262,7 @@ export interface ConversationAgency {
1986
3262
  subscribeEvents(listener: (event: PolymerDomainEvent) => void): () => void;
1987
3263
  subscribe(listener: (event: PolymerStatusEvent) => void): () => void;
1988
3264
  subscribeStatus(listener: (event: PolymerStatusEvent) => void): () => void;
3265
+ startFlow(factory: (context: ConversationFlowContext) => ConversationFlow): void;
1989
3266
  start(): void;
1990
3267
  stop(): void;
1991
3268
  reset(): void;
@@ -2013,6 +3290,8 @@ export interface TranscriptionAgency {
2013
3290
  }
2014
3291
 
2015
3292
  export interface CameraContextAgency {
3293
+ /** Applies to new observations; already pending facts retain their policy. */
3294
+ applyConfiguration(update: { revision: number; configure: CameraContextEditableConfiguration }): AgencyConfigurationReceipt;
2016
3295
  input: PolymerInputStream<CameraContextDispatch>;
2017
3296
  events: PolymerStream<PolymerDomainEvent>;
2018
3297
  dispatch(command: CameraContextDispatch): void;
@@ -2028,12 +3307,32 @@ export interface CameraContextAgency {
2028
3307
  dispose(): void;
2029
3308
  }
2030
3309
 
3310
+ export interface UserPerceptionAgency {
3311
+ input: PolymerInputStream<UserPerceptionDispatch>;
3312
+ events: PolymerStream<PolymerDomainEvent>;
3313
+ dispatch(command: UserPerceptionDispatch): void;
3314
+ snapshot(): UserPerceptionState;
3315
+ subscribeInput(listener: (event: { type: 'command'; agency: 'userPerception'; command: UserPerceptionDispatch }) => void): () => void;
3316
+ subscribeEvents(listener: (event: PolymerDomainEvent) => void): () => void;
3317
+ subscribe(listener: (event: PolymerStatusEvent) => void): () => void;
3318
+ subscribeStatus(listener: (event: PolymerStatusEvent) => void): () => void;
3319
+ configure(config: UserPerceptionConfig): void;
3320
+ start(): void;
3321
+ stop(): void;
3322
+ /** Reset eye contact to the current face, or the next accepted face if none is visible. */
3323
+ recenter(): void;
3324
+ reset(): void;
3325
+ queue(): Array<Record<string, unknown>>;
3326
+ dispose(): void;
3327
+ }
3328
+
2031
3329
  export interface CharacterAgencySnapshot {
2032
3330
  blink: BlinkState;
2033
3331
  gaze: GazeState;
2034
3332
  eyeHeadTracking: EyeHeadTrackingState;
2035
3333
  gesture: GestureState;
2036
3334
  cameraContext: CameraContextState;
3335
+ userPerception: UserPerceptionState;
2037
3336
  transcription: TranscriptionState;
2038
3337
  conversation: ConversationState;
2039
3338
  tts: TTSState;
@@ -2049,6 +3348,7 @@ export type CharacterAgencyDispatch =
2049
3348
  | { agency: 'eyeHeadTracking'; command: EyeHeadTrackingDispatch }
2050
3349
  | { agency: 'gesture'; command: GestureDispatch }
2051
3350
  | { agency: 'cameraContext'; command: CameraContextDispatch }
3351
+ | { agency: 'userPerception'; command: UserPerceptionDispatch }
2052
3352
  | { agency: 'transcription'; command: TranscriptionDispatch }
2053
3353
  | { agency: 'conversation'; command: ConversationDispatch }
2054
3354
  | { agency: 'tts'; command: TTSDispatch }
@@ -2062,7 +3362,10 @@ export interface CharacterAgencies {
2062
3362
  readonly agencySociety: AgencySociety;
2063
3363
  dispatch(message: CharacterAgencyDispatch): void;
2064
3364
  snapshot(): CharacterAgencySnapshot;
2065
- /** Replace live route admission settings without recreating agency-local state. */
3365
+ /** Replace route admission; Prosodic quiesces before disabling. Nested updates
3366
+ * or re-enable during cleanup return the current society without applying.
3367
+ * Does not overwrite agency-local configure/priors values.
3368
+ */
2066
3369
  updateSocietyRouting(society: AgencySociety): AgencySociety;
2067
3370
  agency(name: 'animation'): AnimationAgency;
2068
3371
  agency(name: 'blink'): BlinkAgency | null;
@@ -2070,6 +3373,7 @@ export interface CharacterAgencies {
2070
3373
  agency(name: 'eyeHeadTracking'): EyeHeadTrackingAgency | null;
2071
3374
  agency(name: 'gesture'): GestureAgency | null;
2072
3375
  agency(name: 'cameraContext'): CameraContextAgency | null;
3376
+ agency(name: 'userPerception'): UserPerceptionAgency | null;
2073
3377
  agency(name: 'transcription'): TranscriptionAgency;
2074
3378
  agency(name: 'conversation'): ConversationAgency;
2075
3379
  agency(name: 'tts'): TTSAgency;
@@ -2123,6 +3427,7 @@ export interface AIExpressionSnippetMetadata {
2123
3427
  }
2124
3428
 
2125
3429
  export interface Snippet {
3430
+ semanticAnimation?: SemanticAnimation;
2126
3431
  name?: string;
2127
3432
  au?: Array<{ id: string | number; t?: number; v?: number; time?: number; intensity?: number; inherit?: boolean }>;
2128
3433
  viseme?: Array<{ key: string | number; t?: number; v?: number; time?: number; intensity?: number; inherit?: boolean }>;
@@ -2131,12 +3436,17 @@ export interface Snippet {
2131
3436
  isPlaying?: boolean;
2132
3437
  loop?: boolean;
2133
3438
  maxTime?: number;
3439
+ cleanupMaxTime?: number;
2134
3440
  duration?: number;
2135
3441
  snippetCategory?: 'auSnippet' | 'visemeSnippet' | 'eyeHeadTracking' | 'combined' | 'default' | string;
2136
3442
  snippetPriority?: number;
2137
3443
  snippetPlaybackRate?: number;
2138
3444
  snippetIntensityScale?: number;
2139
3445
  snippetBlendMode?: 'replace' | 'additive';
3446
+ /** Neutral reference sample for converting a clip to additive rotation. */
3447
+ additiveReferenceTimeSec?: number;
3448
+ /** Stable order for zero-fade, once-only additive controls, even after other clips are removed. */
3449
+ additiveCompositionOrder?: number;
2140
3450
  snippetJawScale?: number;
2141
3451
  autoVisemeJaw?: boolean;
2142
3452
  snippetBalance?: number;
@@ -2202,10 +3512,10 @@ export interface SnippetUIState {
2202
3512
  }
2203
3513
 
2204
3514
  export interface BakedClipChannelInfo {
2205
- channel: string;
3515
+ channel: BakedClipChannel;
2206
3516
  trackCount: number;
2207
3517
  playable: boolean;
2208
- blendMode?: 'replace' | 'additive';
3518
+ blendMode?: AnimationBlendMode;
2209
3519
  }
2210
3520
 
2211
3521
  export interface BakedClipInfo {
@@ -2222,7 +3532,6 @@ export interface BakedAnimationUIState {
2222
3532
  duration: number;
2223
3533
  speed: number;
2224
3534
  playbackRate: number;
2225
- weight: number;
2226
3535
  intensityScale: number;
2227
3536
  isPlaying: boolean;
2228
3537
  isPaused: boolean;
@@ -2319,7 +3628,7 @@ export interface AnimationService {
2319
3628
  bakedAnimationState$(clipName: string): AnimationObservable<BakedAnimationUIState | null>;
2320
3629
  loadFromJSON(data: Snippet | NormalizedSnippet): string;
2321
3630
  updateSnippet(data: Snippet | NormalizedSnippet): string;
2322
- schedule(data: Snippet | NormalizedSnippet, opts?: { startInSec?: number; startAtSec?: number; offsetSec?: number; priority?: number; autoPlay?: boolean }): string;
3631
+ schedule(data: Snippet | NormalizedSnippet, opts?: { startInSec?: number; startAtSec?: number; offsetSec?: number; priority?: number; autoPlay?: boolean; loop?: boolean; loopMode?: MixerLoopMode }): string;
2323
3632
  remove(name: string): void;
2324
3633
  play(): void;
2325
3634
  pause(): void;
@@ -2357,6 +3666,8 @@ export interface AnimationService {
2357
3666
  pauseBakedAnimation(clipName: string): void;
2358
3667
  resumeBakedAnimation(clipName: string): void;
2359
3668
  setBakedAnimationSpeed(clipName: string, speed: number): void;
3669
+ setBakedAnimationIntensity(clipName: string, intensity: number): void;
3670
+ /** @deprecated Use setBakedAnimationIntensity. */
2360
3671
  setBakedAnimationWeight(clipName: string, weight: number): void;
2361
3672
  setBakedAnimationLoop(clipName: string, loop: boolean): void;
2362
3673
  setBakedAnimationLoopMode(clipName: string, mode: MixerLoopMode): void;
@@ -2499,9 +3810,17 @@ export interface ConversationCallbacks {
2499
3810
  }
2500
3811
 
2501
3812
  export type ConversationFlow = Generator<string | Promise<string>, string | void, string>;
3813
+ export interface ConversationFlowContext {
3814
+ /** Current request signal. Capture before async work and check before applying results. */
3815
+ readonly signal: AbortSignal;
3816
+ }
2502
3817
 
2503
3818
  export interface ConversationService {
2504
- start(flowGenerator: () => ConversationFlow): void;
3819
+ start(flowGenerator: (context: ConversationFlowContext) => ConversationFlow): void;
3820
+ listen(): void;
3821
+ pauseListening(): void;
3822
+ interrupt(reason?: string): void;
3823
+ updateConfig(config: Partial<ConversationConfig>): void;
2505
3824
  stop(): void;
2506
3825
  getState(): {
2507
3826
  state: ConversationStateName;
@@ -2542,9 +3861,14 @@ export const playingBakedAnimations$: AnimationObservable<BakedAnimationUIState[
2542
3861
  export const bakedAnimationProgress$: AnimationObservable<AnimationEvent>;
2543
3862
  /** @deprecated Use AnimationService.bakedAnimationState$. */
2544
3863
  export function bakedAnimationState$(clipName: string): AnimationObservable<BakedAnimationUIState | null>;
2545
- export function createAnimationService(engine: Embody | EmbodyRuntime): AnimationService;
3864
+ export function createAnimationService(
3865
+ engine: Embody | EmbodyRuntime,
3866
+ options?: { agency?: AnimationAgency },
3867
+ ): AnimationService;
2546
3868
  export function createEyeHeadTrackingService(config?: EyeHeadTrackingConfig, callbacks?: EyeHeadTrackingCallbacks): EyeHeadTrackingService;
2547
3869
  export function createTranscriptionService(config?: TranscriptionConfig, callbacks?: TranscriptionCallbacks): TranscriptionService;
3870
+ export function createCharacterConversationService(agencies: CharacterAgencies, config?: ConversationConfig, callbacks?: ConversationCallbacks): ConversationService;
3871
+ /** @deprecated Character hosts should use createCharacterConversationService. */
2548
3872
  export function createConversationService(tts: { speak(text: string): Promise<unknown>; stop(): void }, transcription: TranscriptionService, config?: ConversationConfig, callbacks?: ConversationCallbacks): ConversationService;
2549
3873
  export const DEFAULT_HAIR_PHYSICS_ENABLED: boolean;
2550
3874
  export const DEFAULT_AGENCY_SOCIETY: AgencySociety;
@@ -2575,6 +3899,8 @@ export interface AgencySocietyEdge {
2575
3899
  enabled?: boolean;
2576
3900
  credit?: number;
2577
3901
  match?: Record<string, unknown>;
3902
+ /** Installed cleanup metadata. Setting this on a custom edge grants no bypass. */
3903
+ delivery?: 'cleanup';
2578
3904
  }
2579
3905
 
2580
3906
  export interface AgencySocietyKLine {
@@ -2602,30 +3928,405 @@ export interface AgencySociety {
2602
3928
 
2603
3929
  export function createBlinkAgency(config?: BlinkAgencyConfig): BlinkAgency;
2604
3930
  export function createAnimationAgency(config?: AnimationAgencyConfig): AnimationAgency;
2605
- export function createGazeAgency(config?: GazeConfig): GazeAgency;
3931
+ export function createGazeAgency(config?: GazeConfig, runtime?: EmbodyGazeRuntime): GazeAgency;
2606
3932
  export function createEyeHeadTrackingAgency(config?: EyeHeadTrackingConfig): EyeHeadTrackingAgency;
2607
3933
  export function createLipSyncAgency(config?: LipSyncConfig, runtime?: LipSyncRuntime): LipSyncAgency;
2608
3934
  export function createTTSAgency(config?: TTSConfig): TTSAgency;
2609
- export function createGestureAgency(config?: GestureConfig): GestureAgency;
2610
- export function createProsodicAgency(config?: ProsodicConfig): ProsodicAgency;
3935
+ export function createGestureAgency(config?: GestureOptions): GestureAgency;
3936
+ export function getProsodicDefinition(): ProsodicDefinition;
3937
+ export function getGestureDefinition(): GestureDefinition;
3938
+ export function getGazeDefinition(): GazeDefinition;
3939
+ export function getCameraContextDefinition(): CameraContextDefinition;
3940
+ export function createProsodicAgency(config?: ProsodicOptions): ProsodicAgency;
2611
3941
  export function createConversationAgency(config?: ConversationAgencyConfig): ConversationAgency;
2612
3942
  export function createTranscriptionAgency(config?: TranscriptionConfig): TranscriptionAgency;
2613
3943
  export function createCameraContextAgency(config?: CameraContextConfig): CameraContextAgency;
3944
+ export function createUserPerceptionAgency(config?: UserPerceptionConfig, runtime?: UserPerceptionRuntime): UserPerceptionAgency;
2614
3945
  export function createCharacterAgencies(config?: {
2615
3946
  blink?: BlinkAgencyConfig;
2616
3947
  gaze?: GazeConfig;
3948
+ gazeRuntime?: EmbodyGazeRuntime;
2617
3949
  eyeHeadTracking?: EyeHeadTrackingConfig;
2618
- gesture?: GestureConfig;
3950
+ gesture?: GestureOptions;
2619
3951
  cameraContext?: CameraContextConfig;
3952
+ userPerception?: UserPerceptionConfig;
3953
+ userPerceptionRuntime?: UserPerceptionRuntime;
2620
3954
  transcription?: TranscriptionConfig;
2621
3955
  conversation?: ConversationAgencyConfig;
2622
3956
  tts?: TTSConfig;
2623
3957
  lipSync?: LipSyncConfig;
2624
3958
  lipSyncRuntime?: LipSyncRuntime;
2625
- prosodic?: ProsodicConfig;
3959
+ prosodic?: ProsodicOptions;
2626
3960
  animation?: AnimationAgencyConfig;
2627
3961
  /** Compatibility injection point passed into Conversation's private effector. */
2628
3962
  conversationProvider?: ConversationProvider;
2629
3963
  /** Optional society wiring / priors seed (see AgencySociety). */
2630
3964
  agencySociety?: AgencySociety;
2631
3965
  }): CharacterAgencies;
3966
+
3967
+ // Versioned module definitions are pure JSON authoring data, not live agencies.
3968
+ export type ModuleType = 'aiChat' | 'frenchQuiz' | 'slideshow' | 'questionnaire';
3969
+ export interface ModuleVoiceOverride {
3970
+ engine: 'azure' | 'webSpeech';
3971
+ voiceName: string;
3972
+ rate: number;
3973
+ pitch: number;
3974
+ volume: number;
3975
+ }
3976
+ export interface ModuleCommonSettings {
3977
+ language?: string;
3978
+ instructions?: string;
3979
+ greeting?: string;
3980
+ inputMode?: 'voice' | 'text' | 'voiceAndText';
3981
+ interruptionMode?: 'cancel-agent' | 'ignore';
3982
+ /** null inherits voice from the character profile referenced by characterBinding. */
3983
+ voiceOverride?: ModuleVoiceOverride | null;
3984
+ }
3985
+ export interface ModuleUISettings {
3986
+ layout?: 'conversation' | 'stage' | 'backdrop' | 'contentFocus';
3987
+ panels?: Array<'agent' | 'transcript' | 'content' | 'input' | 'progress' | 'feedback'>;
3988
+ actions?: Array<'answer' | 'continue' | 'back' | 'skip' | 'exit' | 'discuss'>;
3989
+ }
3990
+ export interface ModuleTypeSettings {
3991
+ aiChat: { autoEmotion?: boolean; emotionIntensity?: number };
3992
+ frenchQuiz: { autoPlay?: boolean; showInstructions?: boolean };
3993
+ slideshow: { autoAdvance?: boolean; allowBrowse?: boolean; followPresenter?: boolean; initialSlide?: number };
3994
+ questionnaire: { mode?: 'quiz' | 'diagnostic' | 'unscored'; feedbackTiming?: 'immediate' | 'end' | 'none'; allowBack?: boolean; maxAttempts?: number };
3995
+ }
3996
+ export interface ModuleSettings<T extends ModuleType = ModuleType> {
3997
+ common?: ModuleCommonSettings;
3998
+ type?: ModuleTypeSettings[T];
3999
+ ui?: ModuleUISettings;
4000
+ }
4001
+ export interface ModuleContentReference {
4002
+ contentId: string;
4003
+ contentType: 'htmlDeck' | 'workbook' | 'questionnaire';
4004
+ /** Positive immutable content revision. */
4005
+ revision: number;
4006
+ }
4007
+ export interface ModuleCharacterBinding {
4008
+ characterId: string;
4009
+ /** Positive profile revision; profile data is not copied into the definition. */
4010
+ profileRevision: number;
4011
+ }
4012
+ export type ModuleDefinition<T extends ModuleType = ModuleType> = T extends ModuleType ? {
4013
+ schemaVersion: 1;
4014
+ moduleId: string;
4015
+ moduleType: T;
4016
+ /** 0 is an unsaved draft; saved definitions use positive revisions. */
4017
+ revision: number;
4018
+ name: string;
4019
+ description: string;
4020
+ characterBinding: ModuleCharacterBinding | null;
4021
+ contentRefs: ModuleContentReference[];
4022
+ settings: ModuleSettingsPatch<ModuleSettings<T>>;
4023
+ } : never;
4024
+ export type ResolvedModuleDefinition<T extends ModuleType = ModuleType> = T extends ModuleType
4025
+ ? Omit<ModuleDefinition<T>, 'settings'> & { settings: ModuleSettings<T> } : never;
4026
+ export interface ModuleValidationError { path: string; code: string; message: string }
4027
+ export interface ModuleValidationResult { valid: boolean; errors: ModuleValidationError[] }
4028
+ export interface ModuleSettingDescriptor {
4029
+ key: string;
4030
+ control: 'text' | 'textarea' | 'toggle' | 'number' | 'select' | 'multiSelect' | 'editor';
4031
+ label: string;
4032
+ help: string;
4033
+ default: unknown;
4034
+ group: 'common' | 'type' | 'ui';
4035
+ requiredCapabilities: string[];
4036
+ authorVisible: boolean;
4037
+ /** Editor visibility hint, not an authorization rule for participant data. */
4038
+ participantVisible: boolean;
4039
+ applyTiming: 'liveUi' | 'nextRun';
4040
+ options?: Array<{ value: string; label: string }>;
4041
+ min?: number;
4042
+ max?: number;
4043
+ maxLength?: number;
4044
+ editorSlot?: 'voiceOverride';
4045
+ }
4046
+ export interface ModuleTypeDescriptor {
4047
+ moduleType: ModuleType;
4048
+ label: string;
4049
+ available: boolean;
4050
+ unavailableReason: string | null;
4051
+ fields: ModuleSettingDescriptor[];
4052
+ editorSlots: Array<{ key: 'contentRefs'; editorSlot: 'deck' | 'questionnaire'; label: string; applyTiming: 'nextRun' }>;
4053
+ }
4054
+ export type ModuleReset = { $reset: true };
4055
+ /** Reset removes layered customization by restoring the built-in type default. */
4056
+ export type ModuleSettingsPatch<T> = T extends readonly unknown[] ? T | ModuleReset
4057
+ : T extends object ? { [K in keyof T]?: ModuleSettingsPatch<T[K]> } | ModuleReset
4058
+ : T | ModuleReset;
4059
+ export interface ModuleResolutionOptions {
4060
+ userDefaults?: ModuleSettingsPatch<ModuleSettings>;
4061
+ sessionOverrides?: ModuleSettingsPatch<ModuleSettings>;
4062
+ }
4063
+ export interface ModuleLaunchOptions extends ModuleResolutionOptions { sessionId: string; launchedAt: string }
4064
+ export interface ModuleLaunchSnapshot {
4065
+ schemaVersion: 1;
4066
+ sessionId: string;
4067
+ launchedAt: string;
4068
+ definitionIdentity: { moduleId: string; moduleType: ModuleType; schemaVersion: 1; revision: number };
4069
+ definition: ResolvedModuleDefinition;
4070
+ }
4071
+ export function getModuleTypeDescriptors(): ModuleTypeDescriptor[];
4072
+ export function getModuleDefinitionSchema(): Record<string, unknown>;
4073
+ export function getModuleSettingsDefaults<T extends ModuleType>(moduleType: T): ModuleSettings<T>;
4074
+ export function validateModuleDefinition(definition: unknown): ModuleValidationResult;
4075
+ /** Throws on unknown/invalid fields or versions; never mutates supplied data. */
4076
+ export function resolveModuleDefinition<T extends ModuleType>(definition: ModuleDefinition<T>, options?: ModuleResolutionOptions): ResolvedModuleDefinition<T>;
4077
+ /** coveredKeys must enumerate both rendered editors and effective runtime consumers. */
4078
+ export function validateModuleCapabilityCoverage(moduleType: ModuleType, coveredKeys: string[]): ModuleValidationResult;
4079
+ /** Detached JSON snapshot; unavailable type registrations cannot launch. */
4080
+ export function createModuleLaunchSnapshot(definition: ModuleDefinition, options: ModuleLaunchOptions): ModuleLaunchSnapshot;
4081
+
4082
+ /** Immutable authored content. This first version supports unscored questions only. */
4083
+ export interface QuestionnaireQuestionBase {
4084
+ /** Stable ASCII identifier, 1–120 characters; compatible with InteractionItemRef. */
4085
+ questionId: string;
4086
+ prompt: string;
4087
+ required: boolean;
4088
+ /** Private editorial notes, excluded from participant/Conversation projections. */
4089
+ authorNotes?: string;
4090
+ }
4091
+ export interface QuestionnaireChoiceQuestion extends QuestionnaireQuestionBase {
4092
+ kind: 'singleChoice';
4093
+ options: Array<{ optionId: string; label: string }>;
4094
+ }
4095
+ export interface QuestionnaireTextQuestion extends QuestionnaireQuestionBase {
4096
+ kind: 'text';
4097
+ /** Unicode codepoints after ECMAScript whitespace trimming; 0–16000. */
4098
+ minLength: number;
4099
+ /** 1–16000; no smaller than minLength. Optional empty answers bypass length bounds. */
4100
+ maxLength: number;
4101
+ }
4102
+ export type QuestionnaireQuestion = QuestionnaireChoiceQuestion | QuestionnaireTextQuestion;
4103
+ export interface QuestionnaireContent {
4104
+ schemaVersion: 1;
4105
+ contentId: string;
4106
+ contentType: 'questionnaire';
4107
+ /** Positive immutable revision. Draft revision zero is unsupported here. */
4108
+ revision: number;
4109
+ mode: 'unscored';
4110
+ locale: string;
4111
+ instructions: string;
4112
+ order: 'authored';
4113
+ questions: QuestionnaireQuestion[];
4114
+ completionMessage: string;
4115
+ authorNotes?: string;
4116
+ }
4117
+ export type QuestionnaireParticipantQuestion = Omit<QuestionnaireChoiceQuestion, 'authorNotes'>
4118
+ | Omit<QuestionnaireTextQuestion, 'authorNotes'>;
4119
+ export type QuestionnaireParticipantContent = Omit<QuestionnaireContent, 'authorNotes' | 'questions'>
4120
+ & { questions: QuestionnaireParticipantQuestion[] };
4121
+ /** Validation data only; this does not commit an answer or advance a run. */
4122
+ export interface QuestionnaireAnswer {
4123
+ questionId: string;
4124
+ /** Stable option ID, trimmed text, or explicit null for an optional empty answer. */
4125
+ value: string | null;
4126
+ }
4127
+ export type QuestionnaireAnswerValidationResult =
4128
+ | { valid: true; errors: []; answer: QuestionnaireAnswer }
4129
+ | { valid: false; errors: ModuleValidationError[]; answer: null };
4130
+ export interface QuestionnaireEditorDescriptor {
4131
+ schemaVersion: 1;
4132
+ editorSlot: 'questionnaire';
4133
+ contentType: 'questionnaire';
4134
+ contentSchemaId: string;
4135
+ supportedModes: ['unscored'];
4136
+ questionKinds: ['singleChoice', 'text'];
4137
+ order: 'authored';
4138
+ runtimeAvailable: false;
4139
+ fields: Array<{
4140
+ /** Canonical content path; [] denotes an authored list item. */
4141
+ key: string;
4142
+ control: 'identity' | 'text' | 'textarea' | 'toggle' | 'number' | 'select' | 'questionList' | 'optionList';
4143
+ label: string;
4144
+ help: string;
4145
+ participantVisible: boolean;
4146
+ applyTiming: 'nextRun';
4147
+ options?: string[];
4148
+ }>;
4149
+ }
4150
+ /** Structural JSON Schema; duplicate IDs, nonblank text, bounds ordering and total size also need semantic validation. */
4151
+ export function getQuestionnaireContentSchema(): Record<string, unknown>;
4152
+ export function getQuestionnaireEditorDescriptor(): QuestionnaireEditorDescriptor;
4153
+ export function validateQuestionnaireContent(content: unknown): ModuleValidationResult;
4154
+ /** Validates and returns detached allowlisted content. Throws on malformed/unsupported authored data. */
4155
+ export function projectQuestionnaireContent(content: QuestionnaireContent): QuestionnaireParticipantContent;
4156
+ /** No coercion, correctness, attempt counting or state mutation. Required blanks fail; optional blanks normalize to null. */
4157
+ export function validateQuestionnaireAnswer(content: unknown, questionId: string, value: unknown): QuestionnaireAnswerValidationResult;
4158
+ /** Pass the resolved launch definition. Checks unscored mode and one exact pinned bank; does not authorize or launch it. */
4159
+ export function validateQuestionnaireBinding(definition: unknown, content: unknown): ModuleValidationResult;
4160
+
4161
+ /** Shared data contracts. Each feature agency owns its state and scheduling. */
4162
+ export interface InteractionIdentity {
4163
+ schemaVersion: 1;
4164
+ /** Fresh for every mount/account generation, 1–64 ASCII characters. Never reuse. */
4165
+ instanceId: string;
4166
+ /** Pinned run identity; an account change requires a new session as well. */
4167
+ sessionId: string;
4168
+ definition: ModuleLaunchSnapshot['definitionIdentity'];
4169
+ contentRefs: ModuleContentReference[];
4170
+ }
4171
+ export interface InteractionItemRef { itemId: string; revision: number }
4172
+ export type InteractionPhase = 'configured' | 'running' | 'paused' | 'completed' | 'ended';
4173
+ export type InteractionTarget = 'session' | 'active' | 'viewed';
4174
+ export interface InteractionState {
4175
+ identity: InteractionIdentity;
4176
+ phase: InteractionPhase;
4177
+ lifecycleRevision: number;
4178
+ active: { item: InteractionItemRef | null; revision: number };
4179
+ viewed: { item: InteractionItemRef | null; revision: number };
4180
+ }
4181
+ /** Confirmed feature-planner decisions, not unvalidated host navigation requests. */
4182
+ export type InteractionConfirmation =
4183
+ | { type: 'lifecycle'; phase: InteractionPhase }
4184
+ | { type: 'active' | 'viewed'; item: InteractionItemRef | null };
4185
+ export type InteractionJSON = null | boolean | number | string | InteractionJSON[] | { [key: string]: InteractionJSON };
4186
+ export interface InteractionCheckpoint<T extends InteractionJSON = InteractionJSON> {
4187
+ schemaVersion: 1;
4188
+ identity: InteractionIdentity;
4189
+ activeItem: InteractionItemRef | null;
4190
+ viewedItem: InteractionItemRef | null;
4191
+ /** Feature-owned, versioned domain data. This API does not persist it. */
4192
+ payload: T;
4193
+ }
4194
+ export interface InteractionCorrelation {
4195
+ schemaVersion: 1;
4196
+ identity: InteractionIdentity;
4197
+ lifecycleRevision: number;
4198
+ target: InteractionTarget;
4199
+ contextRevision: number;
4200
+ item: InteractionItemRef | null;
4201
+ /** Instance-qualified ID. Send this returned value to Conversation/adapters. */
4202
+ requestId: string;
4203
+ }
4204
+ /** IDs describe edge-adapter resources; DOM/Embody handles never enter snapshots. */
4205
+ export interface InteractionViewDescriptor {
4206
+ layout: NonNullable<ModuleUISettings['layout']>;
4207
+ panels: NonNullable<ModuleUISettings['panels']>;
4208
+ /** Visible controls; feature policy separately determines allowed intents. */
4209
+ actions: NonNullable<ModuleUISettings['actions']>;
4210
+ availableActions: NonNullable<ModuleUISettings['actions']>;
4211
+ contentSurfaceId: string | null;
4212
+ }
4213
+ export interface InteractionSnapshot<T extends InteractionJSON = InteractionJSON> {
4214
+ interaction: InteractionState;
4215
+ view: InteractionViewDescriptor;
4216
+ /** Participant-safe projection selected by the owning feature. */
4217
+ domain: T;
4218
+ }
4219
+ export interface InteractionCapabilities {
4220
+ moduleType: ModuleType;
4221
+ available: boolean;
4222
+ unavailableReason: string | null;
4223
+ layouts: Array<NonNullable<ModuleUISettings['layout']>>;
4224
+ actions: NonNullable<ModuleUISettings['actions']>;
4225
+ contentSurface: boolean;
4226
+ }
4227
+ /** Contract implemented by feature composition inside Polymer. No generic
4228
+ * mutable session manager is supplied. Peers route internally; hosts send
4229
+ * external intents and render snapshots. configure/mount must validate before
4230
+ * changing state; later configuration applies to a new pinned run. dispose is
4231
+ * idempotent and releases only owned work/subscriptions/edge resources. */
4232
+ export interface InteractionModule<TIntent extends InteractionJSON, TDomain extends InteractionJSON> {
4233
+ configure(definition: ResolvedModuleDefinition): void;
4234
+ mount(launch: ModuleLaunchSnapshot, instanceId: string): void;
4235
+ dispatch(intent: TIntent): void;
4236
+ snapshot(): InteractionSnapshot<TDomain>;
4237
+ events: PolymerStream<{ type: 'interaction.snapshot'; snapshot: InteractionSnapshot<TDomain> }>;
4238
+ capabilities(): InteractionCapabilities;
4239
+ dispose(): void;
4240
+ }
4241
+ /** Throws on unsupported identities/versions or non-JSON data; returns detached data. */
4242
+ export function createInteractionState(identity: InteractionIdentity): InteractionState;
4243
+ export function confirmInteractionTransition(state: InteractionState, confirmation: InteractionConfirmation): InteractionState;
4244
+ export function captureInteractionCheckpoint<T extends InteractionJSON>(state: InteractionState, payload: T): InteractionCheckpoint<T>;
4245
+ /** Caller validates/restores the feature payload. An ended instance cannot resume. */
4246
+ export function restoreInteractionCheckpoint(state: InteractionState, checkpoint: InteractionCheckpoint): InteractionState;
4247
+ /** Local key: 1–56 Conversation-compatible ASCII characters, unique per mount. */
4248
+ export function createInteractionCorrelation(state: InteractionState, localRequestKey: string, target: InteractionTarget): InteractionCorrelation;
4249
+ /** Use the feature's current pending request; pass null after retirement. Accepts
4250
+ * correlated admission errors as well as outcomes. Event-type policy is local. */
4251
+ export function matchesInteractionOutcome(state: InteractionState, pending: InteractionCorrelation | null, outcome: { requestId?: string }): boolean;
4252
+
4253
+ /** Embody owns the complete Three camera/annotation runtime and lifecycle. */
4254
+ export {
4255
+ DPthree,
4256
+ DPthreeCameraController,
4257
+ DPthree3DMarkers,
4258
+ DPthreeHTMLMarkers,
4259
+ DPthreeMarkers,
4260
+ CameraDOMControls,
4261
+ CameraController,
4262
+ Markers3D,
4263
+ MarkersHTML,
4264
+ Controls,
4265
+ RUNTIME_ANNOTATION_REGION_PREFIX,
4266
+ filterCameraViewRegions,
4267
+ isRuntimeAnnotationRegionName,
4268
+ resolveMarkerAnchorRegion,
4269
+ shouldProjectMarkerAnchorToSurface,
4270
+ shouldUseFaceCenterForMarkerAnchor,
4271
+ createMarkerVisibilityLifecycle,
4272
+ createMarkerLifecycleRevealOptions,
4273
+ DEFAULT_MARKER_AUTO_HIDE_MS,
4274
+ boneResolutionProfile,
4275
+ createRuntimeAnnotationPreviewLifecycle,
4276
+ buildRegionDisplayOptions,
4277
+ getRuntimeAnnotationSide,
4278
+ getRuntimeAUMorphAnchorPoint,
4279
+ getRuntimeAUMeshSideOffset,
4280
+ DEFAULT_PREVIEW_AUTO_CLEAR_MS,
4281
+ inferRuntimeAnnotationPreviewSide,
4282
+ pickContinuumAnnotationAuId,
4283
+ resolveAnnotationCharacterConfig,
4284
+ RustAnnotationCameraCore,
4285
+ createRustAnnotationCameraCore,
4286
+ getDefaultAnnotationLaterality,
4287
+ getSemanticHorizontalSignForSide,
4288
+ getSemanticHorizontalSign,
4289
+ resolveRegionCameraAngle,
4290
+ resolveRegionVisibilityCameraAngle,
4291
+ toWorldDirection,
4292
+ getWorldDirectionForCameraAngle,
4293
+ getModelLocalOrbitAngle,
4294
+ passesMarkerCameraAngleGate,
4295
+ resolveFaceCenter,
4296
+ detectAnnotationLaterality,
4297
+ } from '@lovelace_lol/embody/three';
4298
+ export type {
4299
+ DPthreeConfig,
4300
+ CharacterValidationResult,
4301
+ ValidationCategory,
4302
+ ValidationItem,
4303
+ AnnotationCharacterConfig,
4304
+ AnnotationAnchorPoint,
4305
+ AnnotationMarkerAnchor,
4306
+ AnnotationMarkerAnchorType,
4307
+ AnnotationFocusTarget,
4308
+ AnnotationAnchoredRegion,
4309
+ RuntimeAnnotationMetadata,
4310
+ RuntimeAnnotationOptions,
4311
+ RuntimeAnnotationRegionSummary,
4312
+ RuntimeAnnotationSide,
4313
+ RuntimeAnnotationTargetType,
4314
+ CameraState,
4315
+ FocusPosition,
4316
+ DPthreeCameraControllerConfig,
4317
+ RegionChangeCallback,
4318
+ CharacterChangeCallback,
4319
+ DPthreeRegion,
4320
+ DPthreeCharacterConfig,
4321
+ DPthreeRegistry,
4322
+ DPthreeRegionChangeCallback,
4323
+ DPthreeCharacterChangeCallback,
4324
+ MarkerAnchorProjectionMode,
4325
+ ResolvedMarkerAnchorRegion,
4326
+ MarkerLifecycleRevealOptions,
4327
+ MarkerLifecycleRevealSource,
4328
+ DOMControlRegion,
4329
+ DOMControlsConfig,
4330
+ MarkerStateSnapshot,
4331
+ RuntimeAnnotationPreviewLifecycleOptions,
4332
+ } from '@lovelace_lol/embody/three';