stablyai/orca · error · Error
Animation "${name}" declares ${anim.frameDurationsMs.length}
Error message
Animation "${name}" declares ${anim.frameDurationsMs.length} frame durations but ${anim.frames} frames. What it means
Thrown at pet.ts:357-360 when an animation provides frameDurationsMs (optional per pet.ts:107) but its array length does not equal anim.frames. Each frame must have exactly one duration, so a mismatch would either drop frames or index out of range during playback.
Source
Thrown at src/main/ipc/pet.ts:358
if (dims.width % fw !== 0 || dims.height % fh !== 0) {
throw new Error(
`Spritesheet ${dims.width}×${dims.height} is not a clean multiple of frame ${fw}×${fh}.`
)
}
const columns = dims.width / fw
const rows = dims.height / fh
if (manifest.animations) {
for (const [name, anim] of Object.entries(manifest.animations)) {
if (anim.row >= rows) {
throw new Error(`Animation "${name}" references row ${anim.row} but sheet has ${rows}.`)
}
if (anim.frames > columns) {
throw new Error(
`Animation "${name}" has ${anim.frames} frames but sheet only has ${columns} columns.`
)
}
if (anim.frameDurationsMs && anim.frameDurationsMs.length !== anim.frames) {
throw new Error(
`Animation "${name}" declares ${anim.frameDurationsMs.length} frame durations but ${anim.frames} frames.`
)
}
}
if (manifest.defaultAnimation && !manifest.animations[manifest.defaultAnimation]) {
throw new Error(`defaultAnimation "${manifest.defaultAnimation}" not in animations.`)
}
}
sprite = {
frameWidth: fw,
frameHeight: fh,
columns,
rows,
sheetWidth: dims.width,
sheetHeight: dims.height,
fps: manifest.fps ?? 8,
defaultAnimation: manifest.defaultAnimation,
animations: manifest.animationsView on GitHub (pinned to 1136503c6a)
Solutions
- Make frameDurationsMs have exactly anim.frames entries (one duration per frame).
- If you do not need per-frame timing, omit frameDurationsMs and rely on fps.
- Re-export the bundle after fixing pet.json.
Example fix
// before — 6 frames, 3 durations
{ "animations": { "idle": { "row": 0, "frames": 6, "frameDurationsMs": [100,200,300] } } }
// after
{ "animations": { "idle": { "row": 0, "frames": 6, "frameDurationsMs": [100,100,100,100,100,100] } } } Defensive patterns
Strategy: validation
Validate before calling
function validateFrameDurations(animations: Record<string, {frames:number;frameDurationsMs?:number[]}>) {
for (const [name, a] of Object.entries(animations)) {
if (a.frameDurationsMs && a.frameDurationsMs.length !== a.frames) {
throw new Error(`animation ${name}: ${a.frameDurationsMs.length} durations vs ${a.frames} frames`)
}
}
} Try / catch
try { await importPetBundle(p) }
catch (e) { if (e instanceof Error && /frame durations but/.test(e.message)) { /* align durations array to frame count */ } else throw e } Prevention
- If you do not need per-frame timing, omit frameDurationsMs entirely.
- When adding/removing a frame, update frameDurationsMs in the same edit.
When it happens
Trigger: An animation sets frames: 6 but frameDurationsMs: [100, 200, 300] (3 entries). Runs only when frameDurationsMs is present (line 357 guards with &&).
Common situations: Author added/removed a frame but forgot to update the durations array; durations were copied from a template with a different frame count.
Related errors
- Animation "${name}" references row ${anim.row} but sheet has
- Animation "${name}" has ${anim.frames} frames but sheet only
- defaultAnimation "${manifest.defaultAnimation}" not in anima
- Invalid pet.json: ${error instanceof Error ? error.message :
- spritesheetPath must be relative to the bundle.
AI-assisted analysis of stablyai/orca@1136503c6a (2026-08-12).
Data as JSON: /api/errors/b04f872eb68b31b6.
Report an issue: GitHub.