Skip to content

Commit 4ca0110

Browse files
author
DavidQ
committed
Reset Collision Inspector V2 layout to template patterns and normalize object scale - PR_26139_008-collision-inspector-template-reset
1 parent 04b6b0c commit 4ca0110

6 files changed

Lines changed: 433 additions & 178 deletions

File tree

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# PR_26139_008-collision-inspector-template-reset
2+
3+
## Scope
4+
- Rebuilt Collision Inspector V2 shell/body styling around First-Class Tool Starter V2 class patterns.
5+
- Added Tool Starter panel, accordion, field, and output classes to the Collision Inspector V2 markup.
6+
- Kept the shared engine collision path and manifest-only Object Vector Studio V2 geometry usage unchanged.
7+
- Kept the existing A/B rotation wiring and made the controls first-class, visible Tool Starter fields.
8+
- Formatted Origins output as fixed three-decimal, separate-line text.
9+
10+
## Scale Normalization Rule
11+
- Collision Inspector V2 uses the loaded manifest `screen.width` and `screen.height` as the canvas intrinsic size and CSS size at zoom `1x`.
12+
- At Collision Inspector zoom `1x`, one manifest/world unit equals one canvas CSS pixel. Diagnostic zoom is applied only as a canvas transform for inspection and does not mutate object geometry.
13+
- Asteroids runtime uses the same manifest screen dimensions and the shared Object Vector runtime renderer defaults object `scale` to `1` unless a runtime instance intentionally supplies a scale.
14+
- Object Vector Studio V2 keeps its authoring grid/view scale separate from runtime geometry: its work surface uses `OBJECT_PREVIEW_DRAWING_SCALE = GRID_STEP` for editing, then maps pointer positions back by dividing by that drawing scale. Export/runtime object geometry remains manifest-unit `objects[].shapes[]` data.
15+
16+
## Layout Decisions
17+
- Removed the conflicting custom app-shell sizing rules and replaced them with Tool Starter V2 shell, panel, accordion, field, menu, and output patterns.
18+
- Left panel open accordions use `flex: 1 1 0`, so Manifest and Collision Pair share available vertical space evenly.
19+
- Right panel open accordions use the same rule, so Live Result, Collision Summary, and Collision Logs share available vertical space evenly.
20+
- Collision Summary and Live Result remain vertically scrollable within their accordion bodies.
21+
- The manifest-size canvas is hosted in a scrollable viewport instead of being scaled down by CSS.
22+
23+
## Validation
24+
- PASS: `node --check tools/collision-inspector-v2/js/CollisionInspectorV2Controls.js`
25+
- PASS: `node --check tools/collision-inspector-v2/js/CollisionInspectorV2Renderer.js`
26+
- PASS: `node --check tests/playwright/tools/CollisionInspectorV2.spec.mjs`
27+
- PASS: `npx playwright test tests/playwright/tools/CollisionInspectorV2.spec.mjs --project=playwright --workers=1 --reporter=list`
28+
- Validates Tool Starter layout classes.
29+
- Validates left/right accordion equal-space layout.
30+
- Validates A and B rotation inputs.
31+
- Validates Origins fixed 3-decimal line formatting.
32+
- Validates Collision Inspector canvas 1:1 CSS scale and Asteroids runtime canvas 1:1 scale.
33+
- Validates Object Vector Studio V2 authoring scale remains separate from runtime manifest-unit scale.
34+
- PASS: `npm run build:manifest`
35+
- This repo does not define a plain `npm run build`; `build:manifest` is the available build script.
36+
- Removed generated `docs/build` output after validation.
37+
- PASS: `git diff --check`
38+
- Only CRLF working-copy warnings were reported.
39+
- FAIL: `npm run test:workspace-v2`
40+
- 54 passed, 2 failed.
41+
- Failure 1: `validates optional Text to Speech V2 schema contract through Workspace Manager V2 schema` expected `activeContext.tools` to contain `text2speech-V2`.
42+
- Failure 2: `tracks Object Vector Studio V2 dirty state through persisted edits and save outcomes` expected a Workspace Manager schema-failure save log, but the save path succeeded.
43+
- No tracked files were changed by this validation run.
44+
45+
## Full Samples Smoke Test
46+
- Skipped. This PR is limited to Collision Inspector V2 template/layout, rotate controls, and diagnostic scale display. It does not broadly change shared runtime sample loading.
47+
48+
## Changed Files
49+
- `tools/collision-inspector-v2/index.html`
50+
- `tools/collision-inspector-v2/styles/collisionInspectorV2.css`
51+
- `tools/collision-inspector-v2/js/CollisionInspectorV2Controls.js`
52+
- `tools/collision-inspector-v2/js/CollisionInspectorV2Renderer.js`
53+
- `tests/playwright/tools/CollisionInspectorV2.spec.mjs`

