Skip to content

Commit 74feb99

Browse files
author
DavidQ
committed
Add Object Vector Studio V2 animation states, frame timeline foundation, and playback preview controls - PR_26132_014-object-vector-studio-v2-animation-states
1 parent b8bcc8d commit 74feb99

9 files changed

Lines changed: 960 additions & 22 deletions

File tree

docs/dev/reports/playwright_v8_coverage.txt

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,9 @@ Exercised tool entry points detected:
2020
(0%) Workspace Manager - not exercised by this Playwright run
2121

2222
Changed runtime JS files covered:
23-
(44%) src/engine/runtime/fullscreenBezel.js - executed lines 1034/1034; executed functions 29/66
24-
(71%) src/engine/runtime/FullscreenService.js - executed lines 123/123; executed functions 10/14
25-
(80%) tools/object-vector-studio-v2/js/bootstrap.js - executed lines 78/78; executed functions 4/5
26-
(91%) tools/object-vector-studio-v2/js/ToolStarterApp.js - executed lines 2062/2062; executed functions 220/242
23+
(80%) tools/object-vector-studio-v2/js/bootstrap.js - executed lines 90/90; executed functions 4/5
24+
(92%) tools/object-vector-studio-v2/js/ToolStarterApp.js - executed lines 2553/2553; executed functions 293/317
25+
(93%) tools/object-vector-studio-v2/js/services/ObjectVectorStudioV2SchemaService.js - executed lines 307/307; executed functions 37/40
2726

2827
Files with executed line/function counts where available:
2928
(2%) src/engine/input/ActionInputService.js - executed lines 397/397; executed functions 1/51
@@ -171,7 +170,7 @@ Files with executed line/function counts where available:
171170
(80%) src/engine/persistence/StorageService.js - executed lines 49/49; executed functions 4/5
172171
(80%) tools/asset-manager-v2/js/controls/AccordionSection.js - executed lines 27/27; executed functions 4/5
173172
(80%) tools/asset-manager-v2/js/controls/AssetFormControl.js - executed lines 563/563; executed functions 49/61
174-
(80%) tools/object-vector-studio-v2/js/bootstrap.js - executed lines 78/78; executed functions 4/5
173+
(80%) tools/object-vector-studio-v2/js/bootstrap.js - executed lines 90/90; executed functions 4/5
175174
(80%) tools/palette-manager-v2/modules/PaletteHistoryStack.js - executed lines 54/54; executed functions 8/10
176175
(80%) tools/preview-generator-v2/controls/AccordionSection.js - executed lines 31/31; executed functions 4/5
177176
(80%) tools/preview-generator-v2/PreviewGeneratorV2Logger.js - executed lines 19/19; executed functions 4/5
@@ -201,12 +200,12 @@ Files with executed line/function counts where available:
201200
(90%) tools/text2speech-V2/js/TextToSpeechToolApp.js - executed lines 807/807; executed functions 62/69
202201
(91%) games/Asteroids/entities/Asteroid.js - executed lines 72/72; executed functions 10/11
203202
(91%) tools/object-vector-studio-v2/js/controls/ActionNavControl.js - executed lines 75/75; executed functions 10/11
204-
(91%) tools/object-vector-studio-v2/js/ToolStarterApp.js - executed lines 2062/2062; executed functions 220/242
205203
(91%) tools/toolRegistry.js - executed lines 526/526; executed functions 10/11
206204
(91%) tools/workspace-manager-v2/js/services/WorkspaceManagerV2ContextService.js - executed lines 1598/1598; executed functions 145/159
207205
(92%) tools/object-vector-studio-v2/js/controls/ToolStarterShellControl.js - executed lines 112/112; executed functions 11/12
208-
(92%) tools/object-vector-studio-v2/js/services/ObjectVectorStudioV2SchemaService.js - executed lines 286/286; executed functions 33/36
206+
(92%) tools/object-vector-studio-v2/js/ToolStarterApp.js - executed lines 2553/2553; executed functions 293/317
209207
(93%) tools/asset-manager-v2/js/services/WorkspaceBridge.js - executed lines 305/305; executed functions 25/27
208+
(93%) tools/object-vector-studio-v2/js/services/ObjectVectorStudioV2SchemaService.js - executed lines 307/307; executed functions 37/40
210209
(93%) tools/session-inspector-v2/js/SessionInspectorV2App.js - executed lines 337/337; executed functions 42/45
211210
(93%) tools/text2speech-V2/js/controls/QueueControl.js - executed lines 122/122; executed functions 26/28
212211
(93%) tools/workspace-manager-v2/js/controls/GameSelectorControl.js - executed lines 59/59; executed functions 13/14
@@ -290,11 +289,10 @@ Files with executed line/function counts where available:
290289
(100%) tools/world-vector-studio-v2/js/services/ToolStateSerializer.js - executed lines 13/13; executed functions 3/3
291290

