create-aura3d 1.3.2 → 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.
Files changed (226) hide show
  1. package/dist/cli.js +10 -0
  2. package/dist/cli.js.map +1 -1
  3. package/dist/index.d.ts +1 -1
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +11 -2
  6. package/dist/index.js.map +1 -1
  7. package/dist/showcase-spec-artifacts.d.ts +104 -0
  8. package/dist/showcase-spec-artifacts.d.ts.map +1 -0
  9. package/dist/showcase-spec-artifacts.js +142 -0
  10. package/dist/showcase-spec-artifacts.js.map +1 -0
  11. package/dist/showcase-spec-category-plan.d.ts +15 -0
  12. package/dist/showcase-spec-category-plan.d.ts.map +1 -0
  13. package/dist/showcase-spec-category-plan.js +45 -0
  14. package/dist/showcase-spec-category-plan.js.map +1 -0
  15. package/dist/showcase-spec-compiler.d.ts +5 -0
  16. package/dist/showcase-spec-compiler.d.ts.map +1 -0
  17. package/dist/showcase-spec-compiler.js +240 -0
  18. package/dist/showcase-spec-compiler.js.map +1 -0
  19. package/dist/showcase-spec-evidence.d.ts +7 -0
  20. package/dist/showcase-spec-evidence.d.ts.map +1 -0
  21. package/dist/showcase-spec-evidence.js +815 -0
  22. package/dist/showcase-spec-evidence.js.map +1 -0
  23. package/dist/showcase-spec-game-geometry-extractor.d.ts +21 -0
  24. package/dist/showcase-spec-game-geometry-extractor.d.ts.map +1 -0
  25. package/dist/showcase-spec-game-geometry-extractor.js +725 -0
  26. package/dist/showcase-spec-game-geometry-extractor.js.map +1 -0
  27. package/dist/showcase-spec-game-replacement-evidence.d.ts +14 -0
  28. package/dist/showcase-spec-game-replacement-evidence.d.ts.map +1 -0
  29. package/dist/showcase-spec-game-replacement-evidence.js +219 -0
  30. package/dist/showcase-spec-game-replacement-evidence.js.map +1 -0
  31. package/dist/showcase-spec-game-template-evidence.d.ts +72 -0
  32. package/dist/showcase-spec-game-template-evidence.d.ts.map +1 -0
  33. package/dist/showcase-spec-game-template-evidence.js +435 -0
  34. package/dist/showcase-spec-game-template-evidence.js.map +1 -0
  35. package/dist/showcase-spec-gameplay-evidence.d.ts +3 -0
  36. package/dist/showcase-spec-gameplay-evidence.d.ts.map +1 -0
  37. package/dist/showcase-spec-gameplay-evidence.js +108 -0
  38. package/dist/showcase-spec-gameplay-evidence.js.map +1 -0
  39. package/dist/showcase-spec-parser.d.ts +3 -0
  40. package/dist/showcase-spec-parser.d.ts.map +1 -0
  41. package/dist/showcase-spec-parser.js +662 -0
  42. package/dist/showcase-spec-parser.js.map +1 -0
  43. package/dist/showcase-spec-platformer-artifacts.d.ts +3 -0
  44. package/dist/showcase-spec-platformer-artifacts.d.ts.map +1 -0
  45. package/dist/showcase-spec-platformer-artifacts.js +276 -0
  46. package/dist/showcase-spec-platformer-artifacts.js.map +1 -0
  47. package/dist/showcase-spec-racing-artifacts.d.ts +3 -0
  48. package/dist/showcase-spec-racing-artifacts.d.ts.map +1 -0
  49. package/dist/showcase-spec-racing-artifacts.js +273 -0
  50. package/dist/showcase-spec-racing-artifacts.js.map +1 -0
  51. package/dist/showcase-spec-replacement-apply.d.ts +10 -0
  52. package/dist/showcase-spec-replacement-apply.d.ts.map +1 -0
  53. package/dist/showcase-spec-replacement-apply.js +166 -0
  54. package/dist/showcase-spec-replacement-apply.js.map +1 -0
  55. package/dist/showcase-spec-replacement-candidates.d.ts +16 -0
  56. package/dist/showcase-spec-replacement-candidates.d.ts.map +1 -0
  57. package/dist/showcase-spec-replacement-candidates.js +244 -0
  58. package/dist/showcase-spec-replacement-candidates.js.map +1 -0
  59. package/dist/showcase-spec-replacement-game-geometry.d.ts +4 -0
  60. package/dist/showcase-spec-replacement-game-geometry.d.ts.map +1 -0
  61. package/dist/showcase-spec-replacement-game-geometry.js +175 -0
  62. package/dist/showcase-spec-replacement-game-geometry.js.map +1 -0
  63. package/dist/showcase-spec-replacement-manifest.d.ts +41 -0
  64. package/dist/showcase-spec-replacement-manifest.d.ts.map +1 -0
  65. package/dist/showcase-spec-replacement-manifest.js +62 -0
  66. package/dist/showcase-spec-replacement-manifest.js.map +1 -0
  67. package/dist/showcase-spec-replacement-values.d.ts +9 -0
  68. package/dist/showcase-spec-replacement-values.d.ts.map +1 -0
  69. package/dist/showcase-spec-replacement-values.js +41 -0
  70. package/dist/showcase-spec-replacement-values.js.map +1 -0
  71. package/dist/showcase-spec-replacement.d.ts +13 -0
  72. package/dist/showcase-spec-replacement.d.ts.map +1 -0
  73. package/dist/showcase-spec-replacement.js +144 -0
  74. package/dist/showcase-spec-replacement.js.map +1 -0
  75. package/dist/showcase-spec-types.d.ts +304 -0
  76. package/dist/showcase-spec-types.d.ts.map +1 -0
  77. package/dist/showcase-spec-types.js +2 -0
  78. package/dist/showcase-spec-types.js.map +1 -0
  79. package/package.json +21 -2
  80. package/templates/animation-channel/package.json +1 -1
  81. package/templates/animation-channel/tests/screenshot.spec.ts +16 -0
  82. package/templates/animation-studio/index.html +12 -0
  83. package/templates/animation-studio/node_modules.publish-aside/.vite/deps/_metadata.json +8 -0
  84. package/templates/animation-studio/node_modules.publish-aside/.vite/deps/package.json +3 -0
  85. package/templates/animation-studio/package.json +1 -1
  86. package/templates/animation-studio/scripts/animation-scene.ts +1 -1
  87. package/templates/animation-studio/scripts/asset-motion-probe.ts +7 -1
  88. package/templates/animation-studio/scripts/author-office-scene.ts +1 -1
  89. package/templates/animation-studio/scripts/build-characters.ts +1 -1
  90. package/templates/animation-studio/scripts/render-live.ts +24 -0
  91. package/templates/animation-studio/src/director/director-heuristics.ts +2 -2
  92. package/templates/animation-studio/src/director/{prompt-to-scene.ts → prompt-scene.ts} +74 -6
  93. package/templates/animation-studio/studio/src/App.tsx +146 -78
  94. package/templates/animation-studio/studio/src/components/Console.tsx +8 -2
  95. package/templates/animation-studio/studio/src/components/Inspector.tsx +57 -1
  96. package/templates/animation-studio/studio/src/components/Stage.tsx +10 -5
  97. package/templates/animation-studio/studio/src/components/Timeline.tsx +49 -2
  98. package/templates/animation-studio/studio/src/state/mapDocument.ts +13 -2
  99. package/templates/animation-studio/studio/src/state/sceneTool.ts +20 -9
  100. package/templates/animation-studio/studio/src/state/types.ts +8 -0
  101. package/templates/animation-studio/tests/route-health.spec.ts +33 -0
  102. package/templates/animation-studio/tests/screenshot.spec.ts +30 -0
  103. package/templates/character-controller/package.json +1 -1
  104. package/templates/character-controller/src/main.ts +18 -1
  105. package/templates/cinematic-scene/package.json +1 -1
  106. package/templates/cinematic-scene/tests/screenshot.spec.ts +3 -3
  107. package/templates/episode-builder/package.json +1 -1
  108. package/templates/episode-builder/tests/screenshot.spec.ts +15 -0
  109. package/templates/falling-blocks-starter/README.md +18 -0
  110. package/templates/falling-blocks-starter/index.html +23 -0
  111. package/templates/falling-blocks-starter/package.json +21 -0
  112. package/templates/falling-blocks-starter/playwright.config.ts +12 -0
  113. package/templates/falling-blocks-starter/public/aura-assets/showcaseBlockfallCabinet.679d52fe.glb +0 -0
  114. package/templates/falling-blocks-starter/public/aura-assets/showcaseBlockfallCabinet.thumb.svg +1 -0
  115. package/templates/falling-blocks-starter/src/aura-assets.ts +20 -0
  116. package/templates/falling-blocks-starter/src/main.ts +358 -0
  117. package/templates/falling-blocks-starter/tests/playable.spec.ts +85 -0
  118. package/templates/falling-blocks-starter/tests/route-health.spec.ts +34 -0
  119. package/templates/falling-blocks-starter/tsconfig.json +13 -0
  120. package/templates/fighting-game/package.json +1 -1
  121. package/templates/fighting-game/tests/screenshot.spec.ts +15 -0
  122. package/templates/mini-game/README.md +13 -0
  123. package/templates/mini-game/index.html +1 -1
  124. package/templates/mini-game/package.json +2 -2
  125. package/templates/mini-game/src/main.ts +251 -21
  126. package/templates/mini-game/tests/playable.spec.ts +49 -0
  127. package/templates/mini-game/tests/screenshot.spec.ts +37 -32
  128. package/templates/product-viewer/package.json +1 -1
  129. package/templates/product-viewer/tests/screenshot.spec.ts +4 -4
  130. package/templates/prompt-animation-channel/package.json +1 -1
  131. package/templates/prompt-animation-channel/tests/screenshot.spec.ts +15 -0
  132. package/templates/racing-starter/README.md +27 -0
  133. package/templates/racing-starter/index.html +23 -0
  134. package/templates/racing-starter/package.json +21 -0
  135. package/templates/racing-starter/playwright.config.ts +12 -0
  136. package/templates/racing-starter/public/aura-assets/showcaseCleanSportsCar.1c69283f.glb +0 -0
  137. package/templates/racing-starter/public/aura-assets/showcaseCleanSportsCar.thumb.svg +1 -0
  138. package/templates/racing-starter/public/aura-assets/showcaseReadableKartCircuit.5cbb912e.glb +0 -0
  139. package/templates/racing-starter/public/aura-assets/showcaseReadableKartCircuit.thumb.svg +1 -0
  140. package/templates/racing-starter/src/aura-assets.ts +36 -0
  141. package/templates/racing-starter/src/main.ts +280 -0
  142. package/templates/racing-starter/tests/playable.spec.ts +93 -0
  143. package/templates/racing-starter/tests/route-health.spec.ts +40 -0
  144. package/templates/racing-starter/tsconfig.json +13 -0
  145. package/templates/three-compat-architecture-interior/index.html +16 -0
  146. package/templates/three-compat-architecture-interior/package.json +21 -0
  147. package/templates/three-compat-architecture-interior/playwright.config.ts +14 -0
  148. package/templates/three-compat-architecture-interior/src/main.ts +27 -0
  149. package/templates/three-compat-architecture-interior/tests/route-health.spec.ts +16 -0
  150. package/templates/three-compat-architecture-interior/tests/screenshot.spec.ts +15 -0
  151. package/templates/three-compat-architecture-interior/tsconfig.json +19 -0
  152. package/templates/three-compat-asset-inspector/index.html +16 -0
  153. package/templates/three-compat-asset-inspector/package.json +21 -0
  154. package/templates/three-compat-asset-inspector/playwright.config.ts +14 -0
  155. package/templates/three-compat-asset-inspector/src/main.ts +17 -0
  156. package/templates/three-compat-asset-inspector/tests/route-health.spec.ts +16 -0
  157. package/templates/three-compat-asset-inspector/tests/screenshot.spec.ts +15 -0
  158. package/templates/three-compat-asset-inspector/tsconfig.json +19 -0
  159. package/templates/three-compat-character-viewer/index.html +16 -0
  160. package/templates/three-compat-character-viewer/package.json +21 -0
  161. package/templates/three-compat-character-viewer/playwright.config.ts +14 -0
  162. package/templates/three-compat-character-viewer/src/main.ts +17 -0
  163. package/templates/three-compat-character-viewer/tests/route-health.spec.ts +16 -0
  164. package/templates/three-compat-character-viewer/tests/screenshot.spec.ts +15 -0
  165. package/templates/three-compat-character-viewer/tsconfig.json +19 -0
  166. package/templates/three-compat-custom-threejs-migration/index.html +16 -0
  167. package/templates/three-compat-custom-threejs-migration/package.json +21 -0
  168. package/templates/three-compat-custom-threejs-migration/playwright.config.ts +14 -0
  169. package/templates/three-compat-custom-threejs-migration/src/main.ts +16 -0
  170. package/templates/three-compat-custom-threejs-migration/tests/route-health.spec.ts +16 -0
  171. package/templates/three-compat-custom-threejs-migration/tests/screenshot.spec.ts +15 -0
  172. package/templates/three-compat-custom-threejs-migration/tsconfig.json +19 -0
  173. package/templates/three-compat-large-scene/index.html +16 -0
  174. package/templates/three-compat-large-scene/package.json +21 -0
  175. package/templates/three-compat-large-scene/playwright.config.ts +14 -0
  176. package/templates/three-compat-large-scene/src/main.ts +31 -0
  177. package/templates/three-compat-large-scene/tests/route-health.spec.ts +16 -0
  178. package/templates/three-compat-large-scene/tests/screenshot.spec.ts +15 -0
  179. package/templates/three-compat-large-scene/tsconfig.json +19 -0
  180. package/templates/three-compat-material-authoring/index.html +16 -0
  181. package/templates/three-compat-material-authoring/package.json +21 -0
  182. package/templates/three-compat-material-authoring/playwright.config.ts +14 -0
  183. package/templates/three-compat-material-authoring/src/main.ts +33 -0
  184. package/templates/three-compat-material-authoring/tests/route-health.spec.ts +16 -0
  185. package/templates/three-compat-material-authoring/tests/screenshot.spec.ts +15 -0
  186. package/templates/three-compat-material-authoring/tsconfig.json +19 -0
  187. package/templates/three-compat-postprocess-scene/index.html +16 -0
  188. package/templates/three-compat-postprocess-scene/package.json +21 -0
  189. package/templates/three-compat-postprocess-scene/playwright.config.ts +14 -0
  190. package/templates/three-compat-postprocess-scene/src/main.ts +17 -0
  191. package/templates/three-compat-postprocess-scene/tests/route-health.spec.ts +16 -0
  192. package/templates/three-compat-postprocess-scene/tests/screenshot.spec.ts +15 -0
  193. package/templates/three-compat-postprocess-scene/tsconfig.json +19 -0
  194. package/templates/three-compat-premium-product-viewer/index.html +16 -0
  195. package/templates/three-compat-premium-product-viewer/package.json +21 -0
  196. package/templates/three-compat-premium-product-viewer/playwright.config.ts +14 -0
  197. package/templates/three-compat-premium-product-viewer/src/main.ts +18 -0
  198. package/templates/three-compat-premium-product-viewer/tests/route-health.spec.ts +16 -0
  199. package/templates/three-compat-premium-product-viewer/tests/screenshot.spec.ts +15 -0
  200. package/templates/three-compat-premium-product-viewer/tsconfig.json +19 -0
  201. package/templates/animation-channel/public/aura-assets/moon-garden-feature-frame.png +0 -0
  202. package/templates/animation-channel/tests/reports/prompt-animation/cartoon-sample-episode.png +0 -0
  203. package/templates/animation-studio/dist/episodes/scene/episode-3d.webm +0 -0
  204. package/templates/animation-studio/dist/episodes/scene/frames/action.png +0 -0
  205. package/templates/animation-studio/dist/episodes/scene/frames/dialogue.png +0 -0
  206. package/templates/animation-studio/dist/episodes/scene/frames/final.png +0 -0
  207. package/templates/animation-studio/dist/episodes/scene/frames/first.png +0 -0
  208. package/templates/animation-studio/dist/episodes/scene/frames/mouth-closed.png +0 -0
  209. package/templates/animation-studio/dist/episodes/scene/frames/mouth-open.png +0 -0
  210. package/templates/animation-studio/dist/episodes/scene/render-live-summary.json +0 -5616
  211. package/templates/animation-studio/dist/episodes/scene/skeleton-overlays/broken.png +0 -0
  212. package/templates/animation-studio/dist/episodes/scene/skeleton-overlays/chefs.png +0 -0
  213. package/templates/animation-studio/dist/episodes/scene/skeleton-overlays/customer.png +0 -0
  214. package/templates/animation-studio/dist/episodes/scene/skeleton-overlays/worker-1.png +0 -0
  215. package/templates/animation-studio/dist/episodes/scene/skeleton-overlays/worker-2.png +0 -0
  216. package/templates/animation-studio/dist/generated/second-scene.document.json +0 -1210
  217. package/templates/animation-studio/dist/generated/studio-edited.document.json +0 -1196
  218. package/templates/animation-studio/dist/scene/inshader-accept.document.json +0 -1
  219. package/templates/animation-studio/dist/scene/inshader-test.document.json +0 -1
  220. package/templates/animation-studio/dist/scene/integ.document.json +0 -1
  221. package/templates/animation-studio/dist/scene/working.document.json +0 -594
  222. package/templates/animation-studio/dist/scene/working.history.json +0 -1
  223. package/templates/animation-studio/studio/dist/assets/index-i8URxOOt.css +0 -1
  224. package/templates/animation-studio/studio/dist/assets/index-pC9hdZ-B.js +0 -11
  225. package/templates/animation-studio/studio/dist/index.html +0 -19
  226. package/templates/animation-studio/tests/reports/animation-studio/hifi-cast-validation.json +0 -938