tests/playwright/tools/CollisionInspectorV2.spec.mjs

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,16 @@ test.describe("Collision Inspector V2", () => {
4646
await expect(page.locator(".tool-starter__header[data-tool-starter-header]")).toBeVisible();
4747
await expect(page.locator("nav.tool-starter__menu.tool-starter__tool__menu")).toBeVisible();
4848
await expect(page.locator("nav.tool-starter__menu.tool-starter__workspace__menu")).toBeHidden();
49+
await expect(page.locator("main.tool-starter.collision-inspector-v2.app-shell")).toBeVisible();
50+
await expect(page.locator(".tool-starter__panel.tool-starter__panel--left.collision-inspector-v2__panel--left")).toBeVisible();
51+
await expect(page.locator(".tool-starter__panel.tool-starter__panel--center.collision-inspector-v2__panel--center")).toBeVisible();
52+
await expect(page.locator(".tool-starter__panel.tool-starter__panel--right.collision-inspector-v2__panel--right")).toBeVisible();
53+
await expect(page.locator(".tool-starter__accordion.collision-inspector-v2__accordion")).toHaveCount(6);
4954
await expect(page.locator("#collisionModeSelect option")).toHaveText(["Bounds", "Vector", "Pixel/Sprite", "Hybrid"]);
5055
await expect(page.locator("#loadAsteroidsManifestButton")).toHaveCount(0);
5156
await expect(page.locator("#collisionManifestInput")).toBeVisible();
57+
await expect(page.locator("label[for='objectARotationInput']")).toContainText("Object A Rotate");
58+
await expect(page.locator("label[for='objectBRotationInput']")).toContainText("Object B Rotate");
5259

5360
await expect(page.locator("#manifestSummary")).toContainText("Asteroids");
5461
await expect(page.locator("#manifestSummary")).toContainText("7 objects loaded");
@@ -70,16 +77,48 @@ test.describe("Collision Inspector V2", () => {
7077
};
7178
});
7279
expect(canvasLayout.canvasBelowZoom).toBe(true);
73-
expect(canvasLayout.canvasWidth).toBeGreaterThan(100);
74-
expect(canvasLayout.canvasHeight).toBeGreaterThan(100);
80+
expect(Math.abs(canvasLayout.canvasWidth - 960)).toBeLessThanOrEqual(1);
81+
expect(Math.abs(canvasLayout.canvasHeight - 720)).toBeLessThanOrEqual(1);
82+
const collisionInspectorScale = await page.locator("#collisionCanvas").evaluate((canvas) => {
83+
const rect = canvas.getBoundingClientRect();
84+
return {
85+
scaleX: rect.width / canvas.width,
86+
scaleY: rect.height / canvas.height
87+
};
88+
});
89+
expect(collisionInspectorScale.scaleX).toBeCloseTo(1, 2);
90+
expect(collisionInspectorScale.scaleY).toBeCloseTo(1, 2);
91+
const runtimeRenderSource = await readFile(join(server.repoRoot, "src", "engine", "rendering", "ObjectVectorRuntimeAssetService.js"), "utf8");
92+
const objectVectorStudioSource = await readFile(join(server.repoRoot, "tools", "object-vector-studio-v2", "js", "ToolStarterApp.js"), "utf8");
93+
expect(runtimeRenderSource).toContain("const scale = Number.isFinite(options.scale) ? options.scale : 1;");
94+
expect(runtimeRenderSource).toContain("context.scale(scale, scale);");
95+
expect(objectVectorStudioSource).toContain("const OBJECT_PREVIEW_DRAWING_SCALE = GRID_STEP;");
96+
expect(objectVectorStudioSource).toContain("point.x / OBJECT_PREVIEW_DRAWING_SCALE");
97+
const asteroidsPage = await page.context().newPage();
98+
try {
99+
await asteroidsPage.goto(`${server.baseUrl}/games/Asteroids/index.html`, { waitUntil: "domcontentloaded" });
100+
await expect(asteroidsPage.locator("#game")).toHaveAttribute("width", "960");
101+
await expect(asteroidsPage.locator("#game")).toHaveAttribute("height", "720");
102+
const asteroidsScale = await asteroidsPage.locator("#game").evaluate((canvas) => {
103+
const rect = canvas.getBoundingClientRect();
104+
return {
105+
scaleX: rect.width / canvas.width,
106+
scaleY: rect.height / canvas.height
107+
};
108+
});
109+
expect(asteroidsScale.scaleX).toBeCloseTo(1, 2);
110+
expect(asteroidsScale.scaleY).toBeCloseTo(1, 2);
111+
} finally {
112+
await asteroidsPage.close();
113+
}
75114
await expect(page.locator("#objectASelect")).toContainText("Asteroids Ship");
76115
await expect(page.locator("#objectBSelect")).toContainText("Large Asteroid");
77116
await page.locator("#objectASelect").selectOption("object.asteroids.ship");
78117
await page.locator("#objectBSelect").selectOption("object.asteroids.large-asteroid");
79118
await expect(page.locator("#collisionModeSelect")).toHaveValue("vector");
80119
await expect(page.locator("#collisionResultBadge")).toHaveText("No Collision");
81120
await expect(page.locator("#overlapState")).toHaveText("false");
82-
await expect(page.locator("#originState")).toContainText("A");
121+
await expect(page.locator("#originState")).toHaveText("Origins:\nA 360.000,320.000\nB 500.000,320.000");
83122
await expect(page.locator("#rotationState")).toHaveText("A 0 / B 0");
84123
await expect(page.locator("#collisionSummary")).toContainText('"enginePath": "src/engine/collision/objectVector.js"');
85124
await expect(page.locator("#collisionSummary")).toContainText('"objectOrigins"');
@@ -94,17 +133,21 @@ test.describe("Collision Inspector V2", () => {
94133
const resultOverflow = await page.locator("#resultContent").evaluate((element) => getComputedStyle(element).overflowY);
95134
expect(["auto", "scroll"]).toContain(resultOverflow);
96135
const outputLayout = await page.evaluate(() => {
136+
const manifest = document.querySelector(".collision-inspector-v2__panel--left .collision-inspector-v2__accordion:nth-of-type(1)").getBoundingClientRect();
137+
const pair = document.querySelector(".collision-inspector-v2__panel--left .collision-inspector-v2__accordion:nth-of-type(2)").getBoundingClientRect();
97138
const result = document.querySelector(".collision-inspector-v2__accordion--result").getBoundingClientRect();
98139
const summary = document.querySelector(".collision-inspector-v2__accordion--summary").getBoundingClientRect();
99140
const logs = document.querySelector(".collision-inspector-v2__accordion--logs").getBoundingClientRect();
100141
const heights = [result.height, summary.height, logs.height];
101142
return {
143+
inputMaxDelta: Math.abs(manifest.height - pair.height),
102144
maxDelta: Math.max(...heights) - Math.min(...heights),
103145
resultWidth: result.width,
104146
summaryWidth: summary.width,
105147
logsWidth: logs.width
106148
};
107149
});
150+
expect(outputLayout.inputMaxDelta).toBeLessThanOrEqual(6);
108151
expect(outputLayout.maxDelta).toBeLessThanOrEqual(6);
109152
expect(Math.abs(outputLayout.resultWidth - outputLayout.summaryWidth)).toBeLessThanOrEqual(1);
110153
expect(Math.abs(outputLayout.summaryWidth - outputLayout.logsWidth)).toBeLessThanOrEqual(1);
@@ -114,7 +157,11 @@ test.describe("Collision Inspector V2", () => {
114157
await page.locator("button[aria-controls='collisionLogContent']").click();
115158
await expect(page.locator("#collisionLogContent")).toBeVisible();
116159

160+
await page.locator("#objectARotationInput").fill("45");
161+
await expect(page.locator("#rotationState")).toHaveText("A 45 / B 0");
117162
await page.locator("#objectBRotationInput").fill("180");
163+
await expect(page.locator("#rotationState")).toHaveText("A 45 / B 180");
164+
await page.locator("#objectARotationInput").fill("0");
118165
await expect(page.locator("#rotationState")).toHaveText("A 0 / B 180");
119166

120167
await dragCanvasPoint(page, { x: 500, y: 320 }, { x: 360, y: 320 });

0 commit comments

Comments
 (0)