create-aura3d 1.3.3 → 1.4.1

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.
Files changed (166) hide show
  1. package/README.md +113 -0
  2. package/dist/cli.js +10 -0
  3. package/dist/cli.js.map +1 -1
  4. package/dist/index.d.ts +1 -1
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +3 -1
  7. package/dist/index.js.map +1 -1
  8. package/dist/showcase-spec-artifacts.d.ts +105 -0
  9. package/dist/showcase-spec-artifacts.d.ts.map +1 -0
  10. package/dist/showcase-spec-artifacts.js +143 -0
  11. package/dist/showcase-spec-artifacts.js.map +1 -0
  12. package/dist/showcase-spec-category-plan.d.ts +15 -0
  13. package/dist/showcase-spec-category-plan.d.ts.map +1 -0
  14. package/dist/showcase-spec-category-plan.js +45 -0
  15. package/dist/showcase-spec-category-plan.js.map +1 -0
  16. package/dist/showcase-spec-compiler.d.ts +5 -0
  17. package/dist/showcase-spec-compiler.d.ts.map +1 -0
  18. package/dist/showcase-spec-compiler.js +240 -0
  19. package/dist/showcase-spec-compiler.js.map +1 -0
  20. package/dist/showcase-spec-evidence.d.ts +7 -0
  21. package/dist/showcase-spec-evidence.d.ts.map +1 -0
  22. package/dist/showcase-spec-evidence.js +881 -0
  23. package/dist/showcase-spec-evidence.js.map +1 -0
  24. package/dist/showcase-spec-game-geometry-extractor.d.ts +21 -0
  25. package/dist/showcase-spec-game-geometry-extractor.d.ts.map +1 -0
  26. package/dist/showcase-spec-game-geometry-extractor.js +725 -0
  27. package/dist/showcase-spec-game-geometry-extractor.js.map +1 -0
  28. package/dist/showcase-spec-game-replacement-evidence.d.ts +14 -0
  29. package/dist/showcase-spec-game-replacement-evidence.d.ts.map +1 -0
  30. package/dist/showcase-spec-game-replacement-evidence.js +219 -0
  31. package/dist/showcase-spec-game-replacement-evidence.js.map +1 -0
  32. package/dist/showcase-spec-game-template-evidence.d.ts +72 -0
  33. package/dist/showcase-spec-game-template-evidence.d.ts.map +1 -0
  34. package/dist/showcase-spec-game-template-evidence.js +435 -0
  35. package/dist/showcase-spec-game-template-evidence.js.map +1 -0
  36. package/dist/showcase-spec-gameplay-evidence.d.ts +3 -0
  37. package/dist/showcase-spec-gameplay-evidence.d.ts.map +1 -0
  38. package/dist/showcase-spec-gameplay-evidence.js +108 -0
  39. package/dist/showcase-spec-gameplay-evidence.js.map +1 -0
  40. package/dist/showcase-spec-parser.d.ts +3 -0
  41. package/dist/showcase-spec-parser.d.ts.map +1 -0
  42. package/dist/showcase-spec-parser.js +690 -0
  43. package/dist/showcase-spec-parser.js.map +1 -0
  44. package/dist/showcase-spec-platformer-artifacts.d.ts +3 -0
  45. package/dist/showcase-spec-platformer-artifacts.d.ts.map +1 -0
  46. package/dist/showcase-spec-platformer-artifacts.js +274 -0
  47. package/dist/showcase-spec-platformer-artifacts.js.map +1 -0
  48. package/dist/showcase-spec-racing-artifacts.d.ts +3 -0
  49. package/dist/showcase-spec-racing-artifacts.d.ts.map +1 -0
  50. package/dist/showcase-spec-racing-artifacts.js +273 -0
  51. package/dist/showcase-spec-racing-artifacts.js.map +1 -0
  52. package/dist/showcase-spec-replacement-apply.d.ts +10 -0
  53. package/dist/showcase-spec-replacement-apply.d.ts.map +1 -0
  54. package/dist/showcase-spec-replacement-apply.js +166 -0
  55. package/dist/showcase-spec-replacement-apply.js.map +1 -0
  56. package/dist/showcase-spec-replacement-candidates.d.ts +16 -0
  57. package/dist/showcase-spec-replacement-candidates.d.ts.map +1 -0
  58. package/dist/showcase-spec-replacement-candidates.js +244 -0
  59. package/dist/showcase-spec-replacement-candidates.js.map +1 -0
  60. package/dist/showcase-spec-replacement-game-geometry.d.ts +4 -0
  61. package/dist/showcase-spec-replacement-game-geometry.d.ts.map +1 -0
  62. package/dist/showcase-spec-replacement-game-geometry.js +175 -0
  63. package/dist/showcase-spec-replacement-game-geometry.js.map +1 -0
  64. package/dist/showcase-spec-replacement-manifest.d.ts +41 -0
  65. package/dist/showcase-spec-replacement-manifest.d.ts.map +1 -0
  66. package/dist/showcase-spec-replacement-manifest.js +62 -0
  67. package/dist/showcase-spec-replacement-manifest.js.map +1 -0
  68. package/dist/showcase-spec-replacement-values.d.ts +9 -0
  69. package/dist/showcase-spec-replacement-values.d.ts.map +1 -0
  70. package/dist/showcase-spec-replacement-values.js +41 -0
  71. package/dist/showcase-spec-replacement-values.js.map +1 -0
  72. package/dist/showcase-spec-replacement.d.ts +13 -0
  73. package/dist/showcase-spec-replacement.d.ts.map +1 -0
  74. package/dist/showcase-spec-replacement.js +144 -0
  75. package/dist/showcase-spec-replacement.js.map +1 -0
  76. package/dist/showcase-spec-types.d.ts +319 -0
  77. package/dist/showcase-spec-types.d.ts.map +1 -0
  78. package/dist/showcase-spec-types.js +2 -0
  79. package/dist/showcase-spec-types.js.map +1 -0
  80. package/package.json +45 -11
  81. package/templates/animation-channel/package.json +1 -1
  82. package/templates/animation-studio/package.json +5 -5
  83. package/templates/animation-studio/scripts/animation-scene.ts +1 -1
  84. package/templates/animation-studio/scripts/author-office-scene.ts +1 -1
  85. package/templates/animation-studio/scripts/build-characters.ts +1 -1
  86. package/templates/animation-studio/src/director/director-heuristics.ts +2 -2
  87. package/templates/animation-studio/src/director/{prompt-to-scene.ts → prompt-scene.ts} +1 -1
  88. package/templates/character-controller/package.json +3 -3
  89. package/templates/cinematic-scene/package.json +1 -1
  90. package/templates/cinematic-scene/tests/screenshot.spec.ts +2 -4
  91. package/templates/episode-builder/package.json +1 -1
  92. package/templates/falling-blocks-starter/README.md +18 -0
  93. package/templates/falling-blocks-starter/index.html +23 -0
  94. package/templates/falling-blocks-starter/package.json +21 -0
  95. package/templates/falling-blocks-starter/playwright.config.ts +12 -0
  96. package/templates/falling-blocks-starter/public/aura-assets/showcaseBlockfallCabinet.679d52fe.glb +0 -0
  97. package/templates/falling-blocks-starter/public/aura-assets/showcaseBlockfallCabinet.thumb.svg +1 -0
  98. package/templates/falling-blocks-starter/src/aura-assets.ts +20 -0
  99. package/templates/falling-blocks-starter/src/main.ts +358 -0
  100. package/templates/falling-blocks-starter/tests/playable.spec.ts +85 -0
  101. package/templates/falling-blocks-starter/tests/route-health.spec.ts +34 -0
  102. package/templates/falling-blocks-starter/tsconfig.json +13 -0
  103. package/templates/fighting-game/package.json +1 -1
  104. package/templates/mini-game/README.md +13 -0
  105. package/templates/mini-game/index.html +1 -1
  106. package/templates/mini-game/package.json +2 -2
  107. package/templates/mini-game/src/main.ts +251 -21
  108. package/templates/mini-game/tests/playable.spec.ts +49 -0
  109. package/templates/mini-game/tests/screenshot.spec.ts +37 -32
  110. package/templates/product-viewer/package.json +1 -1
  111. package/templates/product-viewer/tests/screenshot.spec.ts +3 -5
  112. package/templates/prompt-animation-channel/package.json +1 -1
  113. package/templates/racing-starter/README.md +27 -0
  114. package/templates/racing-starter/index.html +23 -0
  115. package/templates/racing-starter/package.json +21 -0
  116. package/templates/racing-starter/playwright.config.ts +12 -0
  117. package/templates/racing-starter/public/aura-assets/showcaseCleanSportsCar.1c69283f.glb +0 -0
  118. package/templates/racing-starter/public/aura-assets/showcaseCleanSportsCar.thumb.svg +1 -0
  119. package/templates/racing-starter/public/aura-assets/showcaseReadableKartCircuit.5cbb912e.glb +0 -0
  120. package/templates/racing-starter/public/aura-assets/showcaseReadableKartCircuit.thumb.svg +1 -0
  121. package/templates/racing-starter/src/aura-assets.ts +36 -0
  122. package/templates/racing-starter/src/main.ts +280 -0
  123. package/templates/racing-starter/tests/playable.spec.ts +93 -0
  124. package/templates/racing-starter/tests/route-health.spec.ts +40 -0
  125. package/templates/racing-starter/tsconfig.json +13 -0
  126. package/templates/three-compat-architecture-interior/package.json +1 -1
  127. package/templates/three-compat-asset-inspector/package.json +1 -1
  128. package/templates/three-compat-character-viewer/package.json +1 -1
  129. package/templates/three-compat-custom-threejs-migration/package.json +1 -1
  130. package/templates/three-compat-large-scene/package.json +1 -1
  131. package/templates/three-compat-material-authoring/package.json +1 -1
  132. package/templates/three-compat-postprocess-scene/package.json +1 -1
  133. package/templates/three-compat-premium-product-viewer/package.json +1 -1
  134. package/templates/animation-channel/public/aura-assets/moon-garden-feature-frame.png +0 -0
  135. package/templates/animation-channel/test-results/.last-run.json +0 -4
  136. package/templates/animation-channel/tests/reports/prompt-animation/cartoon-sample-episode.png +0 -0
  137. package/templates/animation-studio/dist/episodes/prompt-to-scene-proof/two-robots-fixing-a-car.document.json +0 -680
  138. package/templates/animation-studio/dist/episodes/scene/episode-3d.webm +0 -0
  139. package/templates/animation-studio/dist/episodes/scene/frames/action.png +0 -0
  140. package/templates/animation-studio/dist/episodes/scene/frames/dialogue.png +0 -0
  141. package/templates/animation-studio/dist/episodes/scene/frames/final.png +0 -0
  142. package/templates/animation-studio/dist/episodes/scene/frames/first.png +0 -0
  143. package/templates/animation-studio/dist/episodes/scene/frames/mouth-closed.png +0 -0
  144. package/templates/animation-studio/dist/episodes/scene/frames/mouth-open.png +0 -0
  145. package/templates/animation-studio/dist/episodes/scene/render-live-summary.json +0 -9763
  146. package/templates/animation-studio/dist/episodes/scene/skeleton-overlays/broken.png +0 -0
  147. package/templates/animation-studio/dist/episodes/scene/skeleton-overlays/chefs.png +0 -0
  148. package/templates/animation-studio/dist/episodes/scene/skeleton-overlays/customer.png +0 -0
  149. package/templates/animation-studio/dist/episodes/scene/skeleton-overlays/friend-1.png +0 -0
  150. package/templates/animation-studio/dist/episodes/scene/skeleton-overlays/friend-2.png +0 -0
  151. package/templates/animation-studio/dist/episodes/scene/skeleton-overlays/worker-1.png +0 -0
  152. package/templates/animation-studio/dist/episodes/scene/skeleton-overlays/worker-2.png +0 -0
  153. package/templates/animation-studio/dist/generated/second-scene.document.json +0 -1210
  154. package/templates/animation-studio/dist/generated/studio-edited.document.json +0 -1196
  155. package/templates/animation-studio/dist/scene/inshader-accept.document.json +0 -1
  156. package/templates/animation-studio/dist/scene/inshader-test.document.json +0 -1
  157. package/templates/animation-studio/dist/scene/integ.document.json +0 -1
  158. package/templates/animation-studio/dist/scene/working.document.json +0 -673
  159. package/templates/animation-studio/dist/scene/working.history.json +0 -1
  160. package/templates/animation-studio/node_modules.publish-aside/.vite/deps/_metadata.json +0 -8
  161. package/templates/animation-studio/node_modules.publish-aside/.vite/deps/package.json +0 -3
  162. package/templates/animation-studio/package-lock.json +0 -3503
  163. package/templates/animation-studio/studio/dist/assets/index-C7f8tHfi.css +0 -1
  164. package/templates/animation-studio/studio/dist/assets/index-CLo3GXyN.js +0 -43
  165. package/templates/animation-studio/studio/dist/index.html +0 -19
  166. package/templates/animation-studio/tests/reports/animation-studio/hifi-cast-validation.json +0 -938
