{"record":{"id":"8680a70033642849","repo":"moeru-ai/airi","slug":"the-motion-project-contains-an-invalid-overlay-poi","errorCode":null,"errorMessage":"The motion project contains an invalid overlay point.","messagePattern":"The motion project contains an invalid overlay point\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/stage-ui/src/features/devtools/motion/live2d/composables/keyframes.ts","lineNumber":469,"sourceCode":"  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":451,"sourceCodeEnd":477,"githubUrl":"https://github.com/moeru-ai/airi/blob/9c213115f8bd0fff9e6eabab02b077ac32da21be/packages/stage-ui/src/features/devtools/motion/live2d/composables/keyframes.ts#L451-L477","documentation":"parseLive2DMotionProject requires every overlay point's atMs to lie within the overlay's own span [startMs, endMs]. Points outside the span cannot be interpolated correctly and indicate corrupt or misplaced data. This error is thrown when any overlay point's atMs is less than startMs or greater than endMs.","triggerScenarios":"Calling parseLive2DMotionProject with an overlay whose points contain atMs values outside its [startMs, endMs] window, typically after editing the span or moving points independently.","commonSituations":"Narrowing an overlay's span after points were placed; drag-editing UIs that update points before spans; programmatically shifting overlay spans without re-clamping point timestamps; imported motion data with mismatched units.","solutions":["Clamp each point.atMs to [overlay.startMs, overlay.endMs] before parsing","Recompute or move points whenever an overlay span changes","Regenerate the overlay from its source data if points and span disagree"],"exampleFix":"// before\noverlay.points = importedPoints\n// after\noverlay.points = importedPoints.map(p => ({ ...p, atMs: Math.min(Math.max(p.atMs, overlay.startMs), overlay.endMs) }))","handlingStrategy":"validation","validationCode":"for (const overlay of project.overlays) {\n  for (const point of overlay.points)\n    point.atMs = Math.min(Math.max(point.atMs, overlay.startMs), overlay.endMs)\n}","typeGuard":"const pointsWithinSpan = (o) => o.points.every(p => p.atMs >= o.startMs && p.atMs <= o.endMs)","tryCatchPattern":"try {\n  parseLive2DMotionProject(project)\n}\ncatch (error) {\n  if (error.message.includes('invalid overlay point'))\n    console.error('An overlay point lies outside its span; clamp point.atMs to [startMs, endMs]')\n  throw error\n}","preventionTips":["Re-clamp all points whenever an overlay span is resized","Move points and spans together in editing UIs","Validate imported overlay data before persistence"],"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"}