{"record":{"id":"ed29afc438e96495","repo":"moeru-ai/airi","slug":"the-motion-project-contains-an-invalid-overlay-spa","errorCode":null,"errorMessage":"The motion project contains an invalid overlay span.","messagePattern":"The motion project contains an invalid overlay span\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/stage-ui/src/features/devtools/motion/live2d/composables/keyframes.ts","lineNumber":467,"sourceCode":"\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  }\n  return structuredClone(project)\n}\n","sourceCodeStart":449,"sourceCodeEnd":477,"githubUrl":"https://github.com/moeru-ai/airi/blob/9c213115f8bd0fff9e6eabab02b077ac32da21be/packages/stage-ui/src/features/devtools/motion/live2d/composables/keyframes.ts#L449-L477","documentation":"parseLive2DMotionProject checks that every overlay span fits inside the project duration and has startMs <= endMs. Overlays are time-bounded modulation layers over the base motion, so a span extending beyond the timeline or inverted bounds is structurally invalid. This error is thrown when overlay.endMs > project.durationMs or overlay.startMs > overlay.endMs.","triggerScenarios":"Calling parseLive2DMotionProject where an overlay has startMs greater than endMs (inverted span) or endMs exceeding project.durationMs (span out of timeline bounds).","commonSituations":"Setting overlay bounds from user input without clamping to project.durationMs; editing project.durationMs shorter after overlays were created; copying overlay data between projects with different durations; typos swapping startMs and endMs.","solutions":["Clamp overlay.startMs and overlay.endMs to [0, project.durationMs] before parsing","Ensure startMs <= endMs when constructing the overlay","Recompute overlay spans after any change to project.durationMs"],"exampleFix":"// before\noverlay.startMs = userStart\noverlay.endMs = userEnd\n// after\noverlay.startMs = Math.max(0, Math.min(userStart, project.durationMs))\noverlay.endMs = Math.max(overlay.startMs, Math.min(userEnd, project.durationMs))","handlingStrategy":"validation","validationCode":"for (const overlay of project.overlays) {\n  overlay.startMs = Math.max(0, Math.min(overlay.startMs, project.durationMs))\n  overlay.endMs = Math.max(overlay.startMs, Math.min(overlay.endMs, project.durationMs))\n}","typeGuard":"const hasValidSpan = (o, durationMs) => o.startMs <= o.endMs && o.endMs <= durationMs","tryCatchPattern":"try {\n  parseLive2DMotionProject(project)\n}\ncatch (error) {\n  if (error.message.includes('invalid overlay span'))\n    console.error('Overlay span out of bounds; clamp startMs/endMs to project durationMs')\n  throw error\n}","preventionTips":["Clamp overlay bounds whenever project.durationMs changes","Never build spans from raw user input without clamping","Validate spans in the overlay editing UI before saving"],"tags":["validation","live2d","motion","overlay"],"backgroundTag":"invalid-time-span","analyzedSha":"9c213115f8bd0fff9e6eabab02b077ac32da21be","analyzedAt":"2026-09-02T04:27:24.639Z","contentChangedAt":"2026-09-02T04:27:24.639Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}