Skip to content

Commit ef0e9ca

Browse files
author
DavidQ
committed
Add Asteroids asset manifest for discovery
BUILD_PR_LEVEL_10_08_ASTEROIDS_DATA_MANIFEST_DISCOVERY
1 parent e3e463c commit ef0e9ca

11 files changed

Lines changed: 355 additions & 30 deletions

.gitignore

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,16 @@
1414
/*.zip
1515

1616
# Ignore specific files in the docs/dev directory
17-
docs/dev/CODEX_COMMANDS.md
1817
docs/dev/COMMIT_COMMENT.txt
19-
docs/dev/NEXT_COMMANDS.txt
2018
docs/dev/commit_comment.txt
2119

20+
docs/dev/CODEX_COMMANDS.md
21+
docs/dev/codex_commands.md
22+
23+
docs/dev/NEXT_COMMAND.txt
24+
25+
COMMIT_COMMENT.txt
26+
commit_comment.txt
27+
CODEX_COMMANDS.md
28+
codex_commands.md
29+
NEXT_COMMAND.txt

docs/dev/CODEX_COMMANDS.md

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,3 @@
1-
MODEL: GPT-5.4
2-
REASONING: high
3-
4-
COMMAND:
5-
- add initial real tool-editable JSON bootstrap objects under:
6-
- games/Asteroids/assets/sprites/data/
7-
- games/Asteroids/assets/tilemaps/data/
8-
- games/Asteroids/assets/parallax/data/
9-
- games/Asteroids/assets/vectors/data/
10-
- keep runtime assets untouched
11-
- use minimal but real JSON objects, not placeholders only
12-
- align names with current Asteroids asset intent where practical
13-
- do not modify engine code
14-
- commit format:
15-
description first line
16-
PR name last line
17-
- update roadmap status only
1+
MODEL: GPT-5.3-codex
2+
REASONING: medium
3+
COMMAND: Validate manifest structure and wire loader discovery (no engine modification)

docs/dev/COMMIT_COMMENT.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
Add real Asteroids tool-editable bootstrap JSON objects across sprites/tilemaps/parallax/vectors data folders while keeping runtime assets untouched.
2-
BUILD_PR_LEVEL_10_06_ASSET_DATA_BOOTSTRAP_ASTEROIDS
1+
Validated Asteroids manifest structure and wired loader discovery via shared pipeline contract.
2+
BUILD_PR_LEVEL_10_08_ASTEROIDS_DATA_MANIFEST_DISCOVERY

docs/dev/NEXT_COMMAND.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
BUILD_PR_LEVEL_10_07_ASTEROIDS_PROJECT_JSON_ALIGNMENT
1+
BUILD_PR_LEVEL_10_09_ASTEROIDS_MANIFEST_LOADER_INTEGRATION
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
Adds real bootstrap JSON objects under the Asteroids asset data folders.
1+
Implemented Asteroids game asset manifest and shared manifest-discovery contract.
2+
Wired runtime asset lookup to consume validated manifest discovery output when a game manifest is provided.
3+
Added focused manifest discovery test covering structure validation, runtime-safe path enforcement, and lookup integration.
Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
[ ] run Validate-All.ps1 -ValidateAssetStructure → PASS
2-
[ ] confirm sprites/data contains real JSON
3-
[ ] confirm tilemaps/data contains real JSON
4-
[ ] confirm parallax/data contains real JSON
5-
[ ] confirm vectors/data contains real JSON
6-
[ ] confirm runtime assets were not moved/deleted
1+
- node --check tools/shared/pipeline/gameAssetManifestDiscovery.js
2+
- node --check tools/shared/pipeline/runtimeAssetLookup.js
3+
- node --check tests/tools/GameAssetManifestDiscovery.test.mjs
4+
- PASS GameAssetManifestDiscovery (focused test)
5+
- PASS RuntimeAssetLookupConsolidation (existing regression check)
6+
- PASS AsteroidsManifestJsonParse
7+
- no engine code modified
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# BUILD_PR_LEVEL_10_08_ASTEROIDS_DATA_MANIFEST_DISCOVERY
2+
3+
## Purpose
4+
Introduce manifest discovery layer for Asteroids asset data.
5+
6+
## Scope
7+
- Add manifest JSON
8+
- Define discovery contract
9+
- Validation ready
10+
11+
## Testable Outcome
12+
- Manifest file exists
13+
- Structured for loader integration
14+
15+
## Non-Goals
16+
- No engine changes
17+
- No runtime wiring
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
{
2+
"schema": "html-js-gaming.game-asset-manifest",
3+
"version": 1,
4+
"gameId": "asteroids",
5+
"domains": {
6+
"sprites": [
7+
{
8+
"assetId": "sprite.asteroids.demo",
9+
"runtimePath": "games/Asteroids/assets/sprites/asteroids-demo.sprite.json",
10+
"toolDataPath": "games/Asteroids/assets/sprites/data/asteroids-demo.sprite.data.json",
11+
"sourceToolId": "sprite-editor",
12+
"metadata": {
13+
"runtimeType": "sprite"
14+
}
15+
}
16+
],
17+
"tilemaps": [
18+
{
19+
"assetId": "tilemap.asteroids.stage",
20+
"runtimePath": "games/Asteroids/assets/tilemaps/asteroids-stage.tilemap.json",
21+
"toolDataPath": "games/Asteroids/assets/tilemaps/data/asteroids-stage.tilemap.data.json",
22+
"sourceToolId": "tile-map-editor",
23+
"metadata": {
24+
"runtimeType": "tilemap"
25+
}
26+
}
27+
],
28+
"parallax": [
29+
{
30+
"assetId": "parallax.asteroids.title",
31+
"runtimePath": "games/Asteroids/assets/parallax/asteroids-title.parallax.json",
32+
"toolDataPath": "games/Asteroids/assets/parallax/data/asteroids-title.parallax.data.json",
33+
"sourceToolId": "parallax-editor",
34+
"metadata": {
35+
"runtimeType": "parallax"
36+
}
37+
},
38+
{
39+
"assetId": "parallax.asteroids.overlay",
40+
"runtimePath": "games/Asteroids/assets/parallax/asteroids-overlay.parallax.json",
41+
"toolDataPath": "games/Asteroids/assets/parallax/data/asteroids-overlay.parallax.data.json",
42+
"sourceToolId": "parallax-editor",
43+
"metadata": {
44+
"runtimeType": "parallax"
45+
}
46+
}
47+
],
48+
"vectors": [
49+
{
50+
"assetId": "vector.asteroids.ship",
51+
"runtimePath": "games/Asteroids/assets/vectors/asteroids-ship.vector.json",
52+
"toolDataPath": "games/Asteroids/assets/vectors/data/asteroids-vectors.library.data.json",
53+
"sourceToolId": "vector-asset-studio",
54+
"metadata": {
55+
"runtimeType": "vector"
56+
}
57+
},
58+
{
59+
"assetId": "vector.asteroids.asteroid.large",
60+
"runtimePath": "games/Asteroids/assets/vectors/asteroids-asteroid-large.vector.json",
61+
"toolDataPath": "games/Asteroids/assets/vectors/data/asteroids-vectors.library.data.json",
62+
"sourceToolId": "vector-asset-studio",
63+
"metadata": {
64+
"runtimeType": "vector"
65+
}
66+
},
67+
{
68+
"assetId": "vector.asteroids.asteroid.medium",
69+
"runtimePath": "games/Asteroids/assets/vectors/asteroids-asteroid-medium.vector.json",
70+
"toolDataPath": "games/Asteroids/assets/vectors/data/asteroids-vectors.library.data.json",
71+
"sourceToolId": "vector-asset-studio",
72+
"metadata": {
73+
"runtimeType": "vector"
74+
}
75+
},
76+
{
77+
"assetId": "vector.asteroids.asteroid.small",
78+
"runtimePath": "games/Asteroids/assets/vectors/asteroids-asteroid-small.vector.json",
79+
"toolDataPath": "games/Asteroids/assets/vectors/data/asteroids-vectors.library.data.json",
80+
"sourceToolId": "vector-asset-studio",
81+
"metadata": {
82+
"runtimeType": "vector"
83+
}
84+
},
85+
{
86+
"assetId": "vector.asteroids.ui.title",
87+
"runtimePath": "games/Asteroids/assets/vectors/asteroids-title.vector.json",
88+
"toolDataPath": "games/Asteroids/assets/vectors/data/asteroids-vectors.library.data.json",
89+
"sourceToolId": "vector-asset-studio",
90+
"metadata": {
91+
"runtimeType": "vector"
92+
}
93+
}
94+
]
95+
}
96+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import assert from "node:assert/strict";
2+
import asteroidsManifest from "../../games/Asteroids/assets/asteroids.assets.json" with { type: "json" };
3+
import {
4+
discoverRuntimeAssetSourcesFromManifest,
5+
validateGameAssetManifestStructure
6+
} from "../../tools/shared/pipeline/gameAssetManifestDiscovery.js";
7+
import { createRuntimeManifestAssetLookup } from "../../tools/shared/pipeline/runtimeAssetLookup.js";
8+
9+
export async function run() {
10+
const validation = validateGameAssetManifestStructure(asteroidsManifest, { gameId: "asteroids" });
11+
assert.equal(validation.valid, true);
12+
assert.deepEqual(validation.issues, []);
13+
14+
const discovery = discoverRuntimeAssetSourcesFromManifest(asteroidsManifest, { gameId: "asteroids" });
15+
assert.equal(discovery.status, "ready");
16+
assert.equal(discovery.issues.length, 0);
17+
assert.equal(Object.keys(discovery.runtimeAssetSources).length > 0, true);
18+
assert.equal(discovery.runtimeAssetSources["vector.asteroids.ship"].file.includes("/data/"), false);
19+
20+
const lookup = createRuntimeManifestAssetLookup({
21+
gameId: "asteroids",
22+
gameAssetManifest: asteroidsManifest,
23+
missingBindingBehavior: "null"
24+
});
25+
26+
assert.equal(lookup.binding.status, "ready");
27+
assert.equal(lookup.binding.issues.length, 0);
28+
assert.equal(lookup.resolvePackagedAsset({ id: "vector.asteroids.ship", type: "vector" }).file.includes("/data/"), false);
29+
30+
const invalidManifest = {
31+
...asteroidsManifest,
32+
domains: {
33+
...asteroidsManifest.domains,
34+
vectors: [
35+
{
36+
...asteroidsManifest.domains.vectors[0],
37+
runtimePath: "games/Asteroids/assets/vectors/data/asteroids-ship.vector.json"
38+
}
39+
]
40+
}
41+
};
42+
const invalidLookup = createRuntimeManifestAssetLookup({
43+
gameId: "asteroids",
44+
gameAssetManifest: invalidManifest,
45+
missingBindingBehavior: "null"
46+
});
47+
assert.equal(
48+
invalidLookup.getErrors().some((entry) => entry.code === "RUNTIME_MANIFEST_DISCOVERY_INVALID"),
49+
true
50+
);
51+
}
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
import { safeString } from "../projectSystemValueUtils.js";
2+
import {
3+
GAME_ASSET_MANIFEST_SCHEMA,
4+
GAME_ASSET_MANIFEST_VERSION
5+
} from "./gameAssetManifestCoordinator.js";
6+
7+
const SUPPORTED_DOMAINS = Object.freeze(["sprites", "tilemaps", "parallax", "vectors"]);
8+
9+
function asObject(value) {
10+
return value && typeof value === "object" ? value : {};
11+
}
12+
13+
function asArray(value) {
14+
return Array.isArray(value) ? value : [];
15+
}
16+
17+
function pushIssue(issues, message) {
18+
issues.push(safeString(message, "Invalid manifest structure."));
19+
}
20+
21+
function inferRuntimeKind(domain, metadata) {
22+
const runtimeType = safeString(asObject(metadata).runtimeType, "").toLowerCase();
23+
if (runtimeType) {
24+
return runtimeType;
25+
}
26+
if (domain === "tilemaps") {
27+
return "tilemap";
28+
}
29+
if (domain === "sprites") {
30+
return "sprite";
31+
}
32+
if (domain === "vectors") {
33+
return "vector";
34+
}
35+
if (domain === "parallax") {
36+
return "parallax";
37+
}
38+
return "data";
39+
}
40+
41+
export function validateGameAssetManifestStructure(manifestInput, options = {}) {
42+
const manifest = asObject(manifestInput);
43+
const issues = [];
44+
const expectedGameId = safeString(options.gameId, "").toLowerCase();
45+
46+
if (safeString(manifest.schema, "") !== GAME_ASSET_MANIFEST_SCHEMA) {
47+
pushIssue(issues, `manifest.schema must equal ${GAME_ASSET_MANIFEST_SCHEMA}.`);
48+
}
49+
if (manifest.version !== GAME_ASSET_MANIFEST_VERSION) {
50+
pushIssue(issues, `manifest.version must equal ${GAME_ASSET_MANIFEST_VERSION}.`);
51+
}
52+
53+
const gameId = safeString(manifest.gameId, "").toLowerCase();
54+
if (!gameId) {
55+
pushIssue(issues, "manifest.gameId is required.");
56+
}
57+
if (expectedGameId && gameId && gameId !== expectedGameId) {
58+
pushIssue(issues, `manifest.gameId must match expected gameId ${expectedGameId}.`);
59+
}
60+
61+
const domains = asObject(manifest.domains);
62+
SUPPORTED_DOMAINS.forEach((domain) => {
63+
const domainEntries = domains[domain];
64+
if (!Array.isArray(domainEntries)) {
65+
pushIssue(issues, `manifest.domains.${domain} must be an array.`);
66+
return;
67+
}
68+
domainEntries.forEach((entry, index) => {
69+
const assetId = safeString(entry?.assetId, "");
70+
const runtimePath = safeString(entry?.runtimePath, "");
71+
const toolDataPath = safeString(entry?.toolDataPath, "");
72+
if (!assetId) {
73+
pushIssue(issues, `manifest.domains.${domain}[${index}].assetId is required.`);
74+
}
75+
if (!runtimePath) {
76+
pushIssue(issues, `manifest.domains.${domain}[${index}].runtimePath is required.`);
77+
}
78+
if (!toolDataPath) {
79+
pushIssue(issues, `manifest.domains.${domain}[${index}].toolDataPath is required.`);
80+
}
81+
if (runtimePath.includes("/data/")) {
82+
pushIssue(issues, `manifest.domains.${domain}[${index}] runtimePath cannot point into /data/.`);
83+
}
84+
});
85+
});
86+
87+
return {
88+
valid: issues.length === 0,
89+
issues,
90+
manifest
91+
};
92+
}
93+
94+
export function discoverRuntimeAssetSourcesFromManifest(manifestInput, options = {}) {
95+
const validation = validateGameAssetManifestStructure(manifestInput, options);
96+
if (!validation.valid) {
97+
return {
98+
status: "invalid",
99+
runtimeAssetSources: {},
100+
records: [],
101+
issues: validation.issues,
102+
gameId: safeString(asObject(manifestInput).gameId, "")
103+
};
104+
}
105+
106+
const manifest = validation.manifest;
107+
const sources = {};
108+
const records = [];
109+
110+
SUPPORTED_DOMAINS.forEach((domain) => {
111+
asArray(manifest.domains[domain]).forEach((entry) => {
112+
const assetId = safeString(entry?.assetId, "");
113+
const runtimePath = safeString(entry?.runtimePath, "");
114+
const toolDataPath = safeString(entry?.toolDataPath, "");
115+
if (!assetId || !runtimePath) {
116+
return;
117+
}
118+
const metadata = asObject(entry?.metadata);
119+
sources[assetId] = {
120+
file: runtimePath,
121+
path: runtimePath,
122+
kind: inferRuntimeKind(domain, metadata),
123+
domain,
124+
sourceToolId: safeString(entry?.sourceToolId, "")
125+
};
126+
records.push({
127+
domain,
128+
assetId,
129+
runtimePath,
130+
toolDataPath,
131+
sourceToolId: safeString(entry?.sourceToolId, ""),
132+
metadata
133+
});
134+
});
135+
});
136+
137+
return {
138+
status: "ready",
139+
runtimeAssetSources: sources,
140+
records,
141+
issues: [],
142+
gameId: safeString(manifest.gameId, "")
143+
};
144+
}

0 commit comments

Comments
 (0)