Skip to main content

Genies Web SDK v0.1.35

Updated: April 29, 2026

What's New

  • Shader State Getters
    • naf.isToonEnabled() and naf.isOutlineEnabled() are now exposed on the SDK instance.
  • Channel Info
    • getChannelInfo(channel).currentTime now wraps into [0, duration) for looping channels (idle, breath, base emotion, etc.) instead of accumulating without bound. Consumers no longer need to compute currentTime % duration themselves, and long-running idle channels won't lose float precision after hours of playback. Non-looping channels are unchanged.
  • Channel Scrubbing
    • Added controller.getChannelCurrentTime(channel) and controller.setChannelCurrentTime(channel, time) to read and seek the playback time of any animation channel.
    • setChannelCurrentTime enables backward scrubbing and jump-to-frame on gesture/idle/emotion channels — the per-frame update(dt) loop only advances time, so any non-forward seek previously had no public path. The new time takes effect on the next pose evaluation.
    • getChannelCurrentTime is a lighter alternative to getChannelInfo(channel).currentTime for tight polling loops (no struct allocation).
    • No clamping is applied: callers are responsible for keeping time within [0, duration) (or wrapping themselves for looping channels).
  • Layer-Agnostic Lifecycle Events
    • avatar:ready and avatar:loaded now fire from loadAvatar (Layer 1) too — previously they only fired from loadAvatarProgressive (Layer 3), so consumers subscribing via naf.on('avatar:loaded', ...) never saw completion when using the basic loader. Payloads are unchanged: Layer 1 emits previousMesh: null on avatar:ready (no silhouette handoff), and handle.controller is null on avatar:loaded (attach animation separately via attachAnimation). Layer 3 behavior is unchanged.
  • Animation Debug — Per-Avatar Instances
    • initAnimationDebug(mesh, poseSynchronizer, scene, avatarId?) now creates a fresh debug controller per call instead of mutating a global singleton. Multiple avatars no longer overwrite each other's bind-pose snapshot, sync wrap, or bone filter state.
    • When avatarId is provided, the API is registered at window.nafDebugAvatars[avatarId] for targeted console use. window.nafDebugAnimation continues to alias the most-recently-initted instance for backwards compatibility.
    • New disposeAnimationDebug(avatarId) releases a single avatar's debug instance. The sandbox now calls this on per-avatar avatar:unloaded instead of tearing down all animation UI — fixes a latent bug where removing one of several loaded avatars would clear the panel for the rest.
    • initAnimationDebug now returns the API object so callers can hold a reference instead of going through window.
  • Bind-Pose Correctness
    • The bind-pose snapshot used by resetAllToBindPose / resetBone is now derived from mesh.skeleton.boneInverses for skinned bones — those matrices are immutable from skin-bind time, so the snapshot is correct regardless of whether init() runs before or after animation has been applied. Non-skinned control bones still fall back to the bone's current transform on first init only.
    • init() is now idempotent on the same mesh: re-init preserves the existing bind-pose snapshot and bone filter state instead of clobbering them.
    • The ambiguous nafDebugAnimation.snapshotBindPose() console command (which actually captured current pose, not bind pose) is replaced by two explicit commands:
    • setCurrentAsBindPose() — override bind pose with the avatar's current pose (the previous behavior, made explicit).
    • recaptureBindPose() — re-derive the bind pose from skeleton.boneInverses (revert any earlier override).