Skip to content

Commit e9bf668

Browse files
author
DavidQ
committed
Fix Collision Inspector V2 template layout and require manifest screen dimensions - PR_26139_006-collision-inspector-template-layout-fixes
1 parent 07a942a commit e9bf668

10 files changed

Lines changed: 371 additions & 74 deletions
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# PR_26139_006-collision-inspector-template-layout-fixes Report
2+
3+
## Summary
4+
5+
Updated Collision Inspector V2 to follow the Tool Starter header/menu structure, tightened the layout, and removed hidden screen-dimension defaults. The inspector now hard fails with visible/actionable messaging when a manifest is missing `root.screen.width` or `root.screen.height`.
6+
7+
Playwright impacted: Yes.
8+
9+
## Scope Completed
10+
11+
- Replaced Collision Inspector-specific header/menu markup with Tool Starter-compatible structure:
12+
- `tool-starter__header`
13+
- `tool-starter__menu tool-starter__tool__menu`
14+
- `tool-starter__menu tool-starter__workspace__menu`
15+
- Kept shared-tool and Workspace Manager launched manifest behavior.
16+
- Removed the Collision Inspector canvas screen-size default path before manifest acceptance.
17+
- Removed `DEFAULT_SCREEN_DIMENSIONS` from Asteroids scene setup.
18+
- Made missing manifest screen dimensions a hard failure with:
19+
- visible manifest summary error
20+
- `Manifest Error` result badge
21+
- actionable collision summary JSON
22+
- failure log entry
23+
- no fallback render
24+
- Moved `collisionSummary` into its own accordion.
25+
- Limited the summary accordion body to available space and kept it scrollable.
26+
- Kept the canvas below the Zoom control and preserved manifest-driven aspect ratio.
27+
28+
## Guardrails
29+
30+
- Collision Inspector V2 still uses the shared engine collision path.
31+
- No hardcoded Asteroids geometry was added.
32+
- No fallback/default vector maps were added.
33+
- Workspace-launched manifest loading remains automatic.
34+
- Existing intentional ship flame flicker and asteroid scale tuning were not changed.
35+
36+
## Validation
37+
38+
PASS:
39+
40+
- `npm run build:manifest`
41+
- Passed. This repo does not define a plain `npm run build` script.
42+
- `node --check` on touched Collision Inspector V2 modules, touched Asteroids scene module, and touched tests.
43+
- `npx playwright test tests/playwright/tools/CollisionInspectorV2.spec.mjs --project=playwright --workers=1 --reporter=list`
44+
- 3 passed.
45+
- Validated template header/menu structure, canvas below Zoom, aspect ratio, summary accordion placement/scrolling, missing screen-dimension hard failure, and workspace-launched manifest behavior.
46+
- `node -e "import('./tests/games/AsteroidsManifestScreenDimensions.test.mjs').then(({ run }) => run())"`
47+
- Passed. Confirmed manifest screen dimensions reach runtime/scene and missing dimensions fail before engine creation.
48+
- `npx playwright test tests/playwright/tools/WorkspaceManagerV2.spec.mjs --project=playwright --workers=1 --reporter=list --grep "uses header lifecycle controls and launches tools from fixed Workspace Manager V2 tiles"`
49+
- 1 passed.
50+
- `git diff --check`
51+
- Passed with line-ending warnings only.
52+
- Advisory Playwright V8 coverage report was regenerated by the Collision Inspector V2 Playwright run.
53+
54+
Skipped by request:
55+
56+
- Full regression was not run.
57+
- Full samples smoke test was not run.
58+
59+
## Manual Validation
60+
61+
1. Open `tools/collision-inspector-v2/index.html?manifestPath=/games/Asteroids/game.manifest.json`.
62+
2. Confirm the Tool Starter-style header/menu appears, the Game Manifest JSON picker is in the tool menu, and the canvas sits below Zoom.
63+
3. Confirm Collision Summary is its own scrollable accordion on the right.
64+
4. Launch Collision Inspector V2 from Workspace Manager V2 and confirm it auto-loads the workspace manifest and shows the workspace return menu.
65+
5. Try a manifest without `screen.width` and `screen.height`; expected result is a visible `Manifest Error`, no fallback geometry render, and a log entry naming the missing root fields.

