{"record":{"id":"9444b798134778ed","repo":"moeru-ai/airi","slug":"mmd-zip-must-contain-a-pmx-or-pmd-model-file","errorCode":null,"errorMessage":"MMD ZIP must contain a .pmx or .pmd model file","messagePattern":"MMD ZIP must contain a \\.pmx or \\.pmd model file","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/stage-ui-mmd/src/utils/mmd-zip-loader.ts","lineNumber":120,"sourceCode":"    return byBasename.get(base) ?? requested\n  }\n}\n\n/**\n * Loads an MMD model packaged as a ZIP (the common distribution format: a\n * `.pmx`/`.pmd` plus its texture/toon/sphere-map files) into blob URLs ready\n * for {@link createMMDLoaderContext}.\n *\n * Call `dispose()` on unmount or reload to revoke the blob URLs.\n */\nexport async function loadMMDZip(file: File | Blob | ArrayBuffer): Promise<MMDLoadedAssets> {\n  const zip = new JSZip()\n  const archive = await zip.loadAsync(file)\n\n  const paths = Object.keys(archive.files).filter(name => !archive.files[name].dir)\n  const variants = detectMMDVariants(paths)\n  if (variants.length === 0)\n    throw new Error('MMD ZIP must contain a .pmx or .pmd model file')\n\n  const blobUrls: Record<string, string> = {}\n  await Promise.all(paths.map(async (path) => {\n    const entry = archive.files[path]\n    if (!entry)\n      return\n    const blob = await entry.async('blob')\n    blobUrls[path] = URL.createObjectURL(blob)\n  }))\n\n  const variant = variants[0]\n  const modelBlobUrl = blobUrls[variant.modelPath]\n\n  return {\n    variant,\n    variants,\n    modelBlobUrl,\n    blobUrls,","sourceCodeStart":102,"sourceCodeEnd":138,"githubUrl":"https://github.com/moeru-ai/airi/blob/27111382b4a79a7e983289d6e983a06af185ed0f/packages/stage-ui-mmd/src/utils/mmd-zip-loader.ts#L102-L138","documentation":"`loadMMDZip(file)` loads the ZIP, lists non-directory entries, and runs `detectMMDVariants(paths)`. If `variants.length === 0` it throws `MMD ZIP must contain a .pmx or .pmd model file` — the archive has no recognizable MMD model mesh file at the top level of detection.","triggerScenarios":"A ZIP that contains textures/audio but no `.pmx` or `.pmd`; a ZIP whose model file uses an unsupported extension (`.pmm`, `.vpd`, `.vmd` motion-only); a corrupt ZIP that `loadAsync` parsed but whose file list is incomplete; a Live2D or Spine archive loaded through the MMD path.","commonSituations":"User selected the wrong archive type (Live2D/Spine instead of MMD); motion-only MMD package; model file renamed to `.pmc`/typo; ZIP produced by a tool that nested the model in a way `detectMMDVariants` skips.","solutions":["Confirm the ZIP contains at least one `.pmx` or `.pmd` model file.","Route the archive to the correct loader (Live2D for `.model3.json`, Spine for `.skel`+`.atlas`).","Re-export from the MMD authoring tool ensuring the mesh file is included.","Inspect `Object.keys(zip.files)` after `loadAsync` to see what was actually detected."],"exampleFix":"// before\nconst assets = await loadMMDZip(blob)  // throws if no pmx/pmd\n\n// after\nconst zip = await new JSZip().loadAsync(blob)\nconst hasModel = Object.keys(zip.files).some(n => n.endsWith('.pmx') || n.endsWith('.pmd'))\nif (!hasModel) throw new Error('ZIP has no .pmx/.pmd model file')\nconst assets = await loadMMDZip(blob)","handlingStrategy":"validation","validationCode":"const zip = await new JSZip().loadAsync(file)\nconst names = Object.keys(zip.files).filter(n => !zip.files[n].dir)\nif (!names.some(n => n.endsWith('.pmx') || n.endsWith('.pmd')))\n  throw new Error('ZIP has no .pmx/.pmd model file')\nawait loadMMDZip(file)","typeGuard":"function zipHasMmdModel(names: string[]): boolean {\n  return names.some(n => n.endsWith('.pmx') || n.endsWith('.pmd'))\n}","tryCatchPattern":"try {\n  await loadMMDZip(blob)\n} catch (e) {\n  if (e instanceof Error && e.message.includes('must contain a .pmx or .pmd')) {\n    // suggest the user pick an MMD archive\n  } else throw e\n}","preventionTips":["Confirm the archive is an MMD export before loading.","Route non-MMD archives to the correct loader.","Inspect zip.files entries when in doubt."],"tags":["mmd","model-loading","archive","input-validation"],"backgroundTag":null,"analyzedSha":"27111382b4a79a7e983289d6e983a06af185ed0f","analyzedAt":"2026-08-12T18:33:34.132Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}