Skip to content

Commit 116565d

Browse files
author
DavidQ
committed
Validate Asteroids ship idle move and destroyed visuals - PR_26140_051-validate-asteroids-ship-state-visuals
1 parent c892339 commit 116565d

4 files changed

Lines changed: 220 additions & 100 deletions

File tree

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Asteroids Ship State Visual Trace
2+
3+
PR: PR_26140_051-validate-asteroids-ship-state-visuals
4+
5+
## Summary
6+
- Asteroids ship geometry is manifest-owned by `object.asteroids.ship` in `games/Asteroids/game.manifest.json`.
7+
- The manifest ship object defines `idle`, `move`, and `destroyed` states.
8+
- Normal gameplay uses `idle` when the ship is not thrusting and `move` while thrusting.
9+
- The `destroyed` visual state exists in manifest data but is not used by normal gameplay destruction; ship destruction currently uses `ShipDebrisSystem` fragments generated from manifest collision geometry.
10+
- This PR adds a debug-only preview path, `AsteroidsGameScene.debugPreviewShipVisualState(renderer, stateId, options)`, so existing ship states such as `destroyed` can be validated without changing gameplay behavior.
11+
12+
## Manifest Ownership
13+
- `games/Asteroids/game.manifest.json:301`: active ship object id is `object.asteroids.ship`.
14+
- `games/Asteroids/game.manifest.json:486`: ship state `idle`.
15+
- `games/Asteroids/game.manifest.json:554`: ship state `move`.
16+
- `games/Asteroids/game.manifest.json:810`: ship state `destroyed`.
17+
- Manifest state shape counts observed from current data:
18+
- `idle`: 1 frame, 1 visible shape.
19+
- `move`: 3 frames, visible shape counts 3, 3, and 5 for flame flicker.
20+
- `destroyed`: 1 frame, 5 visible shapes.
21+
22+
## Runtime Usage Trace
23+
- `games/Asteroids/game/asteroidsObjectGeometryManifest.js:13`: `ASTEROIDS_OBJECT_GEOMETRY_IDS.ship` resolves to `object.asteroids.ship`.
24+
- `games/Asteroids/game/AsteroidsGameScene.js:720`: live ship render selects `move` when `world.ship.thrusting` and session mode is `playing`; otherwise it selects `idle`.
25+
- `games/Asteroids/game/AsteroidsGameScene.js:793`: HUD lives render with the ship object and `idle` state.
26+
- `games/Asteroids/game/AsteroidsAttractAdapter.js:142` and `236`: attract/demo ship renders use `object.asteroids.ship` with `idle` state.
27+
- `games/Asteroids/game/AsteroidsGameScene.js:148`: normal destruction owns `ShipDebrisSystem`.
28+
- `games/Asteroids/systems/ShipDebrisSystem.js`: destruction fragments are runtime line effects based on manifest ship geometry points, not the manifest `destroyed` state.
29+
30+
## Term Trace
31+
- `idle`: active in gameplay ship, life icons, attract/demo rendering, and manifest state data.
32+
- `move`: active in gameplay ship rendering while thrusting, preserving flicker shape/frame behavior from manifest state data.
33+
- `destroyed`: present in manifest and now previewable by debug validation; not used by normal gameplay destruction.
34+
- `object.asteroids.ship`: active Object Vector Studio V2 manifest object id used by runtime.
35+
- `vector.asteroids.ship`: no active Asteroids runtime usage found; remaining references are non-runtime asset manifest discovery tests.
36+
- `player-ship`: no active Asteroids runtime usage found; remaining reference is a V2 asset-manager test fixture path.
37+
38+
## Validation Result
39+
- Added Playwright validation in `tests/playwright/tools/AsteroidsShipStateVisuals.spec.mjs`.
40+
- Validation confirms live runtime ship render calls use `idle` and `move` for `object.asteroids.ship`.
41+
- Validation confirms normal ship render calls do not use `destroyed`.
42+
- Validation confirms `debugPreviewShipVisualState(renderer, "destroyed")` renders the manifest `destroyed` state successfully through the existing Object Vector runtime.

games/Asteroids/game.manifest.json

