npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@lovelace_lol/polymer

v0.0.38

Published

Pure ClojureScript character agencies for Character Loom.

Readme

Polymer

Character runtime hosts expose getViseme(index) and getVisemeJawScale(index) to snapshot direct mouth-shape controls before a temporary preview. These are read-only views of Embody's canonical state, not the current mixer pose. Unset, cleared, or out-of-range slots return 0 and 1. Restore a snapshot with setViseme(index, value, jawScale); restoring zero releases the direct override and allows any underlying clip to keep playing.

Clean ClojureScript character agency package for Character Loom.

The forwarded native character exposes readAUContribution(name), readAUState() and measureAUGoal(name, destinations) through the committed Embody 0.0.18 dependency. CharacterAUState and CharacterAUGoalMeasurement are re-exported from that same source API. Reads use the native action time and weight on demand; they do not advance playback. Non-AU animation sources are reported as unreadable. Animation-owned travel duration and authoring keep-pose behavior remain separate integrations; these exports do not implement them. This consumer uses the published stable runtime; no linked Embody preview is required.

Git stores Polymer source only. CI builds and tests package-affecting changes. Verified main builds publish versioned packages to npm for production; PRs and requested preview SHAs publish compiled packages to pkg.pr.new. Install the pkg-pr-new GitHub App on this repository. Committed dependencies pin exact stable npm versions. Linked PRs declare Depends-on: in the consumer PR body; CI installs the immutable compiled preview only in its temporary test checkout. Open downstream drafts before publication without changing committed pins. Git-SHA installs are unsupported because compiled JavaScript is not checked in.

Polymer's manifest and lockfile consume the public npm release @lovelace_lol/[email protected]:

@lovelace_lol/[email protected]

This release includes the Rust-backed ThreeAnnotations API and the mapping queries below.

resolveMorphMeshSelections exposes Embody's batched editor query through Polymer. It returns effective categories, explicit selection presence, loaded mesh names and unresolved configured names. Polymer supplies no second alias or routing implementation. Linked PR CI selects the exact compiled Embody preview from the upstream PR's Depends-on link and installs it only in the temporary test checkout. The committed manifest and lockfile retain exact stable npm versions. The release coordinator adopts the stable npm release after upstream merges; the production dependency check rejects committed preview pins.

decodeLegacyPose(record, profile?) and decodeLegacyAnimation(record, profile?) forward Embody's Rust compatibility readers for saved records containing historical named controls. Call them after await initEmbodyCore(); they are synchronous once the core is initialized. Pass the originating profile when it contains custom historical control mappings, and preserve decoder errors instead of silently dropping unresolved controls. Polymer forwards the functions and their declarations without maintaining another conversion table. Reading does not persist a migrated record: the caller owns any subsequent save. These readers do not introduce new control categories, consolidate eye mappings, or implement slider ownership.

getAUBoneBindingStates(profile, auIds) forwards Embody's read-only batched bone-link query. It returns the existing single-AU binding state keyed by AU ID, while serializing and parsing the bone inputs once for the requested set. The profile contains only assignments, bone-name rules, composite rotations, continuum pairs and humanoid characterization. Mappings consumers can reuse the result across mesh-only edits. Polymer adds no binding-resolution implementation or playback behavior.

The CC4 hip bend, twist and side-bend AUs rotate the mapped hip bone, carrying both torso and legs. Import/setup rotation remains a model transform saved as modelRotation. getAUDescriptors() reads each character's actual AU labels, bone and morph bindings and supported outputs; editors use this data directly. CC4's Elbow flex control maps forearm bones without assuming any muscle morphs. Body deformation requires an explicit character mapping to real model targets; Polymer adds no automatic elbow-to-muscle mapping. Package contract fixtures that exercise combined output author their synthetic morph mappings explicitly.

The character host restores profile.meshes visibility and material settings through Embody when binding a model or applying setProfile. Explicit hidden meshes, zero opacity, disabled depth flags, and render order survive a saved profile round trip. Hosts supply the selected character's profile; they do not need a second mesh-settings replay path.

Polymer is intentionally separate from Latticework and Polyester. It is built as a Society of Mind agency system: small CLJS agencies collaborate through streams, plan locally, schedule their own work, and keep side effects inside the agencies responsible for performing them.

See docs/agency-architecture.md for the agency collaboration, scheduler, side-effect, stream, and runtime-target rules. Live controls execute through native Embody playback; see its AU controls and authored animation contract. The former live-control overlay is a historical reference to the implementation retired in #300, with links to its native replacement. The animation command contract keeps agency planning in Polymer and frame execution in the rendering runtime. The October 10 frame audit preserves historical migration evidence and identifies its limits against current main.

Eye and head tracking (EyeHead) maps normalized pointer, webcam or manual targets onto saved AU clips; there is no geometry-backed focus solver. See the tracking contract for direction, timing, channel ownership and the current geometry limits. EyeHead requires the semantic measurement from linked Embody #174 and its Polymer facade forwarding in #341. Animation owns the travel timing policy. Trusted PR CI selects its published preview through the Depends-on declaration while leaving the committed production dependency stable.

Portable body animations

The native character (host.engine) exposes supportsVrmAnimation(), convertMixamoFbxToVrmAnimations(bytes, { metersPerUnit }), getVrmAnimationInfo(bytes) and registerVrmAnimation(name, bytes, { metersPerUnit }). Interchange bytes are VRM Animation 1.0 GLB (.vrma), not Three animation JSON. The source unit is explicit when converting FBX, and the target unit is explicit when registering.

Embody owns FBX inspection, file validation, bind-reference normalization and retargeting through the current profile's humanoid mapping. Its native Embody character supplies the captured reference and owns clip registration; Polymer forwards that same class and CharacterHost.engine. Registration does not play, reset, or stop an existing action. Hosts dispatch playBaked through the Animation agency after registration, so scheduling, blending and cancellation retain their owners. Unbound/disposed models and unsupported mappings reject before registration. releaseVrmAnimation(name) removes an owned registered source and is safe after host disposal; editors should stop playback through the agency before release. Release checks the captured model and source identity, so delayed cleanup cannot remove a same-name native clip that replaced the VRMA source. Failed conversion or registration preserves ownership of the previously accepted source.

These methods and their types are included in the committed stable Embody dependency. This forwarding extension uses the native runtime contract merged in #340; LoomLarge adopts the Polymer release containing both changes. Polymer adds no fallback runtime or alternate registration state. The existing semantic action-space format remains appropriate for authored AU/morph controls; it is distinct from portable humanoid rotation tracks.

