Skip to content

Commit 19b6496

Browse files
author
DavidQ
committed
Flatten Object Vector Studio roles into object tags and restore Asteroids game discovery - PR_26133_121-object-vector-role-flatten-and-game-discovery
1 parent d0310cf commit 19b6496

18 files changed

Lines changed: 173 additions & 315 deletions
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# PR_26133_121-object-vector-role-flatten-and-game-discovery
2+
3+
## Summary
4+
- Flattened Asteroids Object Vector role ownership out of `vectorMaps.objectVectorRoles`.
5+
- Kept `objects[].shapes[]` as the Object Vector Studio V2 geometry SSoT.
6+
- Fixed Workspace Manager V2 game manifest validation so Asteroids is discovered in the Active Game dropdown.
7+
8+
## Cleanup Decisions
9+
- Removed `object-vector-studio-v2.vectorMaps.objectVectorRoles` from `games/Asteroids/game.manifest.json`.
10+
- Kept the existing role tags on `objects[]` as the manifest-owned runtime role source.
11+
- Updated Asteroids role resolution to derive required runtime roles from `objects[].tags`.
12+
- Kept concrete object IDs for direct object identity checks such as `object.asteroids.bullet`, `object.asteroids.ship`, and attract object IDs.
13+
- Updated the Asteroids manifest loader to reject legacy `vectorMaps.objectVectorRoles` with an actionable error.
14+
- Removed Object Vector Studio V2 schema definitions for `objectVectorRoles` and role bindings.
15+
- Updated Object Vector Studio V2 delete cleanup so it no longer edits a removed role map.
16+
- Updated Workspace Manager V2's game manifest schema reference from the removed `vectorMapDocument` definition to `objectVectorShapeDocument`.
17+
- Updated Workspace Manager V2 summary fallback wording from old vector-map `vectors` to current `vectorMaps.shapes`.
18+
19+
## Validation
20+
- Playwright impacted: Yes. This PR changes Workspace Manager V2 game discovery and Object Vector manifest loading.
21+
- PASS targeted Workspace Manager V2 game dropdown validation:
22+
`npx playwright test tests/playwright/tools/WorkspaceManagerV2.spec.mjs --project=playwright --workers=1 --reporter=list -g "discovers Active Game options from selected repo manifests"`
23+
- PASS direct Workspace Manager V2 Asteroids manifest validation.
24+
- PASS direct Workspace Manager V2 game discovery includes Asteroids.
25+
- PASS targeted Asteroids tag-flatten manifest-load validation.
26+
- PASS targeted Object Vector Studio V2 tag-flatten manifest-load validation.
27+
- PASS `node -e "import('./tests/tools/ObjectVectorStudioV2DeleteCleanup.test.mjs').then(async (m) => { await m.run(); console.log('PASS ObjectVectorStudioV2DeleteCleanup'); })"`
28+
- PASS `node -e "import('./tests/games/AsteroidsValidation.test.mjs').then(async (m) => { await m.run(); console.log('PASS AsteroidsValidation'); })"`
29+
- PASS `node -e "import('./tests/games/AsteroidsVectorTransforms.test.mjs').then(async (m) => { await m.run(); console.log('PASS AsteroidsVectorTransforms'); })"`
30+
- PASS `node -e "import('./tests/games/AsteroidsAssetReferenceAdoption.test.mjs').then(async (m) => { await m.run(); console.log('PASS AsteroidsAssetReferenceAdoption'); })"`
31+
- PASS `node -e "import('./tests/games/AsteroidsPlatformDemo.test.mjs').then(async (m) => { await m.run(); console.log('PASS AsteroidsPlatformDemo'); })"`
32+
- PASS `node -e "import('./tests/games/AsteroidsPresentation.test.mjs').then(async (m) => { await m.run(); console.log('PASS AsteroidsPresentation'); })"`
33+
- PASS `node --check` for changed runtime, tool, schema-service, targeted test, and targeted Playwright files.
34+
- PASS JSON parse for changed manifest/schema JSON files.
35+
- PASS `git diff --check`.
36+
37+
## Skipped
38+
- Full Workspace Manager V2 suite via `npm run test:workspace-v2` skipped in favor of the PR-requested targeted dropdown validation.
39+
- Full regression/full samples smoke test skipped per PR instructions.
40+
41+
## Artifacts
42+
- Review diff: `docs/dev/reports/codex_review.diff`
43+
- Changed files report: `docs/dev/reports/codex_changed_files.txt`
44+
- Delta ZIP: `tmp/PR_26133_121-object-vector-role-flatten-and-game-discovery_delta.zip`

