diff --git a/src/lib/services/swf/nitro-builder.ts b/src/lib/services/swf/nitro-builder.ts index 84ffd4db..f94f7658 100644 --- a/src/lib/services/swf/nitro-builder.ts +++ b/src/lib/services/swf/nitro-builder.ts @@ -520,14 +520,29 @@ function inflateJsonOrGzip( * renamed. `meta.image` is corrected by `createNitroBundle`. * * A bundle that is already lossless WebP is returned untouched, which keeps - * the operation idempotent and avoids a pointless re-encode. + * the operation idempotent and avoids a pointless re-encode — but only when + * its metadata is already consistent. A bundle whose `meta.image` names a + * member that is not in the file is still rebuilt, because a client that + * cannot find the texture renders nothing and a re-encode is not what fixes + * that. * * Throws if the texture claims to be PNG or WebP but cannot actually be * decoded; callers that would rather keep the original should catch. */ export async function toWebpLosslessBundle(bundle: Buffer): Promise { const parsed = parseNitroBundle(bundle); - if (isLosslessWebp(parsed.texture)) return bundle; + const meta = (parsed.json as { spritesheet?: { meta?: { image?: string } } }) + .spritesheet?.meta; + if (isLosslessWebp(parsed.texture)) { + if (meta?.image === parsed.textureFileName) return bundle; + // Stale metadata only: rebuild the archive with the pointer corrected + // and reuse the existing VP8L bytes rather than re-encoding them. + return createNitroBundle( + parsed.json, + parsed.texture, + parsed.textureFileName.replace(/\.(?:png|webp)$/i, ""), + ); + } const image = sharp(parsed.texture); const { width = 0, height = 0 } = await image.metadata(); diff --git a/src/lib/services/swf/nitro-webp.test.ts b/src/lib/services/swf/nitro-webp.test.ts index 36cd40ca..e4c6c83a 100644 --- a/src/lib/services/swf/nitro-webp.test.ts +++ b/src/lib/services/swf/nitro-webp.test.ts @@ -223,6 +223,22 @@ describe("normalising a bundle to WebP Lossless", () => { ); await expect(toWebpLosslessBundle(broken)).rejects.toThrow(); }); + + it("repairs stale meta.image in a bundle that is already lossless", async () => { + // Already VP8L, so there is nothing to re-encode — but the pointer + // names a member the archive does not contain, which a re-encode + // would not be what fixes. + const stale = await bundleWith("webp", "chair.png"); + expect(isLosslessWebp(parseNitroBundle(stale).texture)).toBe(true); + + const after = parseNitroBundle(await toWebpLosslessBundle(stale)); + expect(after.textureFileName).toBe("chair.webp"); + expect( + (after.json.spritesheet as { meta: { image: string } }).meta.image, + ).toBe("chair.webp"); + // The existing VP8L bytes are reused rather than decoded again. + expect(after.texture.equals(parseNitroBundle(stale).texture)).toBe(true); + }); }); describe("spritesheet encoding", () => {