stablyai/orca · error · Error
Spritesheet ${dims.width}×${dims.height} is not a clean mult
Error message
Spritesheet ${dims.width}×${dims.height} is not a clean multiple of frame ${fw}×${fh}. What it means
Thrown at pet.ts:340-343 when the decoded spritesheet's pixel width or height is not evenly divisible by the manifest's frame.width / frame.height. The sprite engine slices the sheet into a grid of frame cells, so the sheet dimensions must be exact integer multiples of the frame size. columns and rows (computed at lines 345-346) would otherwise be fractional.
Source
Thrown at src/main/ipc/pet.ts:341
`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.`
)
}
if (anim.frameDurationsMs && anim.frameDurationsMs.length !== anim.frames) {
throw new Error(
`Animation "${name}" declares ${anim.frameDurationsMs.length} frame durations but ${anim.frames} frames.`View on GitHub (pinned to 1136503c6a)
Solutions
- Set frame.width and frame.height in pet.json to divisors of the actual image dimensions (the message tells you both numbers).
- Or re-export the image so its dimensions are exact multiples of the intended frame size.
- Double-check the frame size matches the art (e.g. a 256x256 sheet with 32x32 frames gives 8x8 grid).
Example fix
// before — 100x100 sheet, 32x32 frame (100%32 != 0)
{ "frame": { "width": 32, "height": 32 } }
// after — re-export sheet at 96x96 (96/32=3) and keep frame 32x32
{ "frame": { "width": 32, "height": 32 } } Defensive patterns
Strategy: validation
Validate before calling
async function assertCleanFrameMultiple(imagePath: string, frameW: number, frameH: number) {
const img = nativeImage.createFromBuffer(await readFile(imagePath))
const { width, height } = img.getSize()
if (!width || !height) throw new Error('cannot read dimensions')
if (width % frameW !== 0 || height % frameH !== 0) throw new Error(`sheet ${width}x${height} not a multiple of frame ${frameW}x${frameH}`)
} Try / catch
try { await importPetBundle(p) }
catch (e) { if (e instanceof Error && e.message.includes('not a clean multiple of frame')) { /* fix frame size or re-export sheet */ } else throw e } Prevention
- Choose frame dimensions that evenly divide the sheet before authoring art.
- Re-export the sheet at a resolution that is an integer multiple of the frame size.
When it happens
Trigger: pet.json declares frame {width:32,height:32} but the image is 100x100 (100/32 is not integer), or the image dimensions were changed after the manifest was written, or the frame size was picked wrong.
Common situations: Author eyeballed the frame size; the sheet was re-exported at a different resolution without updating frame; off-by-one (e.g. 33px frames instead of 32).
Related errors
- Animation "${name}" references row ${anim.row} but sheet has
- Animation "${name}" has ${anim.frames} frames but sheet only
- Invalid pet.json: ${error instanceof Error ? error.message :
- spritesheetPath must be relative to the bundle.
- spritesheetPath must point to a file, not the bundle root.
AI-assisted analysis of stablyai/orca@1136503c6a (2026-08-12).
Data as JSON: /api/errors/0937553b0b44bc78.
Report an issue: GitHub.