Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ jobs:
item: robustness/settings-runtime-batch.yaml
- id: template-click
item: robustness/visual-layout-template-click.yaml
- id: spritesheet-frame-names
item: robustness/visual-spritesheet-default.yaml
- id: layout-guarded-choice
item: robustness/layout-guarded-choice.yaml
- id: inline-layout-click
Expand Down
38 changes: 38 additions & 0 deletions spec/constructRenderState.backgroundSpritesheet.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,17 @@ const atlas = {
},
};

const arrayLikeFrameOrder = ["10", "2", "1"];
const arrayLikeAtlas = {
frames: Object.fromEntries(
arrayLikeFrameOrder.map((frameName, index) => [
frameName,
{ frame: { x: index * 32, y: 0, w: 32, h: 32 } },
]),
),
meta: { size: { w: 96, h: 32 }, scale: "1" },
};

const createResources = () => ({
spritesheets: {
animatedSky: {
Expand Down Expand Up @@ -144,6 +155,33 @@ describe("constructRenderState spritesheet backgrounds", () => {
});
});

it("selects array-like atlas frames by their authored names", () => {
const resources = createResources();
resources.spritesheets.animatedSky.jsonData = arrayLikeAtlas;
resources.spritesheets.animatedSky.animations.indexed = {
frames: ["10", "2"],
animationSpeed: 0.5,
loop: true,
};
const renderState = constructRenderState({
presentationState: {
background: {
resourceId: "animatedSky",
animationName: "indexed",
},
},
resources,
});
const playback = findBackground(renderState).playback;
const renderedFrameNames = playback.frames.map((frame) =>
typeof frame === "number"
? Object.keys(arrayLikeAtlas.frames)[frame]
: frame,
);

expect(renderedFrameNames).toEqual(arrayLikeFrameOrder.slice(0, 2));
});

it("rejects an unknown spritesheet animation", () => {
expect(() =>
constructRenderState({
Expand Down
26 changes: 26 additions & 0 deletions spec/projectDataSchema.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -807,6 +807,32 @@ describe("projectData schema", () => {
);
});

it("accepts spritesheet animation frame names", () => {
const projectData = createMinimalProjectData({
resources: {
spritesheets: {
animatedSky: {
fileId: "animated-sky.png",
width: 32,
height: 32,
jsonData: {
frames: {
10: { frame: { x: 0, y: 0, w: 32, h: 32 } },
2: { frame: { x: 32, y: 0, w: 32, h: 32 } },
},
},
animations: {
calm: { frames: ["10", "2"] },
},
},
},
},
});

expect(validateProjectData(projectData)).toBe(true);
expect(validateProjectData.errors).toBeNull();
});

it("accepts structured and legacy particle resources", () => {
const projectData = createMinimalProjectData({
resources: {
Expand Down
2 changes: 1 addition & 1 deletion src/generated/l10nPayloadValidators.js

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions src/schemas/projectData/resources.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -215,8 +215,10 @@ properties:
frames:
type: array
items:
type: number
description: Array of frame indices
oneOf:
- type: number
- type: string
description: Array of frame indices or names
animationSpeed:
type: number
description: Default playback speed (0.5 is standard)
Expand Down
20 changes: 13 additions & 7 deletions vt/specs/robustness/visual-spritesheet-default.yaml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
---
title: Visual Spritesheet Default Animation
description: A spritesheet visual without animationName uses its first declared animation.
title: Visual Spritesheet Named Frames
description: A spritesheet visual selects named frames despite array-like atlas keys.
specs:
- animationName is intentionally omitted from the visual item
- the schema permits the omission
- the fighter must still render using the first animation
- the animation frames name their atlas entries
- integer-like atlas keys do not change the selected frame names
skipInitialScreenshot: true
viewport:
id: capture
Expand Down Expand Up @@ -46,13 +46,19 @@ resources:
height: 240
jsonData:
frames:
rollSequence0000.png:
"10":
frame: { x: 483, y: 692, w: 169, h: 226 }
rotated: false
trimmed: true
spriteSourceSize: { x: 3, y: 4, w: 169, h: 226 }
sourceSize: { w: 175, h: 240 }
rollSequence0001.png:
"2":
frame: { x: 468, y: 2, w: 169, h: 226 }
rotated: false
trimmed: true
spriteSourceSize: { x: 3, y: 4, w: 169, h: 226 }
sourceSize: { w: 175, h: 240 }
"1":
frame: { x: 468, y: 2, w: 169, h: 226 }
rotated: false
trimmed: true
Expand All @@ -64,7 +70,7 @@ resources:
scale: "1"
animations:
idle:
frames: [0, 1]
frames: ["10", "2"]
animationSpeed: 0.1
loop: true
transforms:
Expand Down
Loading