create-aura3d 1.3.3 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +10 -0
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/showcase-spec-artifacts.d.ts +104 -0
- package/dist/showcase-spec-artifacts.d.ts.map +1 -0
- package/dist/showcase-spec-artifacts.js +142 -0
- package/dist/showcase-spec-artifacts.js.map +1 -0
- package/dist/showcase-spec-category-plan.d.ts +15 -0
- package/dist/showcase-spec-category-plan.d.ts.map +1 -0
- package/dist/showcase-spec-category-plan.js +45 -0
- package/dist/showcase-spec-category-plan.js.map +1 -0
- package/dist/showcase-spec-compiler.d.ts +5 -0
- package/dist/showcase-spec-compiler.d.ts.map +1 -0
- package/dist/showcase-spec-compiler.js +240 -0
- package/dist/showcase-spec-compiler.js.map +1 -0
- package/dist/showcase-spec-evidence.d.ts +7 -0
- package/dist/showcase-spec-evidence.d.ts.map +1 -0
- package/dist/showcase-spec-evidence.js +815 -0
- package/dist/showcase-spec-evidence.js.map +1 -0
- package/dist/showcase-spec-game-geometry-extractor.d.ts +21 -0
- package/dist/showcase-spec-game-geometry-extractor.d.ts.map +1 -0
- package/dist/showcase-spec-game-geometry-extractor.js +725 -0
- package/dist/showcase-spec-game-geometry-extractor.js.map +1 -0
- package/dist/showcase-spec-game-replacement-evidence.d.ts +14 -0
- package/dist/showcase-spec-game-replacement-evidence.d.ts.map +1 -0
- package/dist/showcase-spec-game-replacement-evidence.js +219 -0
- package/dist/showcase-spec-game-replacement-evidence.js.map +1 -0
- package/dist/showcase-spec-game-template-evidence.d.ts +72 -0
- package/dist/showcase-spec-game-template-evidence.d.ts.map +1 -0
- package/dist/showcase-spec-game-template-evidence.js +435 -0
- package/dist/showcase-spec-game-template-evidence.js.map +1 -0
- package/dist/showcase-spec-gameplay-evidence.d.ts +3 -0
- package/dist/showcase-spec-gameplay-evidence.d.ts.map +1 -0
- package/dist/showcase-spec-gameplay-evidence.js +108 -0
- package/dist/showcase-spec-gameplay-evidence.js.map +1 -0
- package/dist/showcase-spec-parser.d.ts +3 -0
- package/dist/showcase-spec-parser.d.ts.map +1 -0
- package/dist/showcase-spec-parser.js +662 -0
- package/dist/showcase-spec-parser.js.map +1 -0
- package/dist/showcase-spec-platformer-artifacts.d.ts +3 -0
- package/dist/showcase-spec-platformer-artifacts.d.ts.map +1 -0
- package/dist/showcase-spec-platformer-artifacts.js +276 -0
- package/dist/showcase-spec-platformer-artifacts.js.map +1 -0
- package/dist/showcase-spec-racing-artifacts.d.ts +3 -0
- package/dist/showcase-spec-racing-artifacts.d.ts.map +1 -0
- package/dist/showcase-spec-racing-artifacts.js +273 -0
- package/dist/showcase-spec-racing-artifacts.js.map +1 -0
- package/dist/showcase-spec-replacement-apply.d.ts +10 -0
- package/dist/showcase-spec-replacement-apply.d.ts.map +1 -0
- package/dist/showcase-spec-replacement-apply.js +166 -0
- package/dist/showcase-spec-replacement-apply.js.map +1 -0
- package/dist/showcase-spec-replacement-candidates.d.ts +16 -0
- package/dist/showcase-spec-replacement-candidates.d.ts.map +1 -0
- package/dist/showcase-spec-replacement-candidates.js +244 -0
- package/dist/showcase-spec-replacement-candidates.js.map +1 -0
- package/dist/showcase-spec-replacement-game-geometry.d.ts +4 -0
- package/dist/showcase-spec-replacement-game-geometry.d.ts.map +1 -0
- package/dist/showcase-spec-replacement-game-geometry.js +175 -0
- package/dist/showcase-spec-replacement-game-geometry.js.map +1 -0
- package/dist/showcase-spec-replacement-manifest.d.ts +41 -0
- package/dist/showcase-spec-replacement-manifest.d.ts.map +1 -0
- package/dist/showcase-spec-replacement-manifest.js +62 -0
- package/dist/showcase-spec-replacement-manifest.js.map +1 -0
- package/dist/showcase-spec-replacement-values.d.ts +9 -0
- package/dist/showcase-spec-replacement-values.d.ts.map +1 -0
- package/dist/showcase-spec-replacement-values.js +41 -0
- package/dist/showcase-spec-replacement-values.js.map +1 -0
- package/dist/showcase-spec-replacement.d.ts +13 -0
- package/dist/showcase-spec-replacement.d.ts.map +1 -0
- package/dist/showcase-spec-replacement.js +144 -0
- package/dist/showcase-spec-replacement.js.map +1 -0
- package/dist/showcase-spec-types.d.ts +304 -0
- package/dist/showcase-spec-types.d.ts.map +1 -0
- package/dist/showcase-spec-types.js +2 -0
- package/dist/showcase-spec-types.js.map +1 -0
- package/package.json +13 -2
- package/templates/animation-studio/scripts/animation-scene.ts +1 -1
- package/templates/animation-studio/scripts/author-office-scene.ts +1 -1
- package/templates/animation-studio/scripts/build-characters.ts +1 -1
- package/templates/animation-studio/src/director/director-heuristics.ts +2 -2
- package/templates/animation-studio/src/director/{prompt-to-scene.ts → prompt-scene.ts} +1 -1
- package/templates/cinematic-scene/tests/screenshot.spec.ts +2 -4
- package/templates/falling-blocks-starter/README.md +18 -0
- package/templates/falling-blocks-starter/index.html +23 -0
- package/templates/falling-blocks-starter/package.json +21 -0
- package/templates/falling-blocks-starter/playwright.config.ts +12 -0
- package/templates/falling-blocks-starter/public/aura-assets/showcaseBlockfallCabinet.679d52fe.glb +0 -0
- package/templates/falling-blocks-starter/public/aura-assets/showcaseBlockfallCabinet.thumb.svg +1 -0
- package/templates/falling-blocks-starter/src/aura-assets.ts +20 -0
- package/templates/falling-blocks-starter/src/main.ts +358 -0
- package/templates/falling-blocks-starter/tests/playable.spec.ts +85 -0
- package/templates/falling-blocks-starter/tests/route-health.spec.ts +34 -0
- package/templates/falling-blocks-starter/tsconfig.json +13 -0
- package/templates/mini-game/README.md +13 -0
- package/templates/mini-game/index.html +1 -1
- package/templates/mini-game/package.json +1 -1
- package/templates/mini-game/src/main.ts +251 -21
- package/templates/mini-game/tests/playable.spec.ts +49 -0
- package/templates/mini-game/tests/screenshot.spec.ts +37 -32
- package/templates/product-viewer/tests/screenshot.spec.ts +3 -5
- package/templates/racing-starter/README.md +27 -0
- package/templates/racing-starter/index.html +23 -0
- package/templates/racing-starter/package.json +21 -0
- package/templates/racing-starter/playwright.config.ts +12 -0
- package/templates/racing-starter/public/aura-assets/showcaseCleanSportsCar.1c69283f.glb +0 -0
- package/templates/racing-starter/public/aura-assets/showcaseCleanSportsCar.thumb.svg +1 -0
- package/templates/racing-starter/public/aura-assets/showcaseReadableKartCircuit.5cbb912e.glb +0 -0
- package/templates/racing-starter/public/aura-assets/showcaseReadableKartCircuit.thumb.svg +1 -0
- package/templates/racing-starter/src/aura-assets.ts +36 -0
- package/templates/racing-starter/src/main.ts +280 -0
- package/templates/racing-starter/tests/playable.spec.ts +93 -0
- package/templates/racing-starter/tests/route-health.spec.ts +40 -0
- package/templates/racing-starter/tsconfig.json +13 -0
- package/templates/animation-channel/public/aura-assets/moon-garden-feature-frame.png +0 -0
- package/templates/animation-channel/tests/reports/prompt-animation/cartoon-sample-episode.png +0 -0
- package/templates/animation-studio/dist/episodes/prompt-to-scene-proof/two-robots-fixing-a-car.document.json +0 -680
- package/templates/animation-studio/dist/episodes/scene/episode-3d.webm +0 -0
- package/templates/animation-studio/dist/episodes/scene/frames/action.png +0 -0
- package/templates/animation-studio/dist/episodes/scene/frames/dialogue.png +0 -0
- package/templates/animation-studio/dist/episodes/scene/frames/final.png +0 -0
- package/templates/animation-studio/dist/episodes/scene/frames/first.png +0 -0
- package/templates/animation-studio/dist/episodes/scene/frames/mouth-closed.png +0 -0
- package/templates/animation-studio/dist/episodes/scene/frames/mouth-open.png +0 -0
- package/templates/animation-studio/dist/episodes/scene/render-live-summary.json +0 -9763
- package/templates/animation-studio/dist/episodes/scene/skeleton-overlays/broken.png +0 -0
- package/templates/animation-studio/dist/episodes/scene/skeleton-overlays/chefs.png +0 -0
- package/templates/animation-studio/dist/episodes/scene/skeleton-overlays/customer.png +0 -0
- package/templates/animation-studio/dist/episodes/scene/skeleton-overlays/friend-1.png +0 -0
- package/templates/animation-studio/dist/episodes/scene/skeleton-overlays/friend-2.png +0 -0
- package/templates/animation-studio/dist/episodes/scene/skeleton-overlays/worker-1.png +0 -0
- package/templates/animation-studio/dist/episodes/scene/skeleton-overlays/worker-2.png +0 -0
- package/templates/animation-studio/dist/generated/second-scene.document.json +0 -1210
- package/templates/animation-studio/dist/generated/studio-edited.document.json +0 -1196
- package/templates/animation-studio/dist/scene/inshader-accept.document.json +0 -1
- package/templates/animation-studio/dist/scene/inshader-test.document.json +0 -1
- package/templates/animation-studio/dist/scene/integ.document.json +0 -1
- package/templates/animation-studio/dist/scene/working.document.json +0 -673
- package/templates/animation-studio/dist/scene/working.history.json +0 -1
- package/templates/animation-studio/studio/dist/assets/index-C7f8tHfi.css +0 -1
- package/templates/animation-studio/studio/dist/assets/index-CLo3GXyN.js +0 -43
- package/templates/animation-studio/studio/dist/index.html +0 -19
- package/templates/animation-studio/tests/reports/animation-studio/hifi-cast-validation.json +0 -938
|
@@ -0,0 +1,662 @@
|
|
|
1
|
+
export function parseShowcaseSpec(input) {
|
|
2
|
+
if (!isRecord(input))
|
|
3
|
+
throw new Error("showcase spec must be a JSON object");
|
|
4
|
+
const routeId = readString(input, "routeId");
|
|
5
|
+
if (!/^showcase-[a-z0-9-]+$/.test(routeId))
|
|
6
|
+
throw new Error(`routeId must match showcase-[a-z0-9-]+: ${routeId}`);
|
|
7
|
+
const path = readString(input, "path");
|
|
8
|
+
if (path !== `/apps/${routeId}/`)
|
|
9
|
+
throw new Error(`path must be /apps/${routeId}/`);
|
|
10
|
+
const layout = readRecord(input, "layout");
|
|
11
|
+
const primaryAssets = readArray(input, "primaryAssets").map(parseAsset);
|
|
12
|
+
const evidence = readRecord(input, "evidence");
|
|
13
|
+
const platformer = parseOptionalPlatformer(input);
|
|
14
|
+
const racing = parseOptionalRacing(input);
|
|
15
|
+
const categoryPlan = parseOptionalCategoryPlan(input);
|
|
16
|
+
const spec = {
|
|
17
|
+
schema: readLiteral(input, "schema", "aura3d-showcase-spec/1.0"),
|
|
18
|
+
routeId,
|
|
19
|
+
label: readString(input, "label"),
|
|
20
|
+
category: readString(input, "category"),
|
|
21
|
+
path,
|
|
22
|
+
globalName: readIdentifier(input, "globalName"),
|
|
23
|
+
claimLabel: readEnum(input, "claimLabel", ["createAuraApp", "prototype", "roadmap"]),
|
|
24
|
+
publicStatus: readEnum(input, "publicStatus", ["release-ready candidate", "prototype-blocked", "internal-diagnostic", "removed-from-public-showcase"]),
|
|
25
|
+
layout: {
|
|
26
|
+
heroAsset: readString(layout, "heroAsset"),
|
|
27
|
+
uiPlacement: readEnum(layout, "uiPlacement", ["left-panel", "right-panel", "bottom-bar", "none"])
|
|
28
|
+
},
|
|
29
|
+
...(platformer ? { platformer } : {}),
|
|
30
|
+
...(racing ? { racing } : {}),
|
|
31
|
+
...(categoryPlan ? { categoryPlan } : {}),
|
|
32
|
+
primaryAssets,
|
|
33
|
+
evidence: {
|
|
34
|
+
routePrimaryProbe: readString(evidence, "routePrimaryProbe"),
|
|
35
|
+
routePrimaryScreenshot: readString(evidence, "routePrimaryScreenshot"),
|
|
36
|
+
deployCommand: readString(evidence, "deployCommand"),
|
|
37
|
+
...optionalString(evidence, "deployEvidence"),
|
|
38
|
+
deployPassed: readBoolean(evidence, "deployPassed"),
|
|
39
|
+
routePrimaryPassed: readBoolean(evidence, "routePrimaryPassed"),
|
|
40
|
+
...optionalString(evidence, "gameplayProof"),
|
|
41
|
+
...optionalBoolean(evidence, "gameplayPassed"),
|
|
42
|
+
...optionalStringRecord(evidence, "releaseAssetProbes")
|
|
43
|
+
},
|
|
44
|
+
capabilities: readArray(input, "capabilities").map(parseCapability)
|
|
45
|
+
};
|
|
46
|
+
if (!spec.primaryAssets.some((asset) => asset.id === spec.layout.heroAsset))
|
|
47
|
+
throw new Error("layout.heroAsset must reference a primary asset");
|
|
48
|
+
validatePlatformerSpec(spec);
|
|
49
|
+
validateRacingSpec(spec);
|
|
50
|
+
validateCategoryPlan(spec);
|
|
51
|
+
return spec;
|
|
52
|
+
}
|
|
53
|
+
function parseAsset(value) {
|
|
54
|
+
if (!isRecord(value))
|
|
55
|
+
throw new Error("primary asset must be a JSON object");
|
|
56
|
+
return {
|
|
57
|
+
id: readAssetId(value, "id"),
|
|
58
|
+
role: readString(value, "role"),
|
|
59
|
+
typedRef: readTypedRef(value),
|
|
60
|
+
quality: readEnum(value, "quality", ["release", "candidate", "prototype"]),
|
|
61
|
+
hasDurableProvenance: readBoolean(value, "hasDurableProvenance"),
|
|
62
|
+
hasRenderedProbe: readBoolean(value, "hasRenderedProbe"),
|
|
63
|
+
hasOrientationEvidence: readBoolean(value, "hasOrientationEvidence"),
|
|
64
|
+
hasForegroundBounds: readBoolean(value, "hasForegroundBounds"),
|
|
65
|
+
...parseOptionalAssetPolicy(value)
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function parseOptionalAssetPolicy(record) {
|
|
69
|
+
const policy = record.assetPolicy;
|
|
70
|
+
if (policy === undefined)
|
|
71
|
+
return {};
|
|
72
|
+
if (!isRecord(policy))
|
|
73
|
+
throw new Error("assetPolicy must be an object");
|
|
74
|
+
return {
|
|
75
|
+
assetPolicy: {
|
|
76
|
+
allowReplacement: readBoolean(policy, "allowReplacement"),
|
|
77
|
+
...optionalString(policy, "replacementQuery"),
|
|
78
|
+
...optionalEnum(policy, "requiredRole", ["architecture", "building", "character", "data-station", "effect-core", "environment", "facility", "industrial", "level", "platformer-world", "stage", "track", "vehicle", "world"]),
|
|
79
|
+
...optionalEnum(policy, "minQuality", ["candidate", "release"]),
|
|
80
|
+
...optionalBoolean(policy, "requireRenderedProbe"),
|
|
81
|
+
...optionalBoolean(policy, "requireDeployPass")
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function parseOptionalCategoryPlan(input) {
|
|
86
|
+
if (!("categoryPlan" in input))
|
|
87
|
+
return undefined;
|
|
88
|
+
const categoryPlan = readRecord(input, "categoryPlan");
|
|
89
|
+
const layoutConstraints = readRecord(categoryPlan, "layoutConstraints");
|
|
90
|
+
const claims = readRecord(categoryPlan, "claims");
|
|
91
|
+
return {
|
|
92
|
+
kind: readEnum(categoryPlan, "kind", ["architecture-environment", "industrial-digital-twin", "particle-diagnostic", "data-diagnostic"]),
|
|
93
|
+
primaryAsset: readAssetId(categoryPlan, "primaryAsset"),
|
|
94
|
+
cameraIntent: readEnum(categoryPlan, "cameraIntent", ["architecture-hero", "industrial-overview", "diagnostic-core", "data-observatory"]),
|
|
95
|
+
...optionalEnum(categoryPlan, "backendClaim", ["webgl-particle", "native-webgpu", "fallback"]),
|
|
96
|
+
layoutConstraints: {
|
|
97
|
+
keepHeroReadable: readBoolean(layoutConstraints, "keepHeroReadable"),
|
|
98
|
+
uiAvoidsEvidenceArea: readBoolean(layoutConstraints, "uiAvoidsEvidenceArea")
|
|
99
|
+
},
|
|
100
|
+
claims: {
|
|
101
|
+
allowed: readArray(claims, "allowed").map((value) => readStringValue(value, "categoryPlan.claims.allowed")),
|
|
102
|
+
notAllowed: readArray(claims, "notAllowed").map((value) => readStringValue(value, "categoryPlan.claims.notAllowed"))
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
function parseCapability(value) {
|
|
107
|
+
if (!isRecord(value))
|
|
108
|
+
throw new Error("capability must be a JSON object");
|
|
109
|
+
return {
|
|
110
|
+
name: readString(value, "name"),
|
|
111
|
+
status: readEnum(value, "status", ["root-proven", "partial", "internal-only", "roadmap", "unsupported"]),
|
|
112
|
+
...optionalString(value, "evidence")
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function parseOptionalPlatformer(input) {
|
|
116
|
+
if (input.category !== "game-platformer" && !("platformer" in input))
|
|
117
|
+
return undefined;
|
|
118
|
+
const platformer = readRecord(input, "platformer");
|
|
119
|
+
const layoutConstraints = readRecord(platformer, "layoutConstraints");
|
|
120
|
+
const levelDesign = readRecord(platformer, "levelDesign");
|
|
121
|
+
const releaseAssetRequirements = readRecord(platformer, "releaseAssetRequirements");
|
|
122
|
+
return {
|
|
123
|
+
characterAsset: readAssetId(platformer, "characterAsset"),
|
|
124
|
+
worldAssets: readArray(platformer, "worldAssets").map((value) => readAssetIdValue(value, "platformer.worldAssets")),
|
|
125
|
+
cameraIntent: readEnum(platformer, "cameraIntent", ["side-scroller"]),
|
|
126
|
+
layoutConstraints: {
|
|
127
|
+
keepCharacterReadable: readBoolean(layoutConstraints, "keepCharacterReadable"),
|
|
128
|
+
uiAvoidsEvidenceArea: readBoolean(layoutConstraints, "uiAvoidsEvidenceArea")
|
|
129
|
+
},
|
|
130
|
+
gameplayRequirements: readArray(platformer, "gameplayRequirements").map(parseGameplayRequirement),
|
|
131
|
+
levelDesign: {
|
|
132
|
+
minPlayableSeconds: readPositiveNumber(levelDesign, "minPlayableSeconds"),
|
|
133
|
+
minCheckpoints: readPositiveInteger(levelDesign, "minCheckpoints"),
|
|
134
|
+
requiresHazardRespawn: readBoolean(levelDesign, "requiresHazardRespawn"),
|
|
135
|
+
requiresFinish: readBoolean(levelDesign, "requiresFinish"),
|
|
136
|
+
authoredLevelFlow: readBoolean(levelDesign, "authoredLevelFlow"),
|
|
137
|
+
playableSurfaceSource: parsePlayableSurfaceSource(levelDesign.playableSurfaceSource),
|
|
138
|
+
playableSurfaceLayoutValidated: readBoolean(levelDesign, "playableSurfaceLayoutValidated"),
|
|
139
|
+
...optionalString(levelDesign, "playableSurfaceEvidence"),
|
|
140
|
+
...parseOptionalPlayableSurfaceMap(levelDesign),
|
|
141
|
+
...parseOptionalGameAssetPairEvidence(levelDesign),
|
|
142
|
+
characterWorldScaleCompatible: readBoolean(levelDesign, "characterWorldScaleCompatible"),
|
|
143
|
+
styleCompatible: readBoolean(levelDesign, "styleCompatible"),
|
|
144
|
+
primitivePrimaryWorldRejected: readBoolean(levelDesign, "primitivePrimaryWorldRejected")
|
|
145
|
+
},
|
|
146
|
+
releaseAssetRequirements: {
|
|
147
|
+
characterRole: readEnum(releaseAssetRequirements, "characterRole", ["character"]),
|
|
148
|
+
worldRoles: readArray(releaseAssetRequirements, "worldRoles").map((value) => readEnumValue(value, "platformer.releaseAssetRequirements.worldRoles", ["level", "world", "stage"])),
|
|
149
|
+
requiresOrientationEvidence: readBoolean(releaseAssetRequirements, "requiresOrientationEvidence"),
|
|
150
|
+
requiresScaleNormalizationEvidence: readBoolean(releaseAssetRequirements, "requiresScaleNormalizationEvidence"),
|
|
151
|
+
requiresRenderedProbe: readBoolean(releaseAssetRequirements, "requiresRenderedProbe"),
|
|
152
|
+
requiresDurableProvenance: readBoolean(releaseAssetRequirements, "requiresDurableProvenance"),
|
|
153
|
+
requiresSuitabilityReason: readBoolean(releaseAssetRequirements, "requiresSuitabilityReason")
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
function parseGameplayRequirement(value) {
|
|
158
|
+
return readEnumValue(value, "platformer.gameplayRequirements", ["movement", "jump", "checkpoint", "progression"]);
|
|
159
|
+
}
|
|
160
|
+
function parsePlayableSurfaceSource(value) {
|
|
161
|
+
return readEnumValue(value, "platformer.levelDesign.playableSurfaceSource", [
|
|
162
|
+
"asset-bound-playable-surfaces",
|
|
163
|
+
"asset-derived-playable-surfaces",
|
|
164
|
+
"authored-route-rectangles"
|
|
165
|
+
]);
|
|
166
|
+
}
|
|
167
|
+
function parseOptionalRacing(input) {
|
|
168
|
+
if (input.category !== "game-racing" && !("racing" in input))
|
|
169
|
+
return undefined;
|
|
170
|
+
const racing = readRecord(input, "racing");
|
|
171
|
+
const layoutConstraints = readRecord(racing, "layoutConstraints");
|
|
172
|
+
const raceDesign = readRecord(racing, "raceDesign");
|
|
173
|
+
const releaseAssetRequirements = readRecord(racing, "releaseAssetRequirements");
|
|
174
|
+
return {
|
|
175
|
+
vehicleAsset: readAssetId(racing, "vehicleAsset"),
|
|
176
|
+
trackAsset: readAssetId(racing, "trackAsset"),
|
|
177
|
+
cameraIntent: readEnum(racing, "cameraIntent", ["track-overview"]),
|
|
178
|
+
layoutConstraints: {
|
|
179
|
+
keepVehicleReadable: readBoolean(layoutConstraints, "keepVehicleReadable"),
|
|
180
|
+
keepTrackReadable: readBoolean(layoutConstraints, "keepTrackReadable"),
|
|
181
|
+
uiAvoidsEvidenceArea: readBoolean(layoutConstraints, "uiAvoidsEvidenceArea")
|
|
182
|
+
},
|
|
183
|
+
gameplayRequirements: readArray(racing, "gameplayRequirements").map(parseRacingGameplayRequirement),
|
|
184
|
+
raceDesign: {
|
|
185
|
+
minCheckpoints: readPositiveInteger(raceDesign, "minCheckpoints"),
|
|
186
|
+
minLaps: readPositiveInteger(raceDesign, "minLaps"),
|
|
187
|
+
minLapSeconds: readPositiveNumber(raceDesign, "minLapSeconds"),
|
|
188
|
+
routeAlignedToTrackAsset: readBoolean(raceDesign, "routeAlignedToTrackAsset"),
|
|
189
|
+
visibleTrackTopology: readEnum(raceDesign, "visibleTrackTopology", [
|
|
190
|
+
"asset-bound-road-topology",
|
|
191
|
+
"authored-route-over-visible-track",
|
|
192
|
+
"mesh-road-topology"
|
|
193
|
+
]),
|
|
194
|
+
...optionalString(raceDesign, "trackTopologyEvidence"),
|
|
195
|
+
...parseOptionalRacingTrackTopology(raceDesign),
|
|
196
|
+
...parseOptionalGameAssetPairEvidence(raceDesign),
|
|
197
|
+
carTrackScaleCompatible: readBoolean(raceDesign, "carTrackScaleCompatible"),
|
|
198
|
+
noDebugLocatorDisk: readBoolean(raceDesign, "noDebugLocatorDisk")
|
|
199
|
+
},
|
|
200
|
+
releaseAssetRequirements: {
|
|
201
|
+
vehicleRole: readEnum(releaseAssetRequirements, "vehicleRole", ["vehicle"]),
|
|
202
|
+
trackRole: readEnum(releaseAssetRequirements, "trackRole", ["track"]),
|
|
203
|
+
requiresOrientationEvidence: readBoolean(releaseAssetRequirements, "requiresOrientationEvidence"),
|
|
204
|
+
requiresScaleNormalizationEvidence: readBoolean(releaseAssetRequirements, "requiresScaleNormalizationEvidence"),
|
|
205
|
+
requiresRenderedProbe: readBoolean(releaseAssetRequirements, "requiresRenderedProbe"),
|
|
206
|
+
requiresDurableProvenance: readBoolean(releaseAssetRequirements, "requiresDurableProvenance"),
|
|
207
|
+
requiresSuitabilityReason: readBoolean(releaseAssetRequirements, "requiresSuitabilityReason")
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
function parseRacingGameplayRequirement(value) {
|
|
212
|
+
return readEnumValue(value, "racing.gameplayRequirements", ["throttle", "steering", "reset", "checkpoint", "lap", "multi-lap"]);
|
|
213
|
+
}
|
|
214
|
+
function validatePlatformerSpec(spec) {
|
|
215
|
+
if (spec.category !== "game-platformer")
|
|
216
|
+
return;
|
|
217
|
+
if (!spec.platformer)
|
|
218
|
+
throw new Error("platformer must be present for game-platformer specs");
|
|
219
|
+
const assetIds = new Set(spec.primaryAssets.map((asset) => asset.id));
|
|
220
|
+
if (!assetIds.has(spec.platformer.characterAsset))
|
|
221
|
+
throw new Error("platformer.characterAsset must reference a primary asset");
|
|
222
|
+
if (spec.platformer.characterAsset !== spec.layout.heroAsset)
|
|
223
|
+
throw new Error("platformer.characterAsset must match layout.heroAsset");
|
|
224
|
+
if (spec.platformer.worldAssets.length === 0)
|
|
225
|
+
throw new Error("platformer.worldAssets must contain at least one world or stage asset");
|
|
226
|
+
for (const assetId of spec.platformer.worldAssets) {
|
|
227
|
+
if (!assetIds.has(assetId))
|
|
228
|
+
throw new Error(`platformer.worldAssets must reference primary assets: ${assetId}`);
|
|
229
|
+
}
|
|
230
|
+
const playableSurfaceMap = spec.platformer.levelDesign.playableSurfaceMap;
|
|
231
|
+
if (playableSurfaceMap) {
|
|
232
|
+
if (!spec.platformer.worldAssets.includes(playableSurfaceMap.assetId)) {
|
|
233
|
+
throw new Error("platformer.levelDesign.playableSurfaceMap.assetId must reference a platformer world asset");
|
|
234
|
+
}
|
|
235
|
+
if (playableSurfaceMap.surfaces.length < 5) {
|
|
236
|
+
throw new Error("platformer.levelDesign.playableSurfaceMap.surfaces must contain at least 5 surfaces");
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
const assetPairEvidence = spec.platformer.levelDesign.assetPairEvidence;
|
|
240
|
+
if (assetPairEvidence) {
|
|
241
|
+
if (assetPairEvidence.category !== "platformer") {
|
|
242
|
+
throw new Error("platformer.levelDesign.assetPairEvidence.category must be platformer");
|
|
243
|
+
}
|
|
244
|
+
validateAssetPairEvidenceAssets(assetPairEvidence.assets, [spec.platformer.characterAsset, ...spec.platformer.worldAssets], "platformer.levelDesign.assetPairEvidence.assets");
|
|
245
|
+
}
|
|
246
|
+
if (!spec.platformer.gameplayRequirements.includes("movement"))
|
|
247
|
+
throw new Error("platformer.gameplayRequirements must include movement");
|
|
248
|
+
if (!spec.platformer.gameplayRequirements.includes("jump"))
|
|
249
|
+
throw new Error("platformer.gameplayRequirements must include jump");
|
|
250
|
+
if (!spec.platformer.gameplayRequirements.includes("checkpoint") && !spec.platformer.gameplayRequirements.includes("progression")) {
|
|
251
|
+
throw new Error("platformer.gameplayRequirements must include checkpoint or progression");
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
function validateRacingSpec(spec) {
|
|
255
|
+
if (spec.category !== "game-racing")
|
|
256
|
+
return;
|
|
257
|
+
if (!spec.racing)
|
|
258
|
+
throw new Error("racing must be present for game-racing specs");
|
|
259
|
+
const assetIds = new Set(spec.primaryAssets.map((asset) => asset.id));
|
|
260
|
+
if (!assetIds.has(spec.racing.vehicleAsset))
|
|
261
|
+
throw new Error("racing.vehicleAsset must reference a primary asset");
|
|
262
|
+
if (!assetIds.has(spec.racing.trackAsset))
|
|
263
|
+
throw new Error("racing.trackAsset must reference a primary asset");
|
|
264
|
+
if (spec.racing.vehicleAsset !== spec.layout.heroAsset)
|
|
265
|
+
throw new Error("racing.vehicleAsset must match layout.heroAsset");
|
|
266
|
+
if (spec.racing.vehicleAsset === spec.racing.trackAsset)
|
|
267
|
+
throw new Error("racing.vehicleAsset and racing.trackAsset must be different assets");
|
|
268
|
+
const trackTopology = spec.racing.raceDesign.trackTopology;
|
|
269
|
+
if (trackTopology) {
|
|
270
|
+
if (trackTopology.assetId !== spec.racing.trackAsset) {
|
|
271
|
+
throw new Error("racing.raceDesign.trackTopology.assetId must match racing.trackAsset");
|
|
272
|
+
}
|
|
273
|
+
if (trackTopology.roadCenterline.length < 8) {
|
|
274
|
+
throw new Error("racing.raceDesign.trackTopology.roadCenterline must contain at least 8 points");
|
|
275
|
+
}
|
|
276
|
+
if (trackTopology.checkpoints.length < spec.racing.raceDesign.minCheckpoints) {
|
|
277
|
+
throw new Error("racing.raceDesign.trackTopology.checkpoints must cover minCheckpoints");
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
const assetPairEvidence = spec.racing.raceDesign.assetPairEvidence;
|
|
281
|
+
if (assetPairEvidence) {
|
|
282
|
+
if (assetPairEvidence.category !== "racing") {
|
|
283
|
+
throw new Error("racing.raceDesign.assetPairEvidence.category must be racing");
|
|
284
|
+
}
|
|
285
|
+
validateAssetPairEvidenceAssets(assetPairEvidence.assets, [spec.racing.vehicleAsset, spec.racing.trackAsset], "racing.raceDesign.assetPairEvidence.assets");
|
|
286
|
+
}
|
|
287
|
+
if (!spec.racing.gameplayRequirements.includes("throttle"))
|
|
288
|
+
throw new Error("racing.gameplayRequirements must include throttle");
|
|
289
|
+
if (!spec.racing.gameplayRequirements.includes("steering"))
|
|
290
|
+
throw new Error("racing.gameplayRequirements must include steering");
|
|
291
|
+
if (!spec.racing.gameplayRequirements.includes("reset"))
|
|
292
|
+
throw new Error("racing.gameplayRequirements must include reset");
|
|
293
|
+
if (!spec.racing.gameplayRequirements.includes("checkpoint") && !spec.racing.gameplayRequirements.includes("lap")) {
|
|
294
|
+
throw new Error("racing.gameplayRequirements must include checkpoint or lap");
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
function validateCategoryPlan(spec) {
|
|
298
|
+
if (!spec.categoryPlan)
|
|
299
|
+
return;
|
|
300
|
+
if (spec.categoryPlan.kind !== spec.category)
|
|
301
|
+
throw new Error("categoryPlan.kind must match category");
|
|
302
|
+
if (spec.categoryPlan.primaryAsset !== spec.layout.heroAsset)
|
|
303
|
+
throw new Error("categoryPlan.primaryAsset must match layout.heroAsset");
|
|
304
|
+
const assetIds = new Set(spec.primaryAssets.map((asset) => asset.id));
|
|
305
|
+
if (!assetIds.has(spec.categoryPlan.primaryAsset))
|
|
306
|
+
throw new Error("categoryPlan.primaryAsset must reference a primary asset");
|
|
307
|
+
}
|
|
308
|
+
function isRecord(value) {
|
|
309
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
310
|
+
}
|
|
311
|
+
function readRecord(record, key) {
|
|
312
|
+
const value = record[key];
|
|
313
|
+
if (!isRecord(value))
|
|
314
|
+
throw new Error(`${key} must be an object`);
|
|
315
|
+
return value;
|
|
316
|
+
}
|
|
317
|
+
function readArray(record, key) {
|
|
318
|
+
const value = record[key];
|
|
319
|
+
if (!Array.isArray(value))
|
|
320
|
+
throw new Error(`${key} must be an array`);
|
|
321
|
+
return value;
|
|
322
|
+
}
|
|
323
|
+
function readString(record, key) {
|
|
324
|
+
const value = record[key];
|
|
325
|
+
if (typeof value !== "string" || !value.trim())
|
|
326
|
+
throw new Error(`${key} must be a non-empty string`);
|
|
327
|
+
return value;
|
|
328
|
+
}
|
|
329
|
+
function readIdentifier(record, key) {
|
|
330
|
+
const value = readString(record, key);
|
|
331
|
+
if (!/^__AURA3D_[A-Z0-9_]+__$/.test(value)) {
|
|
332
|
+
throw new Error(`${key} must be an Aura3D evidence global like __AURA3D_ROUTE_NAME__`);
|
|
333
|
+
}
|
|
334
|
+
return value;
|
|
335
|
+
}
|
|
336
|
+
function readAssetId(record, key) {
|
|
337
|
+
const value = readString(record, key);
|
|
338
|
+
return readAssetIdValue(value, key);
|
|
339
|
+
}
|
|
340
|
+
function readAssetIdValue(value, key) {
|
|
341
|
+
if (typeof value !== "string" || !value.trim())
|
|
342
|
+
throw new Error(`${key} must be a non-empty string`);
|
|
343
|
+
if (!/^[A-Za-z][A-Za-z0-9_]*$/.test(value))
|
|
344
|
+
throw new Error(`asset id must be a valid typed asset key: ${value}`);
|
|
345
|
+
return value;
|
|
346
|
+
}
|
|
347
|
+
function readStringValue(value, key) {
|
|
348
|
+
if (typeof value !== "string" || !value.trim())
|
|
349
|
+
throw new Error(`${key} must contain non-empty strings`);
|
|
350
|
+
return value;
|
|
351
|
+
}
|
|
352
|
+
function readPositiveInteger(record, key) {
|
|
353
|
+
const value = readPositiveNumber(record, key);
|
|
354
|
+
if (!Number.isInteger(value))
|
|
355
|
+
throw new Error(`${key} must be an integer`);
|
|
356
|
+
return value;
|
|
357
|
+
}
|
|
358
|
+
function readPositiveNumber(record, key) {
|
|
359
|
+
const value = record[key];
|
|
360
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0)
|
|
361
|
+
throw new Error(`${key} must be a positive number`);
|
|
362
|
+
return value;
|
|
363
|
+
}
|
|
364
|
+
function parseOptionalRacingTrackTopology(record) {
|
|
365
|
+
const topology = record.trackTopology;
|
|
366
|
+
if (topology === undefined)
|
|
367
|
+
return {};
|
|
368
|
+
if (!isRecord(topology))
|
|
369
|
+
throw new Error("trackTopology must be an object");
|
|
370
|
+
return { trackTopology: parseRacingTrackTopology(topology) };
|
|
371
|
+
}
|
|
372
|
+
function parseRacingTrackTopology(topology) {
|
|
373
|
+
return {
|
|
374
|
+
assetId: readAssetId(topology, "assetId"),
|
|
375
|
+
assetHash: readSha256(topology, "assetHash"),
|
|
376
|
+
source: parseGeometryEvidenceSource(topology.source),
|
|
377
|
+
roadCenterline: readArray(topology, "roadCenterline").map(parseRacingTopologyPoint),
|
|
378
|
+
checkpoints: readArray(topology, "checkpoints").map(parseRacingTopologyCheckpoint),
|
|
379
|
+
...optionalPositiveNumber(topology, "lapLengthMeters"),
|
|
380
|
+
estimatedLapSeconds: readPositiveNumber(topology, "estimatedLapSeconds"),
|
|
381
|
+
confidence: readUnitNumber(topology, "confidence"),
|
|
382
|
+
modelAlignment: parseRacingTopologyModelAlignment(readRecord(topology, "modelAlignment")),
|
|
383
|
+
evidence: parseGeometryEvidenceRef(readRecord(topology, "evidence"))
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
function parseRacingTopologyPoint(value) {
|
|
387
|
+
if (!isRecord(value))
|
|
388
|
+
throw new Error("racing.raceDesign.trackTopology.roadCenterline must contain objects");
|
|
389
|
+
return {
|
|
390
|
+
x: readFiniteNumber(value, "x"),
|
|
391
|
+
z: readFiniteNumber(value, "z"),
|
|
392
|
+
...optionalPositiveNumber(value, "width")
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
function parseRacingTopologyCheckpoint(value) {
|
|
396
|
+
if (!isRecord(value))
|
|
397
|
+
throw new Error("racing.raceDesign.trackTopology.checkpoints must contain objects");
|
|
398
|
+
return {
|
|
399
|
+
progress: readProgress(value, "progress"),
|
|
400
|
+
width: readPositiveNumber(value, "width")
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
function parseOptionalPlayableSurfaceMap(record) {
|
|
404
|
+
const playableSurfaceMap = record.playableSurfaceMap;
|
|
405
|
+
if (playableSurfaceMap === undefined)
|
|
406
|
+
return {};
|
|
407
|
+
if (!isRecord(playableSurfaceMap))
|
|
408
|
+
throw new Error("playableSurfaceMap must be an object");
|
|
409
|
+
return { playableSurfaceMap: parsePlayableSurfaceMap(playableSurfaceMap) };
|
|
410
|
+
}
|
|
411
|
+
function parsePlayableSurfaceMap(map) {
|
|
412
|
+
return {
|
|
413
|
+
assetId: readAssetId(map, "assetId"),
|
|
414
|
+
assetHash: readSha256(map, "assetHash"),
|
|
415
|
+
source: parseGeometryEvidenceSource(map.source),
|
|
416
|
+
surfaces: readArray(map, "surfaces").map(parsePlayableSurface),
|
|
417
|
+
levelLength: readPositiveNumber(map, "levelLength"),
|
|
418
|
+
estimatedCompletionSeconds: readPositiveNumber(map, "estimatedCompletionSeconds"),
|
|
419
|
+
characterScaleRatio: readPositiveNumber(map, "characterScaleRatio"),
|
|
420
|
+
confidence: readUnitNumber(map, "confidence"),
|
|
421
|
+
modelAlignment: parsePlatformerSurfaceModelAlignment(readRecord(map, "modelAlignment")),
|
|
422
|
+
evidence: parseGeometryEvidenceRef(readRecord(map, "evidence"))
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
function parsePlayableSurface(value) {
|
|
426
|
+
if (!isRecord(value))
|
|
427
|
+
throw new Error("platformer.levelDesign.playableSurfaceMap.surfaces must contain objects");
|
|
428
|
+
return {
|
|
429
|
+
id: readString(value, "id"),
|
|
430
|
+
x: readFiniteNumber(value, "x"),
|
|
431
|
+
y: readFiniteNumber(value, "y"),
|
|
432
|
+
width: readPositiveNumber(value, "width"),
|
|
433
|
+
height: readPositiveNumber(value, "height"),
|
|
434
|
+
kind: readEnum(value, "kind", ["ground", "platform", "moving", "hazard", "checkpoint", "finish"])
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
function parseGeometryEvidenceRef(value) {
|
|
438
|
+
return {
|
|
439
|
+
sourceAsset: readString(value, "sourceAsset"),
|
|
440
|
+
...optionalString(value, "renderedProbe"),
|
|
441
|
+
...optionalString(value, "routeOverlay"),
|
|
442
|
+
notes: readString(value, "notes")
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
function parseGeometryModelBounds(value) {
|
|
446
|
+
return {
|
|
447
|
+
min: readVec3(value, "min"),
|
|
448
|
+
max: readVec3(value, "max")
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
function parseRacingTopologyModelAlignment(value) {
|
|
452
|
+
const gamePoint = readRecord(value, "gamePoint");
|
|
453
|
+
return {
|
|
454
|
+
source: parseGeometryEvidenceSource(value.source),
|
|
455
|
+
modelBounds: parseGeometryModelBounds(readRecord(value, "modelBounds")),
|
|
456
|
+
modelPoint: readVec3(value, "modelPoint"),
|
|
457
|
+
gamePoint: {
|
|
458
|
+
x: readFiniteNumber(gamePoint, "x"),
|
|
459
|
+
z: readFiniteNumber(gamePoint, "z")
|
|
460
|
+
},
|
|
461
|
+
...parseOptionalRacingModelAnchorPairs(value),
|
|
462
|
+
evidence: parseModelAlignmentEvidence(readRecord(value, "evidence"))
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
function parsePlatformerSurfaceModelAlignment(value) {
|
|
466
|
+
const gamePoint = readRecord(value, "gamePoint");
|
|
467
|
+
return {
|
|
468
|
+
source: parseGeometryEvidenceSource(value.source),
|
|
469
|
+
modelBounds: parseGeometryModelBounds(readRecord(value, "modelBounds")),
|
|
470
|
+
modelPoint: readVec3(value, "modelPoint"),
|
|
471
|
+
gamePoint: {
|
|
472
|
+
x: readFiniteNumber(gamePoint, "x"),
|
|
473
|
+
y: readFiniteNumber(gamePoint, "y")
|
|
474
|
+
},
|
|
475
|
+
...parseOptionalPlatformerModelAnchorPairs(value),
|
|
476
|
+
evidence: parseModelAlignmentEvidence(readRecord(value, "evidence"))
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
function parseOptionalRacingModelAnchorPairs(value) {
|
|
480
|
+
const anchors = value.anchorPairs;
|
|
481
|
+
if (anchors === undefined)
|
|
482
|
+
return {};
|
|
483
|
+
return {
|
|
484
|
+
anchorPairs: readArray(value, "anchorPairs").map((entry) => {
|
|
485
|
+
if (!isRecord(entry))
|
|
486
|
+
throw new Error("modelAlignment.anchorPairs must contain objects");
|
|
487
|
+
const gamePoint = readRecord(entry, "gamePoint");
|
|
488
|
+
return {
|
|
489
|
+
id: readString(entry, "id"),
|
|
490
|
+
modelPoint: readVec3(entry, "modelPoint"),
|
|
491
|
+
gamePoint: {
|
|
492
|
+
x: readFiniteNumber(gamePoint, "x"),
|
|
493
|
+
z: readFiniteNumber(gamePoint, "z")
|
|
494
|
+
}
|
|
495
|
+
};
|
|
496
|
+
})
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
function parseOptionalPlatformerModelAnchorPairs(value) {
|
|
500
|
+
const anchors = value.anchorPairs;
|
|
501
|
+
if (anchors === undefined)
|
|
502
|
+
return {};
|
|
503
|
+
return {
|
|
504
|
+
anchorPairs: readArray(value, "anchorPairs").map((entry) => {
|
|
505
|
+
if (!isRecord(entry))
|
|
506
|
+
throw new Error("modelAlignment.anchorPairs must contain objects");
|
|
507
|
+
const gamePoint = readRecord(entry, "gamePoint");
|
|
508
|
+
return {
|
|
509
|
+
id: readString(entry, "id"),
|
|
510
|
+
modelPoint: readVec3(entry, "modelPoint"),
|
|
511
|
+
gamePoint: {
|
|
512
|
+
x: readFiniteNumber(gamePoint, "x"),
|
|
513
|
+
y: readFiniteNumber(gamePoint, "y")
|
|
514
|
+
}
|
|
515
|
+
};
|
|
516
|
+
})
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
function parseModelAlignmentEvidence(value) {
|
|
520
|
+
return {
|
|
521
|
+
...optionalString(value, "routeOverlay"),
|
|
522
|
+
notes: readString(value, "notes")
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
function parseOptionalGameAssetPairEvidence(record) {
|
|
526
|
+
const evidence = record.assetPairEvidence;
|
|
527
|
+
if (evidence === undefined)
|
|
528
|
+
return {};
|
|
529
|
+
if (!isRecord(evidence))
|
|
530
|
+
throw new Error("assetPairEvidence must be an object");
|
|
531
|
+
return { assetPairEvidence: parseGameAssetPairEvidence(evidence) };
|
|
532
|
+
}
|
|
533
|
+
function parseGameAssetPairEvidence(evidence) {
|
|
534
|
+
return {
|
|
535
|
+
category: readEnum(evidence, "category", ["racing", "platformer"]),
|
|
536
|
+
assets: readArray(evidence, "assets").map((value) => readAssetIdValue(value, "assetPairEvidence.assets")),
|
|
537
|
+
screenshotEvidence: readString(evidence, "screenshotEvidence"),
|
|
538
|
+
...optionalString(evidence, "routePrimaryProbe"),
|
|
539
|
+
...optionalString(evidence, "screenshotSha256"),
|
|
540
|
+
verdict: readEnum(evidence, "verdict", ["pass", "fail"]),
|
|
541
|
+
notes: readString(evidence, "notes"),
|
|
542
|
+
blockers: readArray(evidence, "blockers").map((value) => readStringValue(value, "assetPairEvidence.blockers"))
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
function validateAssetPairEvidenceAssets(actualAssetIds, expectedAssetIds, label) {
|
|
546
|
+
for (const assetId of expectedAssetIds) {
|
|
547
|
+
if (!actualAssetIds.includes(assetId)) {
|
|
548
|
+
throw new Error(`${label} must include ${assetId}`);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
function parseGeometryEvidenceSource(value) {
|
|
553
|
+
return readEnumValue(value, "geometry evidence source", [
|
|
554
|
+
"asset-mesh-extracted",
|
|
555
|
+
"manifest-authored",
|
|
556
|
+
"manifest-authored-overlay-validated",
|
|
557
|
+
"compiler-authored",
|
|
558
|
+
"compiler-authored-overlay-validated"
|
|
559
|
+
]);
|
|
560
|
+
}
|
|
561
|
+
function readSha256(record, key) {
|
|
562
|
+
const value = readString(record, key);
|
|
563
|
+
if (!/^sha256-[a-f0-9]{64}$/.test(value))
|
|
564
|
+
throw new Error(`${key} must be a sha256- hash`);
|
|
565
|
+
return value;
|
|
566
|
+
}
|
|
567
|
+
function readFiniteNumber(record, key) {
|
|
568
|
+
const value = record[key];
|
|
569
|
+
return readFiniteNumberValue(value, key);
|
|
570
|
+
}
|
|
571
|
+
function readFiniteNumberValue(value, key) {
|
|
572
|
+
if (typeof value !== "number" || !Number.isFinite(value))
|
|
573
|
+
throw new Error(`${key} must be a finite number`);
|
|
574
|
+
return value;
|
|
575
|
+
}
|
|
576
|
+
function readVec3(record, key) {
|
|
577
|
+
const value = readArray(record, key);
|
|
578
|
+
if (value.length !== 3)
|
|
579
|
+
throw new Error(`${key} must contain exactly three numbers`);
|
|
580
|
+
return [
|
|
581
|
+
readFiniteNumberValue(value[0], `${key}.0`),
|
|
582
|
+
readFiniteNumberValue(value[1], `${key}.1`),
|
|
583
|
+
readFiniteNumberValue(value[2], `${key}.2`)
|
|
584
|
+
];
|
|
585
|
+
}
|
|
586
|
+
function readProgress(record, key) {
|
|
587
|
+
const value = readFiniteNumber(record, key);
|
|
588
|
+
if (value < 0 || value > 1)
|
|
589
|
+
throw new Error(`${key} must be between 0 and 1`);
|
|
590
|
+
return value;
|
|
591
|
+
}
|
|
592
|
+
function readUnitNumber(record, key) {
|
|
593
|
+
const value = readFiniteNumber(record, key);
|
|
594
|
+
if (value < 0 || value > 1)
|
|
595
|
+
throw new Error(`${key} must be between 0 and 1`);
|
|
596
|
+
return value;
|
|
597
|
+
}
|
|
598
|
+
function optionalPositiveNumber(record, key) {
|
|
599
|
+
if (!(key in record))
|
|
600
|
+
return {};
|
|
601
|
+
return { [key]: readPositiveNumber(record, key) };
|
|
602
|
+
}
|
|
603
|
+
function readTypedRef(record) {
|
|
604
|
+
const id = readString(record, "id");
|
|
605
|
+
const typedRef = readString(record, "typedRef");
|
|
606
|
+
if (typedRef !== `assets.${id}`)
|
|
607
|
+
throw new Error(`typedRef must equal assets.${id}`);
|
|
608
|
+
return typedRef;
|
|
609
|
+
}
|
|
610
|
+
function readBoolean(record, key) {
|
|
611
|
+
const value = record[key];
|
|
612
|
+
if (typeof value !== "boolean")
|
|
613
|
+
throw new Error(`${key} must be a boolean`);
|
|
614
|
+
return value;
|
|
615
|
+
}
|
|
616
|
+
function readLiteral(record, key, expected) {
|
|
617
|
+
const value = record[key];
|
|
618
|
+
if (value !== expected)
|
|
619
|
+
throw new Error(`${key} must be ${expected}`);
|
|
620
|
+
return expected;
|
|
621
|
+
}
|
|
622
|
+
function readEnum(record, key, allowed) {
|
|
623
|
+
const value = readString(record, key);
|
|
624
|
+
return readEnumValue(value, key, allowed);
|
|
625
|
+
}
|
|
626
|
+
function readEnumValue(value, key, allowed) {
|
|
627
|
+
if (typeof value !== "string" || !value.trim())
|
|
628
|
+
throw new Error(`${key} must be a non-empty string`);
|
|
629
|
+
for (const allowedValue of allowed) {
|
|
630
|
+
if (value === allowedValue)
|
|
631
|
+
return allowedValue;
|
|
632
|
+
}
|
|
633
|
+
throw new Error(`${key} must be one of ${allowed.join(", ")}`);
|
|
634
|
+
}
|
|
635
|
+
function optionalString(record, key) {
|
|
636
|
+
const value = record[key];
|
|
637
|
+
return typeof value === "string" && value.trim() ? { [key]: value } : {};
|
|
638
|
+
}
|
|
639
|
+
function optionalBoolean(record, key) {
|
|
640
|
+
const value = record[key];
|
|
641
|
+
return typeof value === "boolean" ? { [key]: value } : {};
|
|
642
|
+
}
|
|
643
|
+
function optionalEnum(record, key, allowed) {
|
|
644
|
+
const value = record[key];
|
|
645
|
+
return value === undefined ? {} : { [key]: readEnumValue(value, key, allowed) };
|
|
646
|
+
}
|
|
647
|
+
function optionalStringRecord(record, key) {
|
|
648
|
+
const value = record[key];
|
|
649
|
+
if (value === undefined)
|
|
650
|
+
return {};
|
|
651
|
+
if (!isRecord(value))
|
|
652
|
+
throw new Error(`${key} must be an object`);
|
|
653
|
+
const result = {};
|
|
654
|
+
for (const [entryKey, entryValue] of Object.entries(value)) {
|
|
655
|
+
const assetId = readAssetIdValue(entryKey, `${key} key`);
|
|
656
|
+
if (typeof entryValue !== "string" || !entryValue.trim())
|
|
657
|
+
throw new Error(`${key}.${assetId} must be a non-empty string`);
|
|
658
|
+
result[assetId] = entryValue;
|
|
659
|
+
}
|
|
660
|
+
return { [key]: result };
|
|
661
|
+
}
|
|
662
|
+
//# sourceMappingURL=showcase-spec-parser.js.map
|