292291
Uncovered or low-coverage changed JS files:
293-
(44%) src/engine/runtime/fullscreenBezel.js - WARNING: advisory low coverage; executed lines 1034/1034
292+
(100%) none - no low-coverage changed runtime JS files
294293

295294
Changed JS files considered:
296295
(0%) tests/playwright/tools/WorkspaceManagerV2.spec.mjs - changed JS file not collected as browser runtime coverage
297-
(44%) src/engine/runtime/fullscreenBezel.js - changed JS file with browser V8 coverage
298-
(71%) src/engine/runtime/FullscreenService.js - changed JS file with browser V8 coverage
299296
(80%) tools/object-vector-studio-v2/js/bootstrap.js - changed JS file with browser V8 coverage
300-
(91%) tools/object-vector-studio-v2/js/ToolStarterApp.js - changed JS file with browser V8 coverage
297+
(92%) tools/object-vector-studio-v2/js/ToolStarterApp.js - changed JS file with browser V8 coverage
298+
(93%) tools/object-vector-studio-v2/js/services/ObjectVectorStudioV2SchemaService.js - changed JS file with browser V8 coverage
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# PR_26132_014-object-vector-studio-v2-animation-states
2+
3+
## Scope
4+
5+
Adds the Object Vector Studio V2 animation/state foundation only. The change stays within Object Vector Studio V2 schema, runtime UI/CSS/JS, and Workspace Manager V2 Playwright coverage.
6+
7+
## Changes
8+
9+
- Added durable object animation states for idle, thrust, damaged, destroyed, active, and inactive.
10+
- Added state selector and Create State controls.
11+
- Added per-state frame payloads with per-shape visibility and transform overrides.
12+
- Added frame timeline UI with state/frame thumbnails, frame selection, frame duplication, and frame ordering controls.
13+
- Added playback preview controls: Play, Pause, Stop, Loop, FPS, and onion-skin preview.
14+
- Preserved active object selection while changing states and frames.
15+
- Rendered selected state/frame overrides on the center work surface.
16+
- Added state/frame metadata to SVG export.
17+
- Kept JSON copy/export state-aware through the strict Object Vector Studio V2 schema.
18+
- Rejected invalid animation/state/frame payloads through schema validation before render.
19+
20+
## Validation
21+
22+
Playwright impacted: Yes.
23+
24+
Commands run:
25+
26+
- `node --check tools/object-vector-studio-v2/js/ToolStarterApp.js`
27+
- `node --check tools/object-vector-studio-v2/js/bootstrap.js`
28+
- `node --check tools/object-vector-studio-v2/js/services/ObjectVectorStudioV2SchemaService.js`
29+
- `npx playwright test tests/playwright/tools/WorkspaceManagerV2.spec.mjs --project=playwright --workers=1 --reporter=line -g "Object Vector Studio V2"`
30+
- `npm run test:workspace-v2`
31+
32+
Result:
33+
34+
- Targeted Object Vector Studio V2 coverage passed: 4 passed.
35+
- Full Workspace Manager V2 suite passed: 43 passed.
36+
- Playwright V8 coverage report generated at `docs/dev/reports/playwright_v8_coverage_report.txt` and copied to required report path `docs/dev/reports/playwright_v8_coverage.txt`.
37+
- Full samples smoke test skipped per request; this PR is limited to Object Vector Studio V2 animation/state runtime and is covered by targeted Workspace V2 Playwright validation.
38+
39+
## Playwright Coverage
40+
41+
Validates:
42+
43+
- State creation from the fixed Object Vector Studio V2 state set.
44+
- Frame duplication and ordering.
45+
- Playback controls and FPS/loop wiring.
46+
- State/frame selection synchronization while preserving the active object.
47+
- Per-state frame transform overrides.
48+
- Onion-skin preview.
49+
- State-aware JSON copy/export and SVG metadata export.
50+
- Invalid animation payload rejection before render.
51+
52+
Expected pass behavior:
53+
54+
- Valid state/frame operations mutate only schema-valid object payloads and render the selected frame.
55+
56+
Expected fail behavior:
57+
58+
- Invalid animation payloads are rejected through schema validation and logged as FAIL without partial render.
59+
60+
## Manual Validation
61+
62+
1. Open `tools/object-vector-studio-v2/index.html`.
63+
2. Load a schema-valid Object Vector Studio V2 payload and runtime palette.
64+
3. Create a template object, select `Idle`, and click `Create State`.
65+
4. Duplicate and reorder frames, then adjust a selected shape transform.
66+
5. Use Play, Pause, Stop, Loop, FPS, and Onion controls.
67+
6. Export SVG and JSON.
68+
69+
Expected outcome:
70+
71+
- The active object remains selected, frame thumbnails stay synchronized, preview playback advances frames, JSON includes states/frames, and SVG includes state/frame metadata.
72+
73+
## Out Of Scope
74+
75+
- No World Vector Studio V2 changes.
76+
- No sample JSON changes.
77+
- No full samples smoke test.

