Skip to content

Commit a40ec7a

Browse files
author
DavidQ
committed
Resolve Asteroids runtime objects from manifest metadata instead of fragile object IDs - PR_26133_034-asteroids-runtime-object-resolution-by-tags
1 parent edce756 commit a40ec7a

12 files changed

Lines changed: 657 additions & 64 deletions
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# PR_26133_034 Asteroids Runtime Object Resolution Report
2+
3+
Date: 2026-05-14
4+
5+
## Scope
6+
7+
This change makes Asteroids runtime Object Vector lookup role/tag driven so duplicated or recreated objects can keep rendering even when their object ids change.
8+
9+
## Runtime Resolution
10+
11+
- Added Asteroids runtime role metadata for ship, large/medium/small asteroids, and large/small UFOs.
12+
- Asteroids rendering now passes `runtimeRole` and required tags into the Object Vector runtime instead of selecting ship/UFO/asteroids with scene-local hardcoded object ids.
13+
- Asteroid collision profiles now resolve asteroid objects by role tags before extracting polygon geometry.
14+
- Explicit `game.gameData.objectVectorRuntime.objectIds` values are preserved as optional binding hints, but tag matches win when a binding points at a stale or old object.
15+
- Multiple tag matches are ranked by non-old candidates first, then newest manifest order, and an actionable warning lists candidates and the selected object.
16+
17+
## Required Tags
18+
19+
- Large asteroid: `["asteroid", "large"]`
20+
- Medium asteroid: `["asteroid", "medium"]`
21+
- Small asteroid: `["asteroid", "small"]`
22+
- Ship: `["player", "ship"]`
23+
- Large UFO: `["ufo", "large"]`
24+
- Small UFO: `["ufo", "small"]`
25+
26+
## Manifest Data
27+
28+
- Current Asteroids manifest object metadata already contains the required tags for all runtime roles.
29+
- No renamed old Medium Asteroid entry is present in the current manifest data.
30+
- Targeted tests construct a recreated Medium Asteroid with id `object.asteroids.asteroid.medium-recreated` and an old stale medium candidate to verify the active tag-correct object is selected.
31+
- No vector-map-editor fallback geometry was added or restored.
32+
33+
## Validation
34+
35+
- PASS - `npm run test:workspace-v2` -> 49 passed.
36+
- PASS - targeted Asteroids Asset Reference Adoption test for recreated/stale medium asteroid collision profile selection.
37+
- PASS - targeted Asteroids Platform Demo test for Object Vector runtime tag resolution and stale explicit binding warning.
38+
- PASS - targeted Asteroids collision timing/stress checks.
39+
- PASS - targeted Asteroids validation smoke.
40+
- PASS - targeted Asteroids manifest Object Vector runtime validation loaded 6 objects and resolved medium asteroid by tags.

docs/dev/reports/playwright_v8_coverage_report.md

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

3-
Task: PR_26133_033-asteroids-collision-and-object-vector-schema-defaults
3+
Task: PR_26133_034-asteroids-runtime-object-resolution-by-tags
44
Date: 2026-05-14
55

66
## Result
@@ -26,10 +26,7 @@ PASS - Coverage reporting was generated during `npm run test:workspace-v2`.
2626
## Changed Runtime JS Coverage
2727

2828
```text
29-
(83%) tools/object-vector-studio-v2/js/bootstrap.js - executed lines 109/109; executed functions 5/6
30-
(93%) tools/object-vector-studio-v2/js/ToolStarterApp.js - executed lines 4333/4333; executed functions 453/485
31-
(95%) tools/object-vector-studio-v2/js/services/ObjectVectorStudioV2SchemaService.js - executed lines 453/453; executed functions 55/58
32-
(98%) src/engine/rendering/ObjectVectorRuntimeAssetService.js - executed lines 915/915; executed functions 107/109
29+
(97%) src/engine/rendering/ObjectVectorRuntimeAssetService.js - executed lines 1056/1056; executed functions 123/127
3330
```
3431

3532
## Guardrail
@@ -40,4 +37,4 @@ PASS - Coverage reporting was generated during `npm run test:workspace-v2`.
4037

4138
## PR-Specific Note
4239

43-
The Workspace V2 run exercised Object Vector Studio V2 launch, schema loading, schema-driven shape creation, generated Asteroids object-vector payload validation, and Asteroids runtime object-vector loading. Coverage remains advisory only.
40+
The Workspace V2 run exercised Asteroids gameplay Object Vector runtime loading and rendering with role/tag cache diagnostics, plus Object Vector Studio V2 launch/save flows. Coverage remains advisory only.
Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
# PR_26133_033 Workspace V2 Playwright Results
1+
# PR_26133_034 Workspace V2 Playwright Results
22

