create-aura3d 1.3.3 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. package/README.md +113 -0
  2. package/dist/cli.js +10 -0
  3. package/dist/cli.js.map +1 -1
  4. package/dist/index.d.ts +1 -1
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +3 -1
  7. package/dist/index.js.map +1 -1
  8. package/dist/showcase-spec-artifacts.d.ts +105 -0
  9. package/dist/showcase-spec-artifacts.d.ts.map +1 -0
  10. package/dist/showcase-spec-artifacts.js +143 -0
  11. package/dist/showcase-spec-artifacts.js.map +1 -0
  12. package/dist/showcase-spec-category-plan.d.ts +15 -0
  13. package/dist/showcase-spec-category-plan.d.ts.map +1 -0
  14. package/dist/showcase-spec-category-plan.js +45 -0
  15. package/dist/showcase-spec-category-plan.js.map +1 -0
  16. package/dist/showcase-spec-compiler.d.ts +5 -0
  17. package/dist/showcase-spec-compiler.d.ts.map +1 -0
  18. package/dist/showcase-spec-compiler.js +240 -0
  19. package/dist/showcase-spec-compiler.js.map +1 -0
  20. package/dist/showcase-spec-evidence.d.ts +7 -0
  21. package/dist/showcase-spec-evidence.d.ts.map +1 -0
  22. package/dist/showcase-spec-evidence.js +881 -0
  23. package/dist/showcase-spec-evidence.js.map +1 -0
  24. package/dist/showcase-spec-game-geometry-extractor.d.ts +21 -0
  25. package/dist/showcase-spec-game-geometry-extractor.d.ts.map +1 -0
  26. package/dist/showcase-spec-game-geometry-extractor.js +725 -0
  27. package/dist/showcase-spec-game-geometry-extractor.js.map +1 -0
  28. package/dist/showcase-spec-game-replacement-evidence.d.ts +14 -0
  29. package/dist/showcase-spec-game-replacement-evidence.d.ts.map +1 -0
  30. package/dist/showcase-spec-game-replacement-evidence.js +219 -0
  31. package/dist/showcase-spec-game-replacement-evidence.js.map +1 -0
  32. package/dist/showcase-spec-game-template-evidence.d.ts +72 -0
  33. package/dist/showcase-spec-game-template-evidence.d.ts.map +1 -0
  34. package/dist/showcase-spec-game-template-evidence.js +435 -0
  35. package/dist/showcase-spec-game-template-evidence.js.map +1 -0
  36. package/dist/showcase-spec-gameplay-evidence.d.ts +3 -0
  37. package/dist/showcase-spec-gameplay-evidence.d.ts.map +1 -0
  38. package/dist/showcase-spec-gameplay-evidence.js +108 -0
  39. package/dist/showcase-spec-gameplay-evidence.js.map +1 -0
  40. package/dist/showcase-spec-parser.d.ts +3 -0
  41. package/dist/showcase-spec-parser.d.ts.map +1 -0
  42. package/dist/showcase-spec-parser.js +690 -0
  43. package/dist/showcase-spec-parser.js.map +1 -0
  44. package/dist/showcase-spec-platformer-artifacts.d.ts +3 -0
  45. package/dist/showcase-spec-platformer-artifacts.d.ts.map +1 -0
  46. package/dist/showcase-spec-platformer-artifacts.js +274 -0
  47. package/dist/showcase-spec-platformer-artifacts.js.map +1 -0
  48. package/dist/showcase-spec-racing-artifacts.d.ts +3 -0
  49. package/dist/showcase-spec-racing-artifacts.d.ts.map +1 -0
  50. package/dist/showcase-spec-racing-artifacts.js +273 -0
  51. package/dist/showcase-spec-racing-artifacts.js.map +1 -0
  52. package/dist/showcase-spec-replacement-apply.d.ts +10 -0
  53. package/dist/showcase-spec-replacement-apply.d.ts.map +1 -0
  54. package/dist/showcase-spec-replacement-apply.js +166 -0
  55. package/dist/showcase-spec-replacement-apply.js.map +1 -0
  56. package/dist/showcase-spec-replacement-candidates.d.ts +16 -0
  57. package/dist/showcase-spec-replacement-candidates.d.ts.map +1 -0
  58. package/dist/showcase-spec-replacement-candidates.js +244 -0
  59. package/dist/showcase-spec-replacement-candidates.js.map +1 -0
  60. package/dist/showcase-spec-replacement-game-geometry.d.ts +4 -0
  61. package/dist/showcase-spec-replacement-game-geometry.d.ts.map +1 -0
  62. package/dist/showcase-spec-replacement-game-geometry.js +175 -0
  63. package/dist/showcase-spec-replacement-game-geometry.js.map +1 -0
  64. package/dist/showcase-spec-replacement-manifest.d.ts +41 -0
  65. package/dist/showcase-spec-replacement-manifest.d.ts.map +1 -0
  66. package/dist/showcase-spec-replacement-manifest.js +62 -0
  67. package/dist/showcase-spec-replacement-manifest.js.map +1 -0
  68. package/dist/showcase-spec-replacement-values.d.ts +9 -0
  69. package/dist/showcase-spec-replacement-values.d.ts.map +1 -0
  70. package/dist/showcase-spec-replacement-values.js +41 -0
  71. package/dist/showcase-spec-replacement-values.js.map +1 -0
  72. package/dist/showcase-spec-replacement.d.ts +13 -0
  73. package/dist/showcase-spec-replacement.d.ts.map +1 -0
  74. package/dist/showcase-spec-replacement.js +144 -0
  75. package/dist/showcase-spec-replacement.js.map +1 -0
  76. package/dist/showcase-spec-types.d.ts +319 -0
  77. package/dist/showcase-spec-types.d.ts.map +1 -0
  78. package/dist/showcase-spec-types.js +2 -0
  79. package/dist/showcase-spec-types.js.map +1 -0
  80. package/package.json +45 -11
  81. package/templates/animation-channel/package.json +1 -1
  82. package/templates/animation-studio/package.json +5 -5
  83. package/templates/animation-studio/scripts/animation-scene.ts +1 -1
  84. package/templates/animation-studio/scripts/author-office-scene.ts +1 -1
  85. package/templates/animation-studio/scripts/build-characters.ts +1 -1
  86. package/templates/animation-studio/src/director/director-heuristics.ts +2 -2
  87. package/templates/animation-studio/src/director/{prompt-to-scene.ts → prompt-scene.ts} +1 -1
  88. package/templates/character-controller/package.json +3 -3
  89. package/templates/cinematic-scene/package.json +1 -1
  90. package/templates/cinematic-scene/tests/screenshot.spec.ts +2 -4
  91. package/templates/episode-builder/package.json +1 -1
  92. package/templates/falling-blocks-starter/README.md +18 -0
  93. package/templates/falling-blocks-starter/index.html +23 -0
  94. package/templates/falling-blocks-starter/package.json +21 -0
  95. package/templates/falling-blocks-starter/playwright.config.ts +12 -0
  96. package/templates/falling-blocks-starter/public/aura-assets/showcaseBlockfallCabinet.679d52fe.glb +0 -0
  97. package/templates/falling-blocks-starter/public/aura-assets/showcaseBlockfallCabinet.thumb.svg +1 -0
  98. package/templates/falling-blocks-starter/src/aura-assets.ts +20 -0
  99. package/templates/falling-blocks-starter/src/main.ts +358 -0
  100. package/templates/falling-blocks-starter/tests/playable.spec.ts +85 -0
  101. package/templates/falling-blocks-starter/tests/route-health.spec.ts +34 -0
  102. package/templates/falling-blocks-starter/tsconfig.json +13 -0
  103. package/templates/fighting-game/package.json +1 -1
  104. package/templates/mini-game/README.md +13 -0
  105. package/templates/mini-game/index.html +1 -1
  106. package/templates/mini-game/package.json +2 -2
  107. package/templates/mini-game/src/main.ts +251 -21
  108. package/templates/mini-game/tests/playable.spec.ts +49 -0
  109. package/templates/mini-game/tests/screenshot.spec.ts +37 -32
  110. package/templates/product-viewer/package.json +1 -1
  111. package/templates/product-viewer/tests/screenshot.spec.ts +3 -5
  112. package/templates/prompt-animation-channel/package.json +1 -1
  113. package/templates/racing-starter/README.md +27 -0
  114. package/templates/racing-starter/index.html +23 -0
  115. package/templates/racing-starter/package.json +21 -0
  116. package/templates/racing-starter/playwright.config.ts +12 -0
  117. package/templates/racing-starter/public/aura-assets/showcaseCleanSportsCar.1c69283f.glb +0 -0
  118. package/templates/racing-starter/public/aura-assets/showcaseCleanSportsCar.thumb.svg +1 -0
  119. package/templates/racing-starter/public/aura-assets/showcaseReadableKartCircuit.5cbb912e.glb +0 -0
  120. package/templates/racing-starter/public/aura-assets/showcaseReadableKartCircuit.thumb.svg +1 -0
  121. package/templates/racing-starter/src/aura-assets.ts +36 -0
  122. package/templates/racing-starter/src/main.ts +280 -0
  123. package/templates/racing-starter/tests/playable.spec.ts +93 -0
  124. package/templates/racing-starter/tests/route-health.spec.ts +40 -0
  125. package/templates/racing-starter/tsconfig.json +13 -0
  126. package/templates/three-compat-architecture-interior/package.json +1 -1
  127. package/templates/three-compat-asset-inspector/package.json +1 -1
  128. package/templates/three-compat-character-viewer/package.json +1 -1
  129. package/templates/three-compat-custom-threejs-migration/package.json +1 -1
  130. package/templates/three-compat-large-scene/package.json +1 -1
  131. package/templates/three-compat-material-authoring/package.json +1 -1
  132. package/templates/three-compat-postprocess-scene/package.json +1 -1
  133. package/templates/three-compat-premium-product-viewer/package.json +1 -1
  134. package/templates/animation-channel/public/aura-assets/moon-garden-feature-frame.png +0 -0
  135. package/templates/animation-channel/test-results/.last-run.json +0 -4
  136. package/templates/animation-channel/tests/reports/prompt-animation/cartoon-sample-episode.png +0 -0
  137. package/templates/animation-studio/dist/episodes/prompt-to-scene-proof/two-robots-fixing-a-car.document.json +0 -680
  138. package/templates/animation-studio/dist/episodes/scene/episode-3d.webm +0 -0
  139. package/templates/animation-studio/dist/episodes/scene/frames/action.png +0 -0
  140. package/templates/animation-studio/dist/episodes/scene/frames/dialogue.png +0 -0
  141. package/templates/animation-studio/dist/episodes/scene/frames/final.png +0 -0
  142. package/templates/animation-studio/dist/episodes/scene/frames/first.png +0 -0
  143. package/templates/animation-studio/dist/episodes/scene/frames/mouth-closed.png +0 -0
  144. package/templates/animation-studio/dist/episodes/scene/frames/mouth-open.png +0 -0
  145. package/templates/animation-studio/dist/episodes/scene/render-live-summary.json +0 -9763
  146. package/templates/animation-studio/dist/episodes/scene/skeleton-overlays/broken.png +0 -0
  147. package/templates/animation-studio/dist/episodes/scene/skeleton-overlays/chefs.png +0 -0
  148. package/templates/animation-studio/dist/episodes/scene/skeleton-overlays/customer.png +0 -0
  149. package/templates/animation-studio/dist/episodes/scene/skeleton-overlays/friend-1.png +0 -0
  150. package/templates/animation-studio/dist/episodes/scene/skeleton-overlays/friend-2.png +0 -0
  151. package/templates/animation-studio/dist/episodes/scene/skeleton-overlays/worker-1.png +0 -0
  152. package/templates/animation-studio/dist/episodes/scene/skeleton-overlays/worker-2.png +0 -0
  153. package/templates/animation-studio/dist/generated/second-scene.document.json +0 -1210
  154. package/templates/animation-studio/dist/generated/studio-edited.document.json +0 -1196
  155. package/templates/animation-studio/dist/scene/inshader-accept.document.json +0 -1
  156. package/templates/animation-studio/dist/scene/inshader-test.document.json +0 -1
  157. package/templates/animation-studio/dist/scene/integ.document.json +0 -1
  158. package/templates/animation-studio/dist/scene/working.document.json +0 -673
  159. package/templates/animation-studio/dist/scene/working.history.json +0 -1
  160. package/templates/animation-studio/node_modules.publish-aside/.vite/deps/_metadata.json +0 -8
  161. package/templates/animation-studio/node_modules.publish-aside/.vite/deps/package.json +0 -3
  162. package/templates/animation-studio/package-lock.json +0 -3503
  163. package/templates/animation-studio/studio/dist/assets/index-C7f8tHfi.css +0 -1
  164. package/templates/animation-studio/studio/dist/assets/index-CLo3GXyN.js +0 -43
  165. package/templates/animation-studio/studio/dist/index.html +0 -19
  166. package/templates/animation-studio/tests/reports/animation-studio/hifi-cast-validation.json +0 -938
