{"record":{"id":"b7539c3470cfe575","repo":"moeru-ai/airi","slug":"expected-exactly-one-moc-file-got-mocfiles-leng","errorCode":null,"errorMessage":"Expected exactly one moc file, got ${mocFiles.length} ${fileList}","messagePattern":"Expected exactly one moc file, got (.+?) (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/stage-ui-live2d/src/utils/live2d-zip-loader.ts","lineNumber":166,"sourceCode":"}\n\nexport function isMocFile(file: string) {\n  return file.endsWith('.moc3')\n}\n\nexport function basename(path: string): string {\n  // https://stackoverflow.com/a/15270931\n  return path.split(/[\\\\/]/).pop()!\n}\n\n// copy and modified from https://github.com/guansss/live2d-viewer-web/blob/f6060b2ce52c2e26b6b61fa903c837fe343f72d1/src/app/upload.ts#L81-L142\nfunction createFakeSettings(files: string[]): ModelSettings {\n  const mocFiles = files.filter(file => isMocFile(file))\n\n  if (mocFiles.length !== 1) {\n    const fileList = mocFiles.length ? `(${mocFiles.map(f => `\"${f}\"`).join(',')})` : ''\n\n    throw new Error(`Expected exactly one moc file, got ${mocFiles.length} ${fileList}`)\n  }\n\n  const mocFile = mocFiles[0]\n  const modelName = basename(mocFile).replace(/\\.moc3?/, '')\n\n  const textures = files.filter(f => f.endsWith('.png'))\n\n  if (!textures.length) {\n    throw new Error('Textures not found')\n  }\n\n  const motions = files.filter(f => f.endsWith('.mtn') || f.endsWith('.motion3.json'))\n  const physics = files.find(f => f.includes('physics'))\n  const pose = files.find(f => f.includes('pose'))\n\n  const settings = new Cubism4ModelSettings({\n    url: `${modelName}.model3.json`,\n    Version: 3,","sourceCodeStart":148,"sourceCodeEnd":184,"githubUrl":"https://github.com/moeru-ai/airi/blob/27111382b4a79a7e983289d6e983a06af185ed0f/packages/stage-ui-live2d/src/utils/live2d-zip-loader.ts#L148-L184","documentation":"`createFakeSettings(files)` builds a synthetic `Cubism4ModelSettings` when no `.model3.json` is present. It filters `files` for moc files (`isMocFile`) and requires exactly one; otherwise it throws `Expected exactly one moc file, got <count> <fileList>`. With one moc it synthesizes a manifest; with zero or more than one it aborts.","triggerScenarios":"An archive with zero `.moc3`/`.moc` files (moc missing), or an archive containing two or more moc files (multiple models bundled together). `fileList` is empty when count is 0, or a parenthesized list of paths when count > 1.","commonSituations":"User dropped a ZIP that contains multiple characters/moc files in one folder; the moc file was renamed or omitted during packaging; a texture-only archive; a Cubism 2 archive loaded through the Cubism 4 fake-settings path.","solutions":["Ensure the archive contains exactly one `.moc3` (or `.moc`) file at the expected location.","If the archive holds multiple models, split it into one archive per model, or supply a real `.model3.json` so `createFakeSettings` is not used.","Inspect the echoed `<fileList>` to identify and remove the extra moc.","If a `.model3.json` exists, let the normal settings path run instead of falling back to fake settings."],"exampleFix":"// before\n// user archive with two moc3 files -> throws in createFakeSettings\n\n// after\n// package one model per archive, or include a model3.json so the manifest path is used\nconst mocs = files.filter(isMocFile)\nif (mocs.length !== 1) {\n  throw new Error(`Archive must contain exactly one moc file; found ${mocs.length}: ${mocs.join(', ')}`)\n}","handlingStrategy":"validation","validationCode":"const mocs = files.filter(isMocFile)\nif (mocs.length !== 1)\n  throw new Error(`Expected exactly one moc file, got ${mocs.length}`)\ncreateFakeSettings(files)","typeGuard":"function hasExactlyOneMoc(files: string[]): boolean {\n  return files.filter(isMocFile).length === 1\n}","tryCatchPattern":"try {\n  await loadLive2DFromArchive(blob)\n} catch (e) {\n  if (e instanceof Error && e.message.includes('one moc file')) {\n    // ask user to pick an archive with a single model\n  } else throw e\n}","preventionTips":["Package one model per archive or include a real model3.json.","Validate moc count before loading.","Inspect the echoed fileList to find duplicates."],"tags":["live2d","model-loading","archive","input-validation"],"backgroundTag":null,"analyzedSha":"27111382b4a79a7e983289d6e983a06af185ed0f","analyzedAt":"2026-08-12T18:33:34.132Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}