3-
Task: PR_26133_033-asteroids-collision-and-object-vector-schema-defaults
3+
Task: PR_26133_034-asteroids-runtime-object-resolution-by-tags
44
Date: 2026-05-14
55

66
## Result
@@ -14,16 +14,17 @@ PASS - `npm run test:workspace-v2` completed successfully.
1414

1515
## PR-Specific Coverage
1616

17-
- Object Vector Studio V2 schema defaults are present in the game manifest schema and the standalone tool schema.
18-
- New rectangle creation was verified against schema-cloned geometry/style/transform defaults.
19-
- Workspace Manager V2 generated the Asteroids Object Vector payload without `assetLibrary` and with `objects[*].tags` present.
20-
- Launching Object Vector Studio V2 for Asteroids from Workspace Manager V2 loaded 6 objects and validated the payload.
21-
- Dirty-state and save validation still pass after the generated manifest cleanup.
17+
- Asteroids gameplay rendering now resolves Object Vector runtime objects through role/tag options for ship, UFO, and asteroid roles.
18+
- Workspace V2 runtime rendering test was updated to expect role-based cache diagnostics for ship and small UFO.
19+
- Object Vector Studio V2 still loads the Asteroids object payload from `games/Asteroids/game.manifest.json`.
20+
- Asteroids manifest runtime validation confirmed the current large, medium, and small asteroid objects expose the required tag metadata.
2221

2322
## Additional Validation
2423

25-
PASS - Targeted Asteroids collision checks covered ship/asteroid, bullet/asteroid, UFO/asteroid, UFO bullet/asteroid, ship bullet/UFO bullet crossfire, and ship/UFO crash cases.
24+
PASS - Targeted recreated Medium Asteroid resolution tests confirmed `["asteroid", "medium"]` tags select the recreated object even when an explicit stale medium object id is present.
25+
26+
PASS - Targeted Asteroids collision timing/stress checks still load asteroid collision profiles from Object Vector Studio V2 geometry.
2627

2728
PASS - Targeted Asteroids validation smoke completed.
2829

29-
PASS - Node schema validation reported `games/Asteroids/game.manifest.json` and the generated workspace manifest as schema-valid.
30+
PASS - Targeted Asteroids manifest Object Vector runtime validation loaded 6 objects and resolved the current medium asteroid by tags.

games/Asteroids/game.manifest.json

Lines changed: 102 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -490,8 +490,8 @@
490490
]
491491
},
492492
{
493-
"id": "object.asteroids.asteroid.medium",
494-
"name": "Medium Asteroid",
493+
"id": "object.asteroids.medium-asteroid-1",
494+
"name": "Medium Asteroid 1",
495495
"shapes": [
496496
{
497497
"tool": "polygon",
@@ -812,6 +812,106 @@
812812
"ufo",
813813
"small"
814814
]
815+
},
816+
{
817+
"id": "object.asteroids.medium-asteroid",
818+
"name": "Medium Asteroid",
819+
"shapes": [
820+
{
821+
"tool": "polygon",
822+
"order": 0,
823+
"visible": true,
824+
"locked": false,
825+
"geometry": {
826+
"points": [
827+
{
828+
"x": 10,
829+
"y": 40
830+
},
831+
{
832+
"x": 50,
833+
"y": 20
834+
},
835+
{
836+
"x": 45,
837+
"y": 5
838+
},
839+
{
840+
"x": 25,
841+
"y": -10
842+
},
843+
{
844+
"x": 50,
845+
"y": -35
846+
},
847+
{
848+
"x": 30,
849+
"y": -45
850+
},
851+
{
852+
"x": 10,
853+
"y": -38
854+
},
855+
{
856+
"x": -20,
857+
"y": -45
858+
},
859+
{
860+
"x": -43,
861+
"y": -18
862+
},
863+
{
864+
"x": -43,
865+
"y": 20
866+
},
867+
{
868+
"x": -25,
869+
"y": 20
870+
},
871+
{
872+
"x": -25,
873+
"y": 40
874+
}
875+
]
876+
},
877+
"style": {
878+
"fill": "transparent",
879+
"stroke": "#CBD5E1",
880+
"strokeWidth": 2,
881+
"fillOpacity": 1,
882+
"strokeOpacity": 1
883+
},
884+
"transform": {
885+
"x": 0,
886+
"y": 0,
887+
"rotation": 0,
888+
"scaleX": 1,
889+
"scaleY": 1,
890+
"origin": {
891+
"x": -1,
892+
"y": 1
893+
}
894+
}
895+
}
896+
],
897+
"states": [
898+
{
899+
"id": "active",
900+
"name": "Active",
901+
"frames": [
902+
{
903+
"id": "active-frame-1",
904+
"order": 0,
905+
"durationFrames": 1,
906+
"shapeOverrides": []
907+
}
908+
]
909+
}
910+
],
911+
"tags": [
912+
"asteroid",
913+
"large"
914+
]
815915
}
816916
]
817917
},