Camera and annotation runtime

Polymer directly re-exports Embody's Three.js camera and annotation runtime. ThreeAnnotationController, ThreeAnnotationMarkers, HtmlAnnotationMarkers, OverlayAnnotationMarkers, and the ThreeAnnotations convenience wrapper are the same constructors provided by @lovelace_lol/embody/three. Rust in Embody owns annotation state, marker policy, camera transitions, input integration, and visibility timing. Its Three.js adapters own native scene/DOM resources, raycasting, and rendering. Polymer forwards these constructors and lifecycle helpers without a second implementation or annotation agency.

import { ThreeAnnotations } from '@lovelace_lol/polymer';

const annotations = await ThreeAnnotations.create({ scene, camera, domElement: canvas });
annotations.setModel(model);
await annotations.loadCharacter(characterConfig);
await annotations.focus('head');
// Call annotations.update() from the host's existing render loop.
// Call annotations.dispose() when its scene is destroyed.

Await ThreeAnnotations.create(options) or ThreeAnnotationController.create(config) before attaching a model; these factories initialize Embody Wasm. Direct construction requires prior initEmbodyCore() completion. The controller exposes ThreeAnnotationControls for target, enabled/damping/distance settings, updates, and change/start/end events; it is not a Three.js OrbitControls instance.

Hosts own editor UI, persistence, scene attachment, and external input wiring. Use createMarkerVisibilityLifecycle for automatic reveal/hide and createRuntimeAnnotationPreviewLifecycle for transient editor previews rather than implementing host timers. TypeScript declarations are forwarded from Embody so runtime classes and their public signatures stay aligned. The package contract checks constructor identity against the pinned Embody package.

getRuntimeAUMorphAnchorPoint(region, mesh) is also the unchanged Embody export. The helper and live markers share Embody's Rust attachment policy. Cached attachments refresh when native geometry or morph attributes change, using the committed stable Embody dependency. Hosts retain their existing annotation update() call; Polymer adds no attachment state or annotation agency. Geometry and lifecycle regression coverage belongs to Embody; Polymer's package contract checks the forwarded helper and controller identities.

For unmerged runtime changes, a consumer PR's Depends-on: declaration selects the compiled upstream preview in CI, and the resulting Polymer preview carries that exact dependency to the linked host PR. Preview URLs stay out of committed manifests and lockfiles. After upstream merge and publication, adopt the exact stable npm version and matching lockfile before production release.

For a newly attached model, await the existing camera controller's prepareModelForRender(model, { signal }) before reporting render readiness. Embody coordinates compilation and a full-scene preparation draw with its existing loop; Polymer forwards the method without another rendering scheduler. See model render readiness.

Pure unscored questionnaire content contracts cover authored choice/text validation, participant projection and editor metadata. They do not yet provide a Questionnaire runtime agency or participant UI.

Agency API

The package exposes an agency system. Its installed agencies and connections are declared together in src/polymer/agency_system_config.cljc; the code-only factory registry binds that data to existing implementations. Character construction, lookup, subscriptions, snapshots and cleanup use that declaration. The host's saved agencySociety supplies per-character settings and routing overrides, not a second implementation catalog.

const definition = getAgencySystemDefinition(); // Fresh JSON for inspection.
const agencies = createCharacterAgencies();
agencies.dispatch({ agency: "blink", command: { type: "enable" } });

agencies.events.subscribe((message) => {});
agencies.snapshot();

An agency system means the live objects and their scheduled work. It is not a second JavaScript engine or a learned memory system. LoomLarge's scene provider exposes references to this same system and the Embody engine to its UI; agency decisions and peer routing remain in Polymer. See the configuration and runtime terminology for ownership and the remaining agency-migration boundaries.

Agencies communicate through incoming and outgoing streams of plain data. Messages may be facts, goals, requests, constraints, priorities, status, or diagnostics. Requests that imply side effects are handled by the receiving agency's local planner, scheduler, and effector path.

Host applications may dispatch external commands, observe outgoing streams, and read snapshots for diagnostics or configuration. Host applications should not serve as the normal message bus between Polymer agencies.

The character profile may provide an agencySociety document that controls which existing agencies and message routes are enabled. A host-side graph editor can apply route admission changes without recreating agency-local state:

const nextSociety = agencies.updateSocietyRouting(profile.agencySociety);

The update is deliberately limited to routing. Agency-local configure and priors values are applied when createCharacterAgencies creates the agencies; the graph is not a central planner and cannot bypass their planners or schedulers.

Azure speech preparation

Explicit tts.configure(...) with engine azure and a backend or injected voice catalogue provider prepares that catalogue once. Construction performs no request. Unrelated volume/debug changes preserve in-flight loading; changing the engine, backend or providers rejects old results and prepares the new catalogue. An automatic catalogue failure reports ttsVoicesLoaded with status error without failing an active speech turn. Explicit loadVoices remains available for retry.

tts.dispatch({ type: 'warm', engine: 'azure', text: 'Hello', requestId: 'greeting' });

warm explicitly synthesizes into the agency's bounded Azure cache without priming or playing audio, emitting LipSync commands, or changing speech state. ttsWarmed includes text, optional requestId and cacheHit; validation or provider failure emits ttsWarmFailed instead of a speech failure. Warming requires nonblank text and an Azure synthesis provider/backend. Optional voice, style, styleDegree, rate and pitch match the later speak command.

The scheduler accepts one latest warm operation. Replacement, stop, reset, disposal or relevant synthesis-configuration changes suppress stale completion, failure and cache writes, even when the provider cannot abort. The shared speech cache distinguishes exact text, backend, injected provider, voice, style/style degree, rate and pitch. Provider or synthesis-setting changes clear that cache; volume only affects playback. No automatic warm request, playback, polling or retry is introduced. Applications still own choosing when and what to prepare; normal Conversation speech already proceeds without waiting for microphone arm.

Current Agencies

Idle is an opt-in Action agency for saved baked-clip profiles. It defaults to enabled: false, profiles: [], and dormant execution even with saved policy. Prepare/register the selected assets, publish Animation's playable catalog, then call character.agency('idle').start(). See the Idle host contract for strict configuration, manual playback precedence, and lifecycle ownership.

The installed configuration currently contains sixteen agencies: EyeHead Tracking, Idle, Blink, Animation, TTS, LipSync, Prosodic Expression, and Gesture, plus first slices for Conversation, Camera Context, User Perception, User Affect, Transcription, Appraisal, Affect, and Emote. These are implementation milestones, not the limits of the architecture. Each agency should follow the same local GOAP/planner, scheduler, stream, transform, and effector pattern as the package grows.

