@lovelace_lol/polymer 0.0.13 → 0.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -30,13 +30,10 @@ It retains the renderer lifecycle support and speech AU 103 in
30
30
  the existing `JAW.pitch.aus` list in CC4's `compositeRotations`. LipSync can send
31
31
  the AU through Animation and use its saved bone and morph outputs.
32
32
 
33
- The CC4 body-control catalog places hip bend, twist and side bend in **Whole
34
- body**, separate from the waist, spine and chest controls in **Torso**. These
35
- existing animation controls rotate the hip bone, which carries both torso and
36
- legs. They differ from import/setup rotation, which rotates the entire model
37
- object and saves its orientation as `modelRotation`. Hosts using
38
- `getBodyControls` inherit the catalog labels and sections unless a profile
39
- explicitly overrides them. Action IDs and bone motion stay unchanged.
33
+ The CC4 hip bend, twist and side-bend AUs rotate the mapped hip bone, carrying
34
+ both torso and legs. Import/setup rotation remains a model transform saved as
35
+ `modelRotation`. `getAUDescriptors()` reads each character's actual AU labels,
36
+ bone and morph bindings and supported outputs; editors use this data directly.
40
37
 
41
38
  The character host restores `profile.meshes` visibility and material settings
42
39
  through Embody when binding a model or applying `setProfile`. Explicit hidden
@@ -282,6 +279,44 @@ The host supplies the gesture library and may observe streams for diagnostics,
282
279
  but Gesture's own scheduler is responsible for replacement, cancellation, and
283
280
  completion cleanup before Animation owns the runtime side effect.
284
281
 
282
+ ### Animation API removal
283
+
284
+ Animation no longer exports `dispatch` or `AnimationDispatch`, and the character
285
+ `dispatch({ agency: "animation", ... })` envelope is rejected. Other agencies
286
+ retain their dispatch APIs. Hosts use the character's existing Animation agency:
287
+
288
+ ```js
289
+ const animation = agencies.agency("animation");
290
+ animation.loadBakedClips();
291
+ animation.playBaked("Walk", { loop: true });
292
+ animation.pauseBaked("Walk");
293
+ animation.resumeBaked("Walk");
294
+ animation.seekBaked("Walk", 0);
295
+ animation.updateBakedParams("Walk", { intensityScale: 0.5 });
296
+ animation.stopBaked("Walk");
297
+ animation.prepareSnippet(snippet); // Forces autoPlay: false.
298
+ animation.resumeSnippet(snippet.name);
299
+ animation.pauseSnippet(snippet.name);
300
+ animation.removeSnippet(snippet.name);
301
+ ```
302
+
303
+ `clearSnippets()` removes all scheduled snippets, including peer speech/gesture
304
+ work, while preserving baked clips. `stopAllBaked()` stops tracked baked handles,
305
+ including held actions, while preserving snippets. These are whole-agency
306
+ controls, not owner-scoped preview cancellation.
307
+
308
+ Peer messages use `AnimationInput` on `animation.input.write(command)`. Character
309
+ society routing delivers to that port while preserving route admission and
310
+ trusted source attribution. Worker hosts send
311
+ `{ type: "animationInput", id, command }`, without native handles or Three objects.
312
+ Named controls and peer input use the same private planner/scheduler path.
313
+
314
+ This removes Animation dispatch; it does not complete issue #306. The separate
315
+ AnimationService compatibility API, its catalog/event projections and native
316
+ baked bypass remain migration work. Unified source/instance controls,
317
+ configuration/inspection adoption, account storage and regional priority are
318
+ not delivered by this API removal.
319
+
285
320
  ### Animation snippet looping
286
321
 
287
322
  Animation snippets default to repeating when no loop preference is supplied.
@@ -421,19 +456,16 @@ action/composite mappings, and continuum pairs, then reconfigure the runtime. Au
421
456
  remain authoritative across preset upgrades. Clearing a role makes its semantic
422
457
  bone outputs dormant while preserving the rest of the profile.
423
458
 
