{"record":{"id":"831ea758ef655095","repo":"moeru-ai/airi","slug":"cannot-set-motion-model-not-loaded","errorCode":null,"errorMessage":"Cannot set motion: model not loaded","messagePattern":"Cannot set motion: model not loaded","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"packages/stage-ui-live2d/src/components/scenes/live2d/Model.vue","lineNumber":491,"sourceCode":"\n  // Build a function that can read exp3 files relative to the model root.\n  // For URL-loaded models, resolveURL gives us the full URL. For ZIP-loaded\n  // models the resolved URL points to an in-memory blob/object URL.\n  const readExpFile = async (filePath: string): Promise<string> => {\n    const resolvedUrl: string = settings.resolveURL?.(filePath) ?? filePath\n    const response = await fetch(resolvedUrl)\n    if (!response.ok)\n      throw new Error(`Failed to fetch exp3 file: ${filePath} (${response.status})`)\n    return response.text()\n  }\n\n  await expressionController.initialise(expressionRefs, readExpFile)\n}\n\nasync function setMotion(motionName: string, index?: number) {\n  // TODO: motion? Not every Live2D model has motion, we do need to help users to set motion\n  if (!model.value) {\n    console.warn('Cannot set motion: model not loaded')\n    return\n  }\n\n  console.info('Setting motion:', motionName, 'index:', index)\n  try {\n    await model.value.motion(motionName, index, MotionPriority.FORCE)\n    console.info('Motion started successfully:', motionName)\n  }\n  catch (error) {\n    console.error('Failed to start motion:', motionName, error)\n  }\n}\n\nconst dropShadowColorComputer = ref<HTMLDivElement>()\nconst dropShadowAnimationId = ref(0)\n\nfunction updateDropShadowFilter() {\n  if (!model.value)","sourceCodeStart":473,"sourceCodeEnd":509,"githubUrl":"https://github.com/moeru-ai/airi/blob/677329427f32468c74b17f3ec47eeca4e05bec65/packages/stage-ui-live2d/src/components/scenes/live2d/Model.vue#L473-L509","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Only issue motions after the component reports the model is loaded (watch its exposed state or a load event)","Queue motion requests and flush the queue once model.value exists","Verify a modelSrc is actually set — without it the model never loads and every setMotion warns"],"exampleFix":"// before\nonMounted(() => modelRef.value?.setMotion('greeting'))\n\n// after\nwatch(\n  () => modelRef.value?.componentState,\n  (state) => {\n    if (state === 'ready' || state === 'mounted')\n      modelRef.value?.setMotion('greeting')\n  },\n)","handlingStrategy":"type-guard","validationCode":"// Before requesting a motion\nif (!modelRef.value?.model) {\n  pendingMotions.push({ name: motionName, index })\n}\nelse {\n  modelRef.value.setMotion(motionName, index)\n}","typeGuard":"// Inside the component, expose a readiness check\nfunction isModelLoaded(): boolean {\n  return model.value !== undefined && componentState.value === 'mounted'\n}","tryCatchPattern":null,"preventionTips":["Wait for the component's loaded state before issuing motions","Queue motion requests that arrive during load and flush them on ready","Do not call setMotion after destroying the model during a model switch"],"tags":["live2d","motion","initialization-order","vue"],"backgroundTag":"called-before-initialized","analyzedSha":"677329427f32468c74b17f3ec47eeca4e05bec65","analyzedAt":"2026-08-18T17:29:58.153Z","contentChangedAt":"2026-08-18T17:29:58.153Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}