moeru-ai/airi · error · Error

The crop range is outside the motion timeline.

Error message

The crop range is outside the motion timeline.

What it means

cropLive2DMotionProject validates the requested [startMs, endMs) window against the motion project's timeline before cropping. It throws when the range is not a valid finite sub-range of the source motion: non-finite values, negative start, end beyond project.durationMs, or start >= end. This guards downstream slicing logic from empty or out-of-bounds sample windows.

Source

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

    durationMs,
    samples: [
      { atMs: 0, ...neutralLive2DMotionControlPose },
      { atMs: durationMs, ...neutralLive2DMotionControlPose },
    ],
  })
}

/**
 * Crops a project to a timeline range and moves the retained range to time zero.
 * The function interpolates source and overlay values at both crop boundaries.
 */
export function cropLive2DMotionProject(
  project: Live2DMotionProject,
  startMs: number,
  endMs: number,
): Live2DMotionProject {
  if (!Number.isFinite(startMs) || !Number.isFinite(endMs) || startMs < 0 || endMs > project.durationMs || startMs >= endMs)
    throw new Error('The crop range is outside the motion timeline.')

  const durationMs = endMs - startMs
  const sourceTimes = [...new Set([
    startMs,
    ...project.source.samples
      .map(sample => sample.atMs)
      .filter(atMs => atMs > startMs && atMs < endMs),
    endMs,
  ])]
  const source: Live2DMotionRecording = {
    format: 'airi-live2d-motion/v6',
    durationMs,
    samples: sourceTimes.map(atMs => ({
      atMs: atMs - startMs,
      ...evaluateLive2DMotionRecording(project.source, atMs),
    })),
  }

View on GitHub (pinned to 9c213115f8)

Solutions

  1. Clamp and order the range before calling: const s = Math.max(0, Math.min(startMs, project.durationMs)); const e = Math.max(s + 1, Math.min(endMs, project.durationMs)).
  2. Verify startMs/endMs are finite numbers with Number.isFinite before invoking.
  3. Ensure endMs is strictly greater than startMs; if you need a zero-width selection, special-case it instead of calling crop.
  4. Confirm the project's durationMs (milliseconds) matches the units of your range values.

Example fix

// before
cropLive2DMotionProject(project, startInput, endInput) // startInput may be NaN or > duration
// after
const start = Math.max(0, Number.isFinite(startInput) ? startInput : 0)
const end = Math.min(project.durationMs, Math.max(start + 1, endInput))
if (end > start) cropLive2DMotionProject(project, start, end)
Defensive patterns

Strategy: validation

Validate before calling

function canCrop(project, startMs, endMs) {
  return Number.isFinite(startMs)
    && Number.isFinite(endMs)
    && startMs >= 0
    && endMs <= project.durationMs
    && startMs < endMs
}

Try / catch

let cropped
try {
  cropped = cropLive2DMotionProject(project, start, end)
}
catch (error) {
  if (error.message === 'The crop range is outside the motion timeline.') {
    cropped = project // or surface a user-facing range error
  }
  else throw error
}

Prevention

When it happens

Trigger: Calling cropLive2DMotionProject(project, startMs, endMs) where startMs or endMs is NaN/Infinity, startMs < 0, endMs > project.durationMs, or startMs >= endMs (e.g. zero-length or reversed range).

Common situations: Computing crop bounds from UI sliders or parsed user input that yields NaN; assuming durationMs is in seconds instead of milliseconds; passing (end, start) swapped; cropping to the full duration with start === end expecting a no-op.

Related errors


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