@@ -0,0 +1,881 @@
1
+ import { createHash } from "node:crypto";
2
+ import { existsSync, readFileSync, statSync } from "node:fs";
3
+ import { isAbsolute, join } from "node:path";
4
+ import { extractPlatformerPlayableSurfaceMapFromAsset, extractRacingTrackTopologyFromAsset } from "./showcase-spec-game-geometry-extractor.js";
5
+ import { validateGameGameplayProof } from "./showcase-spec-gameplay-evidence.js";
6
+ const PNG_SIGNATURE = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a];
7
+ export function compileEvidenceBlockers(spec, context = {}) {
8
+ if (spec.publicStatus !== "release-ready candidate")
9
+ return [];
10
+ const blockers = [];
11
+ blockers.push(...validateEvidenceFile("evidence:route-primary-screenshot", spec.evidence.routePrimaryScreenshot, context));
12
+ blockers.push(...validateRoutePrimaryProbe(spec));
13
+ blockers.push(...validateGameplayProof(spec));
14
+ blockers.push(...validateReleaseAssetProbes(spec));
15
+ blockers.push(...validateDeployEvidence(spec));
16
+ blockers.push(...validateCategoryTemplateEvidence(spec, context));
17
+ for (const capability of spec.capabilities) {
18
+ if (capability.evidence)
19
+ blockers.push(...validateEvidenceFile(`capability:${capability.name}:evidence`, capability.evidence, context));
20
+ }
21
+ return blockers;
22
+ }
23
+ function validateCategoryTemplateEvidence(spec, context) {
24
+ if (spec.category === "game-racing") {
25
+ const evidencePath = spec.racing?.raceDesign.trackTopologyEvidence;
26
+ const blockers = [];
27
+ if (!evidencePath) {
28
+ blockers.push("evidence:racing-track-topology:missing");
29
+ }
30
+ else {
31
+ const parsed = readJsonFile(evidencePath, "evidence:racing-track-topology", context);
32
+ blockers.push(...(parsed.ok ? validateRacingTrackTopologyEvidence(spec, parsed.value, context) : parsed.blockers));
33
+ }
34
+ blockers.push(...validateGameAssetPairEvidence({
35
+ label: "racing-asset-pair",
36
+ spec,
37
+ evidence: spec.racing?.raceDesign.assetPairEvidence,
38
+ expectedCategory: "racing",
39
+ expectedAssets: spec.racing ? [spec.racing.vehicleAsset, spec.racing.trackAsset] : [],
40
+ context
41
+ }));
42
+ return blockers;
43
+ }
44
+ if (spec.category === "game-platformer") {
45
+ const evidencePath = spec.platformer?.levelDesign.playableSurfaceEvidence;
46
+ const blockers = [];
47
+ if (!evidencePath) {
48
+ blockers.push("evidence:platformer-playable-surface:missing");
49
+ }
50
+ else {
51
+ const parsed = readJsonFile(evidencePath, "evidence:platformer-playable-surface", context);
52
+ blockers.push(...(parsed.ok ? validatePlatformerPlayableSurfaceEvidence(spec, parsed.value, context) : parsed.blockers));
53
+ }
54
+ blockers.push(...validateGameAssetPairEvidence({
55
+ label: "platformer-asset-pair",
56
+ spec,
57
+ evidence: spec.platformer?.levelDesign.assetPairEvidence,
58
+ expectedCategory: "platformer",
59
+ expectedAssets: spec.platformer ? [spec.platformer.characterAsset, ...spec.platformer.worldAssets] : [],
60
+ context
61
+ }));
62
+ return blockers;
63
+ }
64
+ return [];
65
+ }
66
+ function validateGameplayProof(spec) {
67
+ if (!isGameSpec(spec))
68
+ return [];
69
+ if (!spec.evidence.gameplayProof)
70
+ return ["evidence:gameplay-proof:missing"];
71
+ const parsed = readJsonFile(spec.evidence.gameplayProof, "evidence:gameplay-proof");
72
+ if (!parsed.ok)
73
+ return parsed.blockers;
74
+ if (!isRecord(parsed.value))
75
+ return ["evidence:gameplay-proof:invalid-json"];
76
+ return [
77
+ ...(parsed.value.pass === true ? [] : ["evidence:gameplay-proof:not-passing"]),
78
+ ...validateGameGameplayProof(spec, parsed.value)
79
+ ];
80
+ }
81
+ function validateReleaseAssetProbes(spec) {
82
+ if (!isGameSpec(spec))
83
+ return [];
84
+ const blockers = [];
85
+ const releaseAssetProbes = spec.evidence.releaseAssetProbes;
86
+ for (const asset of spec.primaryAssets) {
87
+ const probePath = releaseAssetProbes?.[asset.id];
88
+ if (!probePath) {
89
+ blockers.push(`evidence:release-asset-probe:missing:${asset.id}`);
90
+ continue;
91
+ }
92
+ const parsed = readJsonFile(probePath, `evidence:release-asset-probe:${asset.id}`);
93
+ if (!parsed.ok) {
94
+ blockers.push(...parsed.blockers);
95
+ continue;
96
+ }
97
+ blockers.push(...validateReleaseAssetProbe(asset.id, parsed.value));
98
+ }
99
+ return blockers;
100
+ }
101
+ function isGameSpec(spec) {
102
+ return spec.category === "game-platformer" || spec.category === "game-racing";
103
+ }
104
+ function validateReleaseAssetProbe(assetId, value) {
105
+ if (!isRecord(value))
106
+ return [`evidence:release-asset-probe:invalid-json:${assetId}`];
107
+ const evidence = value.evidence;
108
+ if (!isRecord(evidence))
109
+ return [`evidence:release-asset-probe:missing-evidence:${assetId}`];
110
+ const asset = evidence.asset;
111
+ const blockers = [];
112
+ if (!isRecord(asset) || asset.id !== assetId)
113
+ blockers.push(`evidence:release-asset-probe:asset-mismatch:${assetId}`);
114
+ if (evidence.pass !== true)
115
+ blockers.push(`evidence:release-asset-probe:not-passing:${assetId}`);
116
+ if (Array.isArray(evidence.failures) && evidence.failures.length > 0)
117
+ blockers.push(`evidence:release-asset-probe:has-failures:${assetId}`);
118
+ const renderedProbe = value.renderedProbe;
119
+ if (!isRecord(renderedProbe)) {
120
+ blockers.push(`evidence:release-asset-probe:missing-rendered-probe:${assetId}`);
121
+ }
122
+ else {
123
+ if (typeof renderedProbe.sha256 !== "string" || !renderedProbe.sha256.startsWith("sha256-")) {
124
+ blockers.push(`evidence:release-asset-probe:missing-sha256:${assetId}`);
125
+ }
126
+ if (!isRecord(renderedProbe.foregroundBounds))
127
+ blockers.push(`evidence:release-asset-probe:missing-foreground-bounds:${assetId}`);
128
+ }
129
+ return blockers;
130
+ }
131
+ function validateRoutePrimaryProbe(spec) {
132
+ const parsed = readJsonFile(spec.evidence.routePrimaryProbe, "evidence:route-primary-probe");
133
+ if (!parsed.ok)
134
+ return parsed.blockers;
135
+ if (!isRecord(parsed.value))
136
+ return ["evidence:route-primary-probe:invalid-json"];
137
+ const blockers = [];
138
+ if (parsed.value.pass !== true)
139
+ blockers.push("evidence:route-primary-probe:not-passing");
140
+ if (parsed.value.routePrimaryHeroAsset !== spec.layout.heroAsset)
141
+ blockers.push("evidence:route-primary-probe:hero-mismatch");
142
+ if (!routePrimaryAssetsInclude(parsed.value, spec.layout.heroAsset))
143
+ blockers.push("evidence:route-primary-probe:missing-hero-asset");
144
+ if (!routePrimaryScreenshotMatches(parsed.value, spec.evidence.routePrimaryScreenshot)) {
145
+ blockers.push("evidence:route-primary-probe:screenshot-mismatch");
146
+ }
147
+ return blockers;
148
+ }
149
+ function validateDeployEvidence(spec) {
150
+ const blockers = [];
151
+ if (!isReleaseDeployCommand(spec))
152
+ blockers.push("evidence:deploy-command:not-release-check-deploy");
153
+ if (!spec.evidence.deployEvidence)
154
+ return [...blockers, "evidence:deploy-artifact:missing"];
155
+ const parsed = readJsonFile(spec.evidence.deployEvidence, "evidence:deploy-artifact");
156
+ if (!parsed.ok)
157
+ return [...blockers, ...parsed.blockers];
158
+ return [...blockers, ...validateDeployReport(spec, parsed.value)];
159
+ }
160
+ function isReleaseDeployCommand(spec) {
161
+ const command = spec.evidence.deployCommand;
162
+ return command.startsWith("pnpm exec tsx --tsconfig tsconfig.base.json packages/aura3d-cli/src/cli.ts check-deploy ")
163
+ && command.includes(" --release")
164
+ && command.includes(" --dist ")
165
+ && command.includes(" --source ")
166
+ && spec.primaryAssets.every((asset) => command.includes(` --asset ${asset.id}`));
167
+ }
168
+ function validateEvidenceFile(label, filePath, context = {}) {
169
+ if (!isSafeRelativePath(filePath))
170
+ return [`${label}:unsafe-path`];
171
+ const existingPath = resolveEvidencePath(filePath, context);
172
+ if (!existingPath)
173
+ return [`${label}:missing-file`];
174
+ if (statSync(existingPath).size <= 0)
175
+ return [`${label}:empty-file`];
176
+ return [];
177
+ }
178
+ function validateGameAssetPairEvidence(input) {
179
+ const { label, spec, evidence, expectedCategory, expectedAssets, context } = input;
180
+ if (!evidence)
181
+ return [`evidence:${label}:missing`];
182
+ const blockers = [];
183
+ if (evidence.category !== expectedCategory) {
184
+ blockers.push(`evidence:${label}:category-mismatch:${evidence.category}`);
185
+ }
186
+ for (const assetId of expectedAssets) {
187
+ if (!evidence.assets.includes(assetId))
188
+ blockers.push(`evidence:${label}:asset-missing:${assetId}`);
189
+ }
190
+ blockers.push(...validateEvidenceFile(`evidence:${label}:screenshot`, evidence.screenshotEvidence, context));
191
+ if (evidence.verdict === "pass")
192
+ blockers.push(...validateCurrentRoutePrimaryScreenshotBinding({ label, spec, evidence, context }));
193
+ if (evidence.verdict !== "pass")
194
+ blockers.push(`evidence:${label}:verdict-not-pass:${evidence.verdict}`);
195
+ blockers.push(...validateGameAssetPairGeometryEvidence(input));
196
+ if (!evidence.notes.trim())
197
+ blockers.push(`evidence:${label}:notes-missing`);
198
+ for (const blocker of evidence.blockers) {
199
+ blockers.push(`evidence:${label}:blocker:${blocker}`);
200
+ }
201
+ return blockers;
202
+ }
203
+ function validateGameAssetPairGeometryEvidence(input) {
204
+ const { label, spec, evidence, expectedCategory, expectedAssets, context } = input;
205
+ const geometry = evidence?.geometryEvidence;
206
+ if (!geometry)
207
+ return [`evidence:${label}:geometry-evidence-missing`];
208
+ const blockers = [];
209
+ if (geometry.category !== expectedCategory) {
210
+ blockers.push(`evidence:${label}:geometry-category-mismatch:${geometry.category}`);
211
+ }
212
+ const expectedKind = expectedGameGeometryKind(expectedCategory);
213
+ if (geometry.kind !== expectedKind) {
214
+ blockers.push(`evidence:${label}:geometry-kind-mismatch:${geometry.kind}`);
215
+ }
216
+ if (!isPublicGameGeometrySource(geometry.source)) {
217
+ blockers.push(`evidence:${label}:geometry-source-not-public:${geometry.source}`);
218
+ }
219
+ const expectedReport = expectedGameGeometryReport(spec, expectedCategory);
220
+ if (!expectedReport) {
221
+ blockers.push(`evidence:${label}:geometry-report-reference-missing`);
222
+ }
223
+ else if (geometry.report !== expectedReport) {
224
+ blockers.push(`evidence:${label}:geometry-report-mismatch`);
225
+ }
226
+ blockers.push(...validateEvidenceFile(`evidence:${label}:geometry-report`, geometry.report, context));
227
+ if (geometry.screenshotEvidence !== spec.evidence.routePrimaryScreenshot) {
228
+ blockers.push(`evidence:${label}:geometry-screenshot-not-current-route-primary`);
229
+ }
230
+ if (evidence && geometry.screenshotEvidence !== evidence.screenshotEvidence) {
231
+ blockers.push(`evidence:${label}:geometry-screenshot-not-asset-pair-screenshot`);
232
+ }
233
+ if (!isSha256(geometry.routePrimaryScreenshotSha256)) {
234
+ blockers.push(`evidence:${label}:geometry-screenshot-sha256-missing`);
235
+ }
236
+ else if (evidence?.screenshotSha256 && geometry.routePrimaryScreenshotSha256 !== evidence.screenshotSha256) {
237
+ blockers.push(`evidence:${label}:geometry-screenshot-sha256-mismatch`);
238
+ }
239
+ blockers.push(...validateGameGeometryEvidenceAssets(label, geometry, expectedAssets));
240
+ return blockers;
241
+ }
242
+ function validateGameGeometryEvidenceAssets(label, geometry, expectedAssets) {
243
+ const blockers = [];
244
+ const actualAssetIds = geometry.assets.map((asset) => asset.id);
245
+ const actualAssets = new Set(actualAssetIds);
246
+ for (const assetId of expectedAssets) {
247
+ if (!actualAssets.has(assetId))
248
+ blockers.push(`evidence:${label}:geometry-asset-missing:${assetId}`);
249
+ }
250
+ for (const assetId of actualAssetIds) {
251
+ if (!expectedAssets.includes(assetId))
252
+ blockers.push(`evidence:${label}:geometry-asset-extra:${assetId}`);
253
+ }
254
+ for (const asset of geometry.assets) {
255
+ if (!isSha256(asset.hash))
256
+ blockers.push(`evidence:${label}:geometry-asset-hash-invalid:${asset.id}`);
257
+ }
258
+ return blockers;
259
+ }
260
+ function expectedGameGeometryKind(category) {
261
+ return category === "racing" ? "racing-track-topology" : "platformer-playable-surface-map";
262
+ }
263
+ function expectedGameGeometryReport(spec, category) {
264
+ return category === "racing"
265
+ ? spec.racing?.raceDesign.trackTopologyEvidence
266
+ : spec.platformer?.levelDesign.playableSurfaceEvidence;
267
+ }
268
+ function validateCurrentRoutePrimaryScreenshotBinding(input) {
269
+ const { label, spec, evidence, context } = input;
270
+ const blockers = [];
271
+ if (evidence.screenshotEvidence !== spec.evidence.routePrimaryScreenshot) {
272
+ blockers.push(`evidence:${label}:screenshot-not-current-route-primary`);
273
+ }
274
+ if (!evidence.routePrimaryProbe) {
275
+ blockers.push(`evidence:${label}:route-primary-probe-missing`);
276
+ }
277
+ else if (evidence.routePrimaryProbe !== spec.evidence.routePrimaryProbe) {
278
+ blockers.push(`evidence:${label}:route-primary-probe-mismatch`);
279
+ }
280
+ if (!isSha256(evidence.screenshotSha256)) {
281
+ blockers.push(`evidence:${label}:screenshot-sha256-missing`);
282
+ }
283
+ const parsed = readJsonFile(spec.evidence.routePrimaryProbe, `evidence:${label}:route-primary-probe`, context);
284
+ if (!parsed.ok) {
285
+ blockers.push(...parsed.blockers);
286
+ return blockers;
287
+ }
288
+ if (!isRecord(parsed.value)) {
289
+ blockers.push(`evidence:${label}:route-primary-probe-invalid-json`);
290
+ return blockers;
291
+ }
292
+ const retainedSha256 = routePrimaryScreenshotSha256(parsed.value, spec.evidence.routePrimaryScreenshot);
293
+ if (!retainedSha256) {
294
+ blockers.push(`evidence:${label}:route-primary-sha256-missing`);
295
+ }
296
+ else if (evidence.screenshotSha256 !== retainedSha256) {
297
+ blockers.push(`evidence:${label}:screenshot-sha256-mismatch`);
298
+ }
299
+ if (isSha256(evidence.screenshotSha256)) {
300
+ blockers.push(...validatePngFileSha256(`evidence:${label}:screenshot`, evidence.screenshotEvidence, evidence.screenshotSha256, context));
301
+ }
302
+ return blockers;
303
+ }
304
+ function isSafeRelativePath(filePath) {
305
+ return !isAbsolute(filePath) && !filePath.split(/[\\/]+/).includes("..");
306
+ }
307
+ function readJsonFile(filePath, label, context = {}) {
308
+ const blockers = validateEvidenceFile(label, filePath, context);
309
+ if (blockers.length > 0)
310
+ return { ok: false, blockers };
311
+ const existingPath = resolveEvidencePath(filePath, context);
312
+ if (!existingPath)
313
+ return { ok: false, blockers: [`${label}:missing-file`] };
314
+ try {
315
+ return { ok: true, value: JSON.parse(readFileSync(existingPath, "utf8")) };
316
+ }
317
+ catch (error) {
318
+ if (error instanceof Error)
319
+ return { ok: false, blockers: [`${label}:invalid-json`] };
320
+ throw error;
321
+ }
322
+ }
323
+ function resolveEvidencePath(filePath, context) {
324
+ if (existsSync(filePath))
325
+ return filePath;
326
+ if (context.artifactRoot) {
327
+ const artifactPath = join(context.artifactRoot, filePath);
328
+ if (existsSync(artifactPath))
329
+ return artifactPath;
330
+ }
331
+ return undefined;
332
+ }
333
+ function validateRacingTrackTopologyEvidence(spec, value, context) {
334
+ if (!isRecord(value))
335
+ return ["evidence:racing-track-topology:invalid-json"];
336
+ const racing = spec.racing;
337
+ if (!racing)
338
+ return ["evidence:racing-track-topology:not-racing-spec"];
339
+ const route = value.route;
340
+ const points = isRecord(route) && Array.isArray(route.points) ? route.points : [];
341
+ const checkpoints = isRecord(route) && Array.isArray(route.checkpoints) ? route.checkpoints : [];
342
+ const topology = recordValue(value.topology);
343
+ const meshExtraction = recordValue(value.meshExtraction);
344
+ const roadCenterline = topology && Array.isArray(topology.roadCenterline) ? topology.roadCenterline : [];
345
+ const topologyCheckpoints = topology && Array.isArray(topology.checkpoints) ? topology.checkpoints : [];
346
+ const blockers = [];
347
+ if (value.schema !== "aura3d-racing-track-topology/1.0")
348
+ blockers.push("evidence:racing-track-topology:schema-mismatch");
349
+ if (value.routeId !== spec.routeId)
350
+ blockers.push("evidence:racing-track-topology:route-mismatch");
351
+ if (value.vehicleAsset !== racing.vehicleAsset)
352
+ blockers.push("evidence:racing-track-topology:vehicle-mismatch");
353
+ if (value.trackAsset !== racing.trackAsset)
354
+ blockers.push("evidence:racing-track-topology:track-mismatch");
355
+ if (value.topologySource !== racing.raceDesign.visibleTrackTopology)
356
+ blockers.push("evidence:racing-track-topology:source-mismatch");
357
+ if (!isReleaseSafeRacingTopology(stringValue(value.topologySource))) {
358
+ blockers.push(`evidence:racing-track-topology:topology-not-release-safe:${stringValue(value.topologySource)}`);
359
+ }
360
+ blockers.push(...validateRacingAssetBinding(spec, value));
361
+ if (!topology) {
362
+ blockers.push("evidence:racing-track-topology:missing-structured-topology");
363
+ }
364
+ else {
365
+ if (topology.assetId !== racing.trackAsset)
366
+ blockers.push("evidence:racing-track-topology:structured-topology-track-mismatch");
367
+ if (!isPublicGameGeometrySource(topology.source)) {
368
+ blockers.push(`evidence:racing-track-topology:structured-topology-source-not-overlay-validated:${stringValue(topology.source)}`);
369
+ }
370
+ if (stringValue(topology.source) !== "asset-mesh-extracted") {
371
+ blockers.push(...validateCurrentRouteOverlayEvidence("evidence:racing-track-topology", topology.evidence, spec));
372
+ }
373
+ if (typeof topology.assetHash !== "string" || !topology.assetHash.startsWith("sha256-")) {
374
+ blockers.push("evidence:racing-track-topology:structured-topology-asset-hash-missing");
375
+ }
376
+ if (typeof value.assetHash !== "string" || !value.assetHash.startsWith("sha256-")) {
377
+ blockers.push("evidence:racing-track-topology:asset-hash-missing");
378
+ }
379
+ else if (topology.assetHash !== value.assetHash) {
380
+ blockers.push("evidence:racing-track-topology:asset-hash-mismatch");
381
+ }
382
+ if (roadCenterline.length < 8)
383
+ blockers.push(`evidence:racing-track-topology:structured-topology-too-few-points:${roadCenterline.length}`);
384
+ if (topologyCheckpoints.length < racing.raceDesign.minCheckpoints) {
385
+ blockers.push(`evidence:racing-track-topology:structured-topology-too-few-checkpoints:${topologyCheckpoints.length}`);
386
+ }
387
+ if (numberValue(topology.estimatedLapSeconds) < racing.raceDesign.minLapSeconds) {
388
+ blockers.push(`evidence:racing-track-topology:structured-topology-lap-seconds-too-low:${numberValue(topology.estimatedLapSeconds)}`);
389
+ }
390
+ if (numberValue(topology.confidence) < 0.65)
391
+ blockers.push(`evidence:racing-track-topology:confidence-too-low:${numberValue(topology.confidence)}`);
392
+ blockers.push(...validateGeometryModelAlignment({
393
+ label: "evidence:racing-track-topology:model-alignment",
394
+ value: topology.modelAlignment,
395
+ secondAxis: "z",
396
+ spec
397
+ }));
398
+ blockers.push(...validateLiveRacingMeshExtraction(spec, topology, racing.raceDesign.minCheckpoints, context));
399
+ }
400
+ if (value.pass !== true)
401
+ blockers.push("evidence:racing-track-topology:not-passing");
402
+ if (points.length < 8)
403
+ blockers.push(`evidence:racing-track-topology:too-few-points:${points.length}`);
404
+ if (checkpoints.length < racing.raceDesign.minCheckpoints) {
405
+ blockers.push(`evidence:racing-track-topology:too-few-checkpoints:${checkpoints.length}`);
406
+ }
407
+ if (numberValue(value.authoredLapSeconds) < racing.raceDesign.minLapSeconds) {
408
+ blockers.push(`evidence:racing-track-topology:authored-lap-seconds-too-low:${numberValue(value.authoredLapSeconds)}`);
409
+ }
410
+ if (value.routeAlignedToTrackAsset !== true)
411
+ blockers.push("evidence:racing-track-topology:route-not-aligned");
412
+ if (value.carTrackScaleCompatible !== true)
413
+ blockers.push("evidence:racing-track-topology:scale-incompatible");
414
+ if (value.noDebugLocatorDisk !== true)
415
+ blockers.push("evidence:racing-track-topology:debug-locator-disk-present");
416
+ if (meshExtraction) {
417
+ if (!isPassingGeometryExtractionStatus(meshExtraction.status))
418
+ blockers.push("evidence:racing-track-topology:mesh-extraction-not-passing");
419
+ blockers.push(...stringArrayValue(meshExtraction.blockers).map((blocker) => `evidence:racing-track-topology:${blocker}`));
420
+ }
421
+ return blockers;
422
+ }
423
+ function validatePlatformerPlayableSurfaceEvidence(spec, value, context) {
424
+ if (!isRecord(value))
425
+ return ["evidence:platformer-playable-surface:invalid-json"];
426
+ const platformer = spec.platformer;
427
+ if (!platformer)
428
+ return ["evidence:platformer-playable-surface:not-platformer-spec"];
429
+ const surfaces = Array.isArray(value.surfaces) ? value.surfaces : [];
430
+ const checkpoints = Array.isArray(value.checkpoints) ? value.checkpoints : [];
431
+ const hazards = Array.isArray(value.hazards) ? value.hazards : [];
432
+ const surfaceMap = recordValue(value.surfaceMap);
433
+ const meshExtraction = recordValue(value.meshExtraction);
434
+ const mapSurfaces = surfaceMap && Array.isArray(surfaceMap.surfaces) ? surfaceMap.surfaces : [];
435
+ const playableMapSurfaceCount = countPublicPlayablePlatformerSurfaces(mapSurfaces);
436
+ const blockers = [];
437
+ if (value.schema !== "aura3d-platformer-playable-surfaces/1.0")
438
+ blockers.push("evidence:platformer-playable-surface:schema-mismatch");
439
+ if (value.routeId !== spec.routeId)
440
+ blockers.push("evidence:platformer-playable-surface:route-mismatch");
441
+ if (value.characterAsset !== platformer.characterAsset)
442
+ blockers.push("evidence:platformer-playable-surface:character-mismatch");
443
+ if (!sameStringSet(value.worldAssets, platformer.worldAssets))
444
+ blockers.push("evidence:platformer-playable-surface:world-assets-mismatch");
445
+ if (value.surfaceSource !== platformer.levelDesign.playableSurfaceSource) {
446
+ blockers.push("evidence:platformer-playable-surface:source-mismatch");
447
+ }
448
+ if (!isReleaseSafePlatformerSurfaceSource(stringValue(value.surfaceSource))) {
449
+ blockers.push(`evidence:platformer-playable-surface:not-release-safe:${stringValue(value.surfaceSource)}`);
450
+ }
451
+ blockers.push(...validatePlatformerAssetBindings(spec, value));
452
+ if (!surfaceMap) {
453
+ blockers.push("evidence:platformer-playable-surface:missing-structured-surface-map");
454
+ }
455
+ else {
456
+ if (!platformer.worldAssets.includes(stringValue(surfaceMap.assetId))) {
457
+ blockers.push("evidence:platformer-playable-surface:structured-surface-map-world-mismatch");
458
+ }
459
+ if (!isPublicGameGeometrySource(surfaceMap.source)) {
460
+ blockers.push(`evidence:platformer-playable-surface:structured-surface-map-source-not-overlay-validated:${stringValue(surfaceMap.source)}`);
461
+ }
462
+ if (stringValue(surfaceMap.source) !== "asset-mesh-extracted") {
463
+ blockers.push(...validateCurrentRouteOverlayEvidence("evidence:platformer-playable-surface", surfaceMap.evidence, spec));
464
+ }
465
+ if (typeof surfaceMap.assetHash !== "string" || !surfaceMap.assetHash.startsWith("sha256-")) {
466
+ blockers.push("evidence:platformer-playable-surface:structured-surface-map-asset-hash-missing");
467
+ }
468
+ if (typeof value.assetHash !== "string" || !value.assetHash.startsWith("sha256-")) {
469
+ blockers.push("evidence:platformer-playable-surface:asset-hash-missing");
470
+ }
471
+ else if (surfaceMap.assetHash !== value.assetHash) {
472
+ blockers.push("evidence:platformer-playable-surface:asset-hash-mismatch");
473
+ }
474
+ if (playableMapSurfaceCount < 5) {
475
+ blockers.push(`evidence:platformer-playable-surface:structured-surface-map-too-few-playable-surfaces:${playableMapSurfaceCount}`);
476
+ }
477
+ if (numberValue(surfaceMap.estimatedCompletionSeconds) < platformer.levelDesign.minPlayableSeconds) {
478
+ blockers.push(`evidence:platformer-playable-surface:structured-surface-map-seconds-too-low:${numberValue(surfaceMap.estimatedCompletionSeconds)}`);
479
+ }
480
+ if (numberValue(surfaceMap.characterScaleRatio) <= 0 || numberValue(surfaceMap.characterScaleRatio) > 1.25) {
481
+ blockers.push(`evidence:platformer-playable-surface:character-scale-ratio-invalid:${numberValue(surfaceMap.characterScaleRatio)}`);
482
+ }
483
+ if (numberValue(surfaceMap.confidence) < 0.65) {
484
+ blockers.push(`evidence:platformer-playable-surface:confidence-too-low:${numberValue(surfaceMap.confidence)}`);
485
+ }
486
+ blockers.push(...validateGeometryModelAlignment({
487
+ label: "evidence:platformer-playable-surface:model-alignment",
488
+ value: surfaceMap.modelAlignment,
489
+ secondAxis: "y",
490
+ spec
491
+ }));
492
+ blockers.push(...validateLivePlatformerMeshExtraction(spec, surfaceMap, context));
493
+ }
494
+ if (value.pass !== true)
495
+ blockers.push("evidence:platformer-playable-surface:not-passing");
496
+ if (surfaces.length < 4)
497
+ blockers.push(`evidence:platformer-playable-surface:too-few-surfaces:${surfaces.length}`);
498
+ if (checkpoints.length < platformer.levelDesign.minCheckpoints) {
499
+ blockers.push(`evidence:platformer-playable-surface:too-few-checkpoints:${checkpoints.length}`);
500
+ }
501
+ if (platformer.levelDesign.requiresHazardRespawn && hazards.length === 0) {
502
+ blockers.push("evidence:platformer-playable-surface:missing-hazards");
503
+ }
504
+ if (!isRecord(value.finish) || numberValue(value.finish.x) <= 0)
505
+ blockers.push("evidence:platformer-playable-surface:missing-finish");
506
+ if (numberValue(value.authoredPlayableSeconds) < platformer.levelDesign.minPlayableSeconds) {
507
+ blockers.push(`evidence:platformer-playable-surface:authored-playable-seconds-too-low:${numberValue(value.authoredPlayableSeconds)}`);
508
+ }
509
+ if (value.styleCompatible !== true)
510
+ blockers.push("evidence:platformer-playable-surface:style-incompatible");
511
+ if (value.scaleCompatible !== true)
512
+ blockers.push("evidence:platformer-playable-surface:scale-incompatible");
513
+ if (value.primitivePrimaryWorldRejected !== true)
514
+ blockers.push("evidence:platformer-playable-surface:primitive-primary-world-not-rejected");
515
+ if (meshExtraction) {
516
+ if (!isPassingGeometryExtractionStatus(meshExtraction.status))
517
+ blockers.push("evidence:platformer-playable-surface:mesh-extraction-not-passing");
518
+ blockers.push(...stringArrayValue(meshExtraction.blockers).map((blocker) => `evidence:platformer-playable-surface:${blocker}`));
519
+ }
520
+ return blockers;
521
+ }
522
+ function validateLiveRacingMeshExtraction(spec, topology, minCheckpoints, context) {
523
+ if (stringValue(topology.source) !== "asset-mesh-extracted")
524
+ return [];
525
+ const racing = spec.racing;
526
+ if (!racing)
527
+ return [];
528
+ const extracted = extractRacingTrackTopologyFromAsset(racing.trackAsset, { projectDir: context.projectDir });
529
+ if (!extracted.ok) {
530
+ return [
531
+ "evidence:racing-track-topology:mesh-extraction-not-passing",
532
+ ...extracted.blockers.map((blocker) => `evidence:racing-track-topology:${blocker}`)
533
+ ];
534
+ }
535
+ const blockers = [];
536
+ if (extracted.value.assetHash !== stringValue(topology.assetHash)) {
537
+ blockers.push("evidence:racing-track-topology:live-asset-hash-mismatch");
538
+ }
539
+ if (extracted.value.checkpoints.length < minCheckpoints) {
540
+ blockers.push(`evidence:racing-track-topology:live-extraction-too-few-checkpoints:${extracted.value.checkpoints.length}`);
541
+ }
542
+ if (extracted.value.roadCenterline.length < 8) {
543
+ blockers.push(`evidence:racing-track-topology:live-extraction-too-few-points:${extracted.value.roadCenterline.length}`);
544
+ }
545
+ return blockers;
546
+ }
547
+ function validateLivePlatformerMeshExtraction(spec, surfaceMap, context) {
548
+ if (stringValue(surfaceMap.source) !== "asset-mesh-extracted")
549
+ return [];
550
+ const platformer = spec.platformer;
551
+ if (!platformer)
552
+ return [];
553
+ const worldAsset = stringValue(surfaceMap.assetId);
554
+ if (!platformer.worldAssets.includes(worldAsset))
555
+ return ["evidence:platformer-playable-surface:live-extraction-world-mismatch"];
556
+ const extracted = extractPlatformerPlayableSurfaceMapFromAsset(worldAsset, { projectDir: context.projectDir });
557
+ if (!extracted.ok) {
558
+ return [
559
+ "evidence:platformer-playable-surface:mesh-extraction-not-passing",
560
+ ...extracted.blockers.map((blocker) => `evidence:platformer-playable-surface:${blocker}`)
561
+ ];
562
+ }
563
+ const blockers = [];
564
+ if (extracted.value.assetHash !== stringValue(surfaceMap.assetHash)) {
565
+ blockers.push("evidence:platformer-playable-surface:live-asset-hash-mismatch");
566
+ }
567
+ const playableSurfaceCount = countPublicPlayablePlatformerSurfaces(extracted.value.surfaces);
568
+ if (playableSurfaceCount < 5) {
569
+ blockers.push(`evidence:platformer-playable-surface:live-extraction-too-few-playable-surfaces:${playableSurfaceCount}`);
570
+ }
571
+ if (extracted.value.estimatedCompletionSeconds < platformer.levelDesign.minPlayableSeconds) {
572
+ blockers.push(`evidence:platformer-playable-surface:live-extraction-seconds-too-low:${extracted.value.estimatedCompletionSeconds}`);
573
+ }
574
+ return blockers;
575
+ }
576
+ function validateRacingAssetBinding(spec, value) {
577
+ const racing = spec.racing;
578
+ if (!racing)
579
+ return [];
580
+ const assetBinding = recordValue(value.assetBinding);
581
+ if (value.topologySource === "mesh-road-topology")
582
+ return [];
583
+ const blockers = [];
584
+ if (!assetBinding)
585
+ return ["evidence:racing-track-topology:missing-asset-binding"];
586
+ if (assetBinding.kind !== "aura-game-asset-bound-racing-route") {
587
+ blockers.push("evidence:racing-track-topology:asset-binding-kind-mismatch");
588
+ }
589
+ if (assetBinding.vehicleAsset !== racing.vehicleAsset) {
590
+ blockers.push("evidence:racing-track-topology:asset-binding-vehicle-mismatch");
591
+ }
592
+ if (assetBinding.trackAsset !== racing.trackAsset) {
593
+ blockers.push("evidence:racing-track-topology:asset-binding-track-mismatch");
594
+ }
595
+ if (typeof assetBinding.trackAssetHash !== "string" || !assetBinding.trackAssetHash.startsWith("sha256-")) {
596
+ blockers.push("evidence:racing-track-topology:asset-binding-track-hash-missing");
597
+ }
598
+ else if (typeof value.assetHash === "string" && assetBinding.trackAssetHash !== value.assetHash) {
599
+ blockers.push("evidence:racing-track-topology:asset-binding-track-hash-mismatch");
600
+ }
601
+ if (!isPublicGameGeometrySource(assetBinding.topologySource)) {
602
+ blockers.push(`evidence:racing-track-topology:asset-binding-source-not-overlay-validated:${stringValue(assetBinding.topologySource)}`);
603
+ }
604
+ if (assetBinding.layoutContractVersion !== "1.0") {
605
+ blockers.push("evidence:racing-track-topology:asset-binding-version-missing");
606
+ }
607
+ return blockers;
608
+ }
609
+ function validatePlatformerAssetBindings(spec, value) {
610
+ const platformer = spec.platformer;
611
+ if (!platformer)
612
+ return [];
613
+ if (value.surfaceSource === "asset-derived-playable-surfaces")
614
+ return [];
615
+ const assetBindings = Array.isArray(value.assetBindings) ? value.assetBindings : [];
616
+ const blockers = [];
617
+ if (assetBindings.length === 0)
618
+ return ["evidence:platformer-playable-surface:missing-asset-bindings"];
619
+ const boundWorlds = new Set();
620
+ for (const bindingValue of assetBindings) {
621
+ const binding = recordValue(bindingValue);
622
+ if (!binding) {
623
+ blockers.push("evidence:platformer-playable-surface:asset-binding-invalid");
624
+ continue;
625
+ }
626
+ if (binding.kind !== "aura-game-asset-bound-platformer-level") {
627
+ blockers.push("evidence:platformer-playable-surface:asset-binding-kind-mismatch");
628
+ }
629
+ if (binding.characterAsset !== platformer.characterAsset) {
630
+ blockers.push("evidence:platformer-playable-surface:asset-binding-character-mismatch");
631
+ }
632
+ if (binding.layoutContractVersion !== "1.0") {
633
+ blockers.push("evidence:platformer-playable-surface:asset-binding-version-missing");
634
+ }
635
+ const worldAsset = typeof binding.worldAsset === "string" ? binding.worldAsset : undefined;
636
+ if (worldAsset)
637
+ boundWorlds.add(worldAsset);
638
+ if (binding.worldAssetHash === undefined) {
639
+ blockers.push(`evidence:platformer-playable-surface:asset-binding-world-hash-missing:${worldAsset ?? "unknown"}`);
640
+ }
641
+ else if (typeof binding.worldAssetHash !== "string" || !binding.worldAssetHash.startsWith("sha256-")) {
642
+ blockers.push(`evidence:platformer-playable-surface:asset-binding-world-hash-invalid:${worldAsset ?? "unknown"}`);
643
+ }
644
+ else if (typeof value.assetHash === "string" && binding.worldAssetHash !== value.assetHash) {
645
+ blockers.push(`evidence:platformer-playable-surface:asset-binding-world-hash-mismatch:${worldAsset ?? "unknown"}`);
646
+ }
647
+ if (!isPublicGameGeometrySource(binding.surfaceSource)) {
648
+ blockers.push(`evidence:platformer-playable-surface:asset-binding-source-not-overlay-validated:${worldAsset ?? "unknown"}:${stringValue(binding.surfaceSource)}`);
649
+ }
650
+ const surfaceIds = Array.isArray(binding.surfaceIds) ? binding.surfaceIds.filter((surfaceId) => typeof surfaceId === "string") : [];
651
+ if (surfaceIds.length === 0) {
652
+ blockers.push(`evidence:platformer-playable-surface:asset-binding-empty-surfaces:${worldAsset ?? "unknown"}`);
653
+ }
654
+ }
655
+ for (const worldAsset of platformer.worldAssets) {
656
+ if (!boundWorlds.has(worldAsset))
657
+ blockers.push(`evidence:platformer-playable-surface:asset-binding-world-missing:${worldAsset}`);
658
+ }
659
+ return blockers;
660
+ }
661
+ function isReleaseSafeRacingTopology(source) {
662
+ return source === "asset-bound-road-topology" || source === "mesh-road-topology";
663
+ }
664
+ function isReleaseSafePlatformerSurfaceSource(source) {
665
+ return source === "asset-bound-playable-surfaces" || source === "asset-derived-playable-surfaces";
666
+ }
667
+ function isPublicGameGeometrySource(source) {
668
+ const value = stringValue(source);
669
+ return value === "asset-mesh-extracted" || value === "manifest-authored-overlay-validated" || value === "compiler-authored-overlay-validated";
670
+ }
671
+ function isPassingGeometryExtractionStatus(status) {
672
+ return status === "pass" || status === "overlay-validated";
673
+ }
674
+ function validateCurrentRouteOverlayEvidence(label, value, spec) {
675
+ const evidence = recordValue(value);
676
+ if (typeof evidence?.routeOverlay !== "string" || evidence.routeOverlay.length === 0)
677
+ return [`${label}:missing-route-overlay-evidence`];
678
+ return evidence.routeOverlay === spec.evidence.routePrimaryScreenshot ? [] : [`${label}:route-overlay-not-current-route-primary`];
679
+ }
680
+ function validateGeometryModelAlignment(input) {
681
+ const { label, value, secondAxis, spec } = input;
682
+ const alignment = recordValue(value);
683
+ if (!alignment)
684
+ return [`${label}:missing`];
685
+ const blockers = [];
686
+ const modelBounds = recordValue(alignment.modelBounds);
687
+ const modelMin = Array.isArray(modelBounds?.min) ? modelBounds.min : undefined;
688
+ const modelMax = Array.isArray(modelBounds?.max) ? modelBounds.max : undefined;
689
+ const modelPoint = Array.isArray(alignment.modelPoint) ? alignment.modelPoint : undefined;
690
+ const gamePoint = recordValue(alignment.gamePoint);
691
+ const alignmentSource = stringValue(alignment.source);
692
+ const anchorPairs = Array.isArray(alignment.anchorPairs) ? alignment.anchorPairs : [];
693
+ if (!isPublicGameGeometrySource(alignmentSource)) {
694
+ blockers.push(`${label}:source-not-overlay-validated:${alignmentSource}`);
695
+ }
696
+ if (alignmentSource !== "asset-mesh-extracted") {
697
+ blockers.push(...validateCurrentRouteOverlayEvidence(label, alignment.evidence, spec));
698
+ if (!Array.isArray(alignment.anchorPairs)) {
699
+ blockers.push(`${label}:anchor-pairs-missing`);
700
+ }
701
+ else if (anchorPairs.length < 2) {
702
+ blockers.push(`${label}:too-few-anchor-pairs:${anchorPairs.length}`);
703
+ }
704
+ }
705
+ if (!modelBounds) {
706
+ blockers.push(`${label}:missing-model-bounds`);
707
+ }
708
+ else {
709
+ blockers.push(...validateVec3(`${label}:model-bounds-min`, modelMin));
710
+ blockers.push(...validateVec3(`${label}:model-bounds-max`, modelMax));
711
+ if (modelMin && modelMax) {
712
+ const dimensions = modelMin.map((min, index) => numberValue(modelMax[index]) - numberValue(min));
713
+ if (dimensions.some((dimension) => dimension <= 0))
714
+ blockers.push(`${label}:invalid-model-bounds`);
715
+ }
716
+ }
717
+ blockers.push(...validateVec3(`${label}:model-point`, modelPoint));
718
+ if (modelMin && modelMax && modelPoint && blockers.length === 0) {
719
+ for (let index = 0; index < 3; index += 1) {
720
+ const point = numberValue(modelPoint[index]);
721
+ if (point < numberValue(modelMin[index]) || point > numberValue(modelMax[index])) {
722
+ blockers.push(`${label}:model-point-outside-bounds`);
723
+ break;
724
+ }
725
+ }
726
+ }
727
+ if (!gamePoint) {
728
+ blockers.push(`${label}:missing-game-point`);
729
+ }
730
+ else {
731
+ if (!hasFiniteNumber(gamePoint.x))
732
+ blockers.push(`${label}:missing-game-point-x`);
733
+ if (!hasFiniteNumber(gamePoint[secondAxis]))
734
+ blockers.push(`${label}:missing-game-point-${secondAxis}`);
735
+ }
736
+ blockers.push(...validateGeometryAnchorPairs({
737
+ label,
738
+ values: anchorPairs,
739
+ secondAxis,
740
+ modelMin,
741
+ modelMax
742
+ }));
743
+ const evidence = recordValue(alignment.evidence);
744
+ if (!evidence || stringValue(evidence.notes) === "missing")
745
+ blockers.push(`${label}:missing-notes`);
746
+ return blockers;
747
+ }
748
+ function validateGeometryAnchorPairs(input) {
749
+ const blockers = [];
750
+ for (const [index, value] of input.values.entries()) {
751
+ const anchor = recordValue(value);
752
+ if (!anchor) {
753
+ blockers.push(`${input.label}:anchor-pair-invalid:${index}`);
754
+ continue;
755
+ }
756
+ if (typeof anchor.id !== "string" || anchor.id.trim().length === 0) {
757
+ blockers.push(`${input.label}:anchor-pair-id-missing:${index}`);
758
+ }
759
+ const modelPoint = Array.isArray(anchor.modelPoint) ? anchor.modelPoint : undefined;
760
+ blockers.push(...validateVec3(`${input.label}:anchor-pair-model-point:${index}`, modelPoint));
761
+ if (input.modelMin && input.modelMax && modelPoint) {
762
+ for (let coordinate = 0; coordinate < 3; coordinate += 1) {
763
+ const point = numberValue(modelPoint[coordinate]);
764
+ if (point < numberValue(input.modelMin[coordinate]) || point > numberValue(input.modelMax[coordinate])) {
765
+ blockers.push(`${input.label}:anchor-pair-model-point-outside-bounds:${index}`);
766
+ break;
767
+ }
768
+ }
769
+ }
770
+ const gamePoint = recordValue(anchor.gamePoint);
771
+ if (!gamePoint) {
772
+ blockers.push(`${input.label}:anchor-pair-game-point-missing:${index}`);
773
+ }
774
+ else {
775
+ if (!hasFiniteNumber(gamePoint.x))
776
+ blockers.push(`${input.label}:anchor-pair-game-point-x-missing:${index}`);
777
+ if (!hasFiniteNumber(gamePoint[input.secondAxis])) {
778
+ blockers.push(`${input.label}:anchor-pair-game-point-${input.secondAxis}-missing:${index}`);
779
+ }
780
+ }
781
+ }
782
+ return blockers;
783
+ }
784
+ function validateVec3(label, value) {
785
+ if (!value || value.length !== 3)
786
+ return [`${label}:invalid`];
787
+ return value.every((item) => hasFiniteNumber(item)) ? [] : [`${label}:invalid`];
788
+ }
789
+ function hasFiniteNumber(value) {
790
+ return typeof value === "number" && Number.isFinite(value);
791
+ }
792
+ function sameStringSet(value, expected) {
793
+ if (!Array.isArray(value))
794
+ return false;
795
+ const actualSet = new Set(value.filter((item) => typeof item === "string"));
796
+ return actualSet.size === expected.length && expected.every((item) => actualSet.has(item));
797
+ }
798
+ function recordValue(value) {
799
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : undefined;
800
+ }
801
+ function countPublicPlayablePlatformerSurfaces(surfaces) {
802
+ return surfaces.filter((surface) => {
803
+ const record = recordValue(surface);
804
+ return record?.kind === "ground" || record?.kind === "platform" || record?.kind === "moving";
805
+ }).length;
806
+ }
807
+ function validateDeployReport(spec, value) {
808
+ if (!isRecord(value) || !Array.isArray(value.routes))
809
+ return ["evidence:deploy-artifact:invalid-launch-evidence"];
810
+ const route = value.routes.find((candidate) => isRecord(candidate) && candidate.deployCheckCommand === spec.evidence.deployCommand);
811
+ if (!isRecord(route))
812
+ return ["evidence:deploy-artifact:command-mismatch"];
813
+ const blockers = [];
814
+ if (route.deployCheckOk !== true)
815
+ blockers.push("evidence:deploy-artifact:not-passing");
816
+ if (Array.isArray(route.deployWarnings) && route.deployWarnings.length > 0)
817
+ blockers.push("evidence:deploy-artifact:has-warnings");
818
+ if (Array.isArray(route.deployFailures) && route.deployFailures.length > 0)
819
+ blockers.push("evidence:deploy-artifact:has-failures");
820
+ for (const asset of spec.primaryAssets) {
821
+ if (!deployReportIncludesAsset(route, asset.id))
822
+ blockers.push(`evidence:deploy-artifact:missing-asset:${asset.id}`);
823
+ }
824
+ return blockers;
825
+ }
826
+ function deployReportIncludesAsset(route, assetId) {
827
+ const evidence = route.primaryAssetEvidence;
828
+ return Array.isArray(evidence) && evidence.some((asset) => isRecord(asset) && asset.id === assetId);
829
+ }
830
+ function routePrimaryAssetsInclude(probe, assetId) {
831
+ const primaryAssets = probe.primaryAssets;
832
+ return Array.isArray(primaryAssets) && primaryAssets.some((asset) => isRecord(asset) && asset.id === assetId);
833
+ }
834
+ function routePrimaryScreenshotMatches(probe, screenshotPath) {
835
+ return routePrimaryRenderedProbeForScreenshot(probe, screenshotPath) !== undefined;
836
+ }
837
+ function routePrimaryRenderedProbeForScreenshot(probe, screenshotPath) {
838
+ const primaryAssets = probe.primaryAssets;
839
+ if (!Array.isArray(primaryAssets))
840
+ return undefined;
841
+ for (const asset of primaryAssets) {
842
+ if (isRecord(asset) && isRecord(asset.renderedProbe) && asset.renderedProbe.screenshotPath === screenshotPath) {
843
+ return asset.renderedProbe;
844
+ }
845
+ }
846
+ return undefined;
847
+ }
848
+ function routePrimaryScreenshotSha256(probe, screenshotPath) {
849
+ const renderedProbe = routePrimaryRenderedProbeForScreenshot(probe, screenshotPath);
850
+ const sha256 = renderedProbe?.sha256;
851
+ return isSha256(sha256) ? sha256 : undefined;
852
+ }
853
+ function validatePngFileSha256(label, filePath, expectedSha256, context) {
854
+ const existingPath = resolveEvidencePath(filePath, context);
855
+ if (!existingPath)
856
+ return [`${label}:missing-file`];
857
+ const bytes = readFileSync(existingPath);
858
+ const blockers = [];
859
+ if (!PNG_SIGNATURE.every((byte, index) => bytes[index] === byte))
860
+ blockers.push(`${label}:not-png`);
861
+ const actualSha256 = `sha256-${createHash("sha256").update(bytes).digest("hex")}`;
862
+ if (actualSha256 !== expectedSha256)
863
+ blockers.push(`${label}:file-sha256-mismatch`);
864
+ return blockers;
865
+ }
866
+ function isSha256(value) {
867
+ return typeof value === "string" && /^sha256-[a-f0-9]{64}$/.test(value);
868
+ }
869
+ function isRecord(value) {
870
+ return typeof value === "object" && value !== null && !Array.isArray(value);
871
+ }
872
+ function numberValue(value) {
873
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
874
+ }
875
+ function stringValue(value) {
876
+ return typeof value === "string" ? value : "missing";
877
+ }
878
+ function stringArrayValue(value) {
879
+ return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
880
+ }
881
+ //# sourceMappingURL=showcase-spec-evidence.js.map