fix(catalog): resolve namespaced Nitro frames and empty scale declarations

This commit is contained in:
Simo committed 2026-09-05 17:45:42 +02:00
1 parent cd75361b9f
commit dcb5f46eed
4 files changed
+77 -7

No files matched your search

+17
View File
@@ -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 } },
});
});
+7 -2
View File
@@ -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,
+34
View File
@@ -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<string, unknown> }).frames
.chair_chair_32_a_0_0,
).toBeDefined();
});
+19 -5
View File
@@ -22,6 +22,18 @@ function halfOffsets(value: unknown): unknown {
]),
);
}
function emptyScaleDeclaration(viz: Record<string, unknown>): 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 = {