Skip to content

Commit b62ddc2

Browse files
author
DavidQ
committed
Fix Object Vector Studio V2 object delete cleanup and remove Asteroids manifest leftovers - PR_26133_119-object-vector-delete-and-manifest-cleanup
1 parent 1dd7cba commit b62ddc2

7 files changed

Lines changed: 199 additions & 23 deletions

File tree

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# PR_26133_119-object-vector-delete-and-manifest-cleanup Report
2+
3+
## Scope
4+
- Fixed Object Vector Studio V2 object delete cleanup for `vectorMaps.objectVectorRoles`.
5+
- Kept role bindings strict when present: each role entry still requires `objectId` and `tags`.
6+
- Made role names generic in the Object Vector Studio V2 schema so delete can remove a role entry without requiring Asteroids-specific role names in every tool payload.
7+
- Added schema reference validation so present `objectVectorRoles.*.objectId` values must reference existing `objects[]`.
8+
- Removed the empty `text2speech-V2` payload from `games/Asteroids/game.manifest.json`.
9+
- Kept Asteroids object geometry as the active source and verified required runtime vector/object IDs still resolve.
10+
11+
## Delete Behavior
12+
- Before: deleting an object recursively removed matching `objectId` fields, which could leave `vectorMaps.objectVectorRoles.<role>` entries without required `objectId`.
13+
- After: deleting an object removes matching `vectorMaps.objectVectorRoles` entries before generic reference cleanup runs.
14+
- After: schema validation catches role bindings that keep an `objectId` pointing at a missing object.
15+
- After: deleting a role-bound object can produce a schema-valid Object Vector payload without leaving partial role entries.
16+
17+
## Asteroids Manifest Cleanup
18+
- Removed `tools["text2speech-V2"]: []` from the Asteroids manifest because it is an unrelated empty tool payload.
19+
- Verified every Asteroids `objectVectorRoles` entry references an existing Object Vector object.
20+
- Verified `ASTEROIDS_REQUIRED_VECTOR_MAP_IDS` and `ASTEROIDS_REQUIRED_OBJECT_VECTOR_ROLE_IDS` still resolve.
21+
22+
## Changed Files
23+
- `games/Asteroids/game.manifest.json`
24+
- `tests/games/AsteroidsValidation.test.mjs`
25+
- `tests/tools/ObjectVectorStudioV2DeleteCleanup.test.mjs`
26+
- `tools/object-vector-studio-v2/js/ToolStarterApp.js`
27+
- `tools/object-vector-studio-v2/js/services/ObjectVectorStudioV2SchemaService.js`
28+
- `tools/schemas/tools/object-vector-studio-v2.schema.json`
29+
30+
## Validation
31+
- PASS: `node --check tools/object-vector-studio-v2/js/ToolStarterApp.js`
32+
- PASS: `node --check tools/object-vector-studio-v2/js/services/ObjectVectorStudioV2SchemaService.js`
33+
- PASS: `node --check tests/tools/ObjectVectorStudioV2DeleteCleanup.test.mjs`
34+
- PASS: `node --check tests/games/AsteroidsValidation.test.mjs`
35+
- PASS: JSON parse for `tools/schemas/tools/object-vector-studio-v2.schema.json` and `games/Asteroids/game.manifest.json`
36+
- PASS: `node -e "import('./tests/tools/ObjectVectorStudioV2DeleteCleanup.test.mjs').then(async (m) => { await m.run(); console.log('PASS ObjectVectorStudioV2DeleteCleanup'); })"`
37+
- PASS: targeted Asteroids manifest-load cleanup validation:
38+
- no `text2speech-V2` payload remains
39+
- all `objectVectorRoles` entries keep required `objectId`
40+
- all role `objectId` values reference existing objects
41+
- Object Vector Studio V2 schema validation passes
42+
- Asteroids manifest vector loading passes
43+
- required runtime vector/object IDs resolve
44+
- PASS: `node -e "import('./tests/games/AsteroidsValidation.test.mjs').then(async (m) => { await m.run(); console.log('PASS AsteroidsValidation'); })"`
45+
- PASS: `git diff --check` with line-ending warnings only.
46+
47+
## Skipped Validation
48+
- Skipped full regression and full samples smoke tests as requested.
49+
- Skipped `npm run test:workspace-v2`; PR-specific validation requested targeted Object Vector Studio V2 delete validation and targeted Asteroids manifest-load validation.
50+
- Playwright impacted: No UI layout, browser interaction, capture path, or Workspace Manager lifecycle flow changed; the changed behavior is payload cleanup/schema validation and was covered with targeted Node validation.
51+
52+
## Manual Validation
53+
- Open Object Vector Studio V2 with a payload containing `vectorMaps.objectVectorRoles` entries.
54+
- Delete an object referenced by a role binding.
55+
- Expected: the matching role entry is removed, no role entry remains with a missing `objectId`, and Copy/Export schema validation still succeeds.
56+
- Open Asteroids after applying the manifest cleanup.
57+
- Expected: Asteroids manifest loading succeeds, ship/asteroid/UFO Object Vector geometry still resolves, and no empty `text2speech-V2` payload is present in the Asteroids manifest.
58+

