moeru-ai/airi · error · Error

A motion sample occurs after the recording duration.

Error message

A motion sample occurs after the recording duration.

What it means

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.

Source

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

  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
 */
export function stringifyLive2DMotionRecording(recording: ReadonlyLive2DMotionRecording): string {
  return `${JSON.stringify(recording, null, 2)}\n`
}

/** Owns one in-memory Live2D motion recording and its playback lifecycle. */
export function useLive2DMotionRecording(
  options: UseLive2DMotionRecordingOptions,

View on GitHub (pinned to 9c213115f8)

Solutions

  1. Increase durationMs to at least the last sample's atMs
  2. Remove samples whose atMs exceed durationMs
  3. Fix the recorder to finalize durationMs after all samples are flushed

Example fix

// before
parseLive2DMotionRecording(raw)
// after
const data = JSON.parse(raw)
data.durationMs = Math.max(data.durationMs, data.samples.at(-1).atMs)
parseLive2DMotionRecording(JSON.stringify(data))
Defensive patterns

Strategy: validation

Validate before calling

const data = JSON.parse(raw)
const last = data.samples?.at(-1)?.atMs ?? 0
data.durationMs = Math.max(data.durationMs ?? 0, last)
raw = JSON.stringify(data)

Type guard

const withinDuration = (v) => Array.isArray(v.samples) && (v.samples.length === 0 || v.samples.at(-1).atMs <= v.durationMs)

Try / catch

try {
  const recording = parseLive2DMotionRecording(raw)
}
catch (error) {
  if (error.message === 'A motion sample occurs after the recording duration.')
    showImportError('A sample lies beyond durationMs; fix durationMs or trim samples.')
  else throw error
}

Prevention

When it happens

Trigger: Calling parseLive2DMotionRecording where samples.at(-1).atMs > durationMs, e.g. durationMs was manually shortened after appending samples, or samples were appended after recording stopped.

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

Related errors


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