moeru-ai/airi · warning

[mmd] emotion ${emotion} not found

Error message

[mmd] emotion ${emotion} not found

What it means

useMMDEmote().setEmotion() maps an emotion name to MMD morph influences via the EMOTION_MORPHS table (packages/stage-ui-mmd/src/constants/morphs.ts). When the passed Emotion key has no entry in that table, the function warns and returns without starting a transition, so the model's face stays in its previous state. This is a graceful-degradation guard, not a crash: TypeScript normally prevents bad keys, but the value often arrives from untrusted LLM output at runtime.

Source

Thrown at packages/stage-ui-mmd/src/composables/mmd/expression.ts:56

  const isTransitioning = ref(false)
  const transitionProgress = ref(0)
  const startWeights = new Map<MorphSlot, number>()
  const targetWeights = new Map<MorphSlot, number>()
  let resetTimeout: ReturnType<typeof setTimeout> | undefined

  function clearResetTimeout() {
    if (resetTimeout) {
      clearTimeout(resetTimeout)
      resetTimeout = undefined
    }
  }

  function setEmotion(emotion: Emotion, intensity = 1) {
    clearResetTimeout()

    const state = EMOTION_MORPHS[emotion]
    if (!state) {
      console.warn(`[mmd] emotion ${emotion} not found`)
      return
    }

    currentEmotion.value = emotion
    isTransitioning.value = true
    transitionProgress.value = 0
    startWeights.clear()
    targetWeights.clear()

    const normalized = clampIntensity(intensity)

    // Capture where each expression slot currently sits so the cross-fade
    // starts from the live value instead of snapping to zero first.
    for (const slot of EXPRESSION_SLOTS) {
      startWeights.set(slot, morphs.get(slot))
      targetWeights.set(slot, 0)
    }

View on GitHub (pinned to 677329427f)

Solutions

  1. Check which Emotion keys EMOTION_MORPHS actually defines in packages/stage-ui-mmd/src/constants/morphs.ts and confirm the emotion you send is among them.
  2. Filter/normalize emotion names at the boundary (as queues.ts normalizeEmotionName does) so only mapped emotions reach setEmotion.
  3. Add an EMOTION_MORPHS entry mapping the missing emotion to available morph slots (smile/anger/sad/surprise/troubled/serious).
  4. Fall back to a close mapped emotion (e.g. 'neutral') before calling setEmotion.

Example fix

// before
emote.setEmotion(rawName as Emotion) // warns when unmapped

// after
import { EMOTION_MORPHS } from '../../constants/morphs'
if (rawName in EMOTION_MORPHS) {
  emote.setEmotion(rawName as Emotion, intensity)
}
else {
  emote.setEmotion(Emotion.Neutral, intensity)
}
Defensive patterns

Strategy: type-guard

Validate before calling

import { EMOTION_MORPHS } from '../constants/morphs'
const supported = Object.keys(EMOTION_MORPHS)
if (!supported.includes(name)) name = 'neutral'

Type guard

function isMappedMmdEmotion(name: string): name is Emotion {
  return Object.prototype.hasOwnProperty.call(EMOTION_MORPHS, name)
}

Prevention

When it happens

Trigger: An ACT emotion payload from the agent/LLM resolves to an Emotion enum value (e.g. 'curious', 'question', 'awkward') that has no morph mapping in EMOTION_MORPHS for the loaded MMD model, and the emotions queue forwards it to useMMDEmote().setEmotion(). Calling setEmotion('relaxed' as Emotion) directly with a key outside the table also triggers it.

Common situations: Adding a new value to the shared Emotion enum (packages/stage-ui/src/constants/emotions.ts) without adding a matching EMOTION_MORPHS entry; MMD models whose morph sets only cover a subset of emotions; casting a raw string to Emotion to bypass the compiler.

Related errors


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