424
- The CC4 preset supplies a complete role map and controls across torso, head,
425
- arms, hands/fingers, legs, and feet/toes. Bound UI controls use
426
- `engine.getBodyControls()` to read descriptors cached from the configured Rust
427
- profile/model, avoiding scene traversal and repeated profile parsing. The standalone
428
- `getBodyControls(profile, model)` query remains available for unbound authoring.
429
- `engine.resetBodyControls()` clears the configured catalog's action values and
430
- balances in one Rust call and applies one frame, preserving unrelated facial actions
431
- and morph-strength settings. Head, eye, and jaw controls
432
- share existing facial actuators; all controls use the existing AU/continuum
433
- host methods. Applications should show the full role inventory even when a
434
- model omits optional bones, and allow users to repair its assignments.
435
-
436
- Every configured Body action, including each direction of a signed control,
459
+ The CC4 preset supplies anatomical role bindings and AU mappings across torso,
460
+ head, arms, hands/fingers, legs and feet/toes. `engine.getAUDescriptors()` returns
461
+ metadata from the configured Rust profile/model. The standalone
462
+ `getAUDescriptors(profile, model)` query supports unbound authoring. Descriptors
463
+ include numeric AU IDs, directional pairs when configured, bilateral support,
464
+ actual bone/morph bindings, saved morph strength and availability diagnostics.
465
+ Applications use the normal AU setters and getters; clearing a pose means
466
+ releasing its owned AU values, balances and morph-strength overrides.
467
+
468
+ Every configured AU, including each direction of a signed control,
437
469
  can drive authored left/right/center morph mappings. Morph strength scales
438
470
  those outputs even when the action has no bone bindings. Persist the action's
439
471
  `auToMorphs` and `auMixDefaults`, and retain its mesh category: head, eye, and
@@ -633,31 +665,29 @@ request correlation and opaque checkpoint helpers. See
633
665
  composition interface, account/mount identity and scoped cleanup rules. These
634
666
  contracts do not enable a feature runtime or persist conversation/run data.
635
667
 
636
- ### Semantic pose capture and generated animations
637
-
638
- The Rust host exposes `getSemanticPoseCatalog()`, `captureSemanticPose()`,
639
- `validateSemanticPose(pose)` and `applySemanticPose(pose)`. The catalog follows
640
- the configured profile and actual model, including independent direction support,
641
- VRM role names, owned bone/morph names and authored default tissue strengths.
642
- Captures preserve both opposing directions, bilateral balance and morph strength;
643
- they represent manual semantic state, not inverse inference of a baked pose.
644
- Validation is atomic and lives in Embody. Known missing outputs may be saved for
645
- partial rigs; generation should offer only supported catalog directions.
646
-
647
- Normal `AnimationService.schedule` snippets may include `semanticAnimation`:
648
- `{version:1,durationSeconds,tracks:[{controlId,direction,balance?,morphStrength?,
649
- keyframes:[{time,intensity}]}]}`. Existing `curves` are non-Body facial AUs.
650
- Embody compiles both sets together into bone/morph tracks, preserving each tissue
651
- strength without changing live controls. Service save, pause, resume and cleanup
652
- use the existing snippet lifecycle. `validateSemanticAnimation(animation, options)`
653
- compiles without retaining clips. `semanticAnimationToClip(name, animation,
654
- options)` builds an ordinary playback handle; `options.faceCurves` adds facial
655
- curves for direct callers. IDs and direction labels come from the catalog, amounts
656
- are normalized `0..1`, balance is character-relative `-1` left / `+1` right, and
657
- times are seconds. Unknown controls, unsupported directions and invalid tracks fail.
668
+ ### AU pose capture and generated animations
669
+
670
+ A pose uses numeric AU values, character-relative balances and per-AU morph
671
+ weights. Hosts read `engine.getAUDescriptors()` for actual saved mappings and
672
+ use `getAU`, `getAUBalance` and `getAUMixWeight` to capture values, then their
673
+ corresponding setters to restore them. Host code validates a complete record
674
+ before applying it; Polymer adds no parallel named-control pose format.
675
+
676
+ `AnimationService.schedule` uses ordinary `curves` keyed by numeric AU ID for
677
+ both facial and body motion. A snippet may add `balanceMap` and `mixWeights`:
678
+ `{curves:{1001:[{time:0,intensity:0},{time:0.2,intensity:0.8}]},
679
+ balanceMap:{1001:-1},mixWeights:{1001:0.25}}`. Balance ranges from character-left
680
+ `-1` to character-right `+1`; morph weight changes tissue deformation without
681
+ attenuating the AU's bone movement. Embody compiles the saved bone/morph
682
+ mappings into native clips without changing live AU settings. Existing save,
683
+ pause/resume, held-final-pose, completion and removal behavior applies.
684
+
685
+ `LEGACY_BODY_CONTROL_AUS` is an immutable lookup for host loaders decoding old
686
+ persisted names to numeric AUs. It supplies no labels, availability, profile
687
+ configuration or runtime control behavior. New records use AU IDs directly.
658
688
 
659
689
  Manual `setAU` / `transitionAU` selects one configured continuum direction:
660
690
  nonzero input clears its opposite, negative input selects the opposite, and
661
691
  zero releases only the named action. `setContinuum` controls both directions
