Skip to content

Commit b9dcfb8

Browse files
author
DavidQ
committed
Fix Object Preview viewport pan behavior and enforce strict manifest schema validation - PR_26133_045-object-preview-pan-direction-and-strict-schema-fix
1 parent c80fce5 commit b9dcfb8

7 files changed

Lines changed: 133 additions & 57 deletions

File tree

docs/dev/reports/playwright_v8_coverage_report.md

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
# PR_26133_044 Playwright V8 Coverage Report
1+
# PR_26133_045 Playwright V8 Coverage Report
22

3-
Task: PR_26133_044-remove-duplicated-object-vector-runtime-bindings
3+
Task: PR_26133_045-object-preview-pan-direction-and-strict-schema-fix
44
Date: 2026-05-15
55

66
## Result
@@ -24,12 +24,10 @@ PASS - Coverage reporting was generated during `npm run test:workspace-v2`.
2424
## Relevant Runtime Coverage
2525

2626
```text
27-
(98%) src/engine/rendering/ObjectVectorRuntimeAssetService.js - executed lines 914/914; executed functions 106/108
28-
(52%) games/Asteroids/game/AsteroidsGameScene.js - executed lines 846/846; executed functions 25/48
27+
(90%) tools/object-vector-studio-v2/js/ToolStarterApp.js - executed lines 3187/3187; executed functions 331/369
28+
(95%) tools/object-vector-studio-v2/js/services/ObjectVectorStudioV2SchemaService.js - executed lines 409/409; executed functions 53/56
2929
```
3030

31-
The Asteroids runtime role helpers and asteroid geometry helper are exercised through the Asteroids gameplay rendering path and targeted node smoke checks; they are not surfaced as separate changed-runtime rows in the generated V8 summary.
32-
3331
## Guardrail
3432