games/Asteroids/game.manifest.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1217,7 +1217,6 @@
12171217
}
12181218
}
12191219
]
1220-
},
1221-
"text2speech-V2": []
1220+
}
12221221
}
12231222
}

tests/games/AsteroidsValidation.test.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ export async function run() {
156156
const worldOptions = { asteroidGeometryProfiles, vectorMaps };
157157
const manifestPayload = loadAsteroidsManifest();
158158
assert.equal(manifestPayload.tools['vector-map-editor'], undefined);
159+
assert.equal(Object.hasOwn(manifestPayload.tools, 'text2speech-V2'), false);
159160
const objectVectorPayload = manifestPayload.tools['object-vector-studio-v2'];
160161
const manifestVectorIds = objectVectorPayload.vectorMaps.vectors.map((vector) => vector.id);
161162
assert.equal(manifestVectorIds.includes(ASTEROIDS_VECTOR_MAP_IDS.bullet), true);
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import assert from "node:assert/strict";
2+
import fs from "node:fs";
3+
import { fileURLToPath } from "node:url";
4+
import { ToolStarterApp } from "../../tools/object-vector-studio-v2/js/ToolStarterApp.js";
5+
import { ObjectVectorStudioV2SchemaService } from "../../tools/object-vector-studio-v2/js/services/ObjectVectorStudioV2SchemaService.js";
6+
7+
function createJsonResponse(payload) {
8+
return {
9+
ok: true,
10+
status: 200,
11+
async json() {
12+
return payload;
13+
}
14+
};
15+
}
16+
17+
function createLocalFetch() {
18+
return async (url) => {
19+
const targetPath = fileURLToPath(url);
20+
return createJsonResponse(JSON.parse(fs.readFileSync(targetPath, "utf8")));
21+
};
22+
}
23+
24+
function clone(value) {
25+
return JSON.parse(JSON.stringify(value));
26+
}
27+
28+
function createPayload() {
29+
return {
30+
version: 1,
31+
toolId: "object-vector-studio-v2",
32+
name: "Delete Cleanup Object Set",
33+
vectorMaps: {
34+
schema: "html-js-gaming.test-vector-map",
35+
version: 1,
36+
name: "Delete Cleanup Roles",
37+
source: "object-vector-studio-v2",
38+
objectVectorRoles: {
39+
keepRole: {
40+
objectId: "object.test.keep",
41+
tags: ["keep"]
42+
},
43+
tempRole: {
44+
objectId: "object.test.temp",
45+
tags: ["temp"]
46+
}
47+
},
48+
vectors: []
49+
},
50+
objects: [
51+
{
52+
id: "object.test.keep",
53+
name: "Keep",
54+
tags: ["keep"],
55+
shapes: []
56+
},
57+
{
58+
id: "object.test.temp",
59+
name: "Temp",
60+
tags: ["temp"],
61+
shapes: []
62+
}
63+
]
64+
};
65+
}
66+
67+
async function createSchemaService() {
68+
const service = new ObjectVectorStudioV2SchemaService({
69+
fetchRef: createLocalFetch(),
70+
schemaUrl: new URL("../../tools/schemas/tools/object-vector-studio-v2.schema.json", import.meta.url)
71+
});
72+
await service.loadSchema();
73+
return service;
74+
}
75+
76+
export async function run() {
77+
const service = await createSchemaService();
78+
const payload = createPayload();
79+
assert.deepEqual(service.validatePayload(payload).errors, []);
80+
81+
const missingObjectIdPayload = createPayload();
82+
delete missingObjectIdPayload.vectorMaps.objectVectorRoles.tempRole.objectId;
83+
const missingObjectIdValidation = service.validatePayload(missingObjectIdPayload);
84+
assert.equal(missingObjectIdValidation.ok, false);
85+
assert.equal(
86+
missingObjectIdValidation.errors.some((message) => message === "root.vectorMaps.objectVectorRoles.tempRole.objectId is required."),
87+
true,
88+
);
89+
90+
const staleReferencePayload = createPayload();
91+
staleReferencePayload.objects = staleReferencePayload.objects.filter((object) => object.id !== "object.test.temp");
92+
const staleReferenceValidation = service.validatePayload(staleReferencePayload);
93+
assert.equal(staleReferenceValidation.ok, false);
94+
assert.equal(
95+
staleReferenceValidation.errors.some((message) => message === "root.vectorMaps.objectVectorRoles.tempRole.objectId object.test.temp must reference an existing object."),
96+
true,
97+
);
98+
99+
const deletePayload = clone(payload);
100+
deletePayload.objects = deletePayload.objects.filter((object) => object.id !== "object.test.temp");
101+
const app = Object.create(ToolStarterApp.prototype);
102+
app.removeDeletedObjectReferences(deletePayload, "object.test.temp");
103+
assert.equal(Object.hasOwn(deletePayload.vectorMaps.objectVectorRoles, "tempRole"), false);
104+
assert.equal(deletePayload.vectorMaps.objectVectorRoles.keepRole.objectId, "object.test.keep");
105+
assert.equal(
106+
Object.values(deletePayload.vectorMaps.objectVectorRoles).some((binding) => !Object.hasOwn(binding, "objectId")),
107+
false,
108+
);
109+
assert.deepEqual(service.validatePayload(deletePayload).errors, []);
110+
}

tools/object-vector-studio-v2/js/ToolStarterApp.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6258,9 +6258,22 @@ export class ToolStarterApp {
62586258
delete object.baseObjectId;
62596259
}
62606260
});
6261+
this.removeDeletedObjectVectorRoleBindings(payload, objectId);
62616262
this.removeDirectReferenceEntries(payload, "objectId", objectId);
62626263
}
62636264

