From dcb5f46eede9aaf89c6a88f95714c090695cd4d8 Mon Sep 17 00:00:00 2001 From: simoleo89 Date: Sat, 5 Sep 2026 17:45:42 +0200 Subject: [PATCH] fix(catalog): resolve namespaced Nitro frames and empty scale declarations --- src/lib/furni/nitro-quality.test.ts | 17 +++++++++++++++ src/lib/furni/nitro-quality.ts | 9 ++++++-- src/lib/furni/nitro-scale32.test.ts | 34 +++++++++++++++++++++++++++++ src/lib/furni/nitro-scale32.ts | 24 +++++++++++++++----- 4 files changed, 77 insertions(+), 7 deletions(-) diff --git a/src/lib/furni/nitro-quality.test.ts b/src/lib/furni/nitro-quality.test.ts index e82a908134..c487882ea1 100644 --- a/src/lib/furni/nitro-quality.test.ts +++ b/src/lib/furni/nitro-quality.test.ts @@ -69,3 +69,20 @@ it("does not invent sprites from visualization metadata alone", () => { .state, ).toBe("issues"); }); +it("resolves namespaced atlas frames and mirrored aliases", () => { + const metadata = { + name: "chair", + assets: { + chair_64_a_0_0: {}, + chair_64_a_2_0: { source: "chair_64_a_0_0", flipH: true }, + }, + spritesheet: { + frames: { chair_chair_64_a_0_0: { frame: { x: 0, y: 0, w: 4, h: 6 } } }, + }, + }; + expect(resolveNitroFrame(metadata, "chair_64_a_2_0")).toMatchObject({ + name: "chair_chair_64_a_0_0", + flipH: true, + frame: { frame: { w: 4, h: 6 } }, + }); +}); diff --git a/src/lib/furni/nitro-quality.ts b/src/lib/furni/nitro-quality.ts index 68626e68db..64e66e230d 100644 --- a/src/lib/furni/nitro-quality.ts +++ b/src/lib/furni/nitro-quality.ts @@ -44,10 +44,15 @@ export function resolveNitroFrame( current = asset.source; continue; } - const frame = object(frames[current]); + const frameName = Object.hasOwn(frames, current) + ? current + : typeof metadata.name === "string" + ? `${metadata.name}_${current}` + : current; + const frame = object(frames[frameName]); if (Object.keys(frame).length) return { - name: current, + name: frameName, asset: object(assets[name]), frame, flipH, diff --git a/src/lib/furni/nitro-scale32.test.ts b/src/lib/furni/nitro-scale32.test.ts index 10dc2d5856..d6d2fcb87f 100644 --- a/src/lib/furni/nitro-scale32.test.ts +++ b/src/lib/furni/nitro-scale32.test.ts @@ -177,3 +177,37 @@ it("unrotates atlas artwork before generating the smaller frame", async () => { ).metadata(); expect([size.width, size.height]).toEqual([3, 1]); }); +it("fills an empty scale declaration and retains namespaced frame naming", async () => { + const m = metadata(); + const originalFrames = m.spritesheet.frames; + const source = createNitroBundle( + { + ...m, + visualizations: [ + { size: 32, layerCount: 1, angle: 45, directions: { 0: {}, 2: {} } }, + ...m.visualizations, + ], + spritesheet: { + ...m.spritesheet, + frames: { chair_chair_64_a_0_0: originalFrames.chair_64_a_0_0 }, + }, + }, + await sharp({ + create: { width: 6, height: 8, channels: 4, background: "#ff0000" }, + }) + .png() + .toBuffer(), + "chair", + ); + const result = parseNitroBundle((await generateScale32(source)).bundle); + + expect( + (result.json.visualizations as { size: number }[]).filter( + (v) => v.size === 32, + ), + ).toHaveLength(1); + expect( + (result.json.spritesheet as { frames: Record }).frames + .chair_chair_32_a_0_0, + ).toBeDefined(); +}); diff --git a/src/lib/furni/nitro-scale32.ts b/src/lib/furni/nitro-scale32.ts index 256295905a..278c25d27f 100644 --- a/src/lib/furni/nitro-scale32.ts +++ b/src/lib/furni/nitro-scale32.ts @@ -22,6 +22,18 @@ function halfOffsets(value: unknown): unknown { ]), ); } +function emptyScaleDeclaration(viz: Record): boolean { + return Object.entries(viz).every(([key, value]) => { + if (["size", "layerCount", "angle"].includes(key)) return true; + if (key === "directions") + return Object.values(object(value)).every( + (direction) => Object.keys(object(direction)).length === 0, + ); + return ( + value && typeof value === "object" && Object.keys(value).length === 0 + ); + }); +} export async function generateScale32( input: Buffer, ): Promise<{ bundle: Buffer; before: string; after: string; count: number }> { @@ -35,7 +47,9 @@ export async function generateScale32( if ( Object.keys(assets).some((n) => isScale(n, 32)) || Object.keys(frames).some((n) => isScale(n, 32)) || - visualizations.some((v) => object(v).size === 32) + visualizations.some( + (v) => object(v).size === 32 && !emptyScaleDeclaration(object(v)), + ) ) throw Error("Scale 32 already exists; original artwork is preserved"); const viz = visualizations.find((v) => object(v).size === 64); @@ -62,13 +76,13 @@ export async function generateScale32( (typeof asset[key] !== "number" || !Number.isFinite(asset[key])) ) throw Error("Invalid sprite offset"); - resolveNitroFrame(json, name); + const resolved = resolveNitroFrame(json, name); if (typeof asset.source === "string" && asset.source !== name) { if (!names.includes(asset.source)) throw Error("A sprite references artwork outside scale 64"); continue; } - const frame = object(frames[name]), + const frame = resolved.frame, rect = object(frame.frame); let sprite = await sharp(png) .extract({ @@ -105,7 +119,7 @@ export async function generateScale32( if (!before) before = `data:image/png;base64,${sprite.toString("base64")}`; images.push({ id: images.length, - name: scaleName(name), + name: scaleName(resolved.name), width, height, pixels: await sharp(sprite) @@ -152,7 +166,7 @@ export async function generateScale32( } json.assets = assets; json.visualizations = [ - ...visualizations, + ...visualizations.filter((v) => object(v).size !== 32), { ...object(halfOffsets(viz)), size: 32 }, ]; json.spritesheet = {