662
- explicitly. Body and individual AU/morph previews therefore share the same
663
- live state; separate axes remain independent.
692
+ explicitly. AU and individual morph previews share the same live state;
693
+ separate axes remain independent.
package/dist/index.d.ts CHANGED
@@ -15,6 +15,7 @@ import type {
15
15
  import type { GLTF } from 'three/examples/jsm/loaders/GLTFLoader.js';
16
16
  import type { ThreeModelReferencePose as EmbodyModelReferencePose } from '@lovelace_lol/embody/three';
17
17
  export type ThreeModelReferencePose = EmbodyModelReferencePose;
18
+ export type { AUDescriptor } from '@lovelace_lol/embody';
18
19
  import type {
19
20
  CharacterSceneRenderer,
20
21
  CharacterSceneBackend,
@@ -268,67 +269,6 @@ export interface HumanoidCharacterizationResolution {
268
269
  valid: boolean;
269
270
  }
270
271
 
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
272
  export interface Profile {
333
273
  name?: string;
334
274
  animalType?: string;
@@ -370,8 +310,6 @@ export interface Profile {
370
310
  disabledRegions?: string[];
371
311
  hairPhysics?: HairPhysicsProfileConfig;
372
312
  humanoidCharacterization?: HumanoidCharacterization;
373
- /** Authored overrides may patch individual fields; null removes a preset control. */
374
- bodyControls?: Record<string, Partial<BodyControlDefinition> | null>;
375
313
  }
376
314
 
377
315
  export interface ProfileRuntimeConfig extends Partial<Profile> {
@@ -699,16 +637,8 @@ export interface PolymerCharacterRuntime {
699
637
  getAU(id: number): number;
700
638
  /** Current procedural left/right balance (-1..1), or zero after clear. */
701
639
  getAUBalance(id: number): number;
702
- /** Resolve body controls from the configured Rust profile/model; no scene traversal. */
703
- getBodyControls(): BodyControlDescriptor[];
704
- getSemanticPoseCatalog(): SemanticPoseControl[];
705
- captureSemanticPose(): SemanticPose;
706
- validateSemanticPose(pose: SemanticPose): SemanticPose;
707
- applySemanticPose(pose: SemanticPose): void;
708
- validateSemanticAnimation(animation: SemanticAnimation, options?: SemanticAnimationOptions): void;
709
- semanticAnimationToClip(name: string, animation: SemanticAnimation, options?: SemanticAnimationOptions): AnimationPlaybackHandle;
710
- /** Reset configured body actions atomically; the next native mixer frame applies the pose. */
711
- resetBodyControls(): void;
640
+ /** Saved AU metadata resolved against the configured profile and model. */
641
+ getAUDescriptors(): import('@lovelace_lol/embody').AUDescriptor[];
712
642
  setContinuum(negAU: number, posAU: number, value: number, balance?: number): void;
713
643
  getContinuum(negAU: number, posAU: number): number;
714
644
  /** Mixer-timed live control; defaults to 200 ms. getAU reports its destination immediately. */
@@ -901,7 +831,9 @@ export declare function setHumanoidRoleBinding(profile: Profile, role: VRMHumano
901
831
  export declare function setAUMorphTargets(profile: Profile, auId: number, side: 'left' | 'right' | 'center', targets: Array<string | number>): Profile;
902
832
  export declare function resolveHumanoidCharacterization(profile: Profile): HumanoidCharacterizationResolution;
903
833
  /** With a model, reports missing bone/morph targets against the actual scene. */
904
- export declare function getBodyControls(profile: Profile, model?: Object3D): BodyControlDescriptor[];
834
+ export { getAUDescriptors } from '@lovelace_lol/embody';
835
+ /** @deprecated Immutable decoder lookup for historical host records; never a live control catalog. */
836
+ export { LEGACY_BODY_CONTROL_AUS } from '@lovelace_lol/embody';
905
837
  export declare const VISEME_KEYS: string[];
906
838
  export declare const HAIR_COLOR_PRESETS: Record<string, HairColorAppearance>;
907
839
  export declare const DEFAULT_HAIR_COLOR_APPEARANCE: HairColorAppearance;
@@ -1062,7 +994,7 @@ export interface AnimationAgencyConfig {
1062
994
  runtimeConfig?: Record<string, unknown>;
1063
995
  }
1064
996
 
1065
- export type AnimationDispatch =
997
+ export type AnimationInput =
1066
998
  | {
1067
999
  type: 'scheduleSnippet';
1068
1000
  sourceAgency?: string;
@@ -1071,6 +1003,8 @@ export type AnimationDispatch =
1071
1003
  }
1072
1004
  | { type: 'seekSnippet'; sourceAgency?: string; name: string; offsetSec: number }
1073
1005
  | { type: 'updateSnippet'; sourceAgency?: string; name: string; params: Record<string, unknown> }
1006
+ | { type: 'pauseSnippet'; sourceAgency?: string; name: string }
1007
+ | { type: 'resumeSnippet'; sourceAgency?: string; name: string; snippet?: PolymerAnimationSnippet; options?: { autoPlay?: boolean; [key: string]: unknown } }
1074
1008
  | { type: 'removeSnippet'; sourceAgency?: string; name: string }
1075
1009
  | { type: 'clear'; sourceAgency?: string }
1076
1010
  | { type: 'loadBakedClips'; sourceAgency?: string; clips?: AnimationBakedClipInfo[] }
@@ -2571,11 +2505,10 @@ export interface BlinkAgency {
2571
2505
  }
2572
2506
 
2573
2507
  export interface AnimationAgency {
2574
- input: PolymerInputStream<AnimationDispatch>;
2508
+ input: PolymerInputStream<AnimationInput>;
2575
2509
  events: PolymerStream<PolymerDomainEvent>;
2576
- dispatch(command: AnimationDispatch): void;
2577
2510
  snapshot(): AnimationState;
2578
- subscribeInput(listener: (event: { type: 'command'; agency: 'animation'; command: AnimationDispatch }) => void): () => void;
2511
+ subscribeInput(listener: (event: { type: 'command'; agency: 'animation'; command: AnimationInput }) => void): () => void;
2579
2512
  subscribeEvents(listener: (event: PolymerDomainEvent) => void): () => void;
2580
2513
  /** Compatibility alias for events. Prefer subscribeEvents. */
2581
2514
  subscribe(listener: (event: PolymerStatusEvent) => void): () => void;
@@ -2585,6 +2518,22 @@ export interface AnimationAgency {
2585
2518
  seekSnippet(name: string, offsetSec: number): void;
2586
2519
  updateSnippet(name: string, params: Record<string, unknown>): void;
2587
2520
  removeSnippet(name: string): void;
2521
+ pauseSnippet(name: string): void;
2522
+ resumeSnippet(name: string, snippet?: PolymerAnimationSnippet, options?: { autoPlay?: boolean; [key: string]: unknown }): void;
2523
+ /** Build a private native handle without playing. Forces autoPlay: false. */
2524
+ prepareSnippet(snippet: PolymerAnimationSnippet, options?: { autoPlay?: boolean; [key: string]: unknown }): void;
2525
+ /** Remove all scheduled snippets in this agency, including peer-owned snippets. Does not stop baked clips. */
2526
+ clearSnippets(): void;
2527
+ /** Refresh the catalog from the runtime when omitted; does not start playback or fetch assets. */
2528
+ loadBakedClips(clips?: AnimationBakedClipInfo[]): void;
2529
+ playBaked(name: string, options?: Record<string, unknown>): void;
2530
+ stopBaked(name: string): void;
2531
+ pauseBaked(name: string): void;
2532
+ resumeBaked(name: string): void;
2533
+ seekBaked(name: string, offsetSec: number): void;
2534
+ updateBakedParams(name: string, params: Record<string, unknown>): void;
2535
+ /** Stop all baked handles tracked by this agency, including held actions. Does not remove snippets. */
2536
+ stopAllBaked(): void;
2588
2537
  dispose(): void;
2589
2538
  }
2590
2539
 
@@ -2787,8 +2736,7 @@ export type CharacterAgencyDispatch =
2787
2736
  | { agency: 'conversation'; command: ConversationDispatch }
2788
2737
  | { agency: 'tts'; command: TTSDispatch }
2789
2738
  | { agency: 'lipSync'; command: LipSyncDispatch }
2790
- | { agency: 'prosodic'; command: ProsodicDispatch }
2791
- | { agency: 'animation'; command: AnimationDispatch };
2739
+ | { agency: 'prosodic'; command: ProsodicDispatch };
2792
2740
 
2793
2741
  export interface CharacterAgencies {
2794
2742
  input: PolymerInputStream<CharacterAgencyDispatch>;
@@ -2858,11 +2806,14 @@ export interface AIExpressionSnippetMetadata {
2858
2806
  }
2859
2807
 
2860
2808
  export interface Snippet {
2861
- semanticAnimation?: SemanticAnimation;
2862
2809
  name?: string;
2863
2810
  au?: Array<{ id: string | number; t?: number; v?: number; time?: number; intensity?: number; inherit?: boolean }>;
2864
2811
  viseme?: Array<{ key: string | number; t?: number; v?: number; time?: number; intensity?: number; inherit?: boolean }>;
2865
2812
  curves?: Record<string, CurvePoint[]>;
2813
+ /** Per-AU character-relative balance, -1 left to +1 right; canonical clip input. */
2814
+ balanceMap?: Record<string, number>;
2815
+ /** Per-AU morph strength; does not attenuate the AU's bone contribution. */
2816
+ mixWeights?: Record<string, number>;
2866
2817
  channels?: PolymerSnippetChannel[];
2867
2818
  isPlaying?: boolean;
2868
2819
  loop?: boolean;