Conversation emits user emoji observations immediately and releases an agent's conversation.emojiBatch only when its matching speech actually starts. An interrupted, failed, or superseded utterance cannot release a stale cue. Native TTS requests strip the leading emoji/style controls and preserve inline SSML. The compatibility Conversation service borrows the shared agency without issuing a second TTS request alongside its supplied speech adapter.

Phase-one Appraisal evaluates a mapped emote's optional expression.appraisal checks: goalRelevance (0–1), goalCongruence (−1–1), certainty (0–1), and control (0–1). These are authored display presets, not inferred beliefs or psychological state. Load the same characterEmotes and emojiMappings into Appraisal and Emote using their loadLibrary commands. Legacy mappings without presets retain catalog behavior; explicit mappings take precedence over a saved emote's legacy emoji field.

A full-route preview bypasses speech but uses the same Appraisal → Affect → Emote → Animation path:

agencies.dispatch({ agency: "appraisal", command: {
  type: "appraise", emoteId: "reassure", emoji: "😊",
  checks: { goalRelevance: 0.8, goalCongruence: 0.6, certainty: 0.8, control: 0.5 },
  subject: "character", target: "character",
  evidenceType: "character-expression", source: "preview",
} });

The reassure emote must already exist in that library. Affect treats LLM cues and previews as display-only: they do not accumulate mood from the character's own output. Emote selects one pose and authors onset/attack/hold/release before requesting Animation. Explicit autonomous appraisals retain the existing mood path; contextual inference, persistent social feedback, and simultaneous emotion mixing are deferred. See phase-one scope and Emotion Discourse.

The text-only LipSync pronunciation fallback and its accuracy limits are documented in docs/lipsync-pronunciation-fallback.md.

The six supported live Prosodic settings, revision receipts, safe decision projection, and routing-disable lifecycle are documented in docs/prosodic-configuration-pilot.md. This bounded pilot preserves the other agencies and does not implement a global configuration controller or captured-session replay.

Gesture, Camera Context, EyeHead and Animation also expose portable definitions and agency-local revisioned settings. Animation and EyeHead share a bounded, redacted getInspection() contract showing actual inputs, decisions, queued work and native/request outcomes. Their supported consumers and apply boundaries are in docs/agency-configuration-families.md. The old Gaze agency is retired; the current character has sixteen agencies.

User Perception's camera, worker, model assets, compact fact contract, and privacy boundary are documented in docs/user-perception.md.

Experimental facial-expression hypotheses use the same capture source through the namespaced User Affect agency. Its local rules, configuration, uncertainty, and observation-only scope are documented in docs/user-affect-perception.md.

LipSync receives rig articulation from the resolved Embody preset/profile. After initEmbodyCore(), call resolveVisemeProfile(runtime.getProfile()) once when binding the character and after each profile change. The synchronous result contains visemeSlots in exact runtime order (missing order is zero; ties keep source order), weighted/numeric binding targets, resolved jaw amounts and tongue AU targets. Use its array indices for manual setViseme(index, value) preview and configure LipSync with { visemeProfile: descriptor }. This is a host profile projection boundary, not work to repeat every frame. A live descriptor change rebuilds the current clip at its current audio position. Explicitly configure this field after every runtime setProfile, even when the descriptor is unchanged: profile rebinding can clear animation actions. Polymer re-emits the active clip and seeks to the current audio position without restarting audio.

For custom slots, Azure speech matches providerIds.azure; generated English text matches phonemes using CMU/ARPABET symbols with optional stress digits. External timelines can carry provider and providerId, or a phoneme. A timeline containing only canonical visemeId needs an explicit providerIds.canonical route in a custom profile. An unknown, ambiguous, or unbound route retains neutral timing rather than selecting a guessed slot. Azure zero reaches a declared zero route; otherwise it is neutral. Custom slots can exceed 15: their model indices never become AU or jaw control IDs.

The descriptor overrides both visemeJawAmounts and visemeTongueTargets so articulation uses the same runtime slot order. Without a descriptor, the existing canonical 15-slot behavior and separately supplied tables remain supported. Keys-only profiles and the unchanged stock CC4 routing metadata also retain that behavior, including stock Azure refinement/diphthong rules. The stock metadata contains overlapping aliases, so replacing its routing metadata opts into explicit matching rather than silently changing legacy speech. Reordering all stock slots preserves canonical sound semantics through canonicalSlotIndices, which maps canonical sounds to their new runtime positions.

Resolution describes authored bindings, not successful deformation: a referenced morph can still be missing from the loaded model. Polymer does not infer a VRM loader, a complete system recipe, or target correctness from a slot's name. Weighted targets are resolved by Embody, and manual and speech channels use that same binding. Polymer owns timing, grouping, and curve planning; the profile owns jaw apertures and tongue poses. With no profile jaw amounts, no jaw track is authored.

Runtime-specific work belongs at the edge. The current animation path can target the existing web runtime, but Polymer core should remain able to support other runtime targets such as Babylon, Unity, Godot, robotics, or future animation libraries through separate runtime-specific boundaries.

Hair appearance and physics are model/runtime capabilities rather than character-behavior agencies. Polymer therefore does not expose a Hair agency, Hair planner, or Hair scheduler. It continues to re-export Embody's hair color, material-normalization, and Wasm physics APIs so hosts can configure the character runtime without importing Embody directly.

CC4 hair physics mappings come from the installed Embody preset. Its automatic head-motion response leaves Length_Short and Hairline_High_ALL unchanged. Explicit saved headUp and headDown mappings remain supported: their authored coefficients follow the observed head pitch and configured direction separately from spring gravity and responseScale tuning. Zero coefficients and cleared pose groups release their offsets. Saved structural mappings still apply unless the host clears them or sets their coefficients to zero. Adopting this behavior requires the compiled Embody package in Polymer and then Polymer's compiled package in the host; Polymer does not implement another hair response.

Gesture Agency

Gesture is the arm/hand gesticulation agency. It accepts LoomLarge-authored gesture snapshots as plain data and turns them into typed bone-channel animation snippets. It does not call scene bones, host UI state, storage, or the animation runtime. Gesture snapshots are compatible with the Gestures tab profile shape: id/name, optional description or text representation, emoji trigger, left/right/both/custom scope, captured source metadata, duration, priority, affected bones, static bone targets, and optional time-based keyframes.

