{"record":{"id":"08647f1e2c2285f8","repo":"moeru-ai/airi","slug":"the-first-motion-sample-must-start-at-0-ms","errorCode":null,"errorMessage":"The first motion sample must start at 0 ms.","messagePattern":"The first motion sample must start at 0 ms\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/stage-ui/src/features/devtools/motion/live2d/composables/recording.ts","lineNumber":96,"sourceCode":" * parseLive2DMotionRecording('{\"format\":\"airi-live2d-motion/v6\", ...}')\n * // => a validated recording\n */\nexport function parseLive2DMotionRecording(raw: string): Live2DMotionRecording {\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(live2dMotionRecordingSchema, input)\n  if (!result.success)\n    throw new Error('The file is not an AIRI Live2D motion recording.')\n\n  const { durationMs, samples } = result.output\n  if (samples[0].atMs !== 0)\n    throw new Error('The first motion sample must start at 0 ms.')\n\n  for (let index = 1; index < samples.length; index++) {\n    if (samples[index].atMs < samples[index - 1].atMs)\n      throw new Error('The motion samples must be in time order.')\n  }\n\n  if (samples.at(-1)!.atMs > durationMs)\n    throw new Error('A motion sample occurs after the recording duration.')\n\n  return result.output\n}\n\n/**\n * Serializes a Live2D joystick recording as a readable JSON file.\n *\n * @example\n * stringifyLive2DMotionRecording({ format: 'airi-live2d-motion/v6', ... })\n * // => readable JSON ending with a newline","sourceCodeStart":78,"sourceCodeEnd":114,"githubUrl":"https://github.com/moeru-ai/airi/blob/9c213115f8bd0fff9e6eabab02b077ac32da21be/packages/stage-ui/src/features/devtools/motion/live2d/composables/recording.ts#L78-L114","documentation":"Motion interpolation assumes the recording begins at t=0 so the first sample anchors the timeline. parseLive2DMotionRecording checks samples[0].atMs === 0 after schema validation and throws this error if the first sample starts at a later time. Recordings from the built-in recorder always start at 0, so this usually means the file was edited or trimmed incorrectly.","triggerScenarios":"Calling parseLive2DMotionRecording where the first sample's atMs is non-zero, e.g. after trimming the head of a recording without rebasing timestamps, or importing a fragment recorded starting at an offset.","commonSituations":"Manually deleting the first samples from a JSON export; programmatic trimming that removes samples but keeps original atMs values; concatenating recordings where the second clip retains absolute timestamps.","solutions":["Rebase all sample timestamps so the first sample is at 0 ms (subtract samples[0].atMs from every atMs, and durationMs accordingly)","Re-record the motion with the AIRI recorder","Trim samples via a tool that rebases timestamps automatically"],"exampleFix":"// before\nparseLive2DMotionRecording(raw)\n// after\nconst data = JSON.parse(raw)\nconst offset = data.samples[0].atMs\ndata.samples.forEach(s => { s.atMs -= offset })\ndata.durationMs -= offset\nparseLive2DMotionRecording(JSON.stringify(data))","handlingStrategy":"validation","validationCode":"const data = JSON.parse(raw)\nif (data.samples?.length && data.samples[0].atMs !== 0) {\n  const offset = data.samples[0].atMs\n  data.samples.forEach(s => { s.atMs -= offset })\n  data.durationMs -= offset\n  raw = JSON.stringify(data)\n}","typeGuard":"const startsAtZero = (v) => Array.isArray(v.samples) && v.samples.length > 0 && v.samples[0].atMs === 0","tryCatchPattern":"try {\n  const recording = parseLive2DMotionRecording(raw)\n}\ncatch (error) {\n  if (error.message === 'The first motion sample must start at 0 ms.')\n    showImportError('Recording must start at 0 ms. Trim with timestamp rebasing or re-record.')\n  else throw error\n}","preventionTips":["Rebase timestamps when trimming the head of a recording","Never delete leading samples without adjusting atMs values","Test trimming utilities against the samples[0].atMs === 0 invariant"],"tags":["validation","live2d","motion","timeline"],"backgroundTag":"invalid-timeline-start","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"}