{"record":{"id":"52161f7d0229b4e9","repo":"stablyai/orca","slug":"spritesheet-must-be-a-png-apng-jpg-gif-or-webp","errorCode":null,"errorMessage":"Spritesheet must be a PNG, APNG, JPG, GIF, or WebP.","messagePattern":"Spritesheet must be a PNG, APNG, JPG, GIF, or WebP\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/main/ipc/pet.ts","lineNumber":310,"sourceCode":"    // Why: bundles exported on Windows may be imported on macOS/Linux; normalize separators before resolving.\n    const sheetSrc = resolve(bundleDir, normalizedSpritePath)\n    const bundleResolved = resolve(bundleDir)\n    if (sheetSrc === bundleResolved) {\n      throw new Error('spritesheetPath must point to a file, not the bundle root.')\n    }\n    const bundleRoot = bundleResolved + sep\n    // Why: Windows volumes are case-insensitive; lowercase the prefix compare so case differences can't bypass the escape check.\n    const cmp = process.platform === 'win32' ? (s: string) => s.toLowerCase() : (s: string) => s\n    if (!cmp(sheetSrc + sep).startsWith(cmp(bundleRoot))) {\n      throw new Error('spritesheetPath escapes the bundle.')\n    }\n    if (await isSymlink(sheetSrc)) {\n      throw new Error('spritesheet must not be a symlink.')\n    }\n    const sheetClass = classifyFile(sheetSrc)\n    if (!sheetClass || sheetClass.ext === '.svg') {\n      // SVG can't be used as a sprite sheet (no pixel grid).\n      throw new Error('Spritesheet must be a PNG, APNG, JPG, GIF, or WebP.')\n    }\n    let sheetStat: Awaited<ReturnType<typeof stat>>\n    try {\n      sheetStat = await stat(sheetSrc)\n    } catch {\n      throw new Error('Spritesheet file not found.')\n    }\n    if (!sheetStat.isFile()) {\n      throw new Error('Spritesheet path is not a file.')\n    }\n    if (sheetStat.size > MAX_BYTES) {\n      throw new Error(\n        `Spritesheet is too large (${(sheetStat.size / (1024 * 1024)).toFixed(1)} MB).`\n      )\n    }\n\n    let sprite: NonNullable<CustomPet['sprite']> | undefined\n    if (manifest.frame) {","sourceCodeStart":292,"sourceCodeEnd":328,"githubUrl":"https://github.com/stablyai/orca/blob/1136503c6a231a16dce8f921f6fadb63d181e8db/src/main/ipc/pet.ts#L292-L328","documentation":"Thrown at pet.ts:307-310 when classifyFile(sheetSrc) returns null (the extension is not in IMAGE_FORMATS) or the extension is '.svg'. classifyFile lowercases the extension and looks it up in the PNG/APNG/JPG/JPEG/GIF/WebP/SVG map (pet.ts:18-26), but SVG is explicitly rejected here because a sprite sheet needs a pixel grid that SVG lacks.","triggerScenarios":"spritesheetPath points to a file whose extension is not .png/.apng/.jpg/.jpeg/.gif/.webp (e.g. .bmp, .tiff, .avif, no extension), or to an .svg file.","commonSituations":"Bundle author used an unsupported format; file saved as .svg because the design tool defaulted to vector export; uppercase extension is fine (classifyFile lowercases) but a .bmp slipped in.","solutions":["Convert the spritesheet to PNG, APNG, JPG, GIF, or WebP and update spritesheetPath in pet.json.","If you have an SVG, rasterize it to PNG at the target frame size before bundling.","Confirm the extension matches the actual file format — a .png-named BMP will decode-fail later even if it passes this gate."],"exampleFix":"# before\n{ \"spritesheetPath\": \"cat.svg\" }\n# after — rasterize and rename\nconvert cat.svg -resize 32x32 cat.png\n{ \"spritesheetPath\": \"cat.png\" }","handlingStrategy":"validation","validationCode":"import { extname } from 'node:path'\nconst SHEET_EXTS = new Set(['.png', '.apng', '.jpg', '.jpeg', '.gif', '.webp'])\nfunction assertSheetFormat(p: string) {\n  const ext = extname(p).toLowerCase()\n  if (!SHEET_EXTS.has(ext)) throw new Error(`unsupported spritesheet extension: ${ext}`)\n}","typeGuard":"function isSupportedSheetExt(p: string): boolean {\n  return SHEET_EXTS.has(extname(p).toLowerCase())\n}","tryCatchPattern":"try { await importPetBundle(p) }\ncatch (e) { if (e instanceof Error && e.message === 'Spritesheet must be a PNG, APNG, JPG, GIF, or WebP.') { /* convert the image */ } else throw e }","preventionTips":["Standardize on PNG for static sheets and APNG/WebP for animated ones.","Make sure the file extension matches the actual encoded format."],"tags":["pet-bundle","image-format","validation"],"backgroundTag":null,"analyzedSha":"1136503c6a231a16dce8f921f6fadb63d181e8db","analyzedAt":"2026-08-12T23:15:58.167Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}