Gesture commands can still name an exact gesture or emoji, but the planner also accepts gesture goals. A goal asks Gesture to choose from the authored library by intent, tags, scope, required or avoided effectors, cooldown, active conflicts, and capacity before it emits Animation requests.

Gesture GOAP Planning

Gesture GOAP plans for one local world-state transition: a suitable authored arm/hand gesture is selected and safely scheduled through Animation, or the agency records why that goal cannot be satisfied. It is not a second public playback API.

The planner uses the incoming command, the gesture library, active snippets, and Gesture config to decide:

  • which gesture best satisfies an explicit gesture id, emoji mapping, intent, tags, text hints, scope, and bone constraints
  • whether the selected gesture is valid motion and is out of cooldown
  • whether active snippets conflict on affected bones
  • whether capacity is available under maxActive
  • whether to ignore, remove only conflicting snippets, remove the oldest snippets needed for capacity, build a snippet, schedule Animation, and record the result

Host integrations should prefer the character agency network so Gesture's animation requests are routed to Animation inside Polymer:

const agencies = createCharacterAgencies({
  animation: { engine },
  gesture: {
    gestures: profile.characterGestures,
    emojiMappings: profile.gestureEmojiMappings,
  },
});

agencies.dispatch({ agency: "gesture", command: { type: "playEmoji", emoji: "👋" } });
agencies.dispatch({
  agency: "gesture",
  command: {
    type: "gesture.goal",
    goal: {
      intent: "greeting",
      tags: ["wave"],
      scope: "right",
      avoidBones: ["HAND_L"],
    },
  },
});

The host supplies the gesture library and may observe streams for diagnostics, but Gesture's own scheduler is responsible for replacement, cancellation, and completion cleanup before Animation owns the runtime side effect.

Authoring tools can preview a draft without loading or saving it:

const previewId = `preview:${crypto.randomUUID()}`;
agencies.dispatch({
  agency: "gesture",
  command: { type: "previewGesture", previewId, gesture: draft },
});
agencies.dispatch({ agency: "gesture", command: { type: "stopGesture", gestureId: previewId } });

previewGesture accepts a version 1 timed snapshot with 2–64 keyframes, a duration of 80–10000 milliseconds, and a consistent set of 1–64 bone IDs in every frame. Times must strictly increase from zero to the duration. Each bone uses rotationEuler: { pitch, yaw, roll, order: "XYZ", unit: "degrees" }, with finite angles between -360 and 360 degrees. Position, scale, quaternion, AU and loop payloads are rejected. Optional static bones and affectedBones must agree with the frame targets. Polymer cannot verify that IDs match a particular character; the authoring host must revalidate its current mapping before dispatch.

The caller supplies a unique previewId of 1–256 characters, distinct from the saved library and active saved playback IDs. Reusing it replaces that preview; other conflict and capacity decisions follow the ordinary Gesture configuration. The agency never adds previews to the library, emoji mappings or saved-gesture cooldown history. Stop, replacement and disposal release the owned snippet through Animation. Native removal clears Gesture bookkeeping without a duplicate request; a completed held preview requests release through Animation. Preview completion comes from the native handle through Animation feedback, so pausing does not expire a preview on elapsed wall time. Saved gesture deadline behavior is unchanged. A plan refusal includes plan.goal.previewId; validation errors include previewId. Runtime compilation errors arrive on the character event stream as Animation errors with sourceAgency: "gesture", previewId, and the snippet name.

Previews use the current Gesture configuration at the next decision boundary, including intensity, priority, conflict and capacity policy. Updating configuration does not rewrite an active preview; replaying the draft uses the new revision. Authored keyframe times remain unchanged. Preview commands use the same serialized decision queue and disposal lifecycle as saved gestures.

New snapshots can opt into rotationSpace: "local" to interpret Euler values as absolute local XYZ angles. Gesture emits all three axes, including explicit zero samples, and Embody compiles them together into one quaternion track per bone. Timed snapshots preserve authored endpoints and sign crossings. Static local snapshots inherit their entry pose, hold, then release through normal clip cleanup. With blendMode: "replace", the held pose reaches the authored local rotation. The default "additive" mode applies the authored rotation relative to the rig's captured reference on top of its underlying pose. Local zero is not assumed to be the reference pose; the authored coordinate space does not override blending. Snapshots without rotationSpace retain the legacy reference-relative channel semantics and static return envelope. Catalog or capture angles describe this rig's local transforms, not universal anatomical axes or an IK result.

Animation API removal

Animation no longer exports dispatch or AnimationDispatch, and the character dispatch({ agency: "animation", ... }) envelope is rejected. Other agencies retain their dispatch APIs. Hosts use the character's existing Animation agency:

const animation = agencies.agency("animation");
animation.loadBakedClips();
animation.playBaked("Walk", { loop: true });
animation.pauseBaked("Walk");
animation.resumeBaked("Walk");
animation.seekBaked("Walk", 0);
animation.updateBakedParams("Walk", { intensityScale: 0.5 });
animation.stopBaked("Walk");
animation.prepareSnippet(snippet); // Forces autoPlay: false.
animation.resumeSnippet(snippet.name);
animation.pauseSnippet(snippet.name);
animation.removeSnippet(snippet.name);

Aggregate controls are owner-scoped. Every slot and baked clip records the sourceAgency that admitted it as its owner. Named methods are UI-authored ("direct"), so clearSnippets(), stopAll(), pauseAll(), resumeAll() and stopAllBaked() affect only UI-authored work: slots owned by "direct", "animationService", "host", "host/<surface>", or host input written without a sourceAgency. EyeHead, LipSync, Blink, Prosodic and Gesture slots keep running. A peer writing { type: "stopAll" | "clear" | "pauseAll" | "resumeAll" | "stopAllBaked", sourceAgency } to animation.input affects only its own slots. Each decision is published as animationAggregateControlApplied and kept in snapshot().lastControl.

animation.pauseAll();  // UI-authored snippets and baked clips only
animation.resumeAll();
animation.stopAll();   // removes UI snippets, stops UI baked clips

animationSnippetRemoved is addressed to the slot owner: sourceAgency and owner name the owner, requestedBy names the requester, requestId repeats the admitting request's id when one was supplied, and removalOrigin is "owner" (the owner asked), "external" (another owner's control, a cross-owner same-name replacement, or Animation disposal) or "runtime" (native completed/cancelled/error). An owner can replan after an "external" removal.

Peer messages use AnimationInput on animation.input.write(command). Character society routing delivers to that port while preserving route admission and trusted source attribution. Worker hosts send { type: "animationInput", id, command }, without native handles or Three objects. Named controls and peer input use the same private planner/scheduler path.

