{"record":{"id":"a3fc8b91faa81328","repo":"moeru-ai/airi","slug":"the-motion-project-source-timeline-is-invalid","errorCode":null,"errorMessage":"The motion project source timeline is invalid.","messagePattern":"The motion project source timeline is invalid\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/stage-ui/src/features/devtools/motion/live2d/composables/keyframes.ts","lineNumber":459,"sourceCode":"export 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  }\n  return structuredClone(project)\n}\n","sourceCodeStart":441,"sourceCodeEnd":477,"githubUrl":"https://github.com/moeru-ai/airi/blob/9c213115f8bd0fff9e6eabab02b077ac32da21be/packages/stage-ui/src/features/devtools/motion/live2d/composables/keyframes.ts#L441-L477","documentation":"After schema validation, parseLive2DMotionProject checks timeline invariants that the schema alone cannot express: the first source sample must start at atMs 0 and the last sample must not exceed project.durationMs. A violation means the source timeline does not cover/align with the project timeline and cannot be reliably resampled.","triggerScenarios":"Parsing a project whose source.samples[0].atMs is not 0 (e.g. samples start at 100ms) or whose last sample's atMs exceeds project.durationMs (e.g. last sample at 2500ms with durationMs 2000).","commonSituations":"Hand-trimming sample arrays without rebasing timestamps to 0; cropping samples externally and leaving the original end timestamp; exporter bug writing samples beyond the declared duration.","solutions":["Ensure the first sample has atMs: 0; rebase all sample timestamps by subtracting samples[0].atMs.","Remove or clamp samples whose atMs exceeds project.durationMs, or increase durationMs to cover the last sample.","Re-export the project from the AIRI motion devtools to regenerate a correctly aligned sample timeline."],"exampleFix":"// before\nsamples: [{ atMs: 120, values: {...} }, { atMs: 2400, values: {...} }] // durationMs: 2000\n// after\nsamples: [{ atMs: 0, values: {...} }, { atMs: 2000, values: {...} }]","handlingStrategy":"validation","validationCode":"function hasAlignedTimeline(parsed) {\n  const samples = parsed?.source?.samples\n  if (!Array.isArray(samples) || samples.length === 0)\n    return false\n  return samples[0].atMs === 0\n    && samples[samples.length - 1].atMs <= parsed.durationMs\n}","typeGuard":null,"tryCatchPattern":"try {\n  const project = parseLive2DMotionProject(raw)\n}\ncatch (error) {\n  if (error.message === 'The motion project source timeline is invalid.') {\n    notifyUser('Motion samples do not align with the project timeline (must start at 0ms and end within durationMs).')\n  }\n  else throw error\n}","preventionTips":["Always anchor the first sample at atMs: 0 when generating or trimming samples.","Clamp or drop samples beyond durationMs before export.","Rebase timestamps when externally cropping samples (subtract the crop start).","Include timeline alignment checks in your exporter's test suite."],"tags":["validation","timeline","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"}