stablyai/orca · error · Error

Animation "${name}" references row ${anim.row} but sheet has

Error message

Animation "${name}" references row ${anim.row} but sheet has ${rows}.

What it means

Thrown at pet.ts:349-350 while iterating Object.entries(manifest.animations): if an animation's row index (anim.row) is greater than or equal to the computed rows (sheetHeight / frameHeight). Rows are zero-indexed, so a sheet with 4 rows allows row 0..3.

Source

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

      if (sheetBuf.byteLength > MAX_BYTES) {
        throw new Error('Spritesheet exceeded the size limit.')
      }
      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,

View on GitHub (pinned to 1136503c6a)

Solutions

  1. Set the animation's row to a valid zero-based index less than rows (the message states rows).
  2. If the art needs that row, add rows to the spritesheet so it has enough.
  3. Re-export the bundle after fixing pet.json.

Example fix

// before — 4-row sheet, animation claims row 4
{ "animations": { "idle": { "row": 4, "frames": 8 } } }
// after — valid zero-based row (0..3)
{ "animations": { "idle": { "row": 3, "frames": 8 } } }
Defensive patterns

Strategy: validation

Validate before calling

function validateAnimationsRows(animations: Record<string, {row:number;frames:number}>, rows: number) {
  for (const [name, a] of Object.entries(animations)) {
    if (a.row >= rows) throw new Error(`animation ${name} row ${a.row} out of range (0..${rows-1})`)
  }
}

Try / catch

try { await importPetBundle(p) }
catch (e) { if (e instanceof Error && /references row .* but sheet has/.test(e.message)) { /* fix the animation row index */ } else throw e }

Prevention

When it happens

Trigger: An animation entry sets row: 5 but the sheet only has 4 rows (rows=4). This runs only when manifest.animations is present (line 347).

Common situations: Author miscounted rows; sheet was trimmed but animations not updated; row indexing confusion (1-based in author's head, 0-based in schema per pet.ts:104 min(0)).

Related errors


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