This does not complete issue #306. createAnimationService remains as a compatibility bridge pending removal: its panel catalog mirrors only UI-authored slots and its stop() uses the owner-scoped clear, but the UI-authored snippet catalog, its SNIPPET_* events and the native baked bypass (playBakedAnimation and related setters) still have to move into the agency. The module-level animationEventEmitter, snippetList$, snippetState$, snippetTime$, globalPlaybackState$, bakedClipList$, playingBakedAnimations$, bakedAnimationProgress$ and bakedAnimationState$ exports were retired; use the ports owned by a service instance until the panel moves to the agency. Animation and EyeHead adopt definition-driven configuration and bounded actual-activity inspection. Account storage and regional priority remain separate work.

Animation snippet looping

Animation snippets default to repeating when no loop preference is supplied. Authored mixerLoopMode (repeat, pingpong, or once) takes precedence over the snippet's loop boolean. A scheduleSnippet command's options.loopMode or options.loop overrides that authored preference for the scheduled play; an explicit options mode takes precedence over the options boolean. The compatibility service's schedule method accepts the same options.

Snippets without an explicit blend mode play additive; options.blendMode or an authored snippetBlendMode ("replace"/"normal") still selects normal blend. Behavior agencies (Prosodic, Gesture, Blink, LipSync) each expose a blendMode setting, default "additive". See blend layers.

Animation uses that effective loop policy for the runtime, schedule snapshots, and compatibility UI state. Repeating clips stay scheduled until removed or stopped, including when a one-shot is switched to repeat before its original completion callback resolves. Explicit one-shots retain completion cleanup.

Actor Model file preparation

@lovelace_lol/polymer/model-tools re-exports Embody's inspectActorModel, prepareActorModel and extractGarmentModel functions and their types. Import this subpath when a creator asks to prepare a GLB; it does not load the agency runtime. LoomLarge owns source-file access, mesh choices, upload, and Actor/Mapping persistence. Embody owns byte inspection and transformation. This is a stateless file API, not an agency command or a second runtime Model loader.

const { inspectActorModel, prepareActorModel } = await import('@lovelace_lol/polymer/model-tools');
const inspection = await inspectActorModel(sourceBytes, { signal });
const prepared = await prepareActorModel(sourceBytes, {
  kind: 'low-resolution', targetRatio: 0.5, signal,
});
// Present prepared.after and prepared.warnings before saving prepared.bytes.

Preparation requires a self-contained, extension-free GLB. Mesh removal is explicit, geometry reduction may be limited by skinning and morphs, and textures retain their resolution. Embody's Actor Model tools documentation defines the recipes, cancellation, CLI, and Mapping compatibility limits.

extractGarmentModel(sourceBytes, { selections, ownedBoneNodeIndices, signal }) extracts explicit source node/primitive selections into an independent garment GLB. It preserves retained vertex/index/morph values and original embedded image bytes instead of compacting geometry or re-encoding textures. The host must derive selections from the exact source parser, never display names. Embody validates supported material extensions and garment bone ownership; unsupported formats fail explicitly. Animation clips are excluded. This file operation does not attach the garment to a live character or register runtime morph targets.

The committed manifest and lockfile use published Embody 0.0.18, which includes inspection/preparation from Embody #152 and garment extraction from Embody #187. No upstream preview is required. After this Polymer subpath merges and publishes, LoomLarge #1134 and Wardrobe #1078 must adopt the exact Polymer release before these file operations are available in their flows.

Character host (Embody scene + model loading)

loadCharacterModel and parseCharacterModel directly forward Embody's loaders. The FBX loading integration adds binary/ASCII FBX buffer parsing to the same { model, meshes, animations, gltf } result used for GLB/glTF (gltf is null for FBX). CharacterModelLoadOptions and the result/format types are forwarded from Embody, including format: 'fbx' for blob or extensionless URLs and format: 'gltf' to select glTF explicitly. Buffer parsing accepts a resource path for external textures. The adapter preserves exported rig/morph names; loading does not retarget a rig or supply its character mapping.

The FBX parser and format options are included in the committed stable Embody dependency. This forwarding change requires no upstream preview. LoomLarge must adopt the Polymer release containing these declarations after this PR merges.

Polymer directly forwards Embody's createCharacterHost factory so LoomLarge and other hosts can set up a character without depending on Embody directly. Owned scenes await initial renderer preparation before the host resolves. The async default is auto: Embody tries native WebGPU and releases a failed initialization attempt before preparing WebGL. Later scene-preparation errors remain errors. Use scene: { renderer: 'webgl' } to force WebGL or 'webgpu' to require native WebGPU without fallback. The synchronous scene factory stays WebGL-only; createDefaultCharacterSceneAsync exposes the scene-only Promise API. Three and its declarations use the coordinated r184 contract.

host.engine, createRustEmbodyHost and createRustEmbodyHostUnbound use the same concrete Embody API and authoring types. Animation uses that injected engine directly after admitting work through its scheduler; it does not create another runtime object or maintain an alternate character API.

The new createDefaultCharacterSceneRuntime export and host.rendering forward Embody's renderer-settings controller. It owns switching and recovery; Embody keeps the scene, camera, model, and animation engine intact and exposes current renderer/backend getters. The renamed ThreeAnnotationController accepts this same controller through its rendering option, preserving the renderer lifecycle boundary during the annotation migration. See renderer settings and replacement for subscriptions, capture leases, and ownership.

createSceneSceneryRenderer forwards Embody's saved-layout renderer and its SceneScenery* types. Hosts provide the layout and an authorized asset loader; Embody owns model placement, backgrounds, cancellation, and resource disposal. Hosts send updates through that API rather than mutating the scene for scenery. See scenery ownership.

Pass signal to cancel host creation cooperatively, then call host.dispose() for the resolved host's lifetime. Borrowed external scenes remain caller-owned. See readiness and cleanup semantics, including which GPU/model stages are outside this readiness contract.

Default path — pass a container element and a character URL:

import {
  createCharacterHost,
  createCharacterAgencies,
} from '@lovelace_lol/polymer';

const host = await createCharacterHost({
  container: document.getElementById('viewport'),
  character: {
    modelUrl: '/characters/jonathan.glb',
    presetType: 'cc4',
  },
});

const agencies = createCharacterAgencies({
  animation: { engine: host.engine },
});

Named scene types (studio, showcase, inspection, void) bundle background, lighting preset, and shadow-plane defaults; enumerate them via CHARACTER_SCENE_TYPES / CHARACTER_SCENE_TYPE_IDS:

const host = await createCharacterHost({
  container,
  character: { modelUrl: '/characters/jonathan.glb' },
  scene: { type: 'showcase' },
});

Advanced path — inject an existing Three.js scene:

const host = await createCharacterHost({
  container: mountEl,
  character: { modelUrl: '/characters/jonathan.glb', presetType: 'cc4' },
  external: { scene, renderer, camera },
});

Polymer stays agency-oriented: it does not own the render loop. Use createCharacterHost / loadCharacterModel for scene+model setup, then inject host.engine into createCharacterAgencies.

For an already-managed Three.js scene, Polymer forwards Embody's host API without implementing its own host or playback runtime:

import {
  createRustEmbodyHostUnbound,
  initEmbodyCore,
  supportsRustEmbodyPreset,
} from '@lovelace_lol/polymer';

await initEmbodyCore();
if (supportsRustEmbodyPreset('cc4')) {
  const engine = createRustEmbodyHostUnbound({
    profileMode: 'preset',
    presetId: 'cc4',
    profile: characterProfileOverrides,
  });
  engine.onReady({ model, meshes });
  engine.start();
}

Embody owns profile intake, saved AU compilation and Three.js playback. Polymer's polymer.embody namespace aliases that implementation; it does not restore the removed polymer.runtime.* layer. Animation owns requests, admission and lifecycle, while Embody/Three own the rendered animation. The installed agency configuration contains no engine, renderer or live media handles.

Authoring controls can read procedural state with engine.getAU(id), engine.getAUBalance(id) (left/right balance in -1..1), and engine.getContinuum(negativeId, positiveId). These read Rust's current values, including neutral values after clear() or setProfile(), so a remounted control can resume the current pose without maintaining a second state cache.

Semantic humanoid skeleton

getHumanoidSpecification() exposes Embody's canonical VRM 1.0 catalog: all 55 bone roles, the 15 required roles, and parent/optional-parent requirements. Call it after initEmbodyCore(), like the other synchronous profile queries. resolveHumanoidCharacterization(profile) resolves authored role aliases; validateHumanoidCharacterization(model, profile) additionally checks the actual model hierarchy, unique assignments, and supported transform constraints.

setHumanoidRoleBinding(profile, role, boneName) returns a new profile that assigns an actual model bone; pass null to clear a role. Persist the returned characterization as a complete replacement, together with changed bone aliases, action/composite mappings, and continuum pairs, then reconfigure the runtime. Authored catalogs remain authoritative across preset upgrades. Clearing a role makes its semantic bone outputs dormant while preserving the rest of the profile.

The CC4 preset supplies anatomical role bindings and AU mappings across torso, head, arms, hands/fingers, legs and feet/toes. engine.getAUDescriptors() returns metadata from the configured Rust profile/model. The standalone getAUDescriptors(profile, model) query supports unbound authoring. Descriptors include numeric AU IDs, directional pairs when configured, bilateral support, actual bone/morph bindings, saved morph strength and availability diagnostics. category separates facial AU controls, body movements, bone-axis controls and profile-defined custom controls. section, order and controlLabel carry authored presentation without changing morph mesh routing. A configured mapping can be unavailable on the loaded model: hasConfiguredBones and hasConfiguredMorphs describe that mapping, while hasBones, hasMorphs and available describe its resolved outputs. Hosts retain unavailable configured controls and their diagnostics instead of treating them as deleted mappings. These facts and the ControlCategory/AUInfo types come directly from Embody; Polymer maintains no second category registry or anatomical ID table. Applications use the normal AU setters and getters; clearing a pose means releasing its owned AU values, balances and morph-strength overrides.

Every configured AU, including each direction of a signed control, can drive authored left/right/center morph mappings. Morph strength scales those outputs even when the action has no bone bindings. Persist the action's auToMorphs and auMixDefaults, and retain its mesh category: head, eye, and jaw controls may share facial actions with different target meshes. The package contract tests exercise all six sections through the actual Rust-backed host, including mesh selection, balance, zero strength, and mapping removal. An explicitly configured mesh list restricts these Body actions to that list; an empty list disables their morph outputs rather than selecting other meshes. After a temporary direct morph preview, call engine.releaseMorph(key, meshes) to let current AU/viseme/clip output resume. Setting a morph to zero is still an explicit override and intentionally continues to suppress those other sources.

setAUMorphTargets(profile, auId, side, targets) edits one action side's morph targets without removing the same targets from other actions. Sides are left, right, or center; targets are names or numeric morph indices. The returned profile includes the complete edited AU row, with other sides preserved and explicit empty arrays retained across preset reloads. Hosts can persist just that row as an override and reconfigure the runtime.

Head and eye semantic controls use the existing FACS AU IDs, mappings, balances, and morph-strength settings. CC4 neck controls seed the corresponding head tissue morphs and ratios while keeping separate neck bone actions and profile overrides. Neck morphs use the face mesh route; limb muscle morphs use their authored route. auMixDefaults scales morph deformation without reducing bone movement. Editors should expose these settings even when configured morph targets are missing, so users can repair the assignment or choose another model.

VRM defines anatomical roles and hierarchy. Motion axes/ranges and muscle morphs remain authored rig data. Editing an unmapped import does not silently give it the CC4 preset. Polymer forwards Embody's runtime and profile helpers; it adds no second role catalog, pose evaluator, or character state cache. Use engine.getAU(id) and engine.getAUBalance(id) together when saving a live AU pose. The balance reader returns Rust's current control value in [-1, 1] (-1 left, 0 both sides, 1 right), or 0 for an unset or cleared AU. Save that value in the snippet's snippetBalanceMap to preserve sided playback through AnimationService. These readers report live control state, not the sampled pose of a playing mixer clip.

Stable character reference poses

The first onReady captures the model's loaded transforms and morph weights, before Embody applies the profile or starts playback. setProfile, bindModel, and subsequent onReady calls for that model reuse the capture. Animation intensity is measured from that same reference. Live inherited animation starts still sample the rendered pose when a clip is materialized.

Call onReady while a newly loaded model is intentionally in its reference pose. This does not infer a skin bind pose or use animation frame zero. If the same model survives engine recreation, retain an explicit capture with the model:

import { captureModelReferencePose } from '@lovelace_lol/polymer';
const referencePose = captureModelReferencePose(model); // before playback/edits
engine.onReady({ model, referencePose });
// Pass referencePose again when creating a replacement engine for this model.

