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,280 @@
1
+ import {
2
+ camera,
3
+ createAuraApp,
4
+ game,
5
+ lights,
6
+ material,
7
+ model,
8
+ primitives,
9
+ scene,
10
+ type AuraNodeInput
11
+ } from "@aura3d/engine";
12
+ import { assets } from "./aura-assets";
13
+
14
+ declare global {
15
+ interface Window {
16
+ __AURA3D_RACING_STARTER__?: RacingStarterEvidence;
17
+ }
18
+ }
19
+
20
+ interface RacingStarterEvidence {
21
+ readonly status: string;
22
+ readonly frame: number;
23
+ readonly lap: number;
24
+ readonly checkpoint: number;
25
+ readonly checkpointCount: number;
26
+ readonly speed: number;
27
+ readonly progress: number;
28
+ readonly heading: number;
29
+ readonly position: { readonly x: number; readonly y: number };
30
+ readonly events: readonly string[];
31
+ readonly lapProof: {
32
+ readonly status: string;
33
+ readonly events: readonly string[];
34
+ readonly bestTime?: number;
35
+ readonly checkpointCount: number;
36
+ readonly lapsToWin: number;
37
+ readonly minLapSeconds: number;
38
+ readonly routeAlignedToVisibleTrack: boolean;
39
+ };
40
+ readonly evidence: unknown;
41
+ }
42
+
43
+ const raceContract = {
44
+ checkpointCount: 6,
45
+ lapsToWin: 3,
46
+ minLapSeconds: 22,
47
+ routeAlignedToVisibleTrack: true
48
+ } as const;
49
+
50
+ const routeRibbonSegments = [
51
+ ["route south straight", [2.8, 0.03, 0], [5.8, 0.08, 0.58]],
52
+ ["route east straight", [5.6, 0.03, 1.7], [0.58, 0.08, 3.6]],
53
+ ["route north straight", [2.8, 0.03, 3.4], [5.8, 0.08, 0.58]],
54
+ ["route west straight", [0, 0.03, 1.7], [0.58, 0.08, 3.6]]
55
+ ] as const;
56
+
57
+ const route = {
58
+ id: "starter-kart-loop",
59
+ width: 2.2,
60
+ points: [
61
+ { x: -0.4, y: -0.25 },
62
+ { x: 2.4, y: -0.9 },
63
+ { x: 5.4, y: 0.15 },
64
+ { x: 5.9, y: 2.7 },
65
+ { x: 3.2, y: 3.9 },
66
+ { x: 0.3, y: 3.15 },
67
+ { x: -0.75, y: 1.15 }
68
+ ],
69
+ checkpoints: [0.08, 0.22, 0.36, 0.5, 0.64, 0.78]
70
+ };
71
+
72
+ const input = game.input({
73
+ actions: {
74
+ throttle: ["KeyW", "ArrowUp"],
75
+ brake: ["KeyS", "ArrowDown"],
76
+ left: ["KeyA", "ArrowLeft"],
77
+ right: ["KeyD", "ArrowRight"],
78
+ drift: ["Space"],
79
+ boost: ["ShiftLeft", "ShiftRight"],
80
+ reset: ["KeyR"]
81
+ },
82
+ axes: {
83
+ steer: { negative: "left", positive: "right" }
84
+ },
85
+ bufferMs: 80
86
+ });
87
+
88
+ const racing = game.racing({
89
+ route,
90
+ startProgress: 0.04,
91
+ checkpointRadius: 0.08,
92
+ lapsToWin: raceContract.lapsToWin,
93
+ maxSpeed: 11.5,
94
+ acceleration: 18,
95
+ drag: 1.4,
96
+ steerRate: 2.85
97
+ });
98
+
99
+ const routeEvents = game.eventLog({ label: "racing starter events", maxEvents: 16 });
100
+ const hud = game.hud.bindings([
101
+ game.hud.objective({ valuePath: "appState.objective" }),
102
+ game.hud.timer({ valuePath: "appState.lapTime" }),
103
+ game.hud.checkpoint({ valuePath: "appState.checkpoint" }),
104
+ game.hud.eventLog({ valuePath: "appState.events" })
105
+ ]);
106
+ const lapProof = createLapProof();
107
+
108
+ const app = createAuraApp("#app", {
109
+ diagnostics: { overlay: true, performancePanel: true },
110
+ scene: buildScene()
111
+ });
112
+
113
+ const car = app.nodes.require("race-car");
114
+ const checkpointMarker = app.nodes.require("checkpoint-marker");
115
+ const finishMarker = app.nodes.require("finish-marker");
116
+ const hudRoot = createHud();
117
+ let objective = "Clear six gates across a 3-lap typed-asset route.";
118
+ const raceEventLabels: string[] = [];
119
+
120
+ app.onFrame(({ dt }: { readonly dt: number }) => {
121
+ input.update(dt);
122
+ if (input.pressed("reset")) {
123
+ racing.reset(0.04);
124
+ routeEvents.push({ type: "reset", label: "reset" });
125
+ raceEventLabels.push("reset");
126
+ objective = "Clear six gates across a 3-lap typed-asset route.";
127
+ }
128
+
129
+ const state = racing.step(dt, {
130
+ throttle: input.held("throttle"),
131
+ brake: input.held("brake"),
132
+ steer: input.axis("steer"),
133
+ drift: input.held("drift"),
134
+ boost: input.held("boost")
135
+ });
136
+
137
+ for (const event of state.events) {
138
+ const label = event.id ? `${event.type}:${event.id}` : event.type;
139
+ raceEventLabels.push(label);
140
+ routeEvents.push({
141
+ type: event.type,
142
+ label,
143
+ severity: event.type === "finish" || event.type === "lap" || event.type === "checkpoint" ? "success" : "info",
144
+ frame: event.frame,
145
+ time: event.time
146
+ });
147
+ if (event.type === "checkpoint") objective = `Gate ${state.checkpoint}/${state.checkpointCount}. Stay on the route.`;
148
+ if (event.type === "lap") objective = `Lap ${state.lap}/${state.lapsToWin}. Keep the line.`;
149
+ if (event.type === "finish") objective = "Finished. Press R to run it again.";
150
+ if (event.type === "off-track") objective = "Back onto the racing line.";
151
+ }
152
+
153
+ car
154
+ .setPosition(state.position.x, 0.32, state.position.y)
155
+ .setRotation(0, -state.heading + Math.PI / 2, 0)
156
+ .setScale(state.drift > 0.15 ? [0.2, 0.2, 0.22] : [0.18, 0.18, 0.18]);
157
+ checkpointMarker.setVisible(state.checkpoint === 0);
158
+ finishMarker.setScale(state.status === "finished" ? [1.15, 0.08, 0.14] : [0.86, 0.08, 0.12]);
159
+
160
+ renderHud(state);
161
+ publishEvidence(state);
162
+ });
163
+
164
+ publishEvidence(racing.snapshot());
165
+ renderHud(racing.snapshot());
166
+
167
+ function buildScene() {
168
+ const nodes: AuraNodeInput[] = [
169
+ model(assets.trackModel, { name: "typed kart circuit asset" })
170
+ .position(2.8, -0.08, 1.7)
171
+ .scale(0.18),
172
+ model(assets.carModel, { name: "typed playable sports car", castShadow: true })
173
+ .position(0, 0.32, 0)
174
+ .scale(0.18)
175
+ .runtime(game.runtimeNode("race-car", { tags: ["player", "vehicle", "typed-asset", "runtime"] })),
176
+ ...routeRibbonNodes(),
177
+ primitives.box({ name: "checkpoint marker", material: material.neon({ color: "#7ff0c5", emissive: "#7ff0c5", emissiveIntensity: 0.7 }) })
178
+ .position(0.06, 0.18, 0.2)
179
+ .scale([0.16, 0.28, 0.95])
180
+ .runtime(game.runtimeNode("checkpoint-marker", { tags: ["checkpoint", "runtime"] })),
181
+ primitives.box({ name: "finish stripe", material: material.neon({ color: "#f9f1d0", emissive: "#f9f1d0", emissiveIntensity: 0.75 }) })
182
+ .position(0.1, 0.2, 0.05)
183
+ .scale([0.86, 0.08, 0.12])
184
+ .runtime(game.runtimeNode("finish-marker", { tags: ["finish", "runtime"] }))
185
+ ];
186
+
187
+ return scene()
188
+ .background("#060b10")
189
+ .addMany(nodes)
190
+ .add(lights.ambient({ name: "race ambient", intensity: 0.38, color: "#e9f6ff" }))
191
+ .add(lights.directional({ name: "race key", position: [4, 7, 5], intensity: 1.1, color: "#ffffff" }))
192
+ .camera(camera.perspective({ position: [2.7, 7.8, 8.6], target: [2.7, 0, 1.55], fov: 43 }));
193
+ }
194
+
195
+ function routeRibbonNodes(): AuraNodeInput[] {
196
+ const asphalt = material.pbr({ color: "#5a6570", roughness: 0.78, metallic: 0.03 });
197
+ return routeRibbonSegments.map(([name, position, scale]) =>
198
+ primitives.box({ name, material: asphalt }).position(...position).scale(scale)
199
+ );
200
+ }
201
+
202
+ function createHud(): HTMLElement {
203
+ const root = document.createElement("aside");
204
+ root.id = "racing-starter-hud";
205
+ root.style.cssText = [
206
+ "position:absolute",
207
+ "left:16px",
208
+ "top:16px",
209
+ "z-index:5",
210
+ "min-width:290px",
211
+ "font:600 13px/1.35 Inter, system-ui, sans-serif",
212
+ "color:#f5fbff",
213
+ "background:rgba(3,9,14,0.78)",
214
+ "border:1px solid rgba(125,220,235,0.34)",
215
+ "border-radius:8px",
216
+ "padding:12px",
217
+ "pointer-events:none"
218
+ ].join(";");
219
+ document.body.append(root);
220
+ return root;
221
+ }
222
+
223
+ function renderHud(state: ReturnType<typeof racing.snapshot>): void {
224
+ hudRoot.innerHTML = [
225
+ `<strong>Aura3D Racing Starter</strong>`,
226
+ `<div>Lap ${state.lap}/${state.lapsToWin} | Checkpoint ${state.checkpoint}/${state.checkpointCount}</div>`,
227
+ `<div>Speed ${Math.round(Math.abs(state.speed) * 8)} km/h | Time ${state.lapTime.toFixed(2)}s</div>`,
228
+ `<div>${objective}</div>`,
229
+ `<div>Throttle W/Up. Steer A/D. Drift Space. Reset R.</div>`
230
+ ].join("");
231
+ }
232
+
233
+ function publishEvidence(state: ReturnType<typeof racing.snapshot>): void {
234
+ const evidence = app.evidence({
235
+ input,
236
+ events: routeEvents,
237
+ hud,
238
+ appState: {
239
+ objective,
240
+ lapTime: state.lapTime,
241
+ checkpoint: `${state.checkpoint}/${state.checkpointCount}`,
242
+ events: routeEvents.events().map((event: { readonly label: string }) => event.label)
243
+ },
244
+ assets: {
245
+ typedAssets: Object.keys(assets).length,
246
+ missingAssets: []
247
+ },
248
+ source: { expectsGame: true }
249
+ });
250
+ window.__AURA3D_RACING_STARTER__ = {
251
+ status: state.status,
252
+ frame: state.frame,
253
+ lap: state.lap,
254
+ checkpoint: state.checkpoint,
255
+ checkpointCount: state.checkpointCount,
256
+ speed: state.speed,
257
+ progress: state.progress,
258
+ heading: state.heading,
259
+ position: state.position,
260
+ events: raceEventLabels,
261
+ lapProof,
262
+ evidence
263
+ };
264
+ }
265
+
266
+ function createLapProof(): RacingStarterEvidence["lapProof"] {
267
+ return {
268
+ status: "contract-ready",
269
+ events: [
270
+ ...route.checkpoints.map((_checkpoint, index) => `checkpoint:checkpoint-${index + 1}`),
271
+ "lap:multi-lap-contract",
272
+ "reset:available"
273
+ ],
274
+ bestTime: raceContract.minLapSeconds * raceContract.lapsToWin,
275
+ checkpointCount: raceContract.checkpointCount,
276
+ lapsToWin: raceContract.lapsToWin,
277
+ minLapSeconds: raceContract.minLapSeconds,
278
+ routeAlignedToVisibleTrack: raceContract.routeAlignedToVisibleTrack
279
+ };
280
+ }
@@ -0,0 +1,93 @@
1
+ import { expect, test } from "@playwright/test";
2
+
3
+ test("racing starter responds to keyboard input and exposes a multi-lap route contract", async ({ page }) => {
4
+ await page.goto("/");
5
+ await page.waitForFunction(() => document.body.dataset.aura3dReady === "true", undefined, { timeout: 45_000 });
6
+ await page.waitForFunction(() => Boolean((window as unknown as { __AURA3D_RACING_STARTER__?: unknown }).__AURA3D_RACING_STARTER__));
7
+
8
+ const initial = await racingState(page);
9
+ expect(initial.status).toBe("running");
10
+ expect(initial.speed).toBeCloseTo(0, 5);
11
+ expect(initial.checkpointCount).toBeGreaterThanOrEqual(6);
12
+ expect(initial.lapProof.status).toBe("contract-ready");
13
+ expect(initial.lapProof.events).toEqual(expect.arrayContaining([
14
+ "checkpoint:checkpoint-1",
15
+ "checkpoint:checkpoint-6",
16
+ "lap:multi-lap-contract",
17
+ "reset:available"
18
+ ]));
19
+ expect(initial.lapProof.lapsToWin).toBeGreaterThanOrEqual(3);
20
+ expect(initial.lapProof.minLapSeconds).toBeGreaterThanOrEqual(20);
21
+ expect(initial.lapProof.routeAlignedToVisibleTrack).toBe(true);
22
+
23
+ await page.keyboard.down("ArrowUp");
24
+ await page.waitForTimeout(260);
25
+ const accelerated = await racingState(page);
26
+ expect(accelerated.speed).toBeGreaterThan(initial.speed + 0.5);
27
+ expect(accelerated.progress).not.toBe(initial.progress);
28
+
29
+ await page.keyboard.down("ArrowRight");
30
+ await page.waitForTimeout(300);
31
+ await page.keyboard.up("ArrowRight");
32
+ const steered = await racingState(page);
33
+ expect(steered.heading).not.toBe(accelerated.heading);
34
+
35
+ await page.keyboard.up("ArrowUp");
36
+ const afterInput = await racingState(page);
37
+ expect(afterInput.events.length).toBeGreaterThanOrEqual(initial.events.length);
38
+
39
+ await tapKey(page, "KeyR");
40
+ await page.waitForTimeout(140);
41
+ const reset = await racingState(page);
42
+ expect(reset.status).toBe("running");
43
+ expect(reset.checkpoint).toBe(0);
44
+ expect(reset.events.some((event) => event.includes("reset"))).toBe(true);
45
+ });
46
+
47
+ async function racingState(page: import("@playwright/test").Page): Promise<{
48
+ readonly status: string;
49
+ readonly checkpoint: number;
50
+ readonly checkpointCount: number;
51
+ readonly speed: number;
52
+ readonly progress: number;
53
+ readonly heading: number;
54
+ readonly events: readonly string[];
55
+ readonly lapProof: {
56
+ readonly status: string;
57
+ readonly events: readonly string[];
58
+ readonly checkpointCount: number;
59
+ readonly lapsToWin: number;
60
+ readonly minLapSeconds: number;
61
+ readonly routeAlignedToVisibleTrack: boolean;
62
+ };
63
+ }> {
64
+ return await page.evaluate(() => {
65
+ const state = (window as unknown as {
66
+ readonly __AURA3D_RACING_STARTER__?: {
67
+ readonly status: string;
68
+ readonly checkpoint: number;
69
+ readonly checkpointCount: number;
70
+ readonly speed: number;
71
+ readonly progress: number;
72
+ readonly heading: number;
73
+ readonly events: readonly string[];
74
+ readonly lapProof: {
75
+ readonly status: string;
76
+ readonly events: readonly string[];
77
+ readonly checkpointCount: number;
78
+ readonly lapsToWin: number;
79
+ readonly minLapSeconds: number;
80
+ readonly routeAlignedToVisibleTrack: boolean;
81
+ };
82
+ };
83
+ }).__AURA3D_RACING_STARTER__;
84
+ if (!state) throw new Error("Missing __AURA3D_RACING_STARTER__ state.");
85
+ return state;
86
+ });
87
+ }
88
+
89
+ async function tapKey(page: import("@playwright/test").Page, key: string, holdMs = 90): Promise<void> {
90
+ await page.keyboard.down(key);
91
+ await page.waitForTimeout(holdMs);
92
+ await page.keyboard.up(key);
93
+ }
@@ -0,0 +1,40 @@
1
+ import { mkdirSync, writeFileSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ import { expect, test } from "@playwright/test";
4
+
5
+ test.setTimeout(60_000);
6
+
7
+ test("Aura3D racing starter reaches ready state with gameplay evidence", async ({ page }) => {
8
+ await page.goto("/");
9
+ await expect.poll(() => page.locator("body").getAttribute("data-aura3d-ready"), { timeout: 45_000 }).toBe("true");
10
+
11
+ const drawCalls = Number(await page.locator("body").getAttribute("data-aura3d-draw-calls"));
12
+ const routeState = await page.evaluate(() => {
13
+ const value: unknown = Reflect.get(window, "__AURA3D_RACING_STARTER__");
14
+ const state = isRecord(value) ? value : {};
15
+ const lapProofValue = state.lapProof;
16
+ const lapProof = isRecord(lapProofValue) ? lapProofValue : {};
17
+ return {
18
+ status: typeof state.status === "string" ? state.status : "missing",
19
+ checkpointCount: typeof state.checkpointCount === "number" ? state.checkpointCount : 0,
20
+ lapProofStatus: typeof lapProof.status === "string" ? lapProof.status : "missing",
21
+ lapsToWin: typeof lapProof.lapsToWin === "number" ? lapProof.lapsToWin : 0,
22
+ minLapSeconds: typeof lapProof.minLapSeconds === "number" ? lapProof.minLapSeconds : 0,
23
+ routeAlignedToVisibleTrack: lapProof.routeAlignedToVisibleTrack === true
24
+ };
25
+
26
+ function isRecord(recordValue: unknown): recordValue is Readonly<Record<string, unknown>> {
27
+ return typeof recordValue === "object" && recordValue !== null && !Array.isArray(recordValue);
28
+ }
29
+ });
30
+
31
+ expect(drawCalls).toBeGreaterThan(0);
32
+ expect(routeState.status).toBe("running");
33
+ expect(routeState.checkpointCount).toBeGreaterThanOrEqual(6);
34
+ expect(routeState.lapProofStatus).toBe("contract-ready");
35
+ expect(routeState.lapsToWin).toBeGreaterThanOrEqual(3);
36
+ expect(routeState.minLapSeconds).toBeGreaterThanOrEqual(20);
37
+ expect(routeState.routeAlignedToVisibleTrack).toBe(true);
38
+ mkdirSync(resolve("tests/reports"), { recursive: true });
39
+ writeFileSync(resolve("tests/reports/route-health.json"), `${JSON.stringify({ ready: true, drawCalls, routeState }, null, 2)}\n`);
40
+ });
@@ -0,0 +1,13 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "Bundler",
6
+ "strict": true,
7
+ "skipLibCheck": true,
8
+ "allowSyntheticDefaultImports": true,
9
+ "esModuleInterop": true,
10
+ "types": ["vite/client"]
11
+ },
12
+ "include": ["src/**/*.ts", "tests/**/*.ts"]
13
+ }
@@ -0,0 +1,16 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Aura3D Three-Compat Architecture Interior</title>
7
+ <style>
8
+ html, body, #app { margin: 0; width: 100%; height: 100%; background: #080d15; }
9
+ body { font-family: Inter, ui-sans-serif, system-ui, sans-serif; }
10
+ </style>
11
+ </head>
12
+ <body>
13
+ <div id="app"></div>
14
+ <script type="module" src="/src/main.ts"></script>
15
+ </body>
16
+ </html>
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "aura3d-three-compat-architecture-interior",
3
+ "version": "1.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite --host 127.0.0.1",
8
+ "typecheck": "tsc --noEmit",
9
+ "build": "npm run typecheck && vite build",
10
+ "preview": "vite preview --host 127.0.0.1",
11
+ "test": "playwright test tests/route-health.spec.ts tests/screenshot.spec.ts --workers=1"
12
+ },
13
+ "dependencies": {
14
+ "@aura3d/engine": "1.3.3"
15
+ },
16
+ "devDependencies": {
17
+ "@playwright/test": "^1.52.0",
18
+ "typescript": "^5.8.3",
19
+ "vite": "^7.3.2"
20
+ }
21
+ }
@@ -0,0 +1,14 @@
1
+ import { defineConfig } from "@playwright/test";
2
+
3
+ export default defineConfig({
4
+ testDir: "./tests",
5
+ use: {
6
+ baseURL: "http://127.0.0.1:4191"
7
+ },
8
+ webServer: {
9
+ command: "npm exec vite -- --host 127.0.0.1 --port 4191 --strictPort",
10
+ url: "http://127.0.0.1:4191",
11
+ reuseExistingServer: !process.env.CI,
12
+ timeout: 120_000
13
+ }
14
+ });
@@ -0,0 +1,27 @@
1
+ // Three-compat architecture interior: a small room built from boxes,
2
+ // authored entirely through the public @aura3d/engine scene API.
3
+ import { camera, createAuraApp, lights, material, primitives, scene } from "@aura3d/engine";
4
+
5
+ const plaster = material.pbr({ color: "#cfd6e2", roughness: 0.85 });
6
+ const oakFloor = material.pbr({ color: "#8a6f52", roughness: 0.6 });
7
+ const walnut = material.pbr({ color: "#5b4231", roughness: 0.5 });
8
+
9
+ createAuraApp("#app", {
10
+ scene: scene()
11
+ .background("#10141c")
12
+ .camera(camera.orbit({ target: [0, 1.1, 0], distance: 7 }))
13
+ .add(primitives.box({ name: "floor slab", size: [6, 0.12, 6], position: [0, -0.06, 0], material: oakFloor, receiveShadow: true }))
14
+ .add(primitives.box({ name: "back wall", size: [6, 2.8, 0.12], position: [0, 1.4, -3], material: plaster, receiveShadow: true }))
15
+ .add(primitives.box({ name: "side wall", size: [0.12, 2.8, 6], position: [-3, 1.4, 0], material: plaster, receiveShadow: true }))
16
+ .add(primitives.box({ name: "window header", size: [2.4, 0.5, 0.12], position: [1.2, 2.55, -3.01], material: plaster }))
17
+ .add(primitives.box({ name: "table top", size: [1.8, 0.08, 0.9], position: [0.5, 0.74, -0.8], material: walnut, castShadow: true }))
18
+ .add(primitives.box({ name: "table leg front", size: [0.08, 0.7, 0.08], position: [-0.3, 0.35, -0.45], material: walnut, castShadow: true }))
19
+ .add(primitives.box({ name: "table leg back", size: [0.08, 0.7, 0.08], position: [1.3, 0.35, -1.15], material: walnut, castShadow: true }))
20
+ .add(primitives.box({ name: "sofa seat", size: [2, 0.45, 0.9], position: [-1.8, 0.23, 1.3], material: material.pbr({ color: "#41506b", roughness: 0.78 }), castShadow: true }))
21
+ .add(primitives.box({ name: "sofa backrest", size: [2, 0.6, 0.18], position: [-1.8, 0.75, 1.66], material: material.pbr({ color: "#36435a", roughness: 0.78 }), castShadow: true }))
22
+ .add(primitives.cylinder({ name: "floor lamp pole", size: [0.06, 1.6, 0.06], position: [2.3, 0.8, 1.8], material: material.metal({ color: "#9aa3ad" }), castShadow: true }))
23
+ .add(primitives.sphere({ name: "floor lamp shade", size: 0.32, position: [2.3, 1.7, 1.8], material: material.emissive({ color: "#ffe7b8", emissive: "#ffd28a", emissiveIntensity: 0.8 }) }))
24
+ .add(lights.ambient({ intensity: 0.35 }))
25
+ .add(lights.directional({ name: "window sun", position: [4, 5, 3], intensity: 1.4 }))
26
+ .add(lights.point({ name: "lamp fill", position: [2.3, 1.7, 1.8], intensity: 1.1, color: "#ffd9a0" }))
27
+ });
@@ -0,0 +1,16 @@
1
+ import { mkdirSync, writeFileSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ import { expect, test } from "@playwright/test";
4
+
5
+ test.setTimeout(60_000);
6
+
7
+ test("Aura3D three-compat template reaches ready state", async ({ page }) => {
8
+ await page.goto("/");
9
+ await expect.poll(() => page.locator("body").getAttribute("data-aura3d-ready"), { timeout: 45_000 }).toBe("true");
10
+ const drawCalls = Number(await page.locator("body").getAttribute("data-aura3d-draw-calls"));
11
+ const diagnostics = await page.evaluate(() => (window as unknown as { __AURA3D_ROUTE_READY__?: { diagnostics?: { backend?: string } } }).__AURA3D_ROUTE_READY__?.diagnostics);
12
+ expect(diagnostics?.backend).toBe("webgl2");
13
+ expect(drawCalls).toBeGreaterThan(0);
14
+ mkdirSync(resolve("tests/reports"), { recursive: true });
15
+ writeFileSync(resolve("tests/reports/route-health.json"), `${JSON.stringify({ ready: true, backend: diagnostics?.backend, drawCalls }, null, 2)}\n`);
16
+ });
@@ -0,0 +1,15 @@
1
+ import { mkdirSync, writeFileSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ import { expect, test } from "@playwright/test";
4
+
5
+ test.setTimeout(60_000);
6
+
7
+ test("Aura3D three-compat template screenshot shows content", async ({ page }) => {
8
+ await page.goto("/");
9
+ await expect.poll(() => page.locator("body").getAttribute("data-aura3d-ready"), { timeout: 45_000 }).toBe("true");
10
+ const screenshot = await page.screenshot({ fullPage: false });
11
+ mkdirSync(resolve("tests/reports"), { recursive: true });
12
+ writeFileSync(resolve("tests/reports/screenshot.png"), screenshot);
13
+ writeFileSync(resolve("tests/reports/screenshot.json"), `${JSON.stringify({ bytes: screenshot.byteLength }, null, 2)}\n`);
14
+ expect(screenshot.byteLength).toBeGreaterThan(1000);
15
+ });
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "useDefineForClassFields": true,
5
+ "module": "ESNext",
6
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
7
+ "allowJs": false,
8
+ "skipLibCheck": true,
9
+ "esModuleInterop": true,
10
+ "allowSyntheticDefaultImports": true,
11
+ "strict": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "moduleResolution": "Bundler",
14
+ "resolveJsonModule": true,
15
+ "isolatedModules": true,
16
+ "noEmit": true
17
+ },
18
+ "include": ["src", "tests"]
19
+ }
@@ -0,0 +1,16 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Aura3D Three-Compat Asset Inspector</title>
7
+ <style>
8
+ html, body, #app { margin: 0; width: 100%; height: 100%; background: #080d15; }
9
+ body { font-family: Inter, ui-sans-serif, system-ui, sans-serif; }
10
+ </style>
11
+ </head>
12
+ <body>
13
+ <div id="app"></div>
14
+ <script type="module" src="/src/main.ts"></script>
15
+ </body>
16
+ </html>
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "aura3d-three-compat-asset-inspector",
3
+ "version": "1.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite --host 127.0.0.1",
8
+ "typecheck": "tsc --noEmit",
9
+ "build": "npm run typecheck && vite build",
10
+ "preview": "vite preview --host 127.0.0.1",
11
+ "test": "playwright test tests/route-health.spec.ts tests/screenshot.spec.ts --workers=1"
12
+ },
13
+ "dependencies": {
14
+ "@aura3d/engine": "1.3.3"
15
+ },
16
+ "devDependencies": {
17
+ "@playwright/test": "^1.52.0",
18
+ "typescript": "^5.8.3",
19
+ "vite": "^7.3.2"
20
+ }
21
+ }
@@ -0,0 +1,14 @@
1
+ import { defineConfig } from "@playwright/test";
2
+
3
+ export default defineConfig({
4
+ testDir: "./tests",
5
+ use: {
6
+ baseURL: "http://127.0.0.1:4192"
7
+ },
8
+ webServer: {
9
+ command: "npm exec vite -- --host 127.0.0.1 --port 4192 --strictPort",
10
+ url: "http://127.0.0.1:4192",
11
+ reuseExistingServer: !process.env.CI,
12
+ timeout: 120_000
13
+ }
14
+ });
@@ -0,0 +1,17 @@
1
+ // Three-compat asset inspector: one centered primitive on a neutral stage
2
+ // with inspection lighting, authored through the public @aura3d/engine API.
3
+ // Swap the torus for `model(assets.yourAsset)` once you add typed assets.
4
+ import { camera, createAuraApp, lights, material, primitives, scene } from "@aura3d/engine";
5
+
6
+ createAuraApp("#app", {
7
+ diagnostics: { overlay: true },
8
+ scene: scene()
9
+ .background("#0b0f16")
10
+ .camera(camera.orbit({ target: [0, 0.9, 0], distance: 4.2 }))
11
+ .add(primitives.box({ name: "inspection stage", size: [4, 0.1, 4], position: [0, -0.05, 0], material: material.pbr({ color: "#1d2533", roughness: 0.9 }), receiveShadow: true }))
12
+ .add(primitives.torus({ name: "inspected asset", size: 1.1, position: [0, 0.95, 0], rotation: [0.5, 0.35, 0], material: material.metal({ color: "#c9d4e4", roughness: 0.22 }), castShadow: true }))
13
+ .add(primitives.box({ name: "scale reference cube", size: 0.25, position: [1.4, 0.125, 0.9], material: material.pbr({ color: "#38d6ff" }), castShadow: true }))
14
+ .add(lights.ambient({ intensity: 0.3 }))
15
+ .add(lights.directional({ name: "key", position: [3, 4, 3], intensity: 1.5 }))
16
+ .add(lights.point({ name: "rim", position: [-2.5, 2, -2], intensity: 1.4, color: "#9fc4ff" }))
17
+ });
@@ -0,0 +1,16 @@
1
+ import { mkdirSync, writeFileSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ import { expect, test } from "@playwright/test";
4
+
5
+ test.setTimeout(60_000);
6
+
7
+ test("Aura3D three-compat template reaches ready state", async ({ page }) => {
8
+ await page.goto("/");
9
+ await expect.poll(() => page.locator("body").getAttribute("data-aura3d-ready"), { timeout: 45_000 }).toBe("true");
10
+ const drawCalls = Number(await page.locator("body").getAttribute("data-aura3d-draw-calls"));
11
+ const diagnostics = await page.evaluate(() => (window as unknown as { __AURA3D_ROUTE_READY__?: { diagnostics?: { backend?: string } } }).__AURA3D_ROUTE_READY__?.diagnostics);
12
+ expect(diagnostics?.backend).toBe("webgl2");
13
+ expect(drawCalls).toBeGreaterThan(0);
14
+ mkdirSync(resolve("tests/reports"), { recursive: true });
15
+ writeFileSync(resolve("tests/reports/route-health.json"), `${JSON.stringify({ ready: true, backend: diagnostics?.backend, drawCalls }, null, 2)}\n`);
16
+ });