Skip to main content

Genies Web SDK v0.1.33

Updated: April 22, 2026

What's New

  • Behavior Defaults & Mirroring

    • Gesture and boredom clips now hold for 15 seconds by default (was ~0.5s). Configurable via BehaviorTimingConfig::gestureHoldTime.
    • Breathing animation now plays at weight 1.0 by default. If your app previously relied on breathing being muted until explicitly enabled, call setBreathWeight(0, ...) after avatar load.
    • Gestures, boredom, and body-emotion clips now play mirrored with 50% probability each time they start. Mirroring swaps left/right skeleton joints and bilateral blendshapes (e.g. BROW_LOWERER_LBROW_LOWERER_R, eye-gaze cross-swapped) so the same clip reads as two natural variants.
  • Mirroring Performance

    • Blendshape mirror pair names are now resolved to channel indices once per clip/pose-definition change (in ClipSampler::UpdateContextData) instead of rebuilt every frame. Removes ~32 std::string allocations + hash lookups per mirrored-clip frame per avatar.
  • Init Performance

    • Asset-resolver config and texture-settings JSON are now cached per path inside NafInitializer. Concurrent initialize() calls share a single fetch + parse instead of hitting the network once per call; failed fetches evict so the next caller can retry cleanly.
  • Off-Screen Pause

    • Added controller.isPaused — when true, per-frame controller.update() and controller.syncPose() short-circuit without touching native state or the mesh. Native playback time does not advance while paused, so on resume the animation continues from the exact frame it was at.
    • Intended for off-screen avatars (e.g. gallery cells scrolled out of view) to avoid burning CPU on invisible work. Toggling it is safe at any time.
  • Shader Validation Controls

    • Added shaderManager.pauseValidation(avatarId) / resumeValidation(avatarId) — temporarily suppress the per-frame material self-heal so consumers can swap materials (e.g. wireframe debug) without the shader system reverting the swap.
    • Added shaderManager.isValidationPaused(avatarId) — query paused state.
    • detachAvatar(avatarId) now clears any pause flag for that avatar.
  • Animation Channel Introspection

    • Added controller.getChannelInfo(channel) — read the current { guid, status, duration, currentTime, speedMultiplier, isLooping } for any animation channel ('gesture', 'boredom', 'secondary', 'idle', 'breath', 'baseEmotion', 'emote1..4', 'talking').
    • Useful when a clip was started via a tag-based API (e.g. playGestureTag) and the consumer never held a clip reference — duration and playback position are now readable directly.
  • Channel Lifecycle Events

    • Two new events on the global event bus (NAF.on(...)):
      • 'avatar:channel-start'{ avatarId, channel, guid, duration, isLooping }. Fires when a channel starts playing, including auto-boredom picks and behavior-driven starts.
      • 'avatar:channel-end'{ avatarId, channel, guid, completed }. Fires when a channel ends. completed: true if it ran through its transition-out naturally; false if it was cut short (e.g. pre-empted by a higher-priority gesture). guid identifies the clip that just ended.
    • Consecutive avatar:channel-end followed by avatar:channel-start on the same channel is the "clip changed" signal for idle cycling.
    • Progress (current playback time) is not emitted as an event — poll getChannelInfo(channel).currentTime from your render loop instead.