games/Asteroids/game/AsteroidsGameScene.js

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,11 @@ import ShipDebrisSystem from '../systems/ShipDebrisSystem.js';
1313
import AsteroidsAttractAdapter from './AsteroidsAttractAdapter.js';
1414
import AsteroidsHighScoreService from '../systems/AsteroidsHighScoreService.js';
1515
import AsteroidsInitialsEntry from '../systems/AsteroidsInitialsEntry.js';
16+
import { createAsteroidGeometryProfilesFromObjectVectorAssets } from './asteroidObjectGeometry.js';
1617
import {
17-
ASTEROID_OBJECT_VECTOR_OBJECT_IDS,
18-
createAsteroidGeometryProfilesFromObjectVectorAssets
19-
} from './asteroidObjectGeometry.js';
18+
ASTEROID_SIZE_RUNTIME_ROLES,
19+
runtimeObjectRoleOptions
20+
} from './asteroidsObjectVectorRoles.js';
2021
import {
2122
ASTEROIDS_GAME_OVER_AUTO_EXIT_SECONDS, ASTEROIDS_GAME_OVER_RETURN_MODE
2223
} from "../rules/flowRules.js";
@@ -29,11 +30,6 @@ const SCORE_TWO_X = 824;
2930
const LIFE_SPACING = 22;
3031
const PAUSE_OVERLAY_COLOR = 'rgba(2, 6, 23, 0.58)';
3132
const INITIALS_OVERLAY_COLOR = 'rgba(1, 6, 19, 0.62)';
32-
const UFO_OBJECT_VECTOR_OBJECT_IDS = Object.freeze({
33-
large: "object.asteroids.ufo.large",
34-
small: "object.asteroids.ufo.small",
35-
});
36-
const SHIP_OBJECT_VECTOR_OBJECT_ID = "object.asteroids.ship";
3733
const LIFE_ICON_POINTS = [
3834
[14, 0],
3935
[-10, -8],
@@ -114,8 +110,11 @@ export default class AsteroidsGameScene extends Scene {
114110
this.devConsoleIntegration = options.devConsoleIntegration || null;
115111
this.objectVectorAssets = options.objectVectorAssets || null;
116112
this.objectVectorRuntime = options.objectVectorRuntime || null;
113+
this.objectVectorRuntimeBindings = this.objectVectorAssets?.runtimeBindings || {};
117114
this.asteroidGeometryProfiles = options.asteroidGeometryProfiles
118-
|| createAsteroidGeometryProfilesFromObjectVectorAssets(this.objectVectorAssets);
115+
|| createAsteroidGeometryProfilesFromObjectVectorAssets(this.objectVectorAssets, {
116+
logger: this.objectVectorRuntime,
117+
});
119118
this.objectVectorPlaybackMs = 0;
120119
this.objectVectorRenderCounts = {
121120
asteroids: 0,
@@ -735,10 +734,11 @@ export default class AsteroidsGameScene extends Scene {
735734
}
736735

737736
this.world.asteroids.forEach((asteroid) => {
737+
const roleId = ASTEROID_SIZE_RUNTIME_ROLES[asteroid.size];
738738
this.drawObjectVectorAsset(renderer, "asteroids", {
739+
...this.objectVectorRoleOptions(roleId),
739740
elapsedMs: this.objectVectorPlaybackMs,
740741
fps: 12,
741-
objectId: ASTEROID_OBJECT_VECTOR_OBJECT_IDS[asteroid.size],
742742
rotation: asteroid.angle,
743743
stateId: "active",
744744
x: asteroid.x,
@@ -747,10 +747,11 @@ export default class AsteroidsGameScene extends Scene {
747747
});
748748

749749
if (this.world.ufo) {
750+
const roleId = this.world.ufo.type === "small" ? "ufoSmall" : "ufoLarge";
750751
this.drawObjectVectorAsset(renderer, "ufo", {
752+
...this.objectVectorRoleOptions(roleId),
751753
elapsedMs: this.objectVectorPlaybackMs,
752754
fps: 12,
753-
objectId: UFO_OBJECT_VECTOR_OBJECT_IDS[this.world.ufo.type],
754755
stateId: "active",
755756
x: this.world.ufo.x,
756757
y: this.world.ufo.y,
@@ -769,9 +770,9 @@ export default class AsteroidsGameScene extends Scene {
769770

770771
if (this.world.shipActive && !this.session.isTurnIntroActive() && this.session.mode !== 'menu') {
771772
this.drawObjectVectorAsset(renderer, "ship", {
773+
...this.objectVectorRoleOptions("ship"),
772774
elapsedMs: this.objectVectorPlaybackMs,
773775
fps: 12,
774-
objectId: SHIP_OBJECT_VECTOR_OBJECT_ID,
775776
rotation: this.world.ship.angle + Math.PI / 2,
776777
stateId: this.world.ship.thrusting && this.session.mode === 'playing' ? "thrust" : "idle",
777778
x: this.world.ship.x,
@@ -846,14 +847,18 @@ export default class AsteroidsGameScene extends Scene {
846847
this.publishObjectVectorRuntimeDiagnostics();
847848
}
848849

850+
objectVectorRoleOptions(roleId) {
851+
return runtimeObjectRoleOptions(roleId, this.objectVectorRuntimeBindings);
852+
}
853+
849854
drawObjectVectorAsset(renderer, renderKey, options) {
850855
if (!this.objectVectorRuntime || !this.objectVectorAssets) {
851-
this.recordObjectVectorRenderFailure(renderKey, options.assetId || options.objectId, "validated Object Vector runtime assets are not loaded");
856+
this.recordObjectVectorRenderFailure(renderKey, options.assetId || options.objectId || options.runtimeRole, "validated Object Vector runtime assets are not loaded");
852857
return false;
853858
}
854859
const result = this.objectVectorRuntime.renderObject(renderer, this.objectVectorAssets, options);
855860
if (!result.ok) {
856-
this.recordObjectVectorRenderFailure(renderKey, options.assetId || options.objectId, "runtime render returned failed result");
861+
this.recordObjectVectorRenderFailure(renderKey, options.assetId || options.objectId || options.runtimeRole, "runtime render returned failed result");
857862
return false;
858863
}
859864
this.objectVectorRenderCounts[renderKey] = (this.objectVectorRenderCounts[renderKey] || 0) + 1;

games/Asteroids/game/asteroidObjectGeometry.js

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
export const ASTEROID_OBJECT_VECTOR_OBJECT_IDS = Object.freeze({
2-
1: 'object.asteroids.asteroid.small',
3-
2: 'object.asteroids.asteroid.medium',
4-
3: 'object.asteroids.asteroid.large',
5-
});
1+
import {
2+
ASTEROID_SIZE_RUNTIME_ROLES,
3+
resolveAsteroidsObjectVectorRole,
4+
} from './asteroidsObjectVectorRoles.js';
65

76
const ASTEROID_SIZE_LABELS = Object.freeze({
87
1: 'SML',
@@ -66,20 +65,20 @@ function extractPrimaryPolygonPoints(object) {
6665
return asArray(shape?.geometry?.points).map(cleanPoint);
6766
}
6867

69-
function createProfilesFromObjects(objects) {
70-
const objectById = new Map(asArray(objects).map((object) => [object?.id, object]));
68+
function createProfilesFromObjects(objects, options = {}) {
7169
const profiles = {};
7270

73-
Object.entries(ASTEROID_OBJECT_VECTOR_OBJECT_IDS).forEach(([size, objectId]) => {
74-
const points = centerPoints(extractPrimaryPolygonPoints(objectById.get(objectId)));
71+
Object.entries(ASTEROID_SIZE_RUNTIME_ROLES).forEach(([size, roleId]) => {
72+
const object = resolveAsteroidsObjectVectorRole(objects, roleId, options);
73+
const points = centerPoints(extractPrimaryPolygonPoints(object));
7574
if (points.length < 3) {
7675
return;
7776
}
7877
const sizeId = Number(size);
7978
profiles[sizeId] = {
8079
id: sizeId,
8180
label: ASTEROID_SIZE_LABELS[sizeId] || String(sizeId),
82-
objectId,
81+
objectId: object.id,
8382
points,
8483
radius: maxRadius(points),
8584
};
@@ -88,13 +87,16 @@ function createProfilesFromObjects(objects) {
8887
return profiles;
8988
}
9089

91-
export function createAsteroidGeometryProfilesFromObjectVectorPayload(payload) {
92-
return createProfilesFromObjects(asRecord(payload).objects);
90+
export function createAsteroidGeometryProfilesFromObjectVectorPayload(payload, options = {}) {
91+
return createProfilesFromObjects(asRecord(payload).objects, options);
9392
}
9493

95-
export function createAsteroidGeometryProfilesFromObjectVectorAssets(assetSet) {
94+
export function createAsteroidGeometryProfilesFromObjectVectorAssets(assetSet, options = {}) {
9695
if (assetSet?.objectsById instanceof Map) {
97-
return createProfilesFromObjects([...assetSet.objectsById.values()]);
96+
return createProfilesFromObjects([...assetSet.objectsById.values()], {
97+
runtimeBindings: asRecord(assetSet.runtimeBindings),
98+
...options,
99+
});
98100
}
99-
return createAsteroidGeometryProfilesFromObjectVectorPayload(assetSet?.payload);
101+
return createAsteroidGeometryProfilesFromObjectVectorPayload(assetSet?.payload, options);
100102
}

0 commit comments

Comments
 (0)