@@ -0,0 +1,12 @@
1
+ import { defineConfig } from "@playwright/test";
2
+
3
+ export default defineConfig({
4
+ webServer: {
5
+ command: "npm run dev -- --port 5179",
6
+ url: "http://127.0.0.1:5179",
7
+ reuseExistingServer: !process.env.CI
8
+ },
9
+ use: {
10
+ baseURL: "http://127.0.0.1:5179"
11
+ }
12
+ });
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180"><rect width="320" height="180" fill="#101720"/><path d="M80 126 160 46l80 80-80 28z" fill="#77a7ff"/><text x="160" y="160" text-anchor="middle" font-family="Arial" font-size="18" fill="#f4f7fb">showcaseBlockfallCabinet 2x2x3.27</text></svg>
@@ -0,0 +1,20 @@
1
+ import { defineAuraAssets } from "@aura3d/engine";
2
+
3
+ export const assets = defineAuraAssets({
4
+ cabinetModel: {
5
+ type: "model",
6
+ format: "glb",
7
+ url: "/aura-assets/showcaseBlockfallCabinet.679d52fe.glb",
8
+ bounds: [2, 2, 3.27],
9
+ hash: "sha256-679d52fe0c7bf99373fc873f3c3892548f6c5758ae9c81be91f09acae6a35a36",
10
+ metadata: {
11
+ materials: ["arcade_machine"],
12
+ animations: [],
13
+ textures: ["image-0", "image-1", "image-2", "image-3"],
14
+ license: "CC-BY-4.0",
15
+ author: "Dmitry Blagodaryov",
16
+ sourcePage: "https://huggingface.co/datasets/allenai/objaverse/blob/main/glbs/000-012/f73986356b6e4d72b7a889279837aec2.glb",
17
+ thumbnailUrl: "/aura-assets/showcaseBlockfallCabinet.thumb.svg"
18
+ }
19
+ }
20
+ } as const);
@@ -0,0 +1,358 @@
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_FALLING_BLOCKS_STARTER__?: FallingBlocksStarterEvidence;
17
+ }
18
+ }
19
+
20
+ type Piece = "I" | "J" | "L" | "O" | "S" | "T" | "Z";
21
+ type Cell = Piece | null;
22
+
23
+ interface FallingBlocksStarterEvidence {
24
+ readonly frame: number;
25
+ readonly score: number;
26
+ readonly lines: number;
27
+ readonly checksum: string;
28
+ readonly active: { readonly kind: Piece; readonly x: number; readonly y: number; readonly rotation: number } | null;
29
+ readonly hold: Piece | null;
30
+ readonly gameOver: boolean;
31
+ readonly events: readonly string[];
32
+ readonly lineClearProof: {
33
+ readonly lines: number;
34
+ readonly events: readonly string[];
35
+ readonly checksum: string;
36
+ };
37
+ readonly evidence: unknown;
38
+ }
39
+
40
+ const boardWidth = 10;
41
+ const boardHeight = 22;
42
+ const hiddenRows = 2;
43
+ const cellSize = 0.24;
44
+ const boardOrigin = { x: -1.2, y: -2.18 };
45
+ const pieceColors: Record<Piece, string> = {
46
+ I: "#5ed7df",
47
+ J: "#7fa5df",
48
+ L: "#dfbd65",
49
+ O: "#dccf78",
50
+ S: "#84d09a",
51
+ T: "#b987d0",
52
+ Z: "#d88791"
53
+ };
54
+ const activeShapes: Record<Piece, readonly (readonly { readonly x: number; readonly y: number }[])[]> = {
55
+ I: [
56
+ [{ x: 0, y: 1 }, { x: 1, y: 1 }, { x: 2, y: 1 }, { x: 3, y: 1 }],
57
+ [{ x: 2, y: 0 }, { x: 2, y: 1 }, { x: 2, y: 2 }, { x: 2, y: 3 }],
58
+ [{ x: 0, y: 2 }, { x: 1, y: 2 }, { x: 2, y: 2 }, { x: 3, y: 2 }],
59
+ [{ x: 1, y: 0 }, { x: 1, y: 1 }, { x: 1, y: 2 }, { x: 1, y: 3 }]
60
+ ],
61
+ J: [
62
+ [{ x: 0, y: 0 }, { x: 0, y: 1 }, { x: 1, y: 1 }, { x: 2, y: 1 }],
63
+ [{ x: 1, y: 0 }, { x: 2, y: 0 }, { x: 1, y: 1 }, { x: 1, y: 2 }],
64
+ [{ x: 0, y: 1 }, { x: 1, y: 1 }, { x: 2, y: 1 }, { x: 2, y: 2 }],
65
+ [{ x: 1, y: 0 }, { x: 1, y: 1 }, { x: 0, y: 2 }, { x: 1, y: 2 }]
66
+ ],
67
+ L: [
68
+ [{ x: 2, y: 0 }, { x: 0, y: 1 }, { x: 1, y: 1 }, { x: 2, y: 1 }],
69
+ [{ x: 1, y: 0 }, { x: 1, y: 1 }, { x: 1, y: 2 }, { x: 2, y: 2 }],
70
+ [{ x: 0, y: 1 }, { x: 1, y: 1 }, { x: 2, y: 1 }, { x: 0, y: 2 }],
71
+ [{ x: 0, y: 0 }, { x: 1, y: 0 }, { x: 1, y: 1 }, { x: 1, y: 2 }]
72
+ ],
73
+ O: [
74
+ [{ x: 1, y: 0 }, { x: 2, y: 0 }, { x: 1, y: 1 }, { x: 2, y: 1 }],
75
+ [{ x: 1, y: 0 }, { x: 2, y: 0 }, { x: 1, y: 1 }, { x: 2, y: 1 }],
76
+ [{ x: 1, y: 0 }, { x: 2, y: 0 }, { x: 1, y: 1 }, { x: 2, y: 1 }],
77
+ [{ x: 1, y: 0 }, { x: 2, y: 0 }, { x: 1, y: 1 }, { x: 2, y: 1 }]
78
+ ],
79
+ S: [
80
+ [{ x: 1, y: 0 }, { x: 2, y: 0 }, { x: 0, y: 1 }, { x: 1, y: 1 }],
81
+ [{ x: 1, y: 0 }, { x: 1, y: 1 }, { x: 2, y: 1 }, { x: 2, y: 2 }],
82
+ [{ x: 1, y: 1 }, { x: 2, y: 1 }, { x: 0, y: 2 }, { x: 1, y: 2 }],
83
+ [{ x: 0, y: 0 }, { x: 0, y: 1 }, { x: 1, y: 1 }, { x: 1, y: 2 }]
84
+ ],
85
+ T: [
86
+ [{ x: 1, y: 0 }, { x: 0, y: 1 }, { x: 1, y: 1 }, { x: 2, y: 1 }],
87
+ [{ x: 1, y: 0 }, { x: 1, y: 1 }, { x: 2, y: 1 }, { x: 1, y: 2 }],
88
+ [{ x: 0, y: 1 }, { x: 1, y: 1 }, { x: 2, y: 1 }, { x: 1, y: 2 }],
89
+ [{ x: 1, y: 0 }, { x: 0, y: 1 }, { x: 1, y: 1 }, { x: 1, y: 2 }]
90
+ ],
91
+ Z: [
92
+ [{ x: 0, y: 0 }, { x: 1, y: 0 }, { x: 1, y: 1 }, { x: 2, y: 1 }],
93
+ [{ x: 2, y: 0 }, { x: 1, y: 1 }, { x: 2, y: 1 }, { x: 1, y: 2 }],
94
+ [{ x: 0, y: 1 }, { x: 1, y: 1 }, { x: 1, y: 2 }, { x: 2, y: 2 }],
95
+ [{ x: 1, y: 0 }, { x: 0, y: 1 }, { x: 1, y: 1 }, { x: 0, y: 2 }]
96
+ ]
97
+ };
98
+
99
+ const input = game.input({
100
+ actions: {
101
+ left: ["KeyA", "ArrowLeft"],
102
+ right: ["KeyD", "ArrowRight"],
103
+ rotateRight: ["KeyW", "ArrowUp", "KeyX"],
104
+ rotateLeft: ["KeyQ", "KeyZ"],
105
+ softDrop: ["KeyS", "ArrowDown"],
106
+ hardDrop: ["Space"],
107
+ hold: ["KeyC", "ShiftLeft", "ShiftRight"],
108
+ reset: ["KeyR"]
109
+ },
110
+ bufferMs: 100
111
+ });
112
+
113
+ const falling = game.fallingBlocks({
114
+ width: boardWidth,
115
+ height: boardHeight,
116
+ hiddenRows,
117
+ seed: 7,
118
+ gravityFrames: 999,
119
+ lockDelayFrames: 18
120
+ });
121
+ const routeEvents = game.eventLog({ label: "falling blocks starter events", maxEvents: 18 });
122
+ const hud = game.hud.bindings([
123
+ game.hud.score({ valuePath: "appState.score" }),
124
+ game.hud.objective({ valuePath: "appState.objective" }),
125
+ game.hud.eventLog({ valuePath: "appState.events" })
126
+ ]);
127
+ const lineClearProof = createLineClearProof();
128
+
129
+ setupPracticeBoard();
130
+
131
+ const app = createAuraApp("#app", {
132
+ diagnostics: { overlay: true, performancePanel: true },
133
+ scene: buildScene()
134
+ });
135
+
136
+ const boardNodes = Array.from({ length: boardHeight - hiddenRows }, (_, rowIndex) =>
137
+ Array.from({ length: boardWidth }, (_, x) => app.nodes.require(`board-cell-${x}-${rowIndex + hiddenRows}`))
138
+ );
139
+ const activeNodes = Array.from({ length: 4 }, (_, index) => app.nodes.require(`active-cell-${index}`));
140
+ const holdNode = app.nodes.require("hold-preview");
141
+ const hudRoot = createHud();
142
+ let objective = "Clear the prepared line. Move, rotate, hold, or hard drop.";
143
+ let tickAccumulator = 0;
144
+
145
+ app.onFrame(({ dt }: { readonly dt: number }) => {
146
+ input.update(dt);
147
+ if (input.pressed("reset")) {
148
+ setupPracticeBoard();
149
+ routeEvents.push({ type: "reset", label: "reset" });
150
+ objective = "Clear the prepared line. Move, rotate, hold, or hard drop.";
151
+ }
152
+ if (input.pressed("left")) recordKitEvents(falling.move(-1));
153
+ if (input.pressed("right")) recordKitEvents(falling.move(1));
154
+ if (input.pressed("rotateRight")) recordKitEvents(falling.rotate(1));
155
+ if (input.pressed("rotateLeft")) recordKitEvents(falling.rotate(-1));
156
+ if (input.pressed("softDrop")) recordKitEvents(falling.softDrop());
157
+ if (input.pressed("hold")) recordKitEvents(falling.hold());
158
+ if (input.pressed("hardDrop")) recordKitEvents(falling.hardDrop());
159
+
160
+ tickAccumulator += dt;
161
+ while (tickAccumulator >= 1 / 30) {
162
+ recordKitEvents(falling.tick(1));
163
+ tickAccumulator -= 1 / 30;
164
+ }
165
+
166
+ const state = falling.snapshot();
167
+ renderBoard(state);
168
+ renderHud(state);
169
+ publishEvidence(state);
170
+ });
171
+
172
+ renderBoard(falling.snapshot());
173
+ renderHud(falling.snapshot());
174
+ publishEvidence(falling.snapshot());
175
+
176
+ function recordKitEvents(state: ReturnType<typeof falling.snapshot>): void {
177
+ for (const event of state.events) {
178
+ routeEvents.push({
179
+ type: event.type,
180
+ label: event.piece ? `${event.type}:${event.piece}` : event.type,
181
+ severity: event.type === "line-clear" ? "success" : event.type === "game-over" ? "warning" : "info",
182
+ frame: event.frame
183
+ });
184
+ if (event.type === "line-clear") objective = `Line clear. ${state.lines} line cleared. Press R to replay.`;
185
+ if (event.type === "hold") objective = "Held piece. Press R or continue.";
186
+ if (event.type === "rotate") objective = "Rotation accepted.";
187
+ if (event.type === "move") objective = "Move accepted.";
188
+ }
189
+ }
190
+
191
+ function setupPracticeBoard(): void {
192
+ falling.reset(7);
193
+ falling.setBoard(createPracticeBoard());
194
+ falling.setActive({ kind: "I", x: 3, y: boardHeight - 3, rotation: 0 });
195
+ }
196
+
197
+ function createPracticeBoard(): Cell[][] {
198
+ const board = Array.from({ length: boardHeight }, () => Array.from({ length: boardWidth }, () => null as Cell));
199
+ board[boardHeight - 1] = board[boardHeight - 1].map((_, x) => (x >= 3 && x <= 6 ? null : "O"));
200
+ board[boardHeight - 2][0] = "J";
201
+ board[boardHeight - 2][9] = "L";
202
+ return board;
203
+ }
204
+
205
+ function buildScene() {
206
+ const nodes: AuraNodeInput[] = [
207
+ model(assets.cabinetModel, { name: "typed arcade cabinet", castShadow: true })
208
+ .position(1.75, 0.28, -0.55)
209
+ .scale(0.2),
210
+ primitives.box({ name: "falling blocks board backplate", material: material.pbr({ color: "#172027", roughness: 0.82, metallic: 0.04 }) })
211
+ .position(0, 0, -0.08)
212
+ .scale([2.76, 5.25, 0.08]),
213
+ primitives.box({ name: "falling blocks board frame", material: material.neon({ color: "#8ee8d5", emissive: "#8ee8d5", emissiveIntensity: 0.22 }) })
214
+ .position(0, 0, -0.02)
215
+ .scale([2.92, 5.42, 0.04]),
216
+ ...boardCellNodes(),
217
+ ...activeCellNodes(),
218
+ primitives.box({ name: "hold preview", material: material.neon({ color: "#b987d0", emissive: "#b987d0", emissiveIntensity: 0.38 }) })
219
+ .position(-2.02, 1.92, 0.08)
220
+ .scale([0.42, 0.42, 0.16])
221
+ .runtime(game.runtimeNode("hold-preview", { tags: ["hold", "runtime"] }))
222
+ ];
223
+
224
+ return scene()
225
+ .background("#06090d")
226
+ .addMany(nodes)
227
+ .add(lights.ambient({ name: "blockfall ambient", intensity: 0.36, color: "#e8fbff" }))
228
+ .add(lights.directional({ name: "blockfall key", position: [3.8, 6.2, 5], intensity: 1.15, color: "#ffffff" }))
229
+ .camera(camera.perspective({ position: [0.25, 1.2, 7.2], target: [0.1, 0.1, 0], fov: 38 }));
230
+ }
231
+
232
+ function boardCellNodes(): AuraNodeInput[] {
233
+ const nodes: AuraNodeInput[] = [];
234
+ for (let y = hiddenRows; y < boardHeight; y += 1) {
235
+ for (let x = 0; x < boardWidth; x += 1) {
236
+ nodes.push(
237
+ primitives.box({ name: `board cell ${x} ${y}`, material: material.pbr({ color: "#dccf78", roughness: 0.62, metallic: 0.02 }) })
238
+ .position(...cellPosition(x, y), 0)
239
+ .scale([cellSize * 0.86, cellSize * 0.86, 0.12])
240
+ .runtime(game.runtimeNode(`board-cell-${x}-${y}`, { tags: ["board-cell", "runtime"] }))
241
+ );
242
+ }
243
+ }
244
+ return nodes;
245
+ }
246
+
247
+ function activeCellNodes(): AuraNodeInput[] {
248
+ return Array.from({ length: 4 }, (_, index) =>
249
+ primitives.box({ name: `active piece cell ${index}`, material: material.neon({ color: "#5ed7df", emissive: "#5ed7df", emissiveIntensity: 0.4 }) })
250
+ .position(-3, -3, 0.12)
251
+ .scale([cellSize * 0.92, cellSize * 0.92, 0.16])
252
+ .runtime(game.runtimeNode(`active-cell-${index}`, { tags: ["active-cell", "runtime"] }))
253
+ );
254
+ }
255
+
256
+ function renderBoard(state: ReturnType<typeof falling.snapshot>): void {
257
+ for (let y = hiddenRows; y < boardHeight; y += 1) {
258
+ for (let x = 0; x < boardWidth; x += 1) {
259
+ const node = boardNodes[y - hiddenRows][x];
260
+ const cell = state.board[y][x];
261
+ node.setVisible(Boolean(cell)).setPosition(...cellPosition(x, y), 0);
262
+ }
263
+ }
264
+
265
+ activeNodes.forEach((node) => node.setVisible(false));
266
+ if (state.active) {
267
+ const cells = activeShapes[state.active.kind][state.active.rotation] ?? activeShapes[state.active.kind][0];
268
+ cells.forEach((cell, index) => {
269
+ const x = state.active!.x + cell.x;
270
+ const y = state.active!.y + cell.y;
271
+ activeNodes[index]
272
+ .setVisible(y >= hiddenRows)
273
+ .setPosition(...cellPosition(x, y), 0.14)
274
+ .setScale([cellSize * 0.94, cellSize * 0.94, 0.16]);
275
+ });
276
+ }
277
+ holdNode.setVisible(Boolean(state.hold));
278
+ }
279
+
280
+ function cellPosition(x: number, y: number): [number, number] {
281
+ return [
282
+ boardOrigin.x + (x + 0.5) * cellSize,
283
+ boardOrigin.y + (boardHeight - hiddenRows - (y - hiddenRows) - 0.5) * cellSize
284
+ ];
285
+ }
286
+
287
+ function createHud(): HTMLElement {
288
+ const root = document.createElement("aside");
289
+ root.id = "falling-blocks-starter-hud";
290
+ root.style.cssText = [
291
+ "position:absolute",
292
+ "left:16px",
293
+ "top:16px",
294
+ "z-index:5",
295
+ "min-width:310px",
296
+ "font:600 13px/1.35 Inter, system-ui, sans-serif",
297
+ "color:#f5fbff",
298
+ "background:rgba(3,9,14,0.78)",
299
+ "border:1px solid rgba(125,220,235,0.34)",
300
+ "border-radius:8px",
301
+ "padding:12px",
302
+ "pointer-events:none"
303
+ ].join(";");
304
+ document.body.append(root);
305
+ return root;
306
+ }
307
+
308
+ function renderHud(state: ReturnType<typeof falling.snapshot>): void {
309
+ hudRoot.innerHTML = [
310
+ `<strong>Aura3D Falling Blocks Starter</strong>`,
311
+ `<div>Score ${state.score} | Lines ${state.lines} | Level ${state.level}</div>`,
312
+ `<div>Active ${state.active?.kind ?? "--"} | Hold ${state.hold ?? "--"} | Checksum ${state.checksum.slice(0, 8)}</div>`,
313
+ `<div>${objective}</div>`,
314
+ `<div>Move arrows/A-D. Rotate W/Q. Drop Space. Hold C. Reset R.</div>`
315
+ ].join("");
316
+ }
317
+
318
+ function publishEvidence(state: ReturnType<typeof falling.snapshot>): void {
319
+ const evidence = app.evidence({
320
+ input,
321
+ events: routeEvents,
322
+ hud,
323
+ appState: {
324
+ score: state.score,
325
+ objective,
326
+ events: routeEvents.events().map((event: { readonly label: string }) => event.label)
327
+ },
328
+ assets: {
329
+ typedAssets: Object.keys(assets).length,
330
+ missingAssets: []
331
+ },
332
+ source: { expectsGame: true }
333
+ });
334
+ window.__AURA3D_FALLING_BLOCKS_STARTER__ = {
335
+ frame: state.frame,
336
+ score: state.score,
337
+ lines: state.lines,
338
+ checksum: state.checksum,
339
+ active: state.active,
340
+ hold: state.hold,
341
+ gameOver: state.gameOver,
342
+ events: routeEvents.events().map((event: { readonly label: string }) => event.label),
343
+ lineClearProof,
344
+ evidence
345
+ };
346
+ }
347
+
348
+ function createLineClearProof(): FallingBlocksStarterEvidence["lineClearProof"] {
349
+ const proof = game.fallingBlocks({ width: boardWidth, height: boardHeight, hiddenRows, seed: 7, gravityFrames: 999 });
350
+ proof.setBoard(createPracticeBoard());
351
+ proof.setActive({ kind: "I", x: 3, y: boardHeight - 3, rotation: 0 });
352
+ const cleared = proof.hardDrop();
353
+ return {
354
+ lines: cleared.lines,
355
+ events: cleared.events.map((event) => event.piece ? `${event.type}:${event.piece}` : event.type),
356
+ checksum: cleared.checksum
357
+ };
358
+ }
@@ -0,0 +1,85 @@
1
+ import { expect, test } from "@playwright/test";
2
+
3
+ test("falling-blocks starter responds to keyboard input and clears a line", 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_FALLING_BLOCKS_STARTER__?: unknown }).__AURA3D_FALLING_BLOCKS_STARTER__));
7
+
8
+ const initial = await fallingState(page);
9
+ expect(initial.active?.kind).toBe("I");
10
+ expect(initial.active?.x).toBe(3);
11
+ expect(initial.lineClearProof.lines).toBe(1);
12
+ expect(initial.lineClearProof.events).toContain("line-clear");
13
+
14
+ await tapKey(page, "ArrowRight");
15
+ await page.waitForTimeout(120);
16
+ const moved = await fallingState(page);
17
+ expect(moved.active?.x).toBe((initial.active?.x ?? 0) + 1);
18
+
19
+ await tapKey(page, "KeyR");
20
+ await page.waitForTimeout(120);
21
+ await tapKey(page, "ArrowUp");
22
+ await page.waitForTimeout(120);
23
+ const rotated = await fallingState(page);
24
+ expect(rotated.active?.rotation).not.toBe(initial.active?.rotation);
25
+
26
+ await tapKey(page, "KeyR");
27
+ await page.waitForTimeout(120);
28
+ await tapKey(page, "KeyC");
29
+ await page.waitForTimeout(120);
30
+ const held = await fallingState(page);
31
+ expect(held.hold).toBe("I");
32
+ expect(held.events).toEqual(expect.arrayContaining(["hold:I"]));
33
+
34
+ await tapKey(page, "KeyR");
35
+ await page.waitForTimeout(120);
36
+ await tapKey(page, "Space");
37
+ await page.waitForTimeout(160);
38
+ const cleared = await fallingState(page);
39
+ expect(cleared.lines).toBe(1);
40
+ expect(cleared.score).toBeGreaterThanOrEqual(100);
41
+ expect(cleared.events).toEqual(expect.arrayContaining(["hard-drop:I", "line-clear"]));
42
+
43
+ await tapKey(page, "KeyR");
44
+ await page.waitForTimeout(120);
45
+ const reset = await fallingState(page);
46
+ expect(reset.lines).toBe(0);
47
+ expect(reset.active?.kind).toBe("I");
48
+ expect(reset.events.some((event) => event.includes("reset"))).toBe(true);
49
+ });
50
+
51
+ async function fallingState(page: import("@playwright/test").Page): Promise<{
52
+ readonly score: number;
53
+ readonly lines: number;
54
+ readonly active: { readonly kind: string; readonly x: number; readonly rotation: number } | null;
55
+ readonly hold: string | null;
56
+ readonly events: readonly string[];
57
+ readonly lineClearProof: {
58
+ readonly lines: number;
59
+ readonly events: readonly string[];
60
+ };
61
+ }> {
62
+ return await page.evaluate(() => {
63
+ const state = (window as unknown as {
64
+ readonly __AURA3D_FALLING_BLOCKS_STARTER__?: {
65
+ readonly score: number;
66
+ readonly lines: number;
67
+ readonly active: { readonly kind: string; readonly x: number; readonly rotation: number } | null;
68
+ readonly hold: string | null;
69
+ readonly events: readonly string[];
70
+ readonly lineClearProof: {
71
+ readonly lines: number;
72
+ readonly events: readonly string[];
73
+ };
74
+ };
75
+ }).__AURA3D_FALLING_BLOCKS_STARTER__;
76
+ if (!state) throw new Error("Missing __AURA3D_FALLING_BLOCKS_STARTER__ state.");
77
+ return state;
78
+ });
79
+ }
80
+
81
+ async function tapKey(page: import("@playwright/test").Page, key: string, holdMs = 90): Promise<void> {
82
+ await page.keyboard.down(key);
83
+ await page.waitForTimeout(holdMs);
84
+ await page.keyboard.up(key);
85
+ }
@@ -0,0 +1,34 @@
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 falling-blocks starter reaches ready state with line-clear 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_FALLING_BLOCKS_STARTER__");
14
+ const state = isRecord(value) ? value : {};
15
+ const lineClearProofValue = state.lineClearProof;
16
+ const lineClearProof = isRecord(lineClearProofValue) ? lineClearProofValue : {};
17
+ return {
18
+ score: typeof state.score === "number" ? state.score : -1,
19
+ lines: typeof state.lines === "number" ? state.lines : -1,
20
+ proofLines: typeof lineClearProof.lines === "number" ? lineClearProof.lines : 0
21
+ };
22
+
23
+ function isRecord(recordValue: unknown): recordValue is Readonly<Record<string, unknown>> {
24
+ return typeof recordValue === "object" && recordValue !== null && !Array.isArray(recordValue);
25
+ }
26
+ });
27
+
28
+ expect(drawCalls).toBeGreaterThan(0);
29
+ expect(routeState.score).toBeGreaterThanOrEqual(0);
30
+ expect(routeState.lines).toBeGreaterThanOrEqual(0);
31
+ expect(routeState.proofLines).toBeGreaterThan(0);
32
+ mkdirSync(resolve("tests/reports"), { recursive: true });
33
+ writeFileSync(resolve("tests/reports/route-health.json"), `${JSON.stringify({ ready: true, drawCalls, routeState }, null, 2)}\n`);
34
+ });
@@ -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
+ }
@@ -11,7 +11,7 @@
11
11
  "test": "playwright test tests/route-health.spec.ts tests/gameplay-smoke.spec.ts --workers=1"
