moeru-ai/airi · warning

[mmd] playAction skipped: "${name}" is not registered

Error message

[mmd] playAction skipped: "${name}" is not registered

What it means

The MMD animation manager keeps clips in a Map populated by registerClip(name, clip). playAction(name) looks the name up; when no clip is registered under it, it warns and returns false instead of throwing — the current action is left untouched (idle keeps playing).

Source

Thrown at packages/stage-ui-mmd/src/composables/mmd/animation-manager.ts:148

    }

    if (currentAction && currentAction !== idleAction)
      currentAction.fadeOut(crossfade)
    // A one-shot may reuse the same action, so restore its looping contract.
    idleAction.reset().setLoop(LoopRepeat, Number.POSITIVE_INFINITY).setEffectiveWeight(1).fadeIn(crossfade).play()
    currentAction = idleAction
  }

  /**
   * Plays a registered motion and cross-fades from the current action.
   * One-shots return to idle; looping actions remain active until replaced.
   *
   * @returns `false` when no clip is registered under `name`.
   */
  function playAction(name: string, actionOptions: PlayActionOptions = {}): boolean {
    const clip = registry.get(name)
    if (!clip) {
      console.warn(`[mmd] playAction skipped: "${name}" is not registered`)
      return false
    }

    const loop = actionOptions.loop ?? false
    const crossfade = actionOptions.crossfade ?? DEFAULT_CROSSFADE
    const action = mixer.clipAction(clip)
    action.reset()
    action.setLoop(loop ? LoopRepeat : LoopOnce, loop ? Number.POSITIVE_INFINITY : 1)
    action.clampWhenFinished = !loop
    action.setEffectiveWeight(1)
    action.fadeIn(crossfade).play()

    if (currentAction && currentAction !== action) {
      removeFinishListener(currentAction)
      currentAction.fadeOut(crossfade)
    }
    currentAction = action

View on GitHub (pinned to 677329427f)

Solutions

  1. Register the clip with registerClip(name, clip) before calling playAction
  2. Check the boolean return value and fall back (keep idle or try a default motion) instead of assuming playback started
  3. Log availableClips() when the miss happens to catch typos and case mismatches against the real registry keys

Example fix

// before
animation.playAction('Wave') // registry has 'wave' -> skipped, returns false

// after
if (!animation.playAction('Wave'))
  animation.playAction(animation.availableClips().find(c => c.toLowerCase() === 'wave') ?? 'idle')
Defensive patterns

Strategy: validation

Validate before calling

if (!animation.availableClips().includes(name)) {
  console.warn(`motion ${name} not registered, have:`, animation.availableClips().join(', '))
  return
}
animation.playAction(name)

Type guard

function isRegisteredMotion(manager: { availableClips(): string[] }, name: string): name is string {
  return manager.availableClips().includes(name)
}

Prevention

When it happens

Trigger: Calling playAction with a name never passed to registerClip: typo, case mismatch, a motion file that failed to import, or calling playAction before clip registration finished (e.g. before the VMD import promise resolved).

Common situations: Hardcoded motion names that don't match VMD-derived names; user-configurable motion mappings pointing at files that weren't loaded; race between model load and an autoplay trigger.

Related errors


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