moeru-ai/airi · warning

Emotion ${emotionName} not found

Error message

Emotion ${emotionName} not found

What it means

useVRMEmote(vrm).setEmotion() looks up emotionName in a local emotionStates Map that defines exactly: happy, sad, angry, surprised, neutral, think, relaxed. Unknown names warn and return — no expression change, no transition. Note the VRM table is intentionally narrower than the shared Emotion enum (no curious/question/awkward), so valid upstream emotions can still miss here.

Source

Thrown at packages/stage-ui-three/src/composables/vrm/expression.ts:100

      expression: [
        { name: 'relaxed', value: 0.7 },
      ],
      blendDuration: 0.4,
    }],
  ])

  const clearResetTimeout = () => {
    if (resetTimeout.value) {
      clearTimeout(resetTimeout.value)
      resetTimeout.value = undefined
    }
  }

  const setEmotion = (emotionName: string, intensity = 1) => {
    clearResetTimeout()

    if (!emotionStates.has(emotionName)) {
      console.warn(`Emotion ${emotionName} not found`)
      return
    }

    const emotionState = emotionStates.get(emotionName)!
    currentEmotion.value = emotionName
    isTransitioning.value = true
    transitionProgress.value = 0

    // Store current expression values as starting point BEFORE resetting,
    // so the lerp transition starts from the actual displayed values
    // instead of snapping to 0 first (fixes #590).
    currentExpressionValues.value.clear()
    targetExpressionValues.value.clear()

    const normalizedIntensity = clampIntensity(intensity)

    if (vrm.expressionManager) {
      // Capture current values for all expressions we'll be transitioning

View on GitHub (pinned to 677329427f)

Solutions

  1. Map emotion names through EMOTION_VRMExpressionName_value (packages/stage-ui/src/constants/emotions.ts) before calling setEmotion.
  2. Add the missing state to the emotionStates Map in packages/stage-ui-three/src/composables/vrm/expression.ts.
  3. Normalize/validate names at the boundary (queues.ts normalizeEmotionName pattern) so only supported keys reach the renderer.

Example fix

// before
emote.setEmotion(emotionName) // 'curious' warns: not in emotionStates

// after
const VRM_EMOTIONS = new Set(['happy', 'sad', 'angry', 'surprised', 'neutral', 'think', 'relaxed'])
emote.setEmotion(VRM_EMOTIONS.has(emotionName) ? emotionName : 'neutral')
Defensive patterns

Strategy: type-guard

Validate before calling

const VRM_EMOTIONS = new Set(['happy', 'sad', 'angry', 'surprised', 'neutral', 'think', 'relaxed'])
if (!VRM_EMOTIONS.has(name)) name = 'neutral'

Type guard

function isVRMEmotion(name: string): boolean {
  return ['happy', 'sad', 'angry', 'surprised', 'neutral', 'think', 'relaxed'].includes(name)
}

Prevention

When it happens

Trigger: Forwarding Emotion enum values like 'curious', 'question', or 'awkward' straight from the emotions queue to setEmotion; passing arbitrary LLM strings without normalization; typos ('surprise' instead of 'surprised').

Common situations: Shared emotion vocabulary grows but the VRM emote table is not extended; feeding raw ACT payload names into the renderer; a model lacking 'relaxed' custom expressions in a forked table.

Related errors


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