@@ -0,0 +1,725 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ import { readManifestAssets } from "./showcase-spec-replacement-manifest.js";
4
+ const IDENTITY_MATRIX = [
5
+ 1, 0, 0, 0,
6
+ 0, 1, 0, 0,
7
+ 0, 0, 1, 0,
8
+ 0, 0, 0, 1
9
+ ];
10
+ const ROAD_PATTERN = /\b(asph|asphalt|road|track|circuit|route|lane|kerb|curb)\b/i;
11
+ const ROAD_EXCLUDE_PATTERN = /\b(grass|water|lake|mount|terrain|wall|fence|tree|building|sky)\b/i;
12
+ const PLATFORM_PATTERN = /\b(platform|walkway|ground|floor|level|ledge|bridge|runway|road|grass|rock|terrain)\b/i;
13
+ const PLATFORM_EXCLUDE_PATTERN = /\b(wall|cloud|sky|tree|character|prop|rail|pole)\b/i;
14
+ const PLATFORMER_TARGET_GAME_LENGTH = 38;
15
+ const PLATFORMER_MIN_GAME_SCALE = 0.025;
16
+ const PLATFORMER_MAX_GAME_SCALE = 0.18;
17
+ const PLATFORMER_MIN_MESH_PLAYABLE_SURFACES = 5;
18
+ const PLATFORMER_MIN_LEVEL_LENGTH = 12;
19
+ const PLATFORMER_AMBIGUOUS_COLUMN_STEP = 0.5;
20
+ const PLATFORMER_AMBIGUOUS_COLUMN_MIN_SURFACES = 3;
21
+ const PLATFORMER_AMBIGUOUS_COLUMN_MIN_Y_RANGE = 1.5;
22
+ export function extractRacingTrackTopologyFromAsset(assetId, options = {}) {
23
+ const geometry = loadAssetGeometry(assetId, options.projectDir ?? process.cwd());
24
+ if (!geometry.ok)
25
+ return geometry;
26
+ const roadPrimitives = geometry.value.primitives.filter(isRoadPrimitive);
27
+ if (roadPrimitives.length === 0) {
28
+ return failure([`asset-extraction:racing-road-mesh-not-found:${assetId}`], [`No mesh primitive in ${assetId} has a road/track/asphalt/kerb material or node name.`]);
29
+ }
30
+ const roadBounds = boundsForPrimitives(roadPrimitives);
31
+ const roadSize = boundsSize(roadBounds);
32
+ if (Math.max(roadSize[0], roadSize[2]) < 1 || Math.min(roadSize[0], roadSize[2]) < 0.5) {
33
+ return failure([`asset-extraction:racing-road-footprint-too-small:${assetId}`], [`Road candidate footprint ${formatSize(roadSize)} is not large enough to derive a public racing route.`]);
34
+ }
35
+ const centerline = createRoadCenterline(roadPrimitives, roadBounds);
36
+ if (centerline.length < 8) {
37
+ return failure([`asset-extraction:racing-road-centerline-ambiguous:${assetId}`], [`Road mesh in ${assetId} produced only ${centerline.length} reliable centerline samples.`]);
38
+ }
39
+ const lapLength = measureClosedRouteLength(centerline.map((point) => ({ x: point.x, y: point.z })));
40
+ if (lapLength <= 0) {
41
+ return failure([`asset-extraction:racing-road-centerline-zero-length:${assetId}`], [`Road mesh in ${assetId} did not produce a measurable closed route.`]);
42
+ }
43
+ const checkpointCount = 6;
44
+ const topology = {
45
+ assetId,
46
+ assetHash: requireAssetHash(geometry.value.asset),
47
+ source: "asset-mesh-extracted",
48
+ roadCenterline: centerline,
49
+ checkpoints: Array.from({ length: checkpointCount }, (_unused, index) => ({
50
+ progress: round3((index + 1) / checkpointCount),
51
+ width: centerline[index % centerline.length]?.width ?? averageRoadWidth(roadSize)
52
+ })),
53
+ lapLengthMeters: round3(lapLength),
54
+ estimatedLapSeconds: Math.max(30, Math.ceil(lapLength / 0.28)),
55
+ confidence: 0.76,
56
+ modelAlignment: {
57
+ source: "asset-mesh-extracted",
58
+ modelBounds: geometry.value.bounds,
59
+ modelPoint: [center(roadBounds, 0), roadBounds.min[1], center(roadBounds, 2)],
60
+ gamePoint: {
61
+ x: center(roadBounds, 0),
62
+ z: center(roadBounds, 2)
63
+ },
64
+ anchorPairs: createRacingAnchorPairs(centerline, roadBounds),
65
+ evidence: {
66
+ ...(options.routeOverlayPath ? { routeOverlay: options.routeOverlayPath } : {}),
67
+ notes: "Mesh-derived anchors are computed from road/asphalt/kerb primitives in the current GLB and bind the racing route to the visible track asset."
68
+ }
69
+ },
70
+ evidence: {
71
+ sourceAsset: `assets.${assetId}`,
72
+ ...(options.renderedProbePath ? { renderedProbe: options.renderedProbePath } : {}),
73
+ ...(options.routeOverlayPath ? { routeOverlay: options.routeOverlayPath } : {}),
74
+ notes: `Road topology extracted from ${roadPrimitives.length} GLB primitive(s) with road/track material names and bound to the current asset hash.`
75
+ }
76
+ };
77
+ return {
78
+ ok: true,
79
+ value: topology,
80
+ reasons: [
81
+ `mesh-derived racing topology from ${roadPrimitives.length} road primitive(s)`,
82
+ `lapLengthMeters:${topology.lapLengthMeters}`,
83
+ `estimatedLapSeconds:${topology.estimatedLapSeconds}`
84
+ ]
85
+ };
86
+ }
87
+ export function extractPlatformerPlayableSurfaceMapFromAsset(assetId, options = {}) {
88
+ const geometry = loadAssetGeometry(assetId, options.projectDir ?? process.cwd());
89
+ if (!geometry.ok)
90
+ return geometry;
91
+ const surfaceSelection = selectPlatformerSurfacePrimitives(geometry.value);
92
+ if (surfaceSelection.primitives.length === 0) {
93
+ return failure([`asset-extraction:platformer-playable-mesh-not-found:${assetId}`], [
94
+ `No semantic or geometric mesh primitive in ${assetId} can be used as a retained playable platform surface.`,
95
+ ...surfaceSelection.reasons
96
+ ]);
97
+ }
98
+ const surfaceBounds = boundsForPrimitives(surfaceSelection.primitives);
99
+ const surfaceSize = boundsSize(surfaceBounds);
100
+ const horizontalCandidates = surfaceSelection.primitives
101
+ .filter((primitive) => isHorizontalSurface(primitive))
102
+ .sort(sortPlayableSurfaceCandidate);
103
+ if (horizontalCandidates.length === 0) {
104
+ return failure([`asset-extraction:platformer-horizontal-surfaces-not-found:${assetId}`], [`${assetId} has ${surfaceSelection.primitives.length} candidate primitive(s), but none are flat enough for playable surfaces.`]);
105
+ }
106
+ const surfaces = createPlayableSurfaces(horizontalCandidates, surfaceBounds);
107
+ const meshPlayableSurfaces = meshBoundPlayableSurfaces(surfaces);
108
+ const qualityBlockers = platformerMeshSurfaceQualityBlockers(assetId, meshPlayableSurfaces);
109
+ if (qualityBlockers.length > 0) {
110
+ return failure(qualityBlockers, [
111
+ `${assetId} produced ${meshPlayableSurfaces.length} mesh-bound playable surface(s); public platformer worlds require retained game geometry, not generated checkpoint/finish markers.`,
112
+ ...qualityBlockers,
113
+ ...surfaceSelection.reasons
114
+ ]);
115
+ }
116
+ const levelLength = measurePlatformerSurfaceLength(meshPlayableSurfaces);
117
+ const map = {
118
+ assetId,
119
+ assetHash: requireAssetHash(geometry.value.asset),
120
+ source: "asset-mesh-extracted",
121
+ surfaces,
122
+ levelLength: round3(levelLength),
123
+ estimatedCompletionSeconds: Math.max(30, Math.ceil(levelLength / 0.9)),
124
+ characterScaleRatio: 0.42,
125
+ confidence: 0.74,
126
+ modelAlignment: {
127
+ source: "asset-mesh-extracted",
128
+ modelBounds: geometry.value.bounds,
129
+ modelPoint: [center(surfaceBounds, 0), surfaceBounds.min[1], center(surfaceBounds, 2)],
130
+ gamePoint: {
131
+ x: round3(levelLength / 2),
132
+ y: 0
133
+ },
134
+ anchorPairs: createPlatformerAnchorPairs(surfaces, surfaceBounds),
135
+ evidence: {
136
+ ...(options.routeOverlayPath ? { routeOverlay: options.routeOverlayPath } : {}),
137
+ notes: "Mesh-derived playable-surface anchors are computed from flat platform/world primitives and bind the generated level to the visible world asset with the same model-to-game scale used by the generated level."
138
+ }
139
+ },
140
+ evidence: {
141
+ sourceAsset: `assets.${assetId}`,
142
+ ...(options.renderedProbePath ? { renderedProbe: options.renderedProbePath } : {}),
143
+ ...(options.routeOverlayPath ? { routeOverlay: options.routeOverlayPath } : {}),
144
+ notes: `Playable surfaces extracted from ${horizontalCandidates.length} flat ${surfaceSelection.mode} GLB primitive(s) and bound to the current asset hash.`
145
+ }
146
+ };
147
+ return {
148
+ ok: true,
149
+ value: map,
150
+ reasons: [
151
+ `mesh-derived platformer surfaces from ${horizontalCandidates.length} ${surfaceSelection.mode} primitive(s)`,
152
+ ...surfaceSelection.reasons,
153
+ `surfaceCount:${surfaces.length}`,
154
+ `meshPlayableSurfaceCount:${meshPlayableSurfaces.length}`,
155
+ `estimatedCompletionSeconds:${map.estimatedCompletionSeconds}`
156
+ ]
157
+ };
158
+ }
159
+ function loadAssetGeometry(assetId, projectDir) {
160
+ const asset = readManifestAssets(projectDir).find((entry) => entry.id === assetId);
161
+ if (!asset)
162
+ return failure([`asset-extraction:asset-not-found:${assetId}`], [`Asset ${assetId} is not present in aura.assets.json.`]);
163
+ const assetPath = asset.outputPath ?? asset.source ?? asset.url;
164
+ if (!assetPath)
165
+ return failure([`asset-extraction:asset-file-missing:${assetId}`], [`Asset ${assetId} has no local outputPath/source path.`]);
166
+ const absolutePath = resolve(projectDir, assetPath);
167
+ if (!existsSync(absolutePath))
168
+ return failure([`asset-extraction:asset-file-not-found:${assetId}`], [`Asset file ${assetPath} does not exist.`]);
169
+ const document = loadGltfDocument(absolutePath);
170
+ if (!document.ok)
171
+ return document;
172
+ const primitives = collectPrimitiveGeometry(document.value);
173
+ if (primitives.length === 0)
174
+ return failure([`asset-extraction:no-mesh-primitives:${assetId}`], [`Asset ${assetId} has no readable POSITION mesh primitives.`]);
175
+ return {
176
+ ok: true,
177
+ value: {
178
+ asset,
179
+ primitives,
180
+ bounds: boundsForPrimitives(primitives)
181
+ },
182
+ reasons: [`read ${primitives.length} GLB primitive(s) from ${assetPath}`]
183
+ };
184
+ }
185
+ function loadGltfDocument(absolutePath) {
186
+ const buffer = readFileSync(absolutePath);
187
+ if (buffer.length < 20)
188
+ return failure(["asset-extraction:glb-too-small"], [`${absolutePath} is too small to be a GLB file.`]);
189
+ const magic = buffer.readUInt32LE(0);
190
+ if (magic !== 0x46546c67) {
191
+ return failure(["asset-extraction:only-glb-supported"], [`${absolutePath} is not a binary GLB; mesh geometry extraction currently requires retained GLB assets.`]);
192
+ }
193
+ const version = buffer.readUInt32LE(4);
194
+ if (version !== 2)
195
+ return failure([`asset-extraction:unsupported-glb-version:${version}`], [`${absolutePath} uses unsupported GLB version ${version}.`]);
196
+ let offset = 12;
197
+ let json;
198
+ let binaryChunk;
199
+ while (offset + 8 <= buffer.length) {
200
+ const chunkLength = buffer.readUInt32LE(offset);
201
+ const chunkType = buffer.readUInt32LE(offset + 4);
202
+ const chunkStart = offset + 8;
203
+ const chunkEnd = chunkStart + chunkLength;
204
+ if (chunkEnd > buffer.length)
205
+ break;
206
+ const chunk = buffer.subarray(chunkStart, chunkEnd);
207
+ if (chunkType === 0x4e4f534a)
208
+ json = JSON.parse(chunk.toString("utf8").trim());
209
+ if (chunkType === 0x004e4942)
210
+ binaryChunk = chunk;
211
+ offset = chunkEnd;
212
+ }
213
+ if (!json)
214
+ return failure(["asset-extraction:glb-json-missing"], [`${absolutePath} has no JSON chunk.`]);
215
+ return { ok: true, value: { json, ...(binaryChunk ? { binaryChunk } : {}) }, reasons: ["parsed GLB JSON and binary chunks"] };
216
+ }
217
+ function collectPrimitiveGeometry(document) {
218
+ const scene = document.json.scenes?.[document.json.scene ?? 0];
219
+ const sceneNodes = scene?.nodes ?? document.json.nodes?.map((_node, index) => index) ?? [];
220
+ return sceneNodes.flatMap((nodeIndex) => collectNodePrimitives(document, nodeIndex, IDENTITY_MATRIX, []));
221
+ }
222
+ function collectNodePrimitives(document, nodeIndex, parentTransform, path) {
223
+ const node = document.json.nodes?.[nodeIndex];
224
+ if (!node)
225
+ return [];
226
+ const nodeName = node.name ?? `node-${nodeIndex}`;
227
+ const transform = multiplyMat4(parentTransform, nodeMatrix(node));
228
+ const currentPath = [...path, nodeName];
229
+ const meshPrimitives = node.mesh === undefined
230
+ ? []
231
+ : collectMeshPrimitives(document, node.mesh, transform, currentPath.join("/"));
232
+ const childPrimitives = (node.children ?? []).flatMap((childIndex) => collectNodePrimitives(document, childIndex, transform, currentPath));
233
+ return [...meshPrimitives, ...childPrimitives];
234
+ }
235
+ function collectMeshPrimitives(document, meshIndex, transform, nodeName) {
236
+ const mesh = document.json.meshes?.[meshIndex];
237
+ if (!mesh)
238
+ return [];
239
+ return (mesh.primitives ?? []).flatMap((primitive, primitiveIndex) => {
240
+ const positionAccessor = primitive.attributes?.POSITION;
241
+ if (positionAccessor === undefined)
242
+ return [];
243
+ const vertices = readPositionAccessor(document, positionAccessor).map((point) => transformPoint(transform, point));
244
+ if (vertices.length === 0)
245
+ return [];
246
+ const bounds = boundsForVertices(vertices);
247
+ const materialName = document.json.materials?.[primitive.material ?? -1]?.name ?? `material-${primitive.material ?? "none"}`;
248
+ const size = boundsSize(bounds);
249
+ return [{
250
+ nodeName,
251
+ meshName: mesh.name ?? `mesh-${meshIndex}`,
252
+ materialName,
253
+ vertexCount: vertices.length,
254
+ bounds,
255
+ center: [center(bounds, 0), center(bounds, 1), center(bounds, 2)],
256
+ size,
257
+ vertices: vertices.length > 800 ? decimateVertices(vertices, 800) : vertices
258
+ }];
259
+ });
260
+ }
261
+ function readPositionAccessor(document, accessorIndex) {
262
+ const accessor = document.json.accessors?.[accessorIndex];
263
+ const binaryChunk = document.binaryChunk;
264
+ if (!accessor || !binaryChunk || accessor.bufferView === undefined || accessor.componentType !== 5126 || accessor.type !== "VEC3")
265
+ return [];
266
+ const view = document.json.bufferViews?.[accessor.bufferView];
267
+ if (!view || view.buffer !== 0)
268
+ return [];
269
+ const count = accessor.count ?? 0;
270
+ const accessorOffset = accessor.byteOffset ?? 0;
271
+ const viewOffset = view.byteOffset ?? 0;
272
+ const stride = view.byteStride ?? 12;
273
+ const start = viewOffset + accessorOffset;
274
+ const vertices = [];
275
+ for (let index = 0; index < count; index += 1) {
276
+ const offset = start + index * stride;
277
+ if (offset + 12 > binaryChunk.length)
278
+ break;
279
+ vertices.push([
280
+ binaryChunk.readFloatLE(offset),
281
+ binaryChunk.readFloatLE(offset + 4),
282
+ binaryChunk.readFloatLE(offset + 8)
283
+ ]);
284
+ }
285
+ return vertices;
286
+ }
287
+ function isRoadPrimitive(primitive) {
288
+ const label = `${primitive.nodeName} ${primitive.meshName} ${primitive.materialName}`;
289
+ return ROAD_PATTERN.test(label) && !ROAD_EXCLUDE_PATTERN.test(label);
290
+ }
291
+ function isPlatformPrimitive(primitive) {
292
+ const label = `${primitive.nodeName} ${primitive.meshName} ${primitive.materialName}`;
293
+ return PLATFORM_PATTERN.test(label) && !PLATFORM_EXCLUDE_PATTERN.test(label);
294
+ }
295
+ function selectPlatformerSurfacePrimitives(geometry) {
296
+ const semantic = geometry.primitives.filter(isPlatformPrimitive);
297
+ if (semantic.length > 0) {
298
+ return {
299
+ primitives: semantic,
300
+ mode: "semantic",
301
+ reasons: [`selected ${semantic.length} primitive(s) by semantic platform/world labels`]
302
+ };
303
+ }
304
+ const geometric = geometry.primitives.filter((primitive) => isGenericPlatformerSurfacePrimitive(primitive, geometry.bounds));
305
+ return {
306
+ primitives: geometric,
307
+ mode: "geometric",
308
+ reasons: geometric.length > 0
309
+ ? [`selected ${geometric.length} primitive(s) by flat horizontal game-surface geometry because the asset uses generic node/material names`]
310
+ : ["semantic labels were absent and no flat horizontal primitive passed the game-surface footprint filter"]
311
+ };
312
+ }
313
+ function isGenericPlatformerSurfacePrimitive(primitive, modelBounds) {
314
+ const label = `${primitive.nodeName} ${primitive.meshName} ${primitive.materialName}`;
315
+ if (PLATFORM_EXCLUDE_PATTERN.test(label))
316
+ return false;
317
+ if (!isHorizontalSurface(primitive))
318
+ return false;
319
+ const modelSize = boundsSize(modelBounds);
320
+ const modelFootprint = Math.max(0.001, modelSize[0] * modelSize[2]);
321
+ const primitiveFootprint = primitive.size[0] * primitive.size[2];
322
+ const verticalPosition = (primitive.bounds.max[1] - modelBounds.min[1]) / Math.max(0.001, modelSize[1]);
323
+ const longestAxis = Math.max(primitive.size[0], primitive.size[2]);
324
+ const shortestAxis = Math.min(primitive.size[0], primitive.size[2]);
325
+ return primitiveFootprint >= Math.max(0.08, modelFootprint * 0.00045) &&
326
+ longestAxis >= Math.max(0.7, modelSize[0] * 0.006) &&
327
+ shortestAxis >= 0.16 &&
328
+ verticalPosition <= 0.92;
329
+ }
330
+ function isHorizontalSurface(primitive) {
331
+ const horizontal = Math.max(primitive.size[0], primitive.size[2]);
332
+ const depth = Math.min(primitive.size[0], primitive.size[2]);
333
+ const height = primitive.size[1];
334
+ return horizontal >= 0.8 && depth >= 0.2 && height <= Math.max(0.18, horizontal * 0.16);
335
+ }
336
+ function sortPlayableSurfaceCandidate(a, b) {
337
+ const areaDelta = (b.size[0] * b.size[2]) - (a.size[0] * a.size[2]);
338
+ if (Math.abs(areaDelta) > 0.001)
339
+ return areaDelta;
340
+ return a.bounds.min[0] - b.bounds.min[0];
341
+ }
342
+ function createRoadCenterline(primitives, bounds) {
343
+ const allVertices = primitives.flatMap((primitive) => primitive.vertices);
344
+ const centerX = center(bounds, 0);
345
+ const centerZ = center(bounds, 2);
346
+ const bins = 20;
347
+ const samples = Array.from({ length: bins }, (_unused, bin) => {
348
+ const minAngle = -Math.PI + (bin / bins) * Math.PI * 2;
349
+ const maxAngle = -Math.PI + ((bin + 1) / bins) * Math.PI * 2;
350
+ const vertices = allVertices.filter((vertex) => {
351
+ const angle = Math.atan2(vertex[2] - centerZ, vertex[0] - centerX);
352
+ return angle >= minAngle && angle < maxAngle;
353
+ });
354
+ if (vertices.length < 4)
355
+ return undefined;
356
+ const averageRadius = average(vertices.map((vertex) => Math.hypot(vertex[0] - centerX, vertex[2] - centerZ))) * 0.68;
357
+ const angle = (minAngle + maxAngle) / 2;
358
+ return {
359
+ x: round3(centerX + Math.cos(angle) * averageRadius),
360
+ z: round3(centerZ + Math.sin(angle) * averageRadius),
361
+ width: averageRoadWidth(boundsSize(bounds))
362
+ };
363
+ }).filter((point) => Boolean(point));
364
+ if (samples.length > 0) {
365
+ const first = samples[0];
366
+ if (first)
367
+ return [...samples, first];
368
+ }
369
+ return [];
370
+ }
371
+ function createPlayableSurfaces(primitives, bounds) {
372
+ const minX = bounds.min[0];
373
+ const modelToGameScale = platformerModelToGameScale(bounds);
374
+ const uniquePrimitives = uniquePlayableSurfacePrimitives(primitives);
375
+ const candidateSurfaces = uniquePrimitives
376
+ .map((primitive, index) => {
377
+ const left = (primitive.bounds.min[0] - minX) * modelToGameScale;
378
+ return {
379
+ id: `candidate-platform-${String(index).padStart(2, "0")}`,
380
+ x: round3(left + (primitive.size[0] * modelToGameScale) / 2),
381
+ y: 0,
382
+ width: round3(Math.max(1.2, primitive.size[0] * modelToGameScale)),
383
+ height: round3(Math.max(0.22, primitive.size[1] * modelToGameScale)),
384
+ modelTopY: round3(primitive.bounds.max[1]),
385
+ modelCenterZ: center(primitive.bounds, 2),
386
+ kind: "platform"
387
+ };
388
+ });
389
+ const selectedSurfaces = [...dedupePlayableSurfaces(candidateSurfaces)]
390
+ .sort((a, b) => a.x - b.x)
391
+ .slice(0, 8);
392
+ const selectedBaselineTopY = selectedPlayableSurfaceBaseline(selectedSurfaces);
393
+ const topSurfaces = selectedSurfaces.map((surface, index) => ({
394
+ ...surface,
395
+ id: index === 0 ? "asset-main-ground" : `asset-platform-${String(index).padStart(2, "0")}`,
396
+ y: round3(Math.max(0, surface.modelTopY - selectedBaselineTopY) * modelToGameScale),
397
+ kind: index === 0 ? "ground" : "platform"
398
+ }));
399
+ const maxX = Math.max(...topSurfaces.map((surface) => surface.x + surface.width / 2));
400
+ const finish = {
401
+ id: "asset-finish-run",
402
+ x: round3(maxX + 1),
403
+ y: topSurfaces[topSurfaces.length - 1]?.y ?? 0.4,
404
+ width: 1.4,
405
+ height: 0.3,
406
+ kind: "finish"
407
+ };
408
+ const checkpoints = [0.2, 0.36, 0.52, 0.68, 0.84, 0.96].map((progress, index) => ({
409
+ id: `asset-checkpoint-${String(index + 1).padStart(2, "0")}`,
410
+ x: round3(maxX * progress),
411
+ y: round3((topSurfaces[index % topSurfaces.length]?.y ?? 0.4) + 0.9),
412
+ width: 1.1,
413
+ height: 1.1,
414
+ kind: "checkpoint"
415
+ }));
416
+ const hazard = {
417
+ id: "asset-hazard-gap",
418
+ x: round3(maxX * 0.58),
419
+ y: round3((topSurfaces[Math.min(2, topSurfaces.length - 1)]?.y ?? 0.4) + 0.34),
420
+ width: 0.48,
421
+ height: 0.24,
422
+ kind: "hazard"
423
+ };
424
+ return [...topSurfaces, finish, hazard, ...checkpoints];
425
+ }
426
+ function dedupePlayableSurfaces(surfaces) {
427
+ const seen = new Set();
428
+ return surfaces.filter((surface) => {
429
+ const key = [
430
+ surface.x,
431
+ surface.modelTopY,
432
+ surface.width,
433
+ surface.height
434
+ ].map((value) => value.toFixed(2)).join(":");
435
+ if (seen.has(key))
436
+ return false;
437
+ seen.add(key);
438
+ return true;
439
+ });
440
+ }
441
+ function selectedPlayableSurfaceBaseline(surfaces) {
442
+ if (surfaces.length === 0)
443
+ return 0;
444
+ return Math.min(...surfaces.map((surface) => surface.modelTopY));
445
+ }
446
+ function uniquePlayableSurfacePrimitives(primitives) {
447
+ const seen = new Set();
448
+ return primitives.filter((primitive) => {
449
+ const key = [
450
+ primitive.bounds.min[0],
451
+ primitive.bounds.min[1],
452
+ primitive.bounds.min[2],
453
+ primitive.bounds.max[0],
454
+ primitive.bounds.max[1],
455
+ primitive.bounds.max[2]
456
+ ].map((value) => value.toFixed(2)).join(":");
457
+ if (seen.has(key))
458
+ return false;
459
+ seen.add(key);
460
+ return true;
461
+ });
462
+ }
463
+ function createRacingAnchorPairs(centerline, bounds) {
464
+ const indices = [0, Math.floor(centerline.length / 3), Math.floor((centerline.length * 2) / 3)];
465
+ return indices.flatMap((index, anchorIndex) => {
466
+ const point = centerline[index];
467
+ return point
468
+ ? [{
469
+ id: `mesh-road-anchor-${anchorIndex + 1}`,
470
+ modelPoint: [point.x, bounds.min[1], point.z],
471
+ gamePoint: { x: point.x, z: point.z }
472
+ }]
473
+ : [];
474
+ });
475
+ }
476
+ function createPlatformerAnchorPairs(surfaces, bounds) {
477
+ const modelToGameScale = platformerModelToGameScale(bounds);
478
+ const meshBoundSurfaces = meshBoundPlayableSurfaces(surfaces);
479
+ const anchorSource = meshBoundSurfaces.length >= 3 ? meshBoundSurfaces : surfaces;
480
+ const anchorSurfaces = pickPlatformerAnchorSurfaces(anchorSource
481
+ .filter((surface) => isPlayablePlatformerSurfaceKind(surface.kind))
482
+ .sort((a, b) => a.x - b.x));
483
+ return anchorSurfaces.map((surface, index) => ({
484
+ id: `mesh-surface-anchor-${index + 1}`,
485
+ modelPoint: [
486
+ bounds.min[0] + surface.x / modelToGameScale,
487
+ modelTopForSurface(surface, bounds, modelToGameScale),
488
+ modelCenterZForSurface(surface, bounds)
489
+ ],
490
+ gamePoint: { x: surface.x, y: surface.y }
491
+ }));
492
+ }
493
+ function modelTopForSurface(surface, bounds, modelToGameScale) {
494
+ return hasExtractedSurfaceAnchor(surface) ? surface.modelTopY : round3(bounds.min[1] + surface.y / modelToGameScale);
495
+ }
496
+ function modelCenterZForSurface(surface, bounds) {
497
+ return hasExtractedSurfaceAnchor(surface) ? surface.modelCenterZ : center(bounds, 2);
498
+ }
499
+ function hasExtractedSurfaceAnchor(surface) {
500
+ return "modelTopY" in surface &&
501
+ typeof surface.modelTopY === "number" &&
502
+ "modelCenterZ" in surface &&
503
+ typeof surface.modelCenterZ === "number";
504
+ }
505
+ function isPlayablePlatformerSurfaceKind(kind) {
506
+ return kind === "ground" || kind === "platform" || kind === "moving";
507
+ }
508
+ function meshBoundPlayableSurfaces(surfaces) {
509
+ return surfaces.filter((surface) => isPlayablePlatformerSurfaceKind(surface.kind) && hasExtractedSurfaceAnchor(surface));
510
+ }
511
+ function platformerMeshSurfaceQualityBlockers(assetId, surfaces) {
512
+ const blockers = [];
513
+ if (surfaces.length < PLATFORMER_MIN_MESH_PLAYABLE_SURFACES) {
514
+ blockers.push(`asset-extraction:platformer-too-few-mesh-playable-surfaces:${assetId}:${surfaces.length}`);
515
+ }
516
+ const levelLength = measurePlatformerSurfaceLength(surfaces);
517
+ if (levelLength < PLATFORMER_MIN_LEVEL_LENGTH) {
518
+ blockers.push(`asset-extraction:platformer-level-length-too-short:${assetId}:${round3(levelLength)}`);
519
+ }
520
+ if (hasAmbiguousStackedSurfaceColumn(surfaces)) {
521
+ blockers.push(`asset-extraction:platformer-playable-surface-columns-ambiguous:${assetId}`);
522
+ }
523
+ return blockers;
524
+ }
525
+ function measurePlatformerSurfaceLength(surfaces) {
526
+ if (surfaces.length === 0)
527
+ return 0;
528
+ const minX = Math.min(...surfaces.map((surface) => surface.x - surface.width / 2));
529
+ const maxX = Math.max(...surfaces.map((surface) => surface.x + surface.width / 2));
530
+ return round3(maxX - minX);
531
+ }
532
+ function hasAmbiguousStackedSurfaceColumn(surfaces) {
533
+ const columns = new Map();
534
+ for (const surface of surfaces) {
535
+ const column = (Math.round(surface.x / PLATFORMER_AMBIGUOUS_COLUMN_STEP) * PLATFORMER_AMBIGUOUS_COLUMN_STEP).toFixed(2);
536
+ columns.set(column, [...(columns.get(column) ?? []), surface]);
537
+ }
538
+ for (const columnSurfaces of columns.values()) {
539
+ if (columnSurfaces.length < PLATFORMER_AMBIGUOUS_COLUMN_MIN_SURFACES)
540
+ continue;
541
+ const minY = Math.min(...columnSurfaces.map((surface) => surface.y));
542
+ const maxY = Math.max(...columnSurfaces.map((surface) => surface.y));
543
+ if (maxY - minY >= PLATFORMER_AMBIGUOUS_COLUMN_MIN_Y_RANGE)
544
+ return true;
545
+ }
546
+ return false;
547
+ }
548
+ function pickPlatformerAnchorSurfaces(surfaces) {
549
+ if (surfaces.length <= 3)
550
+ return surfaces;
551
+ const candidates = [
552
+ surfaces[0],
553
+ surfaces[Math.floor(surfaces.length / 2)],
554
+ surfaces[surfaces.length - 1]
555
+ ].filter((surface) => Boolean(surface));
556
+ const seen = new Set();
557
+ const anchors = candidates.filter((surface) => {
558
+ const key = `${surface.x.toFixed(2)}:${surface.y.toFixed(2)}`;
559
+ if (seen.has(key))
560
+ return false;
561
+ seen.add(key);
562
+ return true;
563
+ });
564
+ if (anchors.length >= 3)
565
+ return anchors;
566
+ for (const surface of surfaces) {
567
+ const key = `${surface.x.toFixed(2)}:${surface.y.toFixed(2)}`;
568
+ if (seen.has(key))
569
+ continue;
570
+ seen.add(key);
571
+ anchors.push(surface);
572
+ if (anchors.length >= 3)
573
+ break;
574
+ }
575
+ return anchors;
576
+ }
577
+ function platformerModelToGameScale(bounds) {
578
+ const size = boundsSize(bounds);
579
+ const widthScale = PLATFORMER_TARGET_GAME_LENGTH / Math.max(0.001, size[0]);
580
+ return round3(Math.min(PLATFORMER_MAX_GAME_SCALE, Math.max(PLATFORMER_MIN_GAME_SCALE, widthScale)));
581
+ }
582
+ function boundsForPrimitives(primitives) {
583
+ return boundsForVertices(primitives.flatMap((primitive) => [primitive.bounds.min, primitive.bounds.max]));
584
+ }
585
+ function boundsForVertices(vertices) {
586
+ const initial = [
587
+ Number.POSITIVE_INFINITY,
588
+ Number.POSITIVE_INFINITY,
589
+ Number.POSITIVE_INFINITY,
590
+ Number.NEGATIVE_INFINITY,
591
+ Number.NEGATIVE_INFINITY,
592
+ Number.NEGATIVE_INFINITY
593
+ ];
594
+ const bounds = vertices.reduce((acc, vertex) => [
595
+ Math.min(acc[0], vertex[0]),
596
+ Math.min(acc[1], vertex[1]),
597
+ Math.min(acc[2], vertex[2]),
598
+ Math.max(acc[3], vertex[0]),
599
+ Math.max(acc[4], vertex[1]),
600
+ Math.max(acc[5], vertex[2])
601
+ ], initial);
602
+ return {
603
+ min: [round3(bounds[0]), round3(bounds[1]), round3(bounds[2])],
604
+ max: [round3(bounds[3]), round3(bounds[4]), round3(bounds[5])]
605
+ };
606
+ }
607
+ function boundsSize(bounds) {
608
+ return [
609
+ round3(bounds.max[0] - bounds.min[0]),
610
+ round3(bounds.max[1] - bounds.min[1]),
611
+ round3(bounds.max[2] - bounds.min[2])
612
+ ];
613
+ }
614
+ function center(bounds, axis) {
615
+ return round3((bounds.min[axis] + bounds.max[axis]) / 2);
616
+ }
617
+ function averageRoadWidth(size) {
618
+ return round3(Math.max(0.12, Math.min(size[0], size[2]) * 0.08));
619
+ }
620
+ function measureClosedRouteLength(points) {
621
+ let total = 0;
622
+ for (let index = 1; index < points.length; index += 1) {
623
+ const previous = points[index - 1];
624
+ const current = points[index];
625
+ if (previous && current)
626
+ total += Math.hypot(current.x - previous.x, current.y - previous.y);
627
+ }
628
+ return total;
629
+ }
630
+ function decimateVertices(vertices, maxCount) {
631
+ if (vertices.length <= maxCount)
632
+ return vertices;
633
+ const step = Math.ceil(vertices.length / maxCount);
634
+ return vertices.filter((_vertex, index) => index % step === 0);
635
+ }
636
+ function nodeMatrix(node) {
637
+ if (node.matrix && node.matrix.length === 16 && node.matrix.every(Number.isFinite))
638
+ return node.matrix;
639
+ const translation = vectorFromArray(node.translation, [0, 0, 0]);
640
+ const scale = vectorFromArray(node.scale, [1, 1, 1]);
641
+ const rotation = quaternionFromArray(node.rotation, [0, 0, 0, 1]);
642
+ return composeMat4(translation, rotation, scale);
643
+ }
644
+ function vectorFromArray(value, fallback) {
645
+ return value && value.length >= 3 && value.slice(0, 3).every(Number.isFinite)
646
+ ? [value[0] ?? fallback[0], value[1] ?? fallback[1], value[2] ?? fallback[2]]
647
+ : fallback;
648
+ }
649
+ function quaternionFromArray(value, fallback) {
650
+ return value && value.length >= 4 && value.slice(0, 4).every(Number.isFinite)
651
+ ? [value[0] ?? fallback[0], value[1] ?? fallback[1], value[2] ?? fallback[2], value[3] ?? fallback[3]]
652
+ : fallback;
653
+ }
654
+ function composeMat4(translation, rotation, scale) {
655
+ const [x, y, z, w] = rotation;
656
+ const x2 = x + x;
657
+ const y2 = y + y;
658
+ const z2 = z + z;
659
+ const xx = x * x2;
660
+ const xy = x * y2;
661
+ const xz = x * z2;
662
+ const yy = y * y2;
663
+ const yz = y * z2;
664
+ const zz = z * z2;
665
+ const wx = w * x2;
666
+ const wy = w * y2;
667
+ const wz = w * z2;
668
+ return [
669
+ (1 - (yy + zz)) * scale[0],
670
+ (xy + wz) * scale[0],
671
+ (xz - wy) * scale[0],
672
+ 0,
673
+ (xy - wz) * scale[1],
674
+ (1 - (xx + zz)) * scale[1],
675
+ (yz + wx) * scale[1],
676
+ 0,
677
+ (xz + wy) * scale[2],
678
+ (yz - wx) * scale[2],
679
+ (1 - (xx + yy)) * scale[2],
680
+ 0,
681
+ translation[0],
682
+ translation[1],
683
+ translation[2],
684
+ 1
685
+ ];
686
+ }
687
+ function multiplyMat4(a, b) {
688
+ const valueAt = (index) => {
689
+ const row = index % 4;
690
+ const column = Math.floor(index / 4) * 4;
691
+ return a[row] * b[column] + a[row + 4] * b[column + 1] + a[row + 8] * b[column + 2] + a[row + 12] * b[column + 3];
692
+ };
693
+ return [
694
+ valueAt(0), valueAt(1), valueAt(2), valueAt(3),
695
+ valueAt(4), valueAt(5), valueAt(6), valueAt(7),
696
+ valueAt(8), valueAt(9), valueAt(10), valueAt(11),
697
+ valueAt(12), valueAt(13), valueAt(14), valueAt(15)
698
+ ];
699
+ }
700
+ function transformPoint(matrix, point) {
701
+ const x = point[0];
702
+ const y = point[1];
703
+ const z = point[2];
704
+ return [
705
+ matrix[0] * x + matrix[4] * y + matrix[8] * z + matrix[12],
706
+ matrix[1] * x + matrix[5] * y + matrix[9] * z + matrix[13],
707
+ matrix[2] * x + matrix[6] * y + matrix[10] * z + matrix[14]
708
+ ];
709
+ }
710
+ function requireAssetHash(asset) {
711
+ return asset.hash ?? "sha256-0000000000000000000000000000000000000000000000000000000000000000";
712
+ }
713
+ function average(values) {
714
+ return values.length === 0 ? 0 : values.reduce((sum, value) => sum + value, 0) / values.length;
715
+ }
716
+ function round3(value) {
717
+ return Math.round(value * 1000) / 1000;
718
+ }
719
+ function formatSize(size) {
720
+ return `[${size.map((value) => value.toFixed(3)).join(",")}]`;
721
+ }
722
+ function failure(blockers, reasons) {
723
+ return { ok: false, blockers, reasons };
724
+ }
725
+ //# sourceMappingURL=showcase-spec-game-geometry-extractor.js.map