3533
```text
@@ -38,4 +36,4 @@ The Asteroids runtime role helpers and asteroid geometry helper are exercised th
3836

3937
## PR-Specific Note
4038

41-
The Workspace V2 run exercised Object Vector runtime manifest loading from `game.workspace.tools.object-vector-studio-v2.objects`, direct object ID role resolution, runtime rendering for asteroids/ship/UFO, and schema rejection of the removed `game.gameData.objectVectorRuntime` branch.
39+
The Workspace V2 run exercised Object Vector Studio V2 preview pan controls, center-dot visibility, coordinate/origin display updates, transform-origin pivot rendering, and strict schema rejection for unknown runtime/workspace fields.
Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
# PR_26133_044 Workspace V2 Playwright Results
1+
# PR_26133_045 Workspace V2 Playwright Results
22

3-
Task: PR_26133_044-remove-duplicated-object-vector-runtime-bindings
3+
Task: PR_26133_045-object-preview-pan-direction-and-strict-schema-fix
44
Date: 2026-05-15
55

66
## Result
@@ -10,22 +10,22 @@ PASS - `npm run test:workspace-v2` completed successfully.
1010
- Command: `npm run test:workspace-v2`
1111
- Playwright target: `tests/playwright/tools/WorkspaceManagerV2.spec.mjs --project=playwright --workers=1 --reporter=list`
1212
- Final result: 51 passed, 0 failed.
13-
- Runtime/console guard: Asteroids Object Vector runtime and Workspace Manager V2 tests completed with no reported page errors.
13+
- Runtime/console guard: Workspace Manager V2 and Object Vector Studio V2 scenarios completed with no reported page errors.
1414

1515
## PR-Specific Coverage
1616

17-
- Verified Asteroids launches Object Vector runtime assets from `game.workspace.tools.object-vector-studio-v2.objects`.
18-
- Verified runtime role validation resolves `object.asteroids.*` IDs directly from the loaded Object Vector object list.
19-
- Verified `game.gameData.objectVectorRuntime` is absent from `games/Asteroids/game.manifest.json`.
20-
- Verified schema validation rejects a manifest when `game.gameData.objectVectorRuntime` is re-added.
21-
- Verified runtime rendering counts for asteroids, ship, and UFO remain active after removing the duplicate binding map.
17+
- Verified the Center control toggles the center dot without recentering or changing the viewport.
18+
- Verified Up/Down/Left/Right pan only the viewport origin and update the Canvas origin display.
19+
- Verified pan controls do not mutate selected shape geometry or transform state.
20+
- Verified pointer X/Y coordinate display remains logical after the viewport-origin display change.
21+
- Verified the transform origin/pivot marker reflects the applied origin used for rotation/scale behavior.
22+
- Verified `game.gameData.objectVectorRuntime` and unknown `gameData` fields fail strict schema validation.
23+
- Verified the workspace manifest schema rejects an unknown root field.
2224

2325
## Additional Validation
2426

25-
- `node` manifest-schema check: current Asteroids manifest returned `{ ok: true }`.
26-
- `node` manifest-schema check: injected `game.gameData.objectVectorRuntime` returned `{ ok: false }`.
27-
- Targeted node smoke checks passed for `tests/games/AsteroidsPlatformDemo.test.mjs` and `tests/games/AsteroidsAssetReferenceAdoption.test.mjs`.
28-
29-
## Notes
30-
31-
- `npm test` is blocked before the node suite by the existing shared-extraction guard baseline drift; this PR did not touch those unrelated guard violations.
27+
- `node` JSON parse check passed for `tools/schemas/game.manifest.schema.json`, `tools/schemas/workspace.manifest.schema.json`, and `games/Asteroids/game.manifest.json`.
28+
- `node` schema-service check: current Asteroids manifest returned `{ ok: true }`.
29+
- `node` schema-service check: injected `game.gameData.objectVectorRuntime` returned `{ ok: false }`.
30+
- `node` schema-service check: injected `game.gameData.debugUnknown` returned `{ ok: false }`.
31+
- `node` schema-service check: injected workspace root `debugUnknown` returned `{ ok: false }`.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# PR_26133_045 Strict Schema Unknown Field Validation Report
2+
3+
Task: PR_26133_045-object-preview-pan-direction-and-strict-schema-fix
4+
Date: 2026-05-15
5+
6+
## Schema Changes
7+
8+
- `tools/schemas/game.manifest.schema.json` now sets `$defs.gameData.additionalProperties` to `false`.
9+
- The former dedicated `not` branch for `objectVectorRuntime` was removed because `gameData` now allows only explicit properties.
10+
- `objectVectorRuntime` was not added back to the schema or the Asteroids manifest.
11+
- `tools/schemas/workspace.manifest.schema.json` already rejects unknown root and `tools` map fields with `additionalProperties: false`.
12+
13+
## Validation Proof
14+
15+
The Workspace Manager V2 schema service was run against the current Asteroids manifest and targeted invalid clones.
16+
17+
```text
18+
valid Asteroids game manifest: ok true
19+
valid Asteroids workspace manifest: ok true
20+
injected game.gameData.objectVectorRuntime: ok false
21+
message: root.game.gameData.objectVectorRuntime is not allowed
22+
injected game.gameData.debugUnknown: ok false
23+
message: root.game.gameData.debugUnknown is not allowed
24+
injected workspace root debugUnknown: ok false
25+
message: root.debugUnknown is not allowed
26+
```
27+
28+
## Command
29+
30+
```text
31+
node --input-type=module - < inline WorkspaceManagerV2ContextService schema validation script
32+
```
33+
34+
The same rejection paths are also asserted in `tests/playwright/tools/WorkspaceManagerV2.spec.mjs` and covered by `npm run test:workspace-v2`.

tests/playwright/tools/WorkspaceManagerV2.spec.mjs

Lines changed: 56 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1823,18 +1823,27 @@ test.describe("Workspace Manager V2 bootstrap", () => {
18231823
await expect(page.locator("#objectVectorStudioV2RenderSurface [data-center-origin='0,0']")).toHaveCount(1);
18241824
await expect(page.locator("#objectVectorStudioV2RenderSurface [data-center-origin='0,0']")).toHaveAttribute("r", "9");
18251825
await expect(page.locator("#objectVectorStudioV2ViewportControls button")).toHaveText(["Out", "In", "Up", "Down", "Left", "Right", "View", "Center"]);
1826-
await expect(page.locator("#objectVectorStudioV2CenterDotButton")).not.toHaveAttribute("aria-pressed", /.*/);
1826+
await expect(page.locator("#objectVectorStudioV2CenterDotButton")).toHaveAttribute("aria-pressed", "true");
18271827
await page.locator("#objectVectorStudioV2PanRightButton").click();
18281828
await page.locator("#objectVectorStudioV2PanDownButton").click();
1829-
await expect(page.locator("#objectVectorStudioV2CoordinateDisplay")).toHaveText("Origin: 2, 2 | Canvas 0,0 centered | Zoom 100%");
1829+
await expect(page.locator("#objectVectorStudioV2CoordinateDisplay")).toHaveText("Origin: 2, 2 | Canvas origin -2,-2 from center | Zoom 100%");
18301830
await expect(page.locator("#objectVectorStudioV2RenderSurface")).toHaveAttribute("viewBox", "-1580 -1080 3200 2200");
18311831
await page.locator("#objectVectorStudioV2RenderSurface").hover({ position: { x: 12, y: 12 } });
18321832
await expect(page.locator("#objectVectorStudioV2CoordinateDisplay")).toContainText("Pointer");
18331833
await page.locator("#objectVectorStudioV2CenterDotButton").click();
1834+
await expect(page.locator("#objectVectorStudioV2RenderSurface")).toHaveAttribute("viewBox", "-1580 -1080 3200 2200");
1835+
await expect(page.locator("#objectVectorStudioV2CoordinateDisplay")).toContainText("Canvas origin -2,-2 from center | Zoom 100%");
1836+
await expect(page.locator("#objectVectorStudioV2RenderSurface [data-center-origin='0,0']")).toHaveCount(0);
1837+
await expect(page.locator("#objectVectorStudioV2CenterDotButton")).toHaveAttribute("aria-pressed", "false");
1838+
await expect(page.locator("#statusLog")).toHaveValue(/OK Center dot hidden\./);
1839+
await page.locator("#objectVectorStudioV2ResetViewButton").click();
18341840
await expect(page.locator("#objectVectorStudioV2RenderSurface")).toHaveAttribute("viewBox", "-1600 -1100 3200 2200");
1835-
await expect(page.locator("#objectVectorStudioV2CoordinateDisplay")).toHaveText("Origin: 0, 0 | Canvas 0,0 centered | Zoom 100%");
1841+
await expect(page.locator("#objectVectorStudioV2CoordinateDisplay")).toHaveText("Origin: 0, 0 | Canvas origin 0,0 centered | Zoom 100%");
1842+
await expect(page.locator("#objectVectorStudioV2RenderSurface [data-center-origin='0,0']")).toHaveCount(0);
1843+
await page.locator("#objectVectorStudioV2CenterDotButton").click();
18361844
await expect(page.locator("#objectVectorStudioV2RenderSurface [data-center-origin='0,0']")).toHaveCount(1);
1837-
await expect(page.locator("#statusLog")).toHaveValue(/OK Viewport centered at origin 0,0; zoom 100% preserved\./);
1845+
await expect(page.locator("#objectVectorStudioV2CenterDotButton")).toHaveAttribute("aria-pressed", "true");
1846+
await expect(page.locator("#statusLog")).toHaveValue(/OK Center dot visible\./);
18381847
await expect(page.locator("#objectVectorStudioV2JsonDetails")).toContainText('"name": "Asteroids Ship"');
18391848
await expect(page.locator("#objectVectorStudioV2JsonDetails")).toContainText('"selectedShape": null');
18401849
await expect(page.locator("#objectVectorStudioV2JsonDetails")).not.toContainText('"palette"');
@@ -2366,6 +2375,14 @@ test.describe("Workspace Manager V2 bootstrap", () => {
23662375
const originAfterApply = await page.evaluate(() => window.__objectVectorStudioV2App.selectedShape().transform.origin);
23672376
expect(originAfterApply).toEqual({ x: 2, y: -3 });
23682377
await expect(page.locator("#statusLog")).toHaveValue(/OK Updated shape row 0 origin\/pivot to 2, -3\./);
2378+
const pivotMarkerAfterOrigin = await page.locator("#objectVectorStudioV2RenderSurface [data-pivot-origin='0']").evaluate((pivot) => {
2379+
const [horizontal, vertical] = Array.from(pivot.querySelectorAll("line"));
2380+
return {
2381+
x: Number(vertical.getAttribute("x1")),
2382+
y: Number(horizontal.getAttribute("y1"))
2383+
};
2384+
});
2385+
expect(pivotMarkerAfterOrigin).toEqual({ x: 120, y: 70 });
23692386

23702387
await page.locator("#objectVectorStudioV2ScaleInput").fill("0");
23712388
await expect(page.locator("#objectVectorStudioV2ScaleInput")).toHaveAttribute("aria-invalid", "true");
@@ -2521,25 +2538,25 @@ test.describe("Workspace Manager V2 bootstrap", () => {
25212538
await expect(page.locator("#objectVectorStudioV2RenderSurface")).toHaveAttribute("viewBox", "-1454.545 -1000 2909.091 2000");
25222539
await page.locator("#objectVectorStudioV2PanRightButton").click();
25232540
await expect(page.locator("#objectVectorStudioV2RenderSurface")).toHaveAttribute("viewBox", "-1434.545 -1000 2909.091 2000");
2524-
await expect(page.locator("#objectVectorStudioV2CoordinateDisplay")).toHaveText("Origin: 2, 0 | Canvas 0,0 centered | Zoom 110%");
2541+
await expect(page.locator("#objectVectorStudioV2CoordinateDisplay")).toHaveText("Origin: 2, 0 | Canvas origin -2,0 from center | Zoom 110%");
25252542
await page.locator("#objectVectorStudioV2ResetViewButton").click();
25262543
await expect(page.locator("#objectVectorStudioV2RenderSurface")).toHaveAttribute("viewBox", "-1600 -1100 3200 2200");
2527-
await expect(page.locator("#objectVectorStudioV2CoordinateDisplay")).toHaveText("Origin: 0, 0 | Canvas 0,0 centered | Zoom 100%");
2544+
await expect(page.locator("#objectVectorStudioV2CoordinateDisplay")).toHaveText("Origin: 0, 0 | Canvas origin 0,0 centered | Zoom 100%");
25282545
await expect(page.locator("#statusLog")).toHaveValue(/OK Viewport reset to 100% at origin 0,0\./);
25292546
await page.evaluate(() => {
25302547
window.__objectVectorStudioV2App.viewport.zoom = 0.095;
25312548
window.__objectVectorStudioV2App.updateViewport();
25322549
});
2533-
await expect(page.locator("#objectVectorStudioV2CoordinateDisplay")).toHaveText("Origin: 0, 0 | Canvas 0,0 centered | Zoom 100%");
2550+
await expect(page.locator("#objectVectorStudioV2CoordinateDisplay")).toHaveText("Origin: 0, 0 | Canvas origin 0,0 centered | Zoom 100%");
25342551
await page.evaluate(() => {
25352552
window.__objectVectorStudioV2App.zoomViewport(2.5);
25362553
});
2537-
await expect(page.locator("#objectVectorStudioV2CoordinateDisplay")).toHaveText("Origin: 0, 0 | Canvas 0,0 centered | Zoom 500%");
2554+
await expect(page.locator("#objectVectorStudioV2CoordinateDisplay")).toHaveText("Origin: 0, 0 | Canvas origin 0,0 centered | Zoom 500%");
25382555
await expect(page.locator("#objectVectorStudioV2RenderSurface")).toHaveAttribute("viewBox", "-320 -220 640 440");
25392556
await page.evaluate(() => {
25402557
window.__objectVectorStudioV2App.zoomViewport(0);
25412558
});
2542-
await expect(page.locator("#objectVectorStudioV2CoordinateDisplay")).toHaveText("Origin: 0, 0 | Canvas 0,0 centered | Zoom 10%");
2559+
await expect(page.locator("#objectVectorStudioV2CoordinateDisplay")).toHaveText("Origin: 0, 0 | Canvas origin 0,0 centered | Zoom 10%");
25432560
await expect(page.locator("#objectVectorStudioV2RenderSurface")).toHaveAttribute("viewBox", "-16000 -11000 32000 22000");
25442561
await page.locator("#objectVectorStudioV2ResetViewButton").click();
25452562
await expect(page.locator("#objectVectorStudioV2RenderSurface")).toHaveAttribute("viewBox", "-1600 -1100 3200 2200");
@@ -3017,18 +3034,33 @@ test.describe("Workspace Manager V2 bootstrap", () => {
30173034
viewBox: "-640 -440 1280 880"
30183035
});
30193036

3037+
const beforePanShapeState = await page.evaluate(() => {
3038+
const shape = window.__objectVectorStudioV2App.selectedShape();
3039+
return {
3040+
geometry: JSON.stringify(shape.geometry),
3041+
transform: JSON.stringify(shape.transform || null)
3042+
};
3043+
});
30203044
await page.locator("#objectVectorStudioV2PanRightButton").click();
30213045
await expect(page.locator("#objectVectorStudioV2RenderSurface")).toHaveAttribute("viewBox", "-620 -440 1280 880");
3022-
await expect(page.locator("#objectVectorStudioV2CoordinateDisplay")).toHaveText("Origin: 2, 0 | Canvas 0,0 centered | Zoom 250%");
3046+
await expect(page.locator("#objectVectorStudioV2CoordinateDisplay")).toHaveText("Origin: 2, 0 | Canvas origin -2,0 from center | Zoom 250%");
30233047
await page.locator("#objectVectorStudioV2PanLeftButton").click();
30243048
await expect(page.locator("#objectVectorStudioV2RenderSurface")).toHaveAttribute("viewBox", "-640 -440 1280 880");
3025-
await expect(page.locator("#objectVectorStudioV2CoordinateDisplay")).toHaveText("Origin: 0, 0 | Canvas 0,0 centered | Zoom 250%");
3049+
await expect(page.locator("#objectVectorStudioV2CoordinateDisplay")).toHaveText("Origin: 0, 0 | Canvas origin 0,0 centered | Zoom 250%");
30263050
await page.locator("#objectVectorStudioV2PanUpButton").click();
30273051
await expect(page.locator("#objectVectorStudioV2RenderSurface")).toHaveAttribute("viewBox", "-640 -460 1280 880");
3028-
await expect(page.locator("#objectVectorStudioV2CoordinateDisplay")).toHaveText("Origin: 0, -2 | Canvas 0,0 centered | Zoom 250%");
3052+
await expect(page.locator("#objectVectorStudioV2CoordinateDisplay")).toHaveText("Origin: 0, -2 | Canvas origin 0,2 from center | Zoom 250%");
30293053
await page.locator("#objectVectorStudioV2PanDownButton").click();
30303054
await expect(page.locator("#objectVectorStudioV2RenderSurface")).toHaveAttribute("viewBox", "-640 -440 1280 880");
3031-
await expect(page.locator("#objectVectorStudioV2CoordinateDisplay")).toHaveText("Origin: 0, 0 | Canvas 0,0 centered | Zoom 250%");
3055+
await expect(page.locator("#objectVectorStudioV2CoordinateDisplay")).toHaveText("Origin: 0, 0 | Canvas origin 0,0 centered | Zoom 250%");
3056+
const afterPanShapeState = await page.evaluate(() => {
3057+
const shape = window.__objectVectorStudioV2App.selectedShape();
3058+
return {
3059+
geometry: JSON.stringify(shape.geometry),
3060+
transform: JSON.stringify(shape.transform || null)
3061+
};
3062+
});
3063+
expect(afterPanShapeState).toEqual(beforePanShapeState);
30323064
const examplePointerScreen = await page.locator("#objectVectorStudioV2RenderSurface").evaluate((surface) => {
30333065
const point = surface.createSVGPoint();
30343066
point.x = -140;
@@ -6589,6 +6621,10 @@ test.describe("Workspace Manager V2 bootstrap", () => {
65896621
ship: "object.asteroids.ship"
65906622
}
65916623
};
6624+
const invalidUnknownGameDataManifest = structuredClone(manifest);
6625+
invalidUnknownGameDataManifest.game.gameData.debugUnknown = true;
6626+
const invalidUnknownWorkspaceManifest = structuredClone(manifest.game.workspace);
6627+
invalidUnknownWorkspaceManifest.debugUnknown = true;
65926628
return {
65936629
gameManifestValidation: await service.validateGameManifest(manifest),
65946630
hasGameData: Boolean(manifest.game?.gameData),
@@ -6598,6 +6634,8 @@ test.describe("Workspace Manager V2 bootstrap", () => {
65986634
runtimeWorkspaceValidation: await service.validateGameManifest(invalidRuntimeWorkspaceManifest),
65996635
rootDocumentKind: manifest.documentKind || "",
66006636
schema: manifest.schema,
6637+
unknownGameDataValidation: await service.validateGameManifest(invalidUnknownGameDataManifest),
6638+
unknownWorkspaceValidation: await service.validateGeneratedManifest(invalidUnknownWorkspaceManifest),
66016639
workspaceDocumentKind: manifest.game?.workspace?.documentKind,
66026640
workspaceValidation: await service.validateGeneratedManifest(manifest.game.workspace)
66036641
};
@@ -6610,9 +6648,14 @@ test.describe("Workspace Manager V2 bootstrap", () => {
66106648
objectVectorRuntimeValidation: { ok: false },
66116649
rootDocumentKind: "",
66126650
schema: "html-js-gaming.game-manifest",
6651+
unknownGameDataValidation: { ok: false },
6652+
unknownWorkspaceValidation: { ok: false },
66136653
workspaceDocumentKind: "workspace-manifest",
66146654
workspaceValidation: { ok: true }
66156655
});
6656+
expect(asteroidsGameManifestShape.objectVectorRuntimeValidation.message).toContain("root.game.gameData.objectVectorRuntime is not allowed");
6657+
expect(asteroidsGameManifestShape.unknownGameDataValidation.message).toContain("root.game.gameData.debugUnknown is not allowed");
6658+
expect(asteroidsGameManifestShape.unknownWorkspaceValidation.message).toContain("root.debugUnknown is not allowed");
66166659
expect(asteroidsGameManifestShape.runtimeWorkspaceValidation.ok).toBe(false);
66176660
expect(asteroidsGameManifestShape.runtimeWorkspaceValidation.message).toContain("runtime data must not depend on editor/tool workspace state");
66186661

0 commit comments

Comments
 (0)