6265+
removeDeletedObjectVectorRoleBindings(payload, objectId) {
6266+
const objectVectorRoles = payload?.vectorMaps?.objectVectorRoles;
6267+
if (!isPlainObject(objectVectorRoles)) {
6268+
return;
6269+
}
6270+
Object.entries(objectVectorRoles).forEach(([roleId, binding]) => {
6271+
if (isPlainObject(binding) && binding.objectId === objectId) {
6272+
delete objectVectorRoles[roleId];
6273+
}
6274+
});
6275+
}
6276+
62646277
removeDirectReferenceEntries(value, referenceKey, targetId) {
62656278
if (!value || typeof value !== "object") {
62666279
return;

tools/object-vector-studio-v2/js/services/ObjectVectorStudioV2SchemaService.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,13 @@ export class ObjectVectorStudioV2SchemaService {
226226
});
227227
});
228228

229+
if (isPlainObject(payload?.vectorMaps?.objectVectorRoles)) {
230+
Object.entries(payload.vectorMaps.objectVectorRoles).forEach(([roleId, binding]) => {
231+
if (isPlainObject(binding) && !objectsById.has(binding.objectId)) {
232+
errors.push(`root.vectorMaps.objectVectorRoles.${roleId}.objectId ${binding.objectId} must reference an existing object.`);
233+
}
234+
});
235+
}
229236
}
230237

231238
validateInheritanceChain(object, objectsById, path, errors) {
@@ -472,6 +479,13 @@ export class ObjectVectorStudioV2SchemaService {
472479
this.validateValue(childSchema, value[key], `${path}.${key}`, errors);
473480
}
474481
});
482+
if (isPlainObject(schema.additionalProperties)) {
483+
Object.entries(value).forEach(([key, child]) => {
484+
if (!Object.prototype.hasOwnProperty.call(properties, key)) {
485+
this.validateValue(schema.additionalProperties, child, `${path}.${key}`, errors);
486+
}
487+
});
488+
}
475489
if (schema.additionalProperties === false) {
476490
Object.keys(value).forEach((key) => {
477491
if (!Object.prototype.hasOwnProperty.call(properties, key)) {

tools/schemas/tools/object-vector-studio-v2.schema.json

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -119,27 +119,8 @@
119119
},
120120
"objectVectorRoles": {
121121
"type": "object",
122-
"additionalProperties": false,
123-
"required": ["ship", "asteroidLarge", "asteroidMedium", "asteroidSmall", "ufoLarge", "ufoSmall"],
124-
"properties": {
125-
"ship": {
126-
"$ref": "#/$defs/objectVectorRoleBinding"
127-
},
128-
"asteroidLarge": {
129-
"$ref": "#/$defs/objectVectorRoleBinding"
130-
},
131-
"asteroidMedium": {
132-
"$ref": "#/$defs/objectVectorRoleBinding"
133-
},
134-
"asteroidSmall": {
135-
"$ref": "#/$defs/objectVectorRoleBinding"
136-
},
137-
"ufoLarge": {
138-
"$ref": "#/$defs/objectVectorRoleBinding"
139-
},
140-
"ufoSmall": {
141-
"$ref": "#/$defs/objectVectorRoleBinding"
142-
}
122+
"additionalProperties": {
123+
"$ref": "#/$defs/objectVectorRoleBinding"
143124
}
144125
},
145126
"objectVectorRoleBinding": {

0 commit comments

Comments
 (0)