games/Asteroids/game/AsteroidsGameScene.js

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ const SCORE_TWO_X = 824;
3232
const LIFE_SPACING = 22;
3333
const PAUSE_OVERLAY_COLOR = 'rgba(2, 6, 23, 0.58)';
3434
const INITIALS_OVERLAY_COLOR = 'rgba(1, 6, 19, 0.62)';
35-
const DEFAULT_SCREEN_DIMENSIONS = Object.freeze({ width: 960, height: 720 });
3635
const ATTRACT_INPUT_CODES = [
3736
'Digit1',
3837
'Digit2',
@@ -61,16 +60,18 @@ function logSceneBootStage(stage, details = null) {
6160
}
6261
}
6362

64-
function positiveInteger(value, fallback) {
63+
function positiveInteger(value) {
6564
const parsed = Math.floor(Number(value));
66-
return Number.isNaN(parsed) || Math.abs(parsed) === Infinity || parsed <= 0 ? fallback : parsed;
65+
return Number.isNaN(parsed) || Math.abs(parsed) === Infinity || parsed <= 0 ? 0 : parsed;
6766
}
6867

6968
function screenDimensionsFromOptions(options) {
70-
return {
71-
width: positiveInteger(options?.screenDimensions?.width, DEFAULT_SCREEN_DIMENSIONS.width),
72-
height: positiveInteger(options?.screenDimensions?.height, DEFAULT_SCREEN_DIMENSIONS.height),
73-
};
69+
const width = positiveInteger(options?.screenDimensions?.width);
70+
const height = positiveInteger(options?.screenDimensions?.height);
71+
if (!width || !height) {
72+
throw new Error('AsteroidsGameScene requires screenDimensions.width and screenDimensions.height from game.manifest.json.');
73+
}
74+
return { width, height };
7475
}
7576

7677
function getBeatInterval(asteroidCount) {

tests/games/AsteroidsManifestScreenDimensions.test.mjs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,4 +69,30 @@ export async function run() {
6969
assert.equal(engineOptions.width, manifestPayload.screen.width);
7070
assert.equal(engineOptions.height, manifestPayload.screen.height);
7171
assert.deepEqual(sceneOptions.screenDimensions, manifestPayload.screen);
72+
73+
const missingScreenManifest = { ...manifestPayload };
74+
delete missingScreenManifest.screen;
75+
let invalidEngineCreated = false;
76+
await assert.rejects(
77+
() => bootAsteroids({
78+
documentRef: {
79+
body: { style: {} },
80+
documentElement: { style: {} },
81+
getElementById(id) {
82+
return id === 'game' ? createCanvas() : null;
83+
},
84+
},
85+
EngineClass: class {
86+
constructor() {
87+
invalidEngineCreated = true;
88+
}
89+
},
90+
InputServiceClass: class {},
91+
ObjectVectorRuntimeClass: class {},
92+
SceneClass: class {},
93+
manifestPayload: missingScreenManifest,
94+
}),
95+
/requires screen\.width and screen\.height/
96+
);
97+
assert.equal(invalidEngineCreated, false);
7298
}

tests/playwright/tools/CollisionInspectorV2.spec.mjs

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ import { join } from "node:path";
44
import { startRepoServer } from "../../helpers/playwrightRepoServer.mjs";
55
import { workspaceV2CoverageReporter as coverageReporter } from "../../helpers/workspaceV2CoverageReporter.mjs";
66

7+
test.afterAll(async () => {
8+
await coverageReporter.writeReport();
9+
});
10+
711
async function dragCanvasPoint(page, from, to) {
812
await page.locator("#collisionCanvas").evaluate((canvas, points) => {
913
const rect = canvas.getBoundingClientRect();
@@ -39,6 +43,9 @@ test.describe("Collision Inspector V2", () => {
3943
try {
4044
await page.goto(`${server.baseUrl}/tools/collision-inspector-v2/index.html?manifestPath=/games/Asteroids/game.manifest.json`, { waitUntil: "networkidle" });
4145
await expect(page.locator("body.tools-platform-tool-page[data-tool-id='collision-inspector-v2']")).toBeVisible();
46+
await expect(page.locator(".tool-starter__header[data-tool-starter-header]")).toBeVisible();
47+
await expect(page.locator("nav.tool-starter__menu.tool-starter__tool__menu")).toBeVisible();
48+
await expect(page.locator("nav.tool-starter__menu.tool-starter__workspace__menu")).toBeHidden();
4249
await expect(page.locator("#collisionModeSelect option")).toHaveText(["Bounds", "Vector", "Pixel/Sprite", "Hybrid"]);
4350
await expect(page.locator("#loadAsteroidsManifestButton")).toHaveCount(0);
4451
await expect(page.locator("#collisionManifestInput")).toBeVisible();
@@ -53,6 +60,18 @@ test.describe("Collision Inspector V2", () => {
5360
return rect.width / rect.height;
5461
});
5562
expect(Math.abs(aspectRatio - (960 / 720))).toBeLessThan(0.02);
63+
const canvasLayout = await page.evaluate(() => {
64+
const zoom = document.querySelector(".collision-inspector-v2__zoom-row").getBoundingClientRect();
65+
const canvas = document.querySelector("#collisionCanvas").getBoundingClientRect();
66+
return {
67+
canvasBelowZoom: canvas.top >= zoom.bottom,
68+
canvasHeight: canvas.height,
69+
canvasWidth: canvas.width
70+
};
71+
});
72+
expect(canvasLayout.canvasBelowZoom).toBe(true);
73+
expect(canvasLayout.canvasWidth).toBeGreaterThan(100);
74+
expect(canvasLayout.canvasHeight).toBeGreaterThan(100);
5675
await expect(page.locator("#objectASelect")).toContainText("Asteroids Ship");
5776
await expect(page.locator("#objectBSelect")).toContainText("Large Asteroid");
5877
await page.locator("#objectASelect").selectOption("object.asteroids.ship");
@@ -66,6 +85,10 @@ test.describe("Collision Inspector V2", () => {
6685
await expect(page.locator("#collisionSummary")).toContainText('"objectOrigins"');
6786
await expect(page.locator("#collisionSummary")).toContainText('"recommendedMode": "vector"');
6887
await expect(page.locator("#collisionSummary")).toContainText('"transformedPoints"');
88+
await expect(page.locator("#resultContent #collisionSummary")).toHaveCount(0);
89+
await expect(page.locator("#collisionSummaryContent #collisionSummary")).toBeVisible();
90+
const summaryOverflow = await page.locator("#collisionSummaryContent").evaluate((element) => getComputedStyle(element).overflowY);
91+
expect(["auto", "scroll"]).toContain(summaryOverflow);
6992

7093
await page.locator("#objectBRotationInput").fill("180");
7194
await expect(page.locator("#rotationState")).toHaveText("A 0 / B 180");
@@ -128,6 +151,47 @@ test.describe("Collision Inspector V2", () => {
128151
}
129152
});
130153

154+
test("hard fails when manifest screen dimensions are missing", async ({ page }) => {
155+
const server = await startRepoServer();
156+
const pageErrors = [];
157+
page.on("pageerror", (error) => {
158+
pageErrors.push(error.message);
159+
});
160+
161+
await coverageReporter.start(page);
162+
try {
163+
await page.route((url) => url.pathname === "/missing-screen-manifest.json", async (route) => {
164+
await route.fulfill({
165+
status: 200,
166+
contentType: "application/json",
167+
body: JSON.stringify({
168+
game: { name: "Missing Screen Dimensions" },
169+
tools: {
170+
"object-vector-studio-v2": {
171+
objects: []
172+
}
173+
}
174+
})
175+
});
176+
});
177+
await page.goto(`${server.baseUrl}/tools/collision-inspector-v2/index.html?manifestPath=/missing-screen-manifest.json`, { waitUntil: "networkidle" });
178+
179+
await expect(page.locator("#manifestSummary")).toContainText("root.screen.width");
180+
await expect(page.locator("#manifestSummary")).toContainText("root.screen.height");
181+
await expect(page.locator("#collisionResultBadge")).toHaveText("Manifest Error");
182+
await expect(page.locator("#collisionSummary")).toContainText('"required": "root.screen.width and root.screen.height"');
183+
await expect(page.locator("#collisionLog")).toHaveValue(/FAIL URL manifest path/);
184+
await expect(page.locator("#objectASelect option")).toHaveCount(0);
185+
await expect(page.locator("#objectBSelect option")).toHaveCount(0);
186+
await expect(page.locator("#collisionCanvas")).toHaveAttribute("width", "1");
187+
await expect(page.locator("#collisionCanvas")).toHaveAttribute("height", "1");
188+
expect(pageErrors).toEqual([]);
189+
} finally {
190+
await coverageReporter.stop(page);
191+
await server.close();
192+
}
193+
});
194+
131195
test("loads Asteroids Object Vector objects from a Workspace Manager V2 manifest context", async ({ page }) => {
132196
const server = await startRepoServer();
133197
const pageErrors = [];
@@ -157,8 +221,8 @@ test.describe("Collision Inspector V2", () => {
157221
}, workspaceContext);
158222
await page.goto(`${server.baseUrl}/tools/collision-inspector-v2/index.html?launch=workspace&fromTool=workspace-manager-v2&hostContextId=workspace-manager-v2-collision-test`, { waitUntil: "networkidle" });
159223

160-
await expect(page.locator('[data-launch-mode-nav="workspace"]')).toBeVisible();
161-
await expect(page.locator('[data-launch-mode-nav="tool"]')).toBeHidden();
224+
await expect(page.locator("nav.tool-starter__workspace__menu[data-launch-mode-nav='workspace']")).toBeVisible();
225+
await expect(page.locator("nav.tool-starter__tool__menu[data-launch-mode-nav='tool']")).toBeHidden();
162226
await expect(page.locator("#collisionManifestInput")).toBeDisabled();
163227
await expect(page.locator("#manifestSummary")).toContainText("Asteroids Workspace Manager V2 Context");
164228
await expect(page.locator("#manifestSummary")).toContainText("screen 960x720");

tools/collision-inspector-v2/index.html

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@
1212
</head>
1313
<body class="tools-platform-tool-page tools-platform-surface hub-page-tools" data-tool-id="collision-inspector-v2">
1414
<details class="is-collapsible" open>
15-
<summary class="is-collapsible__summary" data-collision-inspector-summary>Hide Header and Details</summary>
15+
<summary class="is-collapsible__summary" data-tool-starter-summary>Hide Header and Details</summary>
1616
<div class="is-collapsible__content">
1717
<div id="shared-theme-header"></div>
18-
<div class="collision-inspector-v2__header" data-collision-inspector-header>
19-
<section class="tools-platform-frame collision-inspector-v2__local-shell-frame">
18+
<div class="tool-starter__header" data-tool-starter-header="">
19+
<section class="tools-platform-frame tool-starter__local-shell-frame collision-inspector-v2__local-shell-frame">
2020
<div class="tools-platform-frame__accordion-content">
2121
<div class="tools-platform-frame__accordion-summary">
2222
<div class="tools-platform-frame__summary-copy">
@@ -29,22 +29,24 @@ <h2 class="tools-platform-frame__eyebrow">Shared Manifest Collision Tool</h2>
2929
</div>
3030
<div class="tools-platform-frame__topline">
3131
<p class="tools-platform-frame__description">Load a game manifest, select two Object Vector V2 objects, and drag them through live collision checks.</p>
32-
<div class="collision-inspector-v2__header-actions">
33-
<label class="collision-inspector-v2__field collision-inspector-v2__header-file" for="collisionManifestInput" data-launch-mode-nav="tool">
34-
<span>Game Manifest JSON</span>
35-
<input id="collisionManifestInput" type="file" accept=".json,application/json">
36-
</label>
37-
<nav class="collision-inspector-v2__workspace-menu" aria-label="Workspace actions" data-launch-mode-nav="workspace" hidden>
38-
<button id="returnToWorkspaceButton" type="button">Return to Workspace</button>
39-
</nav>
40-
</div>
4132
</div>
4233
</div>
4334
</section>
4435
</div>
4536
</div>
4637
</details>
4738

39+
<nav class="tool-starter__menu tool-starter__tool__menu" aria-label="Tool actions" data-launch-mode-nav="tool">
40+
<label class="collision-inspector-v2__field collision-inspector-v2__menu-file" for="collisionManifestInput">
41+
<span>Game Manifest JSON</span>
42+
<input id="collisionManifestInput" type="file" accept=".json,application/json">
43+
</label>
44+
</nav>
45+
46+
<nav class="tool-starter__menu tool-starter__workspace__menu" aria-label="Workspace actions" data-launch-mode-nav="workspace" hidden>
47+
<button id="returnToWorkspaceButton" type="button">Return to Workspace</button>
48+
</nav>
49+
4850
<main class="collision-inspector-v2 app-shell" data-tool-id="collision-inspector-v2">
4951
<aside class="collision-inspector-v2__panel collision-inspector-v2__panel--left" aria-label="Collision inputs">
5052
<section class="accordion-v2 collision-inspector-v2__accordion is-open" data-accordion-v2-open="true">
@@ -109,7 +111,9 @@ <h2 class="tools-platform-frame__eyebrow">Shared Manifest Collision Tool</h2>
109111
</label>
110112
<output id="zoomState" for="collisionZoomInput">1x</output>
111113
</div>
112-
<canvas id="collisionCanvas" width="960" height="640" aria-label="Collision movement simulation"></canvas>
114+
<div class="collision-inspector-v2__canvas-viewport">
115+
<canvas id="collisionCanvas" width="1" height="1" aria-label="Collision movement simulation"></canvas>
116+
</div>
113117
<div class="collision-inspector-v2__legend" aria-label="Collision overlay legend">
114118
<span>Object A</span>
115119
<span>Object B</span>
@@ -153,11 +157,20 @@ <h2 class="tools-platform-frame__eyebrow">Shared Manifest Collision Tool</h2>
153157
<dd id="pointsState">waiting</dd>
154158
</div>
155159
</dl>
160+
</div>
161+
</section>
162+
163+
<section class="accordion-v2 collision-inspector-v2__accordion collision-inspector-v2__accordion--summary is-open" data-accordion-v2-open="true">
164+
<button class="accordion-v2__header" type="button" aria-expanded="true" aria-controls="collisionSummaryContent">
165+
<span>Collision Summary</span>
166+
<span class="accordion-v2__icon" aria-hidden="true">+</span>
167+
</button>
168+
<div id="collisionSummaryContent" class="accordion-v2__content collision-inspector-v2__summary-content">
156169
<pre id="collisionSummary" class="collision-inspector-v2__output">{}</pre>
157170
</div>
158171
</section>
159172

160-
<section class="accordion-v2 collision-inspector-v2__accordion is-open" data-accordion-v2-open="true">
173+
<section class="accordion-v2 collision-inspector-v2__accordion collision-inspector-v2__accordion--logs is-open" data-accordion-v2-open="true">
161174
<div class="accordion-v2__header collision-inspector-v2__status-header" aria-expanded="true" aria-controls="collisionLogContent">
162175
<span>Collision Logs</span>
163176
<div class="collision-inspector-v2__status-actions">

0 commit comments

Comments
 (0)