games/Asteroids/game.manifest.json

Lines changed: 0 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -217,57 +217,6 @@
217217
"version": 1,
218218
"name": "Asteroids Object Vector Shape Roles",
219219
"source": "object-vector-studio-v2",
220-
"objectVectorRoles": {
221-
"bullet": {
222-
"objectId": "object.asteroids.bullet",
223-
"tags": [
224-
"projectile",
225-
"bullet"
226-
]
227-
},
228-
"ship": {
229-
"objectId": "object.asteroids.ship",
230-
"tags": [
231-
"player",
232-
"ship"
233-
]
234-
},
235-
"asteroidLarge": {
236-
"objectId": "object.asteroids.large-asteroid",
237-
"tags": [
238-
"asteroid",
239-
"large"
240-
]
241-
},
242-
"asteroidMedium": {
243-
"objectId": "object.asteroids.medium-asteroid",
244-
"tags": [
245-
"asteroid",
246-
"medium"
247-
]
248-
},
249-
"asteroidSmall": {
250-
"objectId": "object.asteroids.small-asteroid",
251-
"tags": [
252-
"asteroid",
253-
"small"
254-
]
255-
},
256-
"ufoLarge": {
257-
"objectId": "object.asteroids.large-ufo",
258-
"tags": [
259-
"ufo",
260-
"large"
261-
]
262-
},
263-
"ufoSmall": {
264-
"objectId": "object.asteroids.small-ufo",
265-
"tags": [
266-
"ufo",
267-
"small"
268-
]
269-
}
270-
},
271220
"shapes": []
272221
},
273222
"objects": [

games/Asteroids/game/AsteroidsGameScene.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,14 +89,13 @@ export default class AsteroidsGameScene extends Scene {
8989
this.objectVectorAssets = options.objectVectorAssets || null;
9090
this.objectVectorRuntime = options.objectVectorRuntime || null;
9191
this.vectorMaps = options.vectorMaps || null;
92-
if (!this.vectorMaps?.objectVectorMapsById || !this.vectorMaps?.objectVectorRoles) {
92+
if (!this.vectorMaps?.objectVectorMapsById || !this.vectorMaps?.objectsByRole) {
9393
const message = 'Asteroids Object Vector manifest validation failed: object geometry was not loaded from game.manifest.json.';
9494
console.error(message);
9595
throw new Error(message);
9696
}
9797
this.objectVectorRuntimeObjectValidation = this.objectVectorAssets
9898
? validateAsteroidsRuntimeObjectRoles([...this.objectVectorAssets.objectsById.values()], {
99-
roleBindings: this.vectorMaps.objectVectorRoles,
10099
logger: this.objectVectorRuntime,
101100
})
102101
: { errors: [], objectsByRole: {}, ok: false, warnings: [] };
@@ -110,7 +109,6 @@ export default class AsteroidsGameScene extends Scene {
110109
}
111110
this.asteroidGeometryProfiles = options.asteroidGeometryProfiles
112111
|| createAsteroidGeometryProfilesFromObjectVectorAssets(this.objectVectorAssets, {
113-
roleBindings: this.vectorMaps.objectVectorRoles,
114112
logger: this.objectVectorRuntime,
115113
});
116114
this.objectVectorPlaybackMs = 0;
@@ -860,7 +858,7 @@ export default class AsteroidsGameScene extends Scene {
860858
}
861859

862860
objectVectorRoleOptions(roleId) {
863-
return runtimeObjectRoleOptions(roleId, this.vectorMaps.objectVectorRoles);
861+
return runtimeObjectRoleOptions(roleId);
864862
}
865863

866864
drawLives(renderer, centerX, y, lives) {

games/Asteroids/game/asteroidsObjectVectorRoles.js

Lines changed: 31 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,34 @@
11
export const ASTEROIDS_RUNTIME_OBJECT_ROLES = Object.freeze({
22
bullet: Object.freeze({
33
label: 'Bullet',
4+
tags: Object.freeze(['projectile', 'bullet']),
45
}),
56
ship: Object.freeze({
67
label: 'Ship',
8+
tags: Object.freeze(['player', 'ship']),
79
}),
810
asteroidLarge: Object.freeze({
911
label: 'Large Asteroid',
1012
size: 3,
13+
tags: Object.freeze(['asteroid', 'large']),
1114
}),
1215
asteroidMedium: Object.freeze({
1316
label: 'Medium Asteroid',
1417
size: 2,
18+
tags: Object.freeze(['asteroid', 'medium']),
1519
}),
1620
asteroidSmall: Object.freeze({
1721
label: 'Small Asteroid',
1822
size: 1,
23+
tags: Object.freeze(['asteroid', 'small']),
1924
}),
2025
ufoLarge: Object.freeze({
2126
label: 'Large UFO',
27+
tags: Object.freeze(['ufo', 'large']),
2228
}),
2329
ufoSmall: Object.freeze({
2430
label: 'Small UFO',
31+
tags: Object.freeze(['ufo', 'small']),
2532
}),
2633
});
2734

@@ -83,33 +90,29 @@ function logResolution(logger, level, message, details = {}) {
8390
logger[method]?.(message, details);
8491
}
8592

86-
function roleBindingFor(roleBindings, roleId) {
87-
return roleBindings && typeof roleBindings === 'object' && !Array.isArray(roleBindings)
88-
? roleBindings[roleId] || null
89-
: null;
93+
function roleTags(roleId) {
94+
return normalizeTags(ASTEROIDS_RUNTIME_OBJECT_ROLES[roleId]?.tags);
9095
}
9196

92-
export function runtimeObjectRoleOptions(roleId, roleBindings = {}) {
97+
export function runtimeObjectRoleOptions(roleId) {
9398
const role = ASTEROIDS_RUNTIME_OBJECT_ROLES[roleId] || null;
94-
const binding = roleBindingFor(roleBindings, roleId);
9599
if (!role) {
96100
return {
97101
objectId: '',
98-
requireManifestBinding: true,
102+
requireManifestBinding: false,
99103
runtimeRole: roleId,
100104
tags: [],
101105
};
102106
}
103107
return {
104-
objectId: normalizeString(binding?.objectId),
105-
requireManifestBinding: true,
108+
objectId: '',
109+
requireManifestBinding: false,
106110
runtimeRole: roleId,
107-
tags: normalizeTags(binding?.tags),
111+
tags: roleTags(roleId),
108112
};
109113
}
110114

111115
export function resolveAsteroidsObjectVectorRole(objects, roleId, {
112-
roleBindings = {},
113116
logger = null,
114117
} = {}) {
115118
const role = ASTEROIDS_RUNTIME_OBJECT_ROLES[roleId] || null;
@@ -119,93 +122,74 @@ export function resolveAsteroidsObjectVectorRole(objects, roleId, {
119122
return null;
120123
}
121124

122-
const binding = roleBindingFor(roleBindings, roleId);
123-
const targetObjectId = normalizeString(binding?.objectId);
124-
const targetTags = normalizeTags(binding?.tags);
125-
const targetObject = targetObjectId
126-
? objectList.find((object) => object?.id === targetObjectId) || null
127-
: null;
125+
const targetTags = roleTags(roleId);
128126
const candidates = objectList
129127
.map((object, index) => ({
130128
index,
131129
object,
132130
oldSignal: oldObjectSignal(object),
133131
}))
134132
.filter((candidate) => candidate.object && (!targetTags.length || objectHasTags(candidate.object, targetTags)));
133+
const oldCandidates = candidates.filter((candidate) => candidate.oldSignal);
134+
const activeCandidates = candidates.filter((candidate) => !candidate.oldSignal);
135135

136-
if (!binding) {
136+
if (!targetTags.length) {
137137
logResolution(
138138
logger,
139139
'FAIL',
140-
`Asteroids Object Vector runtime role ${roleId} requires a manifest objectVectorRoles.${roleId} binding.`,
140+
`Asteroids Object Vector runtime role ${roleId} does not define required object tags.`,
141141
{
142-
candidates: candidates.map(candidateLabel),
143142
objectCount: objectList.length,
144143
}
145144
);
146145
return null;
147146
}
148147

149-
if (!targetObjectId) {
148+
if (!candidates.length) {
150149
logResolution(
151150
logger,
152151
'FAIL',
153-
`Asteroids Object Vector runtime role ${roleId} manifest binding is missing objectId.`,
152+
`Asteroids Object Vector runtime role ${roleId} could not resolve an object from objects[].tags [${targetTags.join(', ')}].`,
154153
{
155-
candidates: candidates.map(candidateLabel),
156154
objectCount: objectList.length,
155+
targetTags,
157156
}
158157
);
159158
return null;
160159
}
161160

162-
if (!targetObject) {
161+
if (oldCandidates.length) {
163162
logResolution(
164163
logger,
165164
'FAIL',
166-
`Asteroids Object Vector runtime role ${roleId} manifest binding requires object ${targetObjectId} in root.tools.object-vector-studio-v2.objects.`,
165+
`Asteroids Object Vector runtime role ${roleId} matches old/legacy object tag candidates [${targetTags.join(', ')}]; remove deprecated duplicates or retag them outside the active role.`,
167166
{
168-
candidates: candidates.map(candidateLabel),
167+
candidates: oldCandidates.map(candidateLabel),
169168
objectCount: objectList.length,
170-
targetObjectId,
171-
}
172-
);
173-
return null;
174-
}
175-
176-
if (targetTags.length && !objectHasTags(targetObject, targetTags)) {
177-
logResolution(
178-
logger,
179-
'FAIL',
180-
`Asteroids Object Vector runtime role ${roleId} manifest binding ${targetObjectId} is missing required tags [${targetTags.join(', ')}].`,
181-
{
182-
candidates: candidates.map(candidateLabel),
183-
objectTags: normalizeTags(targetObject.tags),
184-
targetObjectId,
185169
targetTags,
186170
}
187171
);
188172
return null;
189173
}
190174

191-
if (oldObjectSignal(targetObject)) {
175+
if (activeCandidates.length > 1) {
192176
logResolution(
193177
logger,
194178
'FAIL',
195-
`Asteroids Object Vector runtime role ${roleId} manifest binding ${targetObjectId} is marked old/legacy; keep runtime object IDs on active Object Vector objects.`,
179+
`Asteroids Object Vector runtime role ${roleId} matched multiple active objects from objects[].tags [${targetTags.join(', ')}].`,
196180
{
197-
candidates: candidates.map(candidateLabel),
198-
targetObjectId,
181+
candidates: activeCandidates.map(candidateLabel),
182+
objectCount: objectList.length,
183+
targetTags,
199184
}
200185
);
201186
return null;
202187
}
203188

204-
return targetObject;
189+
return activeCandidates[0].object;
205190
}
206191

207192
export function validateAsteroidsRuntimeObjectRoles(objects, {
208-
roleBindings = {},
209193
logger = null,
210194
} = {}) {
211195
const errors = [];
@@ -225,7 +209,6 @@ export function validateAsteroidsRuntimeObjectRoles(objects, {
225209

226210
ASTEROIDS_REQUIRED_RUNTIME_OBJECT_ROLE_IDS.forEach((roleId) => {
227211
const object = resolveAsteroidsObjectVectorRole(objects, roleId, {
228-
roleBindings,
229212
logger: collectingLogger,
230213
});
231214
if (object) {

0 commit comments

Comments
 (0)