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_L↔BROW_LOWERER_R, eye-gaze cross-swapped) so the same clip reads as two natural variants.
- Gesture and boredom clips now hold for 15 seconds by default (was ~0.5s). Configurable via
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 ~32std::stringallocations + hash lookups per mirrored-clip frame per avatar.
- Blendshape mirror pair names are now resolved to channel indices once per clip/pose-definition change (in
Init Performance
- Asset-resolver config and texture-settings JSON are now cached per path inside
NafInitializer. Concurrentinitialize()calls share a single fetch + parse instead of hitting the network once per call; failed fetches evict so the next caller can retry cleanly.
- Asset-resolver config and texture-settings JSON are now cached per path inside
Off-Screen Pause
- Added
controller.isPaused— when true, per-framecontroller.update()andcontroller.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.
- Added
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.
- Added
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.
- Added
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: trueif it ran through its transition-out naturally;falseif it was cut short (e.g. pre-empted by a higher-priority gesture).guididentifies the clip that just ended.
- Consecutive
avatar:channel-endfollowed byavatar:channel-starton the same channel is the "clip changed" signal for idle cycling. - Progress (current playback time) is not emitted as an event — poll
getChannelInfo(channel).currentTimefrom your render loop instead.
- Two new events on the global event bus (