{"record":{"id":"b63f313134ce28fc","repo":"moeru-ai/airi","slug":"the-motion-project-source-is-invalid","errorCode":null,"errorMessage":"The motion project source is invalid.","messagePattern":"The motion project source is invalid\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/stage-ui/src/features/devtools/motion/live2d/composables/keyframes.ts","lineNumber":456,"sourceCode":"}\n\n/** Parses a motion project file and checks its structural and timeline invariants. */\nexport function parseLive2DMotionProject(raw: string): Live2DMotionProject {\n  let input: unknown\n  try {\n    input = JSON.parse(raw)\n  }\n  catch {\n    throw new Error('The file does not contain valid JSON.')\n  }\n\n  const result = safeParse(motionProjectSchema, input)\n  if (!result.success)\n    throw new Error('The file is not an AIRI Live2D motion project.')\n\n  const project = result.output\n  if (project.source.durationMs !== project.durationMs)\n    throw new Error('The motion project source is invalid.')\n\n  if (project.source.samples[0].atMs !== 0 || project.source.samples.at(-1)!.atMs > project.durationMs)\n    throw new Error('The motion project source timeline is invalid.')\n  for (let index = 1; index < project.source.samples.length; index++) {\n    if (project.source.samples[index].atMs < project.source.samples[index - 1].atMs)\n      throw new Error('The motion project source samples are not in time order.')\n  }\n\n  for (const overlay of project.overlays) {\n    if (overlay.endMs > project.durationMs || overlay.startMs > overlay.endMs)\n      throw new Error('The motion project contains an invalid overlay span.')\n    if (overlay.points.some(point => point.atMs < overlay.startMs || point.atMs > overlay.endMs))\n      throw new Error('The motion project contains an invalid overlay point.')\n    for (let index = 1; index < overlay.points.length; index++) {\n      if (overlay.points[index].atMs < overlay.points[index - 1].atMs)\n        throw new Error('The motion project overlay points are not in time order.')\n    }\n  }","sourceCodeStart":438,"sourceCodeEnd":474,"githubUrl":"https://github.com/moeru-ai/airi/blob/9c213115f8bd0fff9e6eabab02b077ac32da21be/packages/stage-ui/src/features/devtools/motion/live2d/composables/keyframes.ts#L438-L474","documentation":"This is an internal-consistency check: the embedded source motion's durationMs must equal the project's top-level durationMs. If they diverge, the project file is considered corrupt or incorrectly assembled, since the cropped project derives its timeline from the source.","triggerScenarios":"Parsing a project JSON where source.durationMs differs from project.durationMs (e.g. hand-edited durations, partial migration between schema versions, or a file assembled by copying fields from two different projects).","commonSituations":"Manual editing of exported project files; a buggy external exporter writing mismatched durations; concatenating fields from two motion projects.","solutions":["Set source.durationMs equal to the project's top-level durationMs (or vice versa to the true motion length).","Re-export the motion project from the AIRI devtools instead of hand-editing.","Verify the file was not merged from two different projects."],"exampleFix":"// before\n{ \"durationMs\": 2000, \"source\": { \"durationMs\": 1500, \"samples\": [...] } }\n// after\n{ \"durationMs\": 2000, \"source\": { \"durationMs\": 2000, \"samples\": [...] } }","handlingStrategy":"validation","validationCode":"function hasConsistentDurations(parsed) {\n  return parsed.source != null\n    && typeof parsed.source.durationMs === 'number'\n    && typeof parsed.durationMs === 'number'\n    && parsed.source.durationMs === parsed.durationMs\n}","typeGuard":null,"tryCatchPattern":"try {\n  const project = parseLive2DMotionProject(raw)\n}\ncatch (error) {\n  if (error.message === 'The motion project source is invalid.') {\n    notifyUser('Project file is inconsistent: source duration does not match project duration. Re-export the file.')\n  }\n  else throw error\n}","preventionTips":["Always write source.durationMs and durationMs from the same value at export time.","Run a pre-import check that these two fields match and warn early.","Avoid merging fields from different exported projects.","Re-export rather than manually editing exported files."],"tags":["validation","consistency-check","live2d","import"],"backgroundTag":"schema-validation-failed","analyzedSha":"9c213115f8bd0fff9e6eabab02b077ac32da21be","analyzedAt":"2026-09-02T04:27:24.639Z","contentChangedAt":"2026-09-02T04:27:24.639Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}