# openalice-anim

> Mel-spectrogram → VRM pose stream: ZeroEGGS ONNX gesture synthesis + PFNN procedural locomotion fallback, served over WebSocket to openalice-persona.

## At a glance
| field | value |
|---|---|
| kind | `service` |
| priority | `P3` |
| default branch | `mvp` |
| last activity | 2026-05-22T01:02:33+00:00 |
| features catalogued | **8** |
| runtime deps declared | **3** |

## Manifest
`.atlas-deps.yml` — parsed cleanly: **yes**

### Runtime deps
- **`openalice-persona`** · `ws` · via `INTERNAL_SECRET` — Persona renderer connects to /v1/sessions/{sid}/stream to consume PoseFrame + BlendshapeFrame at tick rate.
- **`openalice-live-control`** · `ws` · via `INTERNAL_SECRET` · _optional_ — Relays LipsyncFrame markers from openalice-voice to anim sessions and forwards IntentFrame / NavigateFrame directives from the agent loop.
- **`openalice-voice`** · `ws` · via `ws` · _optional_ — Streams LipsyncFrame tokens (grapheme offset + viseme) into the session so gesture rhythm and mouth blendshapes are driven by the spoken token timeline.

### Consumed env
- `ANIM_PORT`
- `INTERNAL_SECRET`
- `OTEL_EXPORTER_OTLP_ENDPOINT`
- `OTEL_SERVICE_NAME`

### Exposes
- `docker_image` — openalice-anim
- `ws_endpoint` — ws://<host>:3990/v1/sessions/{sid}/stream
- `http_endpoint` — http://<host>:3990/health
- `http_endpoint` — http://<host>:3990/metrics

## Env drift

**Code reads, manifest omits (1):**
- `MODELS_DIR`

## Features (8)

### anim/audio
- **`anim.audio.mel-extractor`** — Pure-NumPy 81-dim mel feature extractor (80 log-mel bands + per-frame RMS energy) matching ZeroEGGS preprocessing. No librosa/torchaudio dep; cached filter bank; correct STFT hop gives 80 fps feature rate at 16 kHz input.  
  *openalice_anim/audio_features.py:6*

### anim/blend
- **`anim.blend.priority-merge`** — Three-layer bone priority merge: locomotion owns the lower body (root + legs), gesture/ZeroEGGS override the upper body (arms, shoulders), lipsync owns mouth + emotion blendshapes. Override wins on key collision — no quaternion averaging — preventing unnatural drift when competing pose sources name the same bone.  
  *openalice_anim/blender.py:4*

### anim/core
- **`anim.core.graceful-stub-degradation`** — Every synthesis module (gesture, locomotion, lipsync) falls back to identity-pose stubs when its ONNX weight file is absent. The WS protocol contract is fully exercisable with zero model files — critical for CI and cold-start scenarios.  
  *openalice_anim/__init__.py:19*
- **`anim.core.ws-pose-stream`** — Real-time avatar animation via WebSocket: inbound audio/intent/ navigate/tick frames are synthesised into VRM-compatible PoseFrame + BlendshapeFrame messages at up to 60 Hz, consumed by openalice-persona's AvatarAnimator without pre-baked clip data.  
  *openalice_anim/__init__.py:13*

### anim/gesture
- **`anim.gesture.rest-pose-calibration`** — Runs the ZeroEGGS decoder for REST_CALIBRATION_FRAMES (16) silent frames at session start to let the GRU settle, then captures per-VRM-bone rest quaternions. Runtime rotations are expressed as q_delta = q_now * q_rest^-1 so the VRM stays in its own bind pose (not ZEGGS's Mixamo rest) when the agent is silent.  
  *openalice_anim/zeggs_pipeline.py:15*
- **`anim.gesture.zeroeggs-pipeline`** — Full ZeroEGGS (Ubisoft La Forge, Apache-2.0) inference pipeline: speech_encoder → style-conditioned decoder → 75-bone Mixamo skeleton retargeted to 18 VRM 1.0 humanoid bones. Per-session stateful GRU hidden state + rest-pose calibration so the avatar returns to its own rest pose during silence.  
  *openalice_anim/zeggs_pipeline.py:8*

### anim/locomotion
- **`anim.locomotion.pfnn-procedural-fallback`** — Two-path locomotion: PFNN ONNX (Holden et al. 2017 Phase-Functioned Neural Network) when weights are present; sinusoidal foot-cycle procedural fallback otherwise. Procedural path drives root yaw + hip/knee/ankle pitch from speed and a heading-smoothed root quaternion, giving visible walking motion without any model files.  
  *openalice_anim/modules/locomotion.py:3*

### anim/transport
- **`anim.transport.ws-session-security`** — WebSocket sessions enforce an optional shared-secret token check (?token=<INTERNAL_SECRET>) matching the live-control auth pattern, plus a 32 KB per-frame size cap that closes the unbounded receive_json DoS vector. Binary frames are rejected with WS 1003; oversized text frames with WS 1009.  
  *openalice_anim/main.py:3*

---

*Generated by [openalice-atlas](https://atlas.blal.pro) — single source of truth for the openalicelabs ecosystem.*