Intentional skeleton additions must append new children without renaming, removing, or reordering existing nodes. After insertion, call engine.extendReferencePose() before applying new profile mappings. This returns a new immutable capture that includes the added nodes and preserves every old reference transform; retain it if the model will outlive its engine. Other hierarchy edits fail explicitly before the active profile or playback is cleared. Ordinary profile, transform, material, and morph-target edits need no recapture.

Baked-animation pause and resume also publish the current runtime playhead, so the paused timeline remains at the displayed pose instead of reverting to an old time. These are discrete state updates; playback progress stays in the runtime.

Optional baked animation assets

Use engine.loadAnimationClips(initialClips) during initialization. After the model is bound, engine.registerAnimationClips([clip]) adds a downloaded clip without starting it, resetting the mixer, or interrupting native, snippet, or hair playback. Re-registering the same clip object does no serialization work. A new source under an existing name replaces the next playback; the current action continues until playback is explicitly requested. Registered sources survive model rebinding, and removeAnimationClip(name) also removes them from the sources retained for rebinding. Treat registered source objects as immutable.

The Animation agency's existing loadBakedClips command accepts lightweight {name, duration, channels} catalog entries before optional tracks are loaded. Hosts can expose that catalog immediately, fetch and register a selected asset, then request playBaked through Animation. Registration is asset setup; it does not introduce an additional scheduler or playback owner.

Package Distribution Contract

Only source and the hand-authored types/index.d.ts declarations are committed. pnpm build recreates dist, builds the npm and worker targets once, and copies the declarations into that staging directory. pnpm verify checks the source-only rule, validates the runtime exports and packed file list, and runs the CLJS and worker tests.

PR verification uses one concurrency group per PR, so a new revision cancels obsolete work for that PR. Stable npm releases retain their serialized group and are not canceled by new PR runs. Explicit preview requests remain grouped by their requested SHA or ref.

Ordinary PR title/body edits do not start or cancel package CI. The trusted linked-Embody resolver handles changes to Depends-on: embody#<PR> declarations in the body or authorized human comments. Adding, replacing or removing a link requests verification against the current dependency selection; unrelated prose edits do not. The resolver also refreshes verification after a base-branch edit. pull_request_target runs are authenticated by their repository, workflow and PR base targeting main, because GitHub reports their PR head branch in run metadata. Recovery starts only after the resolver finishes.

A lightweight scope job checks the entire PR diff (or the full main push range). Changes limited to README.md, AGENTS.md and Markdown under docs/ skip the package build, runtime tests and publication. Required job names remain present as skipped checks; a failed scope job fails the build check. Fixtures, code, workflows, manifests, lockfiles and unknown paths still require full verification. A linked Embody PR, any explicit preview request or a rerun overrides the docs shortcut, so adding or removing a dependency link on a docs PR still verifies it. The initial stable-npm resolver status alone does not request another CI run.

Tests exercise Polymer's agency routing and runtime adapters. Pure Embody behavior behind unchanged re-exports belongs in Embody; Polymer retains export and declaration checks plus annotation constructor identity checks. The test:gaze-runtime command covers saved-animation EyeHead and agency adoption.

The package intentionally has no prepare, install, or postinstall build lifecycle. Consumers install an already-built npm release or a pkg.pr.new preview for a specific commit and therefore do not need Java, Shadow CLJS, or a local Polymer build. For a package stack, put Depends-on: https://github.com/meekmachine/embody/pull/<number> in the Polymer PR body and Depends-on: https://github.com/meekmachine/Polymer/pull/<number> in the LoomLarge PR body. CI resolves the compiled package for the upstream head and applies it during the preview build; Polymer's preview carries the selected Embody dependency. Committed manifests and lockfiles keep their valid stable versions. The release coordinator adopts a reviewed stable release after upstream merge and publication. The temporary preview URL has this form:

https://pkg.pr.new/meekmachine/Polymer/@lovelace_lol/polymer@<40-character-sha>

On package-affecting pushes to main, CI prepares a stable npm version and validates the committed Embody dependency before the single build. The source manifest must already pin an exact stable Embody version, with a synchronized lockfile. CI reads public npm metadata anonymously and checks that the selected release records a commit in Embody's main history. It rejects preview URLs anywhere in the production lockfile. Publication never replaces a preview with whatever npm calls latest: adopting a release is an ordinary dependency PR, so the dependency tested by PR CI is the one shipped. After upstream merges and a stable release includes its changes, the release coordinator adopts the exact npm version and removes the completed Depends-on: link. Feature PR merges still require user authorization.

After all checks pass, CI packs the verified output and uploads it to a separate publish job using GitHub OIDC trusted publishing in the main-only npm environment. That job publishes the same tarball with public access and provenance disabled, without building again. The tarball must also declare public access. GitHub source access remains separate from distribution of the public compiled npm package.

Package metadata and dependency installation use the public registry without a read token. Registry lookups also stay anonymous when a publishing credential is present in the environment, and reject redirects to another registry endpoint. Only the main-only publish job receives id-token: write; PR installs and checks receive no npm publishing credentials. Same-repository PRs can publish previews; fork PRs can run checks without receiving publication or coordination write credentials.

The package allowlist remains dist and README.md, alongside npm's package metadata. The existing CI package checks require staged runtime files and reject repository source/test inputs. Build output, source maps, minification and obfuscation are unchanged by this release setup.

Successful stable and same-repository PR preview publication dispatch upstream-package-published to LoomLarge. The write credential CROSS_REPO_CI_TOKEN belongs in the main-only release-coordination environment; it must read Embody for release ancestry and dispatch to LoomLarge. PR jobs only upload notification JSON and never receive this credential. The separate workflow_run consumer checks out main, validates the completed build job, the JSON package identity, and the current open same-repository PR head, then sends the event. It never executes a PR script or downloaded artifact. The build job must succeed independently of the committed-pin check. Production npm dependency keeps its required-check name and validates only the already committed stable Embody pin on PR opening, pushes, reopening, and marking ready for review. The trusted linked-Embody resolver handles dependency declaration edits without rerunning unrelated code verification. Draft status does not change dependency validity. An unpublished linked Embody PR is expected during development: build-test-publish selects its exact compiled preview and reports missing preview artifacts there. A broken committed stable pin still fails independently. The main-only verify-upstream-main job checks publication source ancestry before publish-npm; preview success does not authorize a stable release. During migration the consumer accepts both the former CI and package preview name and CI and package publishing, while requiring the exact CI workflow path. Missing credentials or failed dispatch fail visibly.

