Files
Epicnabbo-Catalogus-Updated…/src/lib/furni/nitro-scale32.test.ts
T

214 lines
5.6 KiB
TypeScript

import sharp from "sharp";
import { describe, expect, it } from "vitest";
import {
createNitroBundle,
parseNitroBundle,
} from "@/lib/services/swf/nitro-builder";
import { generateScale32 } from "./nitro-scale32";
const metadata = () => ({
name: "chair",
assets: {
chair_64_a_0_0: { x: -5, y: 7 },
chair_64_a_2_0: { source: "chair_64_a_0_0", x: 5, y: 7, flipH: true },
},
visualizations: [
{
size: 64,
layerCount: 1,
layers: { 0: { x: 6, y: -4, z: 3 } },
animations: {
0: {
layers: {
0: {
frameRepeat: 4,
frameSequences: {
0: { frames: { 0: { id: 0, x: 6, y: 4, randomX: 8 } } },
},
},
},
},
},
},
],
logic: { model: { dimensions: { x: 2, y: 1, z: 1 } } },
spritesheet: {
frames: {
chair_64_a_0_0: {
frame: { x: 0, y: 0, w: 6, h: 8 },
rotated: false,
trimmed: false,
spriteSourceSize: { x: 0, y: 0, w: 6, h: 8 },
sourceSize: { w: 6, h: 8 },
},
},
meta: { size: { w: 6, h: 8 }, scale: "1" },
},
});
async function bundle(json = metadata()) {
return createNitroBundle(
json,
await sharp({
create: { width: 6, height: 8, channels: 4, background: "#ff0000" },
})
.png()
.toBuffer(),
"chair",
);
}
describe("scale 32 generation", () => {
it("preserves originals and adds half-size artwork and offsets without changing timing or geometry", async () => {
const source = await bundle();
const result = await generateScale32(source);
const { json, png } = parseNitroBundle(result.bundle);
const m = json as ReturnType<typeof metadata>;
expect(m.assets.chair_64_a_0_0).toEqual(metadata().assets.chair_64_a_0_0);
expect(m.logic).toEqual(metadata().logic);
expect(
(json.assets as Record<string, unknown>).chair_32_a_0_0,
).toMatchObject({ x: -2, y: 4 });
expect(
(json.assets as Record<string, unknown>).chair_32_a_2_0,
).toMatchObject({
source: "chair_32_a_0_0",
flipH: true,
});
expect(
(json.spritesheet as { frames: Record<string, { frame: unknown }> })
.frames.chair_32_a_0_0.frame,
).toMatchObject({ w: 3, h: 4 });
expect(m.visualizations[1]).toMatchObject({
size: 32,
layers: { 0: { x: 3, y: -2, z: 3 } },
animations: {
0: {
layers: {
0: {
frameRepeat: 4,
frameSequences: {
0: { frames: { 0: { id: 0, x: 3, y: 2, randomX: 4 } } },
},
},
},
},
},
});
expect(
await sharp(png)
.extract({ left: 0, top: 0, width: 6, height: 8 })
.raw()
.toBuffer(),
).toEqual(await sharp(parseNitroBundle(source).png).raw().toBuffer());
});
it("never overwrites a native or partial scale32", async () => {
const m = metadata();
m.visualizations.push({ ...m.visualizations[0], size: 32 });
await expect(generateScale32(await bundle(m))).rejects.toThrow("already");
});
it("rejects missing sprite references", async () => {
const m = metadata();
m.assets.chair_64_a_2_0.source = "missing";
await expect(generateScale32(await bundle(m))).rejects.toThrow("Missing");
});
});
it("restores trimmed sprite geometry before reducing it", async () => {
const png = await sharp({
create: { width: 2, height: 2, channels: 4, background: "#00ff00" },
})
.png()
.toBuffer();
const source = createNitroBundle(
{
name: "trim",
assets: { trim_64_a_0_0: { x: 4, y: 6 } },
visualizations: [{ size: 64 }],
spritesheet: {
frames: {
trim_64_a_0_0: {
frame: { x: 0, y: 0, w: 2, h: 2 },
trimmed: true,
sourceSize: { w: 6, h: 8 },
spriteSourceSize: { x: 2, y: 4, w: 2, h: 2 },
},
},
},
},
png,
"trim",
);
const generated = await generateScale32(source);
const { json } = parseNitroBundle(generated.bundle);
expect(
(json.spritesheet as { frames: Record<string, { sourceSize: unknown }> })
.frames.trim_32_a_0_0.sourceSize,
).toEqual({ w: 3, h: 4 });
const pixels = await sharp(
Buffer.from(generated.after.split(",")[1], "base64"),
)
.raw()
.toBuffer();
expect([...pixels.subarray((2 * 3 + 1) * 4, (2 * 3 + 1) * 4 + 4)]).toEqual([
0, 255, 0, 255,
]);
expect(pixels[3]).toBe(0);
});
it("unrotates atlas artwork before generating the smaller frame", async () => {
const source = createNitroBundle(
{
name: "rot",
assets: { rot_64_a_0_0: {} },
visualizations: [{ size: 64 }],
spritesheet: {
frames: {
rot_64_a_0_0: { frame: { x: 0, y: 0, w: 2, h: 6 }, rotated: true },
},
},
},
await sharp({
create: { width: 2, height: 6, channels: 4, background: "#ff0000" },
})
.png()
.toBuffer(),
"rot",
);
const generated = await generateScale32(source);
const size = await sharp(
Buffer.from(generated.after.split(",")[1], "base64"),
).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();
});