stablyai/orca · error · Error

Could not decode the spritesheet image.

Error message

Could not decode the spritesheet image.

What it means

Thrown at pet.ts:335-337 when readSheetDimensions(sheetBuf) returns null/undefined, meaning the image bytes could not be decoded or the decoder could not extract width/height. This runs only when manifest.frame is set (the decode path at line 328). The comment at line 329 notes nativeImage can fail on some WebP variants in headless contexts.

Source

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

      throw new Error('Spritesheet path is not a file.')
    }
    if (sheetStat.size > MAX_BYTES) {
      throw new Error(
        `Spritesheet is too large (${(sheetStat.size / (1024 * 1024)).toFixed(1)} MB).`
      )
    }

    let sprite: NonNullable<CustomPet['sprite']> | undefined
    if (manifest.frame) {
      // Why: only decode when a frame layout needs validating — nativeImage can fail on some WebP variants in headless contexts.
      const sheetBuf = await readFile(sheetSrc)
      // Why: defend against TOCTOU — file may have grown between stat and read.
      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.`
            )

View on GitHub (pinned to 1136503c6a)

Solutions

  1. Re-export the image with a real encoder so the bytes match the extension.
  2. Open the file in an image viewer to confirm it is valid and not truncated.
  3. If using WebP, try converting to PNG/APNG which have broader decoder support.
  4. Verify the file is not zero-length.

Example fix

# before — cat.jpg renamed to cat.png
mv cat.jpg cat.png
# after — re-encode properly
convert cat.jpg cat.png
Defensive patterns

Strategy: validation

Validate before calling

import { readFile } from 'node:fs/promises'
import { nativeImage } from 'electron'
async function assertSheetDecodes(p: string) {
  const buf = await readFile(p)
  const img = nativeImage.createFromBuffer(buf)
  const size = img.getSize()
  if (!size.width || !size.height) throw new Error('spritesheet does not decode')
}

Try / catch

try { await importPetBundle(p) }
catch (e) { if (e instanceof Error && e.message === 'Could not decode the spritesheet image.') { /* re-encode the image */ } else throw e }

Prevention

When it happens

Trigger: The file passed the extension check (1245) but its bytes are not a valid decodable image of that format: a .png that is actually a JPEG, a truncated/corrupt image, an unsupported WebP variant, or a zero-byte file that somehow passed stat.

Common situations: File renamed without re-encoding (e.g. cat.jpg renamed to cat.png); a partially downloaded/truncated image; an exotic WebP/animated format the decoder rejects; a file with correct extension but wrong magic bytes.

Related errors


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