12
12
  },
13
13
  "dependencies": {
14
- "@aura3d/engine": "1.3.3"
14
+ "@aura3d/engine": "1.4.1"
15
15
  },
16
16
  "devDependencies": {
17
17
  "@playwright/test": "^1.52.0",
@@ -3,6 +3,19 @@
3
3
  Small interactive scene starter for agent-authored controls, movement, and route
4
4
  health checks.
5
5
 
6
+ Status: playable starter, not a production game claim. This template uses a
7
+ typed player asset, the public `game.platformer(...)` kit, runtime nodes, HUD
8
+ evidence, keyboard input, reset, scoring, and screenshot tests. It proves the
9
+ starter boots and responds to keyboard input through the root `@aura3d/engine`
10
+ safe API; it does not prove production art direction, skinned animation,
11
+ advanced PBR, WebGPU, postprocess, or commercial game polish.
12
+
13
+ Before presenting a route from this template as production-quality, replace any
14
+ placeholder art with typed primary assets from the Aura3D CLI, keep
15
+ `model(assets.x)` for real assets, prove each claimed mechanic with keyboard
16
+ tests, and capture desktop/mobile screenshots where the main subject is
17
+ readable.
18
+
6
19
  ```bash
7
20
  npm install
8
21
  npm run dev
@@ -8,6 +8,6 @@
8
8
  </head>
9
9
  <body>
10
10
  <div id="app"></div>
11
- <script type="module" src="/src/main.ts"></script>
11
+ <script type="module" src="./src/main.ts"></script>
12
12
  </body>
13
13
  </html>
@@ -8,10 +8,10 @@
8
8
  "typecheck": "tsc --noEmit",
9
9
  "build": "npm run typecheck && vite build",
10
10
  "preview": "vite preview --host 127.0.0.1",
11
- "test": "playwright test tests/route-health.spec.ts tests/screenshot.spec.ts --workers=1"
11
+ "test": "playwright test tests/route-health.spec.ts tests/playable.spec.ts tests/screenshot.spec.ts --workers=1"
12
12
  },
13
13
  "dependencies": {
14
- "@aura3d/engine": "1.3.3"
14
+ "@aura3d/engine": "1.4.1"
15
15
  },
16
16
  "devDependencies": {
17
17
  "@playwright/test": "^1.52.0",