{"record":{"id":"3fbd9728ebdbc228","repo":"moeru-ai/airi","slug":"a-motion-sample-occurs-after-the-recording-duratio","errorCode":null,"errorMessage":"A motion sample occurs after the recording duration.","messagePattern":"A motion sample occurs after the recording duration\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/stage-ui/src/features/devtools/motion/live2d/composables/recording.ts","lineNumber":104,"sourceCode":"  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\n */\nexport function stringifyLive2DMotionRecording(recording: ReadonlyLive2DMotionRecording): string {\n  return `${JSON.stringify(recording, null, 2)}\\n`\n}\n\n/** Owns one in-memory Live2D motion recording and its playback lifecycle. */\nexport function useLive2DMotionRecording(\n  options: UseLive2DMotionRecordingOptions,","sourceCodeStart":86,"sourceCodeEnd":122,"githubUrl":"https://github.com/moeru-ai/airi/blob/9c213115f8bd0fff9e6eabab02b077ac32da21be/packages/stage-ui/src/features/devtools/motion/live2d/composables/recording.ts#L86-L122","documentation":"A recording's samples must all occur within the declared durationMs; anything beyond the duration can never be played and indicates inconsistent metadata. parseLive2DMotionRecording compares the last sample's atMs against durationMs and throws this error if the sample exceeds it.","triggerScenarios":"Calling parseLive2DMotionRecording where samples.at(-1).atMs > durationMs, e.g. durationMs was manually shortened after appending samples, or samples were appended after recording stopped.","commonSituations":"Extending a recording by appending samples without updating durationMs; editing durationMs in the JSON by hand; a recorder bug that flushes buffered samples after the stop time was computed.","solutions":["Increase durationMs to at least the last sample's atMs","Remove samples whose atMs exceed durationMs","Fix the recorder to finalize durationMs after all samples are flushed"],"exampleFix":"// before\nparseLive2DMotionRecording(raw)\n// after\nconst data = JSON.parse(raw)\ndata.durationMs = Math.max(data.durationMs, data.samples.at(-1).atMs)\nparseLive2DMotionRecording(JSON.stringify(data))","handlingStrategy":"validation","validationCode":"const data = JSON.parse(raw)\nconst last = data.samples?.at(-1)?.atMs ?? 0\ndata.durationMs = Math.max(data.durationMs ?? 0, last)\nraw = JSON.stringify(data)","typeGuard":"const withinDuration = (v) => Array.isArray(v.samples) && (v.samples.length === 0 || v.samples.at(-1).atMs <= v.durationMs)","tryCatchPattern":"try {\n  const recording = parseLive2DMotionRecording(raw)\n}\ncatch (error) {\n  if (error.message === 'A motion sample occurs after the recording duration.')\n    showImportError('A sample lies beyond durationMs; fix durationMs or trim samples.')\n  else throw error\n}","preventionTips":["Update durationMs whenever samples are appended","Finalize durationMs after flushing all recorded samples","Validate duration against last sample in export tooling"],"tags":["validation","live2d","motion","timeline"],"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-14T05:17:10.506Z"}