moeru-ai/airi · error · Error

The motion project source samples are not in time order.

Error message

The motion project source samples are not in time order.

What it means

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.

Source

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

    input = JSON.parse(raw)
  }
  catch {
    throw new Error('The file does not contain valid JSON.')
  }

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

  const project = result.output
  if (project.source.durationMs !== project.durationMs)
    throw new Error('The motion project source is invalid.')

  if (project.source.samples[0].atMs !== 0 || project.source.samples.at(-1)!.atMs > project.durationMs)
    throw new Error('The motion project source timeline is invalid.')
  for (let index = 1; index < project.source.samples.length; index++) {
    if (project.source.samples[index].atMs < project.source.samples[index - 1].atMs)
      throw new Error('The motion project source samples are not in time order.')
  }

  for (const overlay of project.overlays) {
    if (overlay.endMs > project.durationMs || overlay.startMs > overlay.endMs)
      throw new Error('The motion project contains an invalid overlay span.')
    if (overlay.points.some(point => point.atMs < overlay.startMs || point.atMs > overlay.endMs))
      throw new Error('The motion project contains an invalid overlay point.')
    for (let index = 1; index < overlay.points.length; index++) {
      if (overlay.points[index].atMs < overlay.points[index - 1].atMs)
        throw new Error('The motion project overlay points are not in time order.')
    }
  }
  return structuredClone(project)
}

View on GitHub (pinned to 9c213115f8)

Solutions

  1. Sort project.source.samples by atMs ascending before calling parseLive2DMotionProject
  2. Ensure the sample recording/collection code appends samples in chronological order
  3. Validate the JSON file for out-of-order timestamps before loading it

Example fix

// before
parseLive2DMotionProject(project)
// after
project.source.samples.sort((a, b) => a.atMs - b.atMs)
parseLive2DMotionProject(project)
Defensive patterns

Strategy: validation

Validate before calling

function samplesAreSorted(samples) {
  return samples.every((s, i) => i === 0 || s.atMs >= samples[i - 1].atMs)
}
if (!samplesAreSorted(project.source.samples))
  project.source.samples.sort((a, b) => a.atMs - b.atMs)

Type guard

const isSortedByAtMs = (items) => items.every((s, i) => i === 0 || s.atMs >= items[i - 1].atMs)

Try / catch

try {
  parseLive2DMotionProject(project)
}
catch (error) {
  if (error.message.includes('not in time order')) {
    project.source.samples.sort((a, b) => a.atMs - b.atMs)
    parseLive2DMotionProject(project)
  }
  else throw error
}

Prevention

When it happens

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

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

Related errors


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