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.animations

View on GitHub (pinned to 1136503c6a)

Solutions

  1. Make frameDurationsMs have exactly anim.frames entries (one duration per frame).
  2. If you do not need per-frame timing, omit frameDurationsMs and rely on fps.
  3. 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

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


AI-assisted analysis of stablyai/orca@1136503c6a (2026-08-12). Data as JSON: /api/errors/b04f872eb68b31b6. Report an issue: GitHub.