tests/playwright/tools/WorkspaceManagerV2.spec.mjs

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1717,6 +1717,138 @@ test.describe("Workspace Manager V2 bootstrap", () => {
17171717
}
17181718
});
17191719

1720+
test("supports Object Vector Studio V2 animation states and frame timeline foundation", async ({ page }, testInfo) => {
1721+
const server = await startRepoServer();
1722+
const pageErrors = [];
1723+
1724+
page.on("pageerror", (error) => {
1725+
pageErrors.push(error.message);
1726+
});
1727+
1728+
await coverageReporter.start(page);
1729+
try {
1730+
await page.goto(`${server.baseUrl}/tools/object-vector-studio-v2/index.html`, { waitUntil: "networkidle" });
1731+
await page.evaluate(() => {
1732+
sessionStorage.setItem("object-vector-studio-v2.runtimePalette", JSON.stringify({
1733+
id: "animation-palette",
1734+
swatches: [
1735+
{ id: "white", value: "#ffffff" },
1736+
{ id: "cyan", value: "#6fd3ff" }
1737+
]
1738+
}));
1739+
Object.defineProperty(navigator, "clipboard", {
1740+
configurable: true,
1741+
value: {
1742+
async writeText(text) {
1743+
sessionStorage.setItem("object-vector-studio-v2.animation-copied-json", text);
1744+
}
1745+
}
1746+
});
1747+
});
1748+
1749+
const payloadPath = testInfo.outputPath("object-vector-animation.json");
1750+
await writeFile(payloadPath, JSON.stringify({
1751+
name: "Animation Payload",
1752+
objects: [],
1753+
toolId: "object-vector-studio-v2",
1754+
version: 1
1755+
}, null, 2), "utf8");
1756+
await page.locator("#objectVectorStudioV2ImportJsonInput").setInputFiles(payloadPath);
1757+
1758+
await page.locator("#objectVectorStudioV2TemplateSelect").selectOption("ship");
1759+
await page.locator("#objectVectorStudioV2CreateTemplateButton").click();
1760+
await expect(page.locator('[data-object-id="ship-template"]')).toHaveAttribute("aria-pressed", "true");
1761+
1762+
await page.locator("#objectVectorStudioV2StateSelect").selectOption("idle");
1763+
await page.locator("#objectVectorStudioV2CreateStateButton").click();
1764+
await expect(page.locator("#objectVectorStudioV2FrameTimeline [data-state-id='idle']")).toHaveCount(1);
1765+
await expect(page.locator("#objectVectorStudioV2FrameTimeline [data-frame-id='idle-frame-1']")).toHaveAttribute("aria-pressed", "true");
1766+
await expect(page.locator("#objectVectorStudioV2JsonDetails")).toContainText('"id": "idle"');
1767+
await expect(page.locator("#statusLog")).toHaveValue(/OK Created state Idle with frame idle-frame-1\./);
1768+
1769+
await page.locator("#objectVectorStudioV2DuplicateFrameButton").click();
1770+
await expect(page.locator("#objectVectorStudioV2FrameTimeline [data-state-id='idle']")).toHaveCount(2);
1771+
await expect(page.locator("#objectVectorStudioV2FrameTimeline [data-frame-id='idle-frame-2']")).toHaveAttribute("aria-pressed", "true");
1772+
await page.locator("#objectVectorStudioV2FrameEarlierButton").click();
1773+
await expect(page.locator("#objectVectorStudioV2FrameTimeline [data-frame-id]").first()).toHaveAttribute("data-frame-id", "idle-frame-2");
1774+
await expect(page.locator("#statusLog")).toHaveValue(/OK Moved frame idle-frame-2 earlier\./);
1775+
await page.locator("#objectVectorStudioV2FrameTimeline [data-frame-id='idle-frame-1']").click();
1776+
1777+
await page.locator("#objectVectorStudioV2MoveXInput").fill("12");
1778+
await page.locator("#objectVectorStudioV2MoveYInput").fill("6");
1779+
await page.locator("#objectVectorStudioV2MoveShapeButton").click();
1780+
await expect(page.locator("#objectVectorStudioV2JsonDetails")).toContainText('"shapeOverrides"');
1781+
await expect(page.locator("#objectVectorStudioV2JsonDetails")).toContainText('"x": 12');
1782+
await expect(page.locator("#objectVectorStudioV2SelectionMetrics")).toContainText("state idle");
1783+
1784+
await page.locator("#objectVectorStudioV2OnionSkinToggle").check();
1785+
await expect(page.locator("#objectVectorStudioV2RenderSurface [data-onion-skin-frame='idle-frame-2']")).toHaveCount(1);
1786+
await expect(page.locator("#statusLog")).toHaveValue(/OK Onion-skin preview enabled\./);
1787+
1788+
await page.locator("#objectVectorStudioV2StateSelect").selectOption("thrust");
1789+
await page.locator("#objectVectorStudioV2CreateStateButton").click();
1790+
await expect(page.locator('[data-object-id="ship-template"]')).toHaveAttribute("aria-pressed", "true");
1791+
await expect(page.locator("#objectVectorStudioV2FrameTimeline [data-state-id='thrust']")).toHaveCount(1);
1792+
await expect(page.locator("#statusLog")).toHaveValue(/OK Created state Thrust with frame thrust-frame-1\./);
1793+
await page.locator("#objectVectorStudioV2StateSelect").selectOption("idle");
1794+
await expect(page.locator("#objectVectorStudioV2FrameTimeline [data-state-id='idle']")).toHaveCount(2);
1795+
1796+
await page.locator("#objectVectorStudioV2LoopToggle").check();
1797+
await page.locator("#objectVectorStudioV2FpsInput").fill("24");
1798+
await page.locator("#objectVectorStudioV2PlayButton").click();
1799+
await expect(page.locator("#objectVectorStudioV2PauseButton")).toBeEnabled();
1800+
await expect(page.locator("#statusLog")).toHaveValue(/OK Playback started for state Idle at 24 FPS\./);
1801+
await page.waitForTimeout(120);
1802+
await page.locator("#objectVectorStudioV2PauseButton").click();
1803+
await expect(page.locator("#statusLog")).toHaveValue(/OK Playback paused at frame/);
1804+
await page.locator("#objectVectorStudioV2StopButton").click();
1805+
await expect(page.locator("#statusLog")).toHaveValue(/OK Playback stopped\./);
1806+
1807+
await page.locator("#objectVectorStudioV2CopyJsonButton").click();
1808+
const copiedPayload = await page.evaluate(() => JSON.parse(sessionStorage.getItem("object-vector-studio-v2.animation-copied-json")));
1809+
expect(copiedPayload.objects[0].states.map((state) => state.id)).toEqual(expect.arrayContaining(["idle", "thrust"]));
1810+
expect(copiedPayload.objects[0].states.find((state) => state.id === "idle").frames).toHaveLength(2);
1811+
1812+
const svgDownloadPromise = page.waitForEvent("download");
1813+
await page.locator("#objectVectorStudioV2ExportSvgButton").click();
1814+
const svgDownload = await svgDownloadPromise;
1815+
const svgExportPath = testInfo.outputPath("object-vector-animation.svg");
1816+
await svgDownload.saveAs(svgExportPath);
1817+
const exportedSvg = await readFile(svgExportPath, "utf8");
1818+
expect(exportedSvg).toContain('data-object-state="idle"');
1819+
expect(exportedSvg).toContain('"frameId"');
1820+
1821+
const invalidPayloadPath = testInfo.outputPath("object-vector-invalid-animation.json");
1822+
await writeFile(invalidPayloadPath, JSON.stringify({
1823+
name: "Invalid Animation Payload",
1824+
objects: [
1825+
{
1826+
id: "bad-animation",
1827+
name: "Bad Animation",
1828+
shapes: [],
1829+
states: [
1830+
{
1831+
frames: [],
1832+
id: "flying",
1833+
name: "Flying"
1834+
}
1835+
],
1836+
type: "ship"
1837+
}
1838+
],
1839+
toolId: "object-vector-studio-v2",
1840+
version: 1
1841+
}, null, 2), "utf8");
1842+
await page.locator("#objectVectorStudioV2ImportJsonInput").setInputFiles(invalidPayloadPath);
1843+
await expect(page.locator("#statusLog")).toHaveValue(/FAIL Object Vector Studio V2 schema validation failed from import:object-vector-invalid-animation\.json: root\.objects\[0\]\.states\[0\]\.id must be one of idle, thrust, damaged, destroyed, active, inactive\./);
1844+
1845+
expect(pageErrors).toEqual([]);
1846+
} finally {
1847+
await coverageReporter.stop(page);
1848+
await server.close();
1849+
}
1850+
});
1851+
17201852
test("resolves asset-manager-v2 audio catalog paths and plays Asteroids sounds", async ({ page }) => {
17211853
const server = await startRepoServer();
17221854
const pageErrors = [];

tools/object-vector-studio-v2/index.html

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,21 @@ <h2 class="tools-platform-frame__eyebrow">First-Class Tools Surface V2</h2>
106106
<button id="objectVectorStudioV2DeleteObjectButton" type="button">Delete</button>
107107
<button id="objectVectorStudioV2FlattenObjectButton" type="button">Flatten</button>
108108
</div>
109+
<label class="tool-starter__field tool-starter__field--stacked" for="objectVectorStudioV2StateSelect">
110+
<span>State</span>
111+
<select id="objectVectorStudioV2StateSelect">
112+
<option value="">No state selected</option>
113+
<option value="idle">Idle</option>
114+
<option value="thrust">Thrust</option>
115+
<option value="damaged">Damaged</option>
116+
<option value="destroyed">Destroyed</option>
117+
<option value="active">Active</option>
118+
<option value="inactive">Inactive</option>
119+
</select>
120+
</label>
121+
<div class="object-vector-studio-v2__object-actions" aria-label="State actions">
122+
<button id="objectVectorStudioV2CreateStateButton" type="button">Create State</button>
123+
</div>
109124
<div id="objectVectorStudioV2LoadStatus" class="object-vector-studio-v2__callout" role="status">Schema-only loading is idle. Import JSON or launch with workspace toolState data that includes a palette.</div>
110125
</div>
111126
</section>
@@ -208,6 +223,29 @@ <h2 class="tools-platform-frame__eyebrow">First-Class Tools Surface V2</h2>
208223
<div id="objectVectorStudioV2CoordinateDisplay" class="object-vector-studio-v2__coordinate-display">Coordinates: 0, 0 | Zoom 100%</div>
209224
<div id="objectVectorStudioV2SelectedItemVisibility" class="object-vector-studio-v2__selected-visibility">No schema-valid object selected.</div>
210225
<div id="objectVectorStudioV2SelectionMetrics" class="object-vector-studio-v2__selection-metrics">Selection metrics: none.</div>
226+
<div class="object-vector-studio-v2__animation-controls" aria-label="Animation controls">
227+
<button id="objectVectorStudioV2PlayButton" type="button" disabled>Play</button>
228+
<button id="objectVectorStudioV2PauseButton" type="button" disabled>Pause</button>
229+
<button id="objectVectorStudioV2StopButton" type="button" disabled>Stop</button>
230+
<label class="object-vector-studio-v2__inline-field" for="objectVectorStudioV2LoopToggle">
231+
<input id="objectVectorStudioV2LoopToggle" type="checkbox">
232+
<span>Loop</span>
233+
</label>
234+
<label class="object-vector-studio-v2__inline-field" for="objectVectorStudioV2OnionSkinToggle">
235+
<input id="objectVectorStudioV2OnionSkinToggle" type="checkbox">
236+
<span>Onion</span>
237+
</label>
238+
<label class="object-vector-studio-v2__inline-field" for="objectVectorStudioV2FpsInput">
239+
<span>FPS</span>
240+
<input id="objectVectorStudioV2FpsInput" type="number" min="1" max="60" step="1" value="12">
241+
</label>
242+
</div>
243+
<div class="object-vector-studio-v2__animation-controls" aria-label="Frame controls">
244+
<button id="objectVectorStudioV2DuplicateFrameButton" type="button" disabled>Duplicate Frame</button>
245+
<button id="objectVectorStudioV2FrameEarlierButton" type="button" disabled>Frame Earlier</button>
246+
<button id="objectVectorStudioV2FrameLaterButton" type="button" disabled>Frame Later</button>
247+
</div>
248+
<div id="objectVectorStudioV2FrameTimeline" class="object-vector-studio-v2__frame-timeline" aria-label="Frame timeline"></div>
211249
<svg id="objectVectorStudioV2RenderSurface" class="object-vector-studio-v2__render-surface" viewBox="0 0 320 220" tabindex="0" role="img" aria-label="Object shape render surface"></svg>
212250
<div id="objectVectorStudioV2RenderSummary" class="object-vector-studio-v2__render-summary">Render mode: idle. Capture mode: none.</div>
213251
</div>

0 commit comments

Comments
 (0)