moeru-ai/airi · warning

Cannot set motion: model not loaded

Error message

Cannot set motion: model not loaded

What it means

setMotion() in the Live2D Model component requires a fully constructed Live2DModel instance in model.value. If a motion is requested before loadModel() finished (or after it bailed on a missing src or a load failure), the guard logs this warning and returns without throwing — the motion request is dropped.

Source

Thrown at packages/stage-ui-live2d/src/components/scenes/live2d/Model.vue:491

  // Build a function that can read exp3 files relative to the model root.
  // For URL-loaded models, resolveURL gives us the full URL. For ZIP-loaded
  // models the resolved URL points to an in-memory blob/object URL.
  const readExpFile = async (filePath: string): Promise<string> => {
    const resolvedUrl: string = settings.resolveURL?.(filePath) ?? filePath
    const response = await fetch(resolvedUrl)
    if (!response.ok)
      throw new Error(`Failed to fetch exp3 file: ${filePath} (${response.status})`)
    return response.text()
  }

  await expressionController.initialise(expressionRefs, readExpFile)
}

async function setMotion(motionName: string, index?: number) {
  // TODO: motion? Not every Live2D model has motion, we do need to help users to set motion
  if (!model.value) {
    console.warn('Cannot set motion: model not loaded')
    return
  }

  console.info('Setting motion:', motionName, 'index:', index)
  try {
    await model.value.motion(motionName, index, MotionPriority.FORCE)
    console.info('Motion started successfully:', motionName)
  }
  catch (error) {
    console.error('Failed to start motion:', motionName, error)
  }
}

const dropShadowColorComputer = ref<HTMLDivElement>()
const dropShadowAnimationId = ref(0)

function updateDropShadowFilter() {
  if (!model.value)

View on GitHub (pinned to 677329427f)

Solutions

  1. Only issue motions after the component reports the model is loaded (watch its exposed state or a load event)
  2. Queue motion requests and flush the queue once model.value exists
  3. Verify a modelSrc is actually set — without it the model never loads and every setMotion warns

Example fix

// before
onMounted(() => modelRef.value?.setMotion('greeting'))

// after
watch(
  () => modelRef.value?.componentState,
  (state) => {
    if (state === 'ready' || state === 'mounted')
      modelRef.value?.setMotion('greeting')
  },
)
Defensive patterns

Strategy: type-guard

Validate before calling

// Before requesting a motion
if (!modelRef.value?.model) {
  pendingMotions.push({ name: motionName, index })
}
else {
  modelRef.value.setMotion(motionName, index)
}

Type guard

// Inside the component, expose a readiness check
function isModelLoaded(): boolean {
  return model.value !== undefined && componentState.value === 'mounted'
}

Prevention

When it happens

Trigger: Calling the exposed setMotion (or dispatching a motion event that reaches it) while model.value is undefined: during initial load, right after modelSrc changes (old model destroyed, new one loading), or when no modelSrc was provided so loadModel returned early.

Common situations: Parent invokes setMotion in its own onMounted without waiting for the model's ready state; an LLM-driven message triggers a motion before the model finishes loading; motions queued for a previous model fire during a model switch.

Related errors


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