Lines changed: 31 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -312,12 +312,12 @@
312312
"y": -4
313313
},
314314
"point2": {
315-
"x": -14,
315+
"x": -10,
316316
"y": 0
317317
}
318318
},
319319
"style": {
320-
"fill": "transparent",
320+
"fill": "#00000000",
321321
"stroke": "#FFBE64",
322322
"strokeWidth": 2,
323323
"fillOpacity": 1,
@@ -338,7 +338,7 @@
338338
"locked": false,
339339
"geometry": {
340340
"point1": {
341-
"x": -14,
341+
"x": -10,
342342
"y": 0
343343
},
344344
"point2": {
@@ -396,7 +396,7 @@
396396
},
397397
"style": {
398398
"fill": "#00000000",
399-
"stroke": "#FFFFFF",
399+
"stroke": "#DBEAFE",
400400
"strokeWidth": 2,
401401
"fillOpacity": 1,
402402
"strokeOpacity": 1,
@@ -427,12 +427,12 @@
427427
"locked": false,
428428
"geometry": {
429429
"point1": {
430-
"x": -6,
431-
"y": -4
430+
"x": -12,
431+
"y": 0
432432
},
433433
"point2": {
434-
"x": -14,
435-
"y": 0
434+
"x": -6,
435+
"y": 4
436436
}
437437
},
438438
"style": {
@@ -457,16 +457,16 @@
457457
"locked": false,
458458
"geometry": {
459459
"point1": {
460-
"x": -14,
461-
"y": 0
460+
"x": -6,
461+
"y": -4
462462
},
463463
"point2": {
464-
"x": -6,
465-
"y": 4
464+
"x": -12,
465+
"y": 0
466466
}
467467
},
468468
"style": {
469-
"fill": "#00000000",
469+
"fill": "transparent",
470470
"stroke": "#FFBE64",
471471
"strokeWidth": 2,
472472
"fillOpacity": 1,
@@ -493,7 +493,7 @@
493493
"shapeOverrides": [
494494
{
495495
"visible": false,
496-
"shapeIndex": 0,
496+
"shapeIndex": 4,
497497
"transform": {
498498
"rotation": 0,
499499
"scaleX": 1,
@@ -525,7 +525,7 @@
525525
"visible": false
526526
},
527527
{
528-
"shapeIndex": 3,
528+
"shapeIndex": 0,
529529
"transform": {
530530
"rotation": 0,
531531
"scaleX": 1,
@@ -536,7 +536,7 @@
536536
"visible": false
537537
},
538538
{
539-
"shapeIndex": 4,
539+
"shapeIndex": 3,
540540
"transform": {
541541
"rotation": 0,
542542
"scaleX": 1,
@@ -560,8 +560,8 @@
560560
"durationFrames": 1,
561561
"shapeOverrides": [
562562
{
563-
"visible": true,
564-
"shapeIndex": 0,
563+
"visible": false,
564+
"shapeIndex": 4,
565565
"transform": {
566566
"rotation": 0,
567567
"scaleX": 1,
@@ -571,7 +571,7 @@
571571
}
572572
},
573573
{
574-
"shapeIndex": 4,
574+
"shapeIndex": 3,
575575
"transform": {
576576
"rotation": 0,
577577
"scaleX": 1,
@@ -582,15 +582,15 @@
582582
"visible": false
583583
},
584584
{
585-
"shapeIndex": 3,
585+
"shapeIndex": 0,
586586
"transform": {
587587
"rotation": 0,
588588
"scaleX": 1,
589589
"scaleY": 1,
590590
"x": 0,
591591
"y": 0
592592
},
593-
"visible": false
593+
"visible": true
594594
},
595595
{
596596
"shapeIndex": 1,
@@ -622,8 +622,8 @@
622622
"durationFrames": 1,
623623
"shapeOverrides": [
624624
{
625-
"visible": false,
626-
"shapeIndex": 0,
625+
"visible": true,
626+
"shapeIndex": 4,
627627
"transform": {
628628
"rotation": 0,
629629
"scaleX": 1,
@@ -641,10 +641,10 @@
641641
"x": 0,
642642
"y": 0
643643
},
644-
"visible": false
644+
"visible": true
645645
},
646646
{
647-
"shapeIndex": 3,
647+
"shapeIndex": 0,
648648
"transform": {
649649
"rotation": 0,
650650
"scaleX": 1,
@@ -655,7 +655,7 @@
655655
"visible": true
656656
},
657657
{
658-
"shapeIndex": 4,
658+
"shapeIndex": 3,
659659
"transform": {
660660
"rotation": 0,
661661
"scaleX": 1,
@@ -685,7 +685,7 @@
685685
"shapeOverrides": [
686686
{
687687
"visible": true,
688-
"shapeIndex": 0,
688+
"shapeIndex": 4,
689689
"transform": {
690690
"rotation": 0,
691691
"scaleX": 1,
@@ -703,7 +703,7 @@
703703
"x": 0,
704704
"y": 0
705705
},
706-
"visible": true
706+
"visible": false
707707
},
708708
{
709709
"shapeIndex": 2,
@@ -717,18 +717,18 @@
717717
"visible": true
718718
},
719719
{
720-
"shapeIndex": 3,
720+
"shapeIndex": 0,
721721
"transform": {
722722
"rotation": 0,
723723
"scaleX": 1,
724724
"scaleY": 1,
725725
"x": 0,
726726
"y": 0
727727
},
728-
"visible": true
728+
"visible": false
729729
},
730730
{
731-
"shapeIndex": 4,
731+
"shapeIndex": 3,
732732
"transform": {
733733
"rotation": 0,
734734
"scaleX": 1,
@@ -741,74 +741,6 @@
741741
]
742742
}
743743
]
744-
},
745-
{
746-
"frames": [
747-
{
748-
"durationFrames": 1,
749-
"id": "frame-1",
750-
"order": 1,
751-
"shapeOverrides": [
752-
{
753-
"shapeIndex": 2,
754-
"transform": {
755-
"rotation": 270,
756-
"scaleX": 1,
757-
"scaleY": 1,
758-
"x": 0,
759-
"y": 0
760-
},
761-
"visible": true
762-
},
763-
{
764-
"shapeIndex": 0,
765-
"transform": {
766-
"rotation": 270,
767-
"scaleX": 1,
768-
"scaleY": 1,
769-
"x": 0,
770-
"y": 0
771-
},
772-
"visible": true
773-
},
774-
{
775-
"shapeIndex": 1,
776-
"transform": {
777-
"rotation": 270,
778-
"scaleX": 1,
779-
"scaleY": 1,
780-
"x": 0,
781-
"y": 0
782-
},
783-
"visible": true
784-
},
785-
{
786-
"shapeIndex": 3,
787-
"transform": {
788-
"rotation": 270,
789-
"scaleX": 1,
790-
"scaleY": 1,
791-
"x": 0,
792-
"y": 0
793-
},
794-
"visible": true
795-
},
796-
{
797-
"shapeIndex": 4,
798-
"transform": {
799-
"rotation": 270,
800-
"scaleX": 1,
801-
"scaleY": 1,
802-
"x": 0,
803-
"y": 0
804-
},
805-
"visible": true
806-
}
807-
]
808-
}
809-
],
810-
"id": "destroyed",
811-
"name": "Destroyed"
812744
}
813745
],
814746
"tags": [

games/Asteroids/game/AsteroidsGameScene.js

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -812,6 +812,47 @@ export default class AsteroidsGameScene extends Scene {
812812
});
813813
}
814814

815+
getShipVisualStateIds() {
816+
const states = this.objectVectorAssets?.objectsById?.get(ASTEROIDS_OBJECT_GEOMETRY_IDS.ship)?.states
817+
|| this.objectGeometry?.objectsById?.get(ASTEROIDS_OBJECT_GEOMETRY_IDS.ship)?.states;
818+
return Array.isArray(states)
819+
? states.map((state) => String(state?.id || '').trim()).filter(Boolean)
820+
: [];
821+
}
822+
823+
debugPreviewShipVisualState(renderer, stateId, options = {}) {
824+
const shipStateId = String(stateId || '').trim();
825+
const availableStates = this.getShipVisualStateIds();
826+
if (!renderer || !shipStateId || !availableStates.includes(shipStateId)) {
827+
return {
828+
availableStates,
829+
objectId: ASTEROIDS_OBJECT_GEOMETRY_IDS.ship,
830+
ok: false,
831+
rendered: false,
832+
stateId: shipStateId,
833+
};
834+
}
835+
836+
const rendered = this.drawObjectVectorAsset(renderer, "shipVisualStatePreview", {
837+
elapsedMs: Number.isFinite(options.elapsedMs) ? options.elapsedMs : this.objectVectorPlaybackMs,
838+
fps: Number.isFinite(options.fps) ? options.fps : 12,
839+
objectId: ASTEROIDS_OBJECT_GEOMETRY_IDS.ship,
840+
requireManifestBinding: true,
841+
rotation: Number.isFinite(options.rotation) ? options.rotation : this.world.ship.angle,
842+
rotationUnit: options.rotationUnit || 'radians',
843+
stateId: shipStateId,
844+
x: Number.isFinite(options.x) ? options.x : this.world.ship.x,
845+
y: Number.isFinite(options.y) ? options.y : this.world.ship.y,
846+
});
847+
return {
848+
availableStates,
849+
objectId: ASTEROIDS_OBJECT_GEOMETRY_IDS.ship,
850+
ok: rendered,
851+
rendered,
852+
stateId: shipStateId,
853+
};
854+
}
855+
815856
drawObjectVectorAsset(renderer, renderKey, options) {
816857
if (!this.objectVectorRuntime || !this.objectVectorAssets) {
817858
this.recordObjectVectorRenderFailure(renderKey, options.assetId || options.objectId, "validated Object Vector runtime assets are not loaded");
@@ -849,6 +890,7 @@ export default class AsteroidsGameScene extends Scene {
849890
objectCount: this.objectVectorAssets?.objectsById?.size || 0,
850891
objectVectorObjectIds: this.objectGeometry?.objects?.map((object) => object.id) || [],
851892
runtimeObjectsValid: Boolean(this.objectVectorRuntimeObjectValidation?.ok),
893+
shipVisualStates: this.getShipVisualStateIds(),
852894
renderCounts: { ...this.objectVectorRenderCounts },
853895
};
854896
} catch {
@@ -893,4 +935,3 @@ export default class AsteroidsGameScene extends Scene {
893935
});
894936
}
895937
}
896-

0 commit comments

Comments
 (0)