A valid preview notification becomes obsolete if its PR closes, advances to a new head, or targets a branch other than main while the notification is queued. The consumer validates the artifact's identity, then skips obsolete notifications successfully without checking the old package or dispatching downstream work. It checks the current PR again immediately before dispatch to catch changes during the package availability request. Malformed artifacts, foreign repository identities, unavailable current previews and dispatch failures still fail visibly.

Stable events include the exact version and source SHA; preview events include the PR number and actual published URL, expanded to its full source SHA and checked before dispatch. LoomLarge's coordinator owns downstream manifest/lockfile PR updates; release-system companion PR links alone do not create package-stack dependencies. The coordinator and trusted notification consumer must be on their respective main branches before activation. Explicit preview requests without a PR publish previews but do not imply a downstream stack to update.

Release versions and source commits are recorded in npm; rerunning a published commit retains its version and exact Embody dependency. If a concurrent publisher claims that source/version with different runtime dependencies, publication fails instead of sending a success notification for a package the job did not verify. Main release runs are serialized. A manual run on main with no alternate ref can retry publication and notification; PRs, repository-dispatch preview requests, and alternate refs cannot publish stable npm packages.

Configure and recover stable publication

The npm package settings must register this GitHub Actions trusted publisher:

| Setting | Value | | --- | --- | | Package | @lovelace_lol/polymer | | Organization or user | meekmachine | | Repository | Polymer | | Workflow filename | ci.yml | | Environment | npm | | Publishing permission | Allow npm publish |

New npm trusted publishers default to staging only; explicitly allow publishing. The package's repository.url must match git+https://github.com/meekmachine/Polymer.git. These settings live on npm and cannot be configured by merging workflow YAML. Retain account 2FA and GitHub environment protections. The job uses Node 24 and requires npm >=11.5.1 (with Node >=22.14.0), publishes without NPM_KEY or NODE_AUTH_TOKEN, and never grants an OIDC token to PR or preview jobs. Provenance remains disabled for this private source repository.

A green pkg.pr.new preview confirms a preview, not stable publication. The main-only publish job writes a Stable npm publication confirmed summary only after an anonymous exact-version registry read confirms the version and gitHead, and records the release's SHA-512 integrity. New uploads must match the verified tarball's integrity. Retries confirm an existing immutable release without assuming that rebuilding the same source reproduces every archive byte. A failed publish or conflicting registry result fails the job and blocks downstream notification. npm authentication failures show the error code and exact publisher settings to repair, without echoing raw npm output or tokens. The publisher checks the exact-version endpoint before any upload, with Cache-Control: no-cache, so a stale package index cannot cause a duplicate upload. Only HTTP 404 means absent; other lookup failures stop publication. A new upload gets one exact-version confirmation lookup, without polling. A transient registry failure can leave an upload present but unconfirmed; rerun the main release after investigation rather than upload a replacement version blindly.

After repairing configuration, run this workflow on current main with its ref input empty. A successful current-main stable release contains all source changes already merged into that repository; individual failed intermediate releases do not need replaying. Explicit refs and preview dispatches publish previews only. An existing matching release is confirmed without publishing it again; source or dependency conflicts and new-upload archive conflicts stop instead of overwriting it. The receipt establishes npm publication, while the LoomLarge coordinator separately opens or updates reviewed stable dependency PRs and lockfiles. It does not establish that Polymer or the application adopted the release or deployed it.

The first restored release reads Polymer 0.0.7's source from the verified canonical v0.0.7 tag because that legacy npm version has no gitHead metadata. The tag must still point to 73feb34f8afa5a36962e86bc0f3e91f9d91205ef, and the new release must contain that commit. Subsequent releases use npm's recorded gitHead; missing or conflicting source information stops publication.

The separate Production npm dependency job rejects preview pins and verifies the committed Embody registry version before merge. A trusted main-only job checks Embody main ancestry before publication. PR build jobs can still publish their immutable previews independently. Bootstrap requires npm trusted-publisher configuration and coordinator credentials, an Embody main release, adoption of that actual version in Polymer's manifest and lockfile, Polymer's main release, then adoption in LoomLarge. Upstream feature stacks keep using immutable previews before this release sequence. Production hosts adopt actual released versions in dependency PRs whose CI checks the selected packages.

Worker Protocol Artifact

The package also ships @lovelace_lol/polymer/worker/agency-worker.js as an opt-in transport around the complete character agency API. It accepts createCharacterAgencies, dispatch, snapshot, and dispose messages and returns character events, snapshots, and lifecycle messages as plain data. The Worker does not add another planner or route between agencies; Polymer's character network keeps that responsibility.

CI executes the compiled artifact in a real Node worker thread. LoomLarge does not use this artifact yet, and this PR does not decide which agencies should be placed in workers. Selective placement, profiling, transferables, and worker pool decisions remain tracked in issue #62.

For generator modules bound to a shared character, see Conversation sessions and interruption.

Presentation and Questionnaire share pure lifecycle, viewed/active context, request correlation and opaque checkpoint helpers. See shared interaction contracts for their module composition interface, account/mount identity and scoped cleanup rules. These contracts do not enable a feature runtime or persist conversation/run data.

AU pose capture and generated animations

A pose uses numeric AU values, character-relative balances and per-AU morph weights. Hosts read engine.getAUDescriptors() for actual saved mappings and use getAU, getAUBalance and getAUMixWeight to capture values, then their corresponding setters to restore them. Host code validates a complete record before applying it; Polymer adds no parallel named-control pose format.

AnimationService.schedule uses ordinary curves keyed by numeric AU ID for both facial and body motion. A snippet may add balanceMap and mixWeights: {curves:{1001:[{time:0,intensity:0},{time:0.2,intensity:0.8}]}, balanceMap:{1001:-1},mixWeights:{1001:0.25}}. Balance ranges from character-left -1 to character-right +1; morph weight changes tissue deformation without attenuating the AU's bone movement. Embody compiles the saved bone/morph mappings into native clips without changing live AU settings. Existing save, pause/resume, held-final-pose, completion and removal behavior applies.

LEGACY_BODY_CONTROL_AUS is an immutable lookup for host loaders decoding old persisted names to numeric AUs. It supplies no labels, availability, profile configuration or runtime control behavior. New records use AU IDs directly.

Manual setAU / transitionAU selects one configured continuum direction: nonzero input clears its opposite, negative input selects the opposite, and zero releases only the named action. setContinuum controls both directions explicitly. AU and individual morph previews share the same live state; separate axes remain independent.

EyeHead tracking uses saved axis snippets through Animation. See the [tracking contract