{"record":{"id":"78a10b9838b28b6d","repo":"moeru-ai/airi","slug":"the-motion-project-source-samples-are-not-in-time","errorCode":null,"errorMessage":"The motion project source samples are not in time order.","messagePattern":"The motion project source samples are not in time order\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/stage-ui/src/features/devtools/motion/live2d/composables/keyframes.ts","lineNumber":462,"sourceCode":"    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":444,"sourceCodeEnd":477,"githubUrl":"https://github.com/moeru-ai/airi/blob/9c213115f8bd0fff9e6eabab02b077ac32da21be/packages/stage-ui/src/features/devtools/motion/live2d/composables/keyframes.ts#L444-L477","documentation":"parseLive2DMotionProject validates a Live2D motion project before use. Source samples must be sorted by atMs in non-decreasing order because the interpolator walks samples forward and binary-searches by time; unsorted samples produce broken animation. This error is thrown when any sample's atMs is less than the previous sample's atMs.","triggerScenarios":"Calling parseLive2DMotionProject with project.source.samples whose atMs values are not non-decreasing (e.g. samples appended out of order, records from parallel sources merged without sorting, or negative/zero timestamps inserted mid-array).","commonSituations":"Hand-edited or externally generated motion JSON where sample entries were reordered; merging recordings from multiple input devices without a stable sort; constructing samples programmatically with async callbacks that resolve out of order.","solutions":["Sort project.source.samples by atMs ascending before calling parseLive2DMotionProject","Ensure the sample recording/collection code appends samples in chronological order","Validate the JSON file for out-of-order timestamps before loading it"],"exampleFix":"// before\nparseLive2DMotionProject(project)\n// after\nproject.source.samples.sort((a, b) => a.atMs - b.atMs)\nparseLive2DMotionProject(project)","handlingStrategy":"validation","validationCode":"function samplesAreSorted(samples) {\n  return samples.every((s, i) => i === 0 || s.atMs >= samples[i - 1].atMs)\n}\nif (!samplesAreSorted(project.source.samples))\n  project.source.samples.sort((a, b) => a.atMs - b.atMs)","typeGuard":"const isSortedByAtMs = (items) => items.every((s, i) => i === 0 || s.atMs >= items[i - 1].atMs)","tryCatchPattern":"try {\n  parseLive2DMotionProject(project)\n}\ncatch (error) {\n  if (error.message.includes('not in time order')) {\n    project.source.samples.sort((a, b) => a.atMs - b.atMs)\n    parseLive2DMotionProject(project)\n  }\n  else throw error\n}","preventionTips":["Always sort samples by atMs right before parsing","Never append samples from async sources without ordering guarantees","Add a unit test asserting sample order for every project fixture"],"tags":["validation","live2d","motion","data-order"],"backgroundTag":"samples-not-in-time-order","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"}