moeru-ai/airi · error · Error

Expected exactly one moc file, got ${mocFiles.length} ${file

Error message

Expected exactly one moc file, got ${mocFiles.length} ${fileList}

What it means

`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.

Source

Thrown at packages/stage-ui-live2d/src/utils/live2d-zip-loader.ts:166

}

export function isMocFile(file: string) {
  return file.endsWith('.moc3')
}

export function basename(path: string): string {
  // https://stackoverflow.com/a/15270931
  return path.split(/[\\/]/).pop()!
}

// copy and modified from https://github.com/guansss/live2d-viewer-web/blob/f6060b2ce52c2e26b6b61fa903c837fe343f72d1/src/app/upload.ts#L81-L142
function createFakeSettings(files: string[]): ModelSettings {
  const mocFiles = files.filter(file => isMocFile(file))

  if (mocFiles.length !== 1) {
    const fileList = mocFiles.length ? `(${mocFiles.map(f => `"${f}"`).join(',')})` : ''

    throw new Error(`Expected exactly one moc file, got ${mocFiles.length} ${fileList}`)
  }

  const mocFile = mocFiles[0]
  const modelName = basename(mocFile).replace(/\.moc3?/, '')

  const textures = files.filter(f => f.endsWith('.png'))

  if (!textures.length) {
    throw new Error('Textures not found')
  }

  const motions = files.filter(f => f.endsWith('.mtn') || f.endsWith('.motion3.json'))
  const physics = files.find(f => f.includes('physics'))
  const pose = files.find(f => f.includes('pose'))

  const settings = new Cubism4ModelSettings({
    url: `${modelName}.model3.json`,
    Version: 3,

View on GitHub (pinned to 27111382b4)

Solutions

  1. Ensure the archive contains exactly one `.moc3` (or `.moc`) file at the expected location.
  2. If the archive holds multiple models, split it into one archive per model, or supply a real `.model3.json` so `createFakeSettings` is not used.
  3. Inspect the echoed `<fileList>` to identify and remove the extra moc.
  4. If a `.model3.json` exists, let the normal settings path run instead of falling back to fake settings.

Example fix

// before
// user archive with two moc3 files -> throws in createFakeSettings

// after
// package one model per archive, or include a model3.json so the manifest path is used
const mocs = files.filter(isMocFile)
if (mocs.length !== 1) {
  throw new Error(`Archive must contain exactly one moc file; found ${mocs.length}: ${mocs.join(', ')}`)
}
Defensive patterns

Strategy: validation

Validate before calling

const mocs = files.filter(isMocFile)
if (mocs.length !== 1)
  throw new Error(`Expected exactly one moc file, got ${mocs.length}`)
createFakeSettings(files)

Type guard

function hasExactlyOneMoc(files: string[]): boolean {
  return files.filter(isMocFile).length === 1
}

Try / catch

try {
  await loadLive2DFromArchive(blob)
} catch (e) {
  if (e instanceof Error && e.message.includes('one moc file')) {
    // ask user to pick an archive with a single model
  } else throw e
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of moeru-ai/airi@27111382b4 (2026-08-12). Data as JSON: /api/errors/b7539c3470cfe575. Report an issue: GitHub.