Skip to content
Merged
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 @@ -107,6 +107,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-named-frames.yaml
- id: layout-each-missing-source
item: robustness/layout-each-missing-source.yaml
- id: layout-guarded-choice
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
52 changes: 52 additions & 0 deletions spec/projectDataSchema.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1063,6 +1063,58 @@ describe("projectData schema", () => {
);
});

const createSpritesheetAnimationProject = (frames) =>
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 },
},
},
},
},
});

it.each([
["frame names", ["10", "2"]],
["numeric frame indexes", [0, 1]],
["mixed frame names and indexes", [0, "2"]],
])("accepts spritesheet animation %s", (_label, frames) => {
expect(validateProjectData(createSpritesheetAnimationProject(frames))).toBe(
true,
);
expect(validateProjectData.errors).toBeNull();
});

it.each([
["boolean", true],
["null", null],
["object", { name: "10" }],
["array", [0]],
])("rejects a %s spritesheet animation frame", (_label, frame) => {
expect(
validateProjectData(createSpritesheetAnimationProject([0, frame])),
).toBe(false);
expect(validateProjectData.errors).toEqual(
expect.arrayContaining([
expect.objectContaining({
instancePath:
"/resources/spritesheets/animatedSky/animations/calm/frames/1",
}),
]),
);
});

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 @@ -219,8 +219,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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
66 changes: 66 additions & 0 deletions vt/specs/robustness/visual-spritesheet-named-frames.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
---
title: Visual Spritesheet Named Frames
description: Named numeric-looking atlas keys select a white frame and a gray frame; zero speed holds the selected frames for deterministic capture.
specs:
- omitted animationName selects the first named-frame animation
- explicit animationName selects another named frame
- index-based frame selection instead shows a large checkerboard and fails comparison
skipInitialScreenshot: true
viewport: { id: capture, width: 960, height: 540 }
steps:
- action: wait
ms: 200
- action: screenshot
---
screen: { width: 960, height: 540 }
resources:
variables:
vtAssetIds: { type: string, scope: context, default: named-frame-atlas }
spritesheets:
tiles:
fileId: named-frame-atlas
width: 240
height: 240
jsonData:
frames:
"10":
frame: { x: 480, y: 0, w: 240, h: 240 }
"2":
frame: { x: 240, y: 0, w: 240, h: 240 }
"1":
frame: { x: 0, y: 0, w: 240, h: 240 }
meta:
format: RGBA8888
size: { w: 720, h: 240 }
scale: "1"
animations:
white:
frames: ["10", "2"]
animationSpeed: 0
loop: true
gray:
frames: ["2", "10"]
animationSpeed: 0
loop: true
transforms:
left: { x: 80, y: 110, scaleX: 1.3, scaleY: 1.3 }
right: { x: 568, y: 110, scaleX: 1.3, scaleY: 1.3 }
story:
initialSceneId: main
scenes:
main:
initialSectionId: main
sections:
main:
lines:
- id: tiles
actions:
visual:
items:
- id: white
resourceId: tiles
transformId: left
- id: gray
resourceId: tiles
transformId: right
animationName: gray
4 changes: 4 additions & 0 deletions vt/static/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,10 @@ const init = async () => {
url: "/public/video-volume-visual.webm",
type: "video/webm",
},
"named-frame-atlas": {
url: "/public/named-frame-atlas.png",
type: "image/png",
},
"video-continuity": {
url: "/public/video-continuity.webm",
type: "video/webm",
Expand Down
3 changes: 3 additions & 0 deletions vt/static/public/named-frame-atlas.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading