moeru-ai/airi · error · Error

The first motion sample must start at 0 ms.

Error message

The first motion sample must start at 0 ms.

What it means

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.

Source

Thrown at packages/stage-ui/src/features/devtools/motion/live2d/composables/recording.ts:96

 * parseLive2DMotionRecording('{"format":"airi-live2d-motion/v6", ...}')
 * // => a validated recording
 */
export function parseLive2DMotionRecording(raw: string): Live2DMotionRecording {
  let input: unknown
  try {
    input = JSON.parse(raw)
  }
  catch {
    throw new Error('The file does not contain valid JSON.')
  }

  const result = safeParse(live2dMotionRecordingSchema, input)
  if (!result.success)
    throw new Error('The file is not an AIRI Live2D motion recording.')

  const { durationMs, samples } = result.output
  if (samples[0].atMs !== 0)
    throw new Error('The first motion sample must start at 0 ms.')

  for (let index = 1; index < samples.length; index++) {
    if (samples[index].atMs < samples[index - 1].atMs)
      throw new Error('The motion samples must be in time order.')
  }

  if (samples.at(-1)!.atMs > durationMs)
    throw new Error('A motion sample occurs after the recording duration.')

  return result.output
}

/**
 * Serializes a Live2D joystick recording as a readable JSON file.
 *
 * @example
 * stringifyLive2DMotionRecording({ format: 'airi-live2d-motion/v6', ... })
 * // => readable JSON ending with a newline

View on GitHub (pinned to 9c213115f8)

Solutions

  1. Rebase all sample timestamps so the first sample is at 0 ms (subtract samples[0].atMs from every atMs, and durationMs accordingly)
  2. Re-record the motion with the AIRI recorder
  3. Trim samples via a tool that rebases timestamps automatically

Example fix

// before
parseLive2DMotionRecording(raw)
// after
const data = JSON.parse(raw)
const offset = data.samples[0].atMs
data.samples.forEach(s => { s.atMs -= offset })
data.durationMs -= offset
parseLive2DMotionRecording(JSON.stringify(data))
Defensive patterns

Strategy: validation

Validate before calling

const data = JSON.parse(raw)
if (data.samples?.length && data.samples[0].atMs !== 0) {
  const offset = data.samples[0].atMs
  data.samples.forEach(s => { s.atMs -= offset })
  data.durationMs -= offset
  raw = JSON.stringify(data)
}

Type guard

const startsAtZero = (v) => Array.isArray(v.samples) && v.samples.length > 0 && v.samples[0].atMs === 0

Try / catch

try {
  const recording = parseLive2DMotionRecording(raw)
}
catch (error) {
  if (error.message === 'The first motion sample must start at 0 ms.')
    showImportError('Recording must start at 0 ms. Trim with timestamp rebasing or re-record.')
  else throw error
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of moeru-ai/airi@9c213115f8 (2026-09-02). Data as JSON: /api/errors/08647f1e2c2285f8. Report an issue: GitHub.