stablyai/orca · error · Error

Animation "${name}" has ${anim.frames} frames but sheet only

Error message

Animation "${name}" has ${anim.frames} frames but sheet only has ${columns} columns.

What it means

Thrown at pet.ts:352-355 when an animation declares more frames (anim.frames) than the sheet has columns (sheetWidth / frameWidth). Columns are zero-based count, so a sheet with 8 columns supports up to 8 frames per animation row.

Source

Thrown at src/main/ipc/pet.ts:353

      const dims = await readSheetDimensions(sheetBuf)
      if (!dims) {
        throw new Error('Could not decode the spritesheet image.')
      }
      const { width: fw, height: fh } = manifest.frame
      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,

View on GitHub (pinned to 1136503c6a)

Solutions

  1. Reduce anim.frames to be less than or equal to columns (the message states columns).
  2. Or widen the sheet / reduce frame.width to increase columns.
  3. Re-export the bundle after fixing pet.json.

Example fix

// before — 8-column sheet, animation claims 10 frames
{ "animations": { "run": { "row": 1, "frames": 10 } } }
// after
{ "animations": { "run": { "row": 1, "frames": 8 } } }
Defensive patterns

Strategy: validation

Validate before calling

function validateAnimationsFrames(animations: Record<string, {frames:number}>, columns: number) {
  for (const [name, a] of Object.entries(animations)) {
    if (a.frames > columns) throw new Error(`animation ${name} frames ${a.frames} exceed columns ${columns}`)
  }
}

Try / catch

try { await importPetBundle(p) }
catch (e) { if (e instanceof Error && /frames but sheet only has/.test(e.message)) { /* reduce frames or widen sheet */ } else throw e }

Prevention

When it happens

Trigger: An animation sets frames: 10 but the sheet only has 8 columns. Runs inside the manifest.animations loop at line 348.

Common situations: Author miscounted columns; frame.width was set too large reducing columns; animation 'frames' confused with total frames across the row.

Related errors


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