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
- 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)).
- Verify startMs/endMs are finite numbers with Number.isFinite before invoking.
- Ensure endMs is strictly greater than startMs; if you need a zero-width selection, special-case it instead of calling crop.
- 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
- Always clamp the range with Math.max/Math.min against 0 and project.durationMs before cropping.
- Enforce start < end in the UI (e.g. dual-slider with min-gap).
- Treat all durations as milliseconds consistently across your code.
- Derive crop bounds from project.durationMs rather than hardcoded values.
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
- The motion project source samples are not in time order.
- The motion project contains an invalid overlay span.
- The motion project contains an invalid overlay point.
- The motion project overlay points are not in time order.
- The first motion sample must start at 0 ms.
AI-assisted analysis of moeru-ai/airi@9c213115f8 (2026-09-02).
Data as JSON: /api/errors/df53e63b722355b6.
Report an issue: GitHub.