{"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/677329427f32468c74b17f3ec47eeca4e05bec65/packages/stage-ui-mmd/src/utils/mmd-zip-loader.ts#L102-L138","documentation":"Thrown by loadMMDZip when detectMMDVariants finds no .pmx or .pmd entry among the archive's non-directory file paths. MMD models must ship the binary mesh in one of those two formats; a ZIP with only textures, motions, or a nested archive yields zero variants and fails before any blob URL materialization matters.","triggerScenarios":"Uploading a texture-only or VMD-motion-only ZIP; a .zip inside the .zip containing the actual model; the model file having an unusual extension case (.PMX passes only if the filter lowercases — verify with detectMMDVariants) or being replaced by a git-lfs pointer file that kept a different name.","commonSituations":"Users zipping the wrong folder (textures instead of model root); archives auto-converted by upload pipelines; downloading an 'all files' bundle where the .pmx sits in a subfolder the variant detector does not scan","solutions":["Download a fresh copy of the model and re-zip including the .pmx/.pmd at the expected location","Pre-scan uploads before calling loadMMDZip: const hasModel = Object.keys(zip.files).some(n => /\\.pm[xd]$/i.test(n))"],"exampleFix":"// before\nconst assets = await loadMMDZip(file) // throws 'MMD ZIP must contain a .pmx or .pmd model file'\n\n// after\nimport JSZip from 'jszip'\nconst zip = await new JSZip().loadAsync(file)\nconst modelEntries = Object.keys(zip.files).filter(n => !zip.files[n].dir && /\\.pm[xd]$/i.test(n))\nif (modelEntries.length === 0) {\n  throw new Error('This ZIP has no .pmx/.pmd model file. Zip the model folder that contains the .pmx (or .pmd) plus its textures.')\n}\nconst assets = await loadMMDZip(file)","handlingStrategy":"validation","validationCode":"import JSZip from 'jszip'\nconst zip = await new JSZip().loadAsync(file)\nconst paths = Object.keys(zip.files).filter(n => !zip.files[n].dir)\nif (!paths.some(n => /\\.pm[xd]$/i.test(n))) {\n  throw new Error('No .pmx/.pmd in this ZIP — include the model file and re-zip')\n}","typeGuard":"const hasMMDModelEntry = (names: string[]): boolean => names.some(n => /\\.pm[xd]$/i.test(n))","tryCatchPattern":"catch (e) { if (e.message.includes('.pmx or .pmd')) { showUserError('This ZIP has no MMD model file'); return } throw e }","preventionTips":["Validate archive contents before invoking the loader","Extract nested zips before loading","Ship pmx/pmd together with textures in one archive"],"tags":["mmd","zip","pmx","missing-model-file"],"backgroundTag":"mmd-model-load-failed","analyzedSha":"677329427f32468c74b17f3ec47eeca4e05bec65","analyzedAt":"2026-08-18T17:29:58.153Z","contentChangedAt":"2026-08-18T17:29:58.153Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}