moeru-ai/airi · error · Error

The motion project contains an invalid overlay span.

Error message

The motion project contains an invalid overlay span.

What it means

parseLive2DMotionProject checks that every overlay span fits inside the project duration and has startMs <= endMs. Overlays are time-bounded modulation layers over the base motion, so a span extending beyond the timeline or inverted bounds is structurally invalid. This error is thrown when overlay.endMs > project.durationMs or overlay.startMs > overlay.endMs.

Source

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

  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. Clamp overlay.startMs and overlay.endMs to [0, project.durationMs] before parsing
  2. Ensure startMs <= endMs when constructing the overlay
  3. Recompute overlay spans after any change to project.durationMs

Example fix

// before
overlay.startMs = userStart
overlay.endMs = userEnd
// after
overlay.startMs = Math.max(0, Math.min(userStart, project.durationMs))
overlay.endMs = Math.max(overlay.startMs, Math.min(userEnd, project.durationMs))
Defensive patterns

Strategy: validation

Validate before calling

for (const overlay of project.overlays) {
  overlay.startMs = Math.max(0, Math.min(overlay.startMs, project.durationMs))
  overlay.endMs = Math.max(overlay.startMs, Math.min(overlay.endMs, project.durationMs))
}

Type guard

const hasValidSpan = (o, durationMs) => o.startMs <= o.endMs && o.endMs <= durationMs

Try / catch

try {
  parseLive2DMotionProject(project)
}
catch (error) {
  if (error.message.includes('invalid overlay span'))
    console.error('Overlay span out of bounds; clamp startMs/endMs to project durationMs')
  throw error
}

Prevention

When it happens

Trigger: Calling parseLive2DMotionProject where an overlay has startMs greater than endMs (inverted span) or endMs exceeding project.durationMs (span out of timeline bounds).

Common situations: Setting overlay bounds from user input without clamping to project.durationMs; editing project.durationMs shorter after overlays were created; copying overlay data between projects with different durations; typos swapping startMs and endMs.

Related errors


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