moeru-ai/airi · warning

No Hips.position track of type VectorKeyframeTrack found in

Error message

No Hips.position track of type VectorKeyframeTrack found in animation.

What it means

After finding the hips node, reAnchorRootPositionTrack() searches clip.tracks for a VectorKeyframeTrack whose name is exactly `${hipNode.name}.position`. If no such track exists — the animation contains only rotations, or the position track uses a different PropertyBinding name/path — it warns and returns, leaving the root position uncorrected.

Source

Thrown at packages/stage-ui-three/src/composables/vrm/animation.ts:68

// Set initial positions for animation
export function reAnchorRootPositionTrack(clip: AnimationClip, _vrm: VRMCore) {
// Get the hips node to re-anchor the root position track
  const hipNode = _vrm.humanoid?.getNormalizedBoneNode('hips')
  if (!hipNode) {
    console.warn('No hips node found in VRM model.')
    return
  }
  hipNode.updateMatrixWorld(true)
  const defaultHipPos = new Vector3()
  hipNode.getWorldPosition(defaultHipPos)

  // Calculate the offset from the hips node to the hips's first frame position
  const hipsTrack = clip.tracks.find(track =>
    track instanceof VectorKeyframeTrack
    && track.name === `${hipNode.name}.position`,
  )
  if (!(hipsTrack instanceof VectorKeyframeTrack)) {
    console.warn('No Hips.position track of type VectorKeyframeTrack found in animation.')
    return
  }

  const animeHipPos = new Vector3(
    hipsTrack.values[0],
    hipsTrack.values[1],
    hipsTrack.values[2],
  )
  const animeDelta = new Vector3().subVectors(animeHipPos, defaultHipPos)

  clip.tracks.forEach((track) => {
    if (track.name.endsWith('.position') && track instanceof VectorKeyframeTrack) {
      for (let i = 0; i < track.values.length; i += 3) {
        track.values[i] -= animeDelta.x
        track.values[i + 1] -= animeDelta.y
        track.values[i + 2] -= animeDelta.z
      }
    }

View on GitHub (pinned to 677329427f)

Solutions

  1. Log clip.tracks.map(t => t.name) and compare with the expected `${hipNode.name}.position` binding.
  2. Use a .vrma that actually keyframes hips translation (full-body animations do).
  3. If the track is optional for your clip type, guard with the same check and skip re-anchoring intentionally.

Example fix

// before
reAnchorRootPositionTrack(clip, vrm) // warns: no hips position track

// after
const hips = vrm.humanoid?.getNormalizedBoneNode('hips')
const hasHipsPos = !!hips && clip.tracks.some(t => t.name === `${hips.name}.position`)
if (hasHipsPos) reAnchorRootPositionTrack(clip, vrm)
Defensive patterns

Strategy: validation

Validate before calling

const hips = vrm.humanoid?.getNormalizedBoneNode('hips')
const hasHipsPosTrack = !!hips && clip.tracks.some(t => t.name === `${hips.name}.position`)
if (!hasHipsPosTrack) { /* rotation-only clip: skip re-anchor */ }

Prevention

When it happens

Trigger: Playing an upper-body or idle .vrma that animates only bone quaternion tracks; a clip whose hips position track is bound to the raw (non-normalized) bone name so the normalized hipNode.name does not match; hand-built AnimationClips without a hips position track.

Common situations: Mix-and-match of clips from different rigs; three.js track-name formats (node.position vs bones[name].position) after retargeting; upper-body-only animations.

Related errors


AI-assisted analysis of moeru-ai/airi@677329427f (2026-08-18). Data as JSON: /api/errors/ff500c36eb7f5cce. Report an issue: GitHub.