create-aura3d 1.5.2 → 2.0.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 (94) hide show
  1. package/README.md +9 -0
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +10 -4
  4. package/dist/index.js.map +1 -1
  5. package/dist/showcase-spec-evidence.js +5 -1
  6. package/dist/showcase-spec-evidence.js.map +1 -1
  7. package/dist/showcase-spec-game-geometry-extractor.d.ts +8 -1
  8. package/dist/showcase-spec-game-geometry-extractor.d.ts.map +1 -1
  9. package/dist/showcase-spec-game-geometry-extractor.js +191 -31
  10. package/dist/showcase-spec-game-geometry-extractor.js.map +1 -1
  11. package/dist/showcase-spec-game-template-evidence.d.ts.map +1 -1
  12. package/dist/showcase-spec-game-template-evidence.js +7 -0
  13. package/dist/showcase-spec-game-template-evidence.js.map +1 -1
  14. package/dist/showcase-spec-parser.js +1 -1
  15. package/dist/showcase-spec-parser.js.map +1 -1
  16. package/dist/showcase-spec-types.d.ts +50 -0
  17. package/dist/showcase-spec-types.d.ts.map +1 -1
  18. package/package.json +2 -2
  19. package/templates/animation-channel/package.json +1 -1
  20. package/templates/animation-channel/src/experimental/README.md +1 -1
  21. package/templates/animation-channel/tests/storyboard-playback.spec.ts +2 -0
  22. package/templates/animation-studio/package.json +2 -5
  23. package/templates/animation-studio/scripts/asset-motion-probe.ts +1 -1
  24. package/templates/animation-studio/scripts/build-characters.ts +1 -1
  25. package/templates/animation-studio/scripts/motion-evidence.ts +1 -1
  26. package/templates/animation-studio/scripts/render-core.ts +4 -2
  27. package/templates/animation-studio/scripts/render-live.ts +3 -1
  28. package/templates/animation-studio/scripts/validate-characters.ts +0 -0
  29. package/templates/animation-studio/src/scene-player.ts +2 -2
  30. package/templates/character-controller/README.md +15 -2
  31. package/templates/character-controller/aura.assets.json +7 -0
  32. package/templates/character-controller/package.json +3 -3
  33. package/templates/character-controller/src/aura-assets.ts +3 -0
  34. package/templates/cinematic-scene/aura.assets.json +31 -0
  35. package/templates/cinematic-scene/package.json +1 -1
  36. package/templates/episode-builder/aura.assets.json +7 -0
  37. package/templates/episode-builder/package.json +1 -1
  38. package/templates/episode-builder/tests/route-health.spec.ts +2 -2
  39. package/templates/falling-blocks-starter/aura.assets.json +37 -0
  40. package/templates/falling-blocks-starter/package.json +2 -1
  41. package/templates/falling-blocks-starter/tests/playable.spec.ts +2 -3
  42. package/templates/falling-blocks-starter/tsconfig.json +1 -1
  43. package/templates/fighting-game/aura.assets.json +7 -0
  44. package/templates/fighting-game/package.json +1 -1
  45. package/templates/fighting-game/src/main.ts +5 -7
  46. package/templates/fighting-game/tests/gameplay-smoke.spec.ts +4 -1
  47. package/templates/fighting-game/tests/route-health.spec.ts +8 -2
  48. package/templates/mini-game/README.md +3 -2
  49. package/templates/mini-game/aura.assets.json +31 -0
  50. package/templates/mini-game/package.json +1 -1
  51. package/templates/mini-game/src/aura-assets.ts +1 -1
  52. package/templates/mini-game/src/main.ts +69 -141
  53. package/templates/product-viewer/README.md +7 -5
  54. package/templates/product-viewer/aura.assets.json +34 -0
  55. package/templates/product-viewer/index.html +1 -1
  56. package/templates/product-viewer/package.json +1 -1
  57. package/templates/product-viewer/src/aura-assets.ts +1 -1
  58. package/templates/product-viewer/src/main.ts +29 -23
  59. package/templates/product-viewer/tests/route-health.spec.ts +2 -2
  60. package/templates/product-viewer/tests/screenshot.spec.ts +2 -2
  61. package/templates/prompt-animation-channel/aura.assets.json +7 -0
  62. package/templates/prompt-animation-channel/package.json +1 -1
  63. package/templates/prompt-animation-channel/src/main.ts +4 -66
  64. package/templates/prompt-animation-channel/src/render-plan.ts +1 -1
  65. package/templates/prompt-animation-channel/tests/route-health.spec.ts +4 -2
  66. package/templates/prompt-animation-channel/tests/storyboard-playback.spec.ts +13 -2
  67. package/templates/racing-starter/aura.assets.json +64 -0
  68. package/templates/racing-starter/package.json +2 -1
  69. package/templates/racing-starter/src/main.ts +2 -2
  70. package/templates/racing-starter/tsconfig.json +1 -1
  71. package/templates/three-compat-architecture-interior/aura.assets.json +7 -0
  72. package/templates/three-compat-architecture-interior/package.json +1 -1
  73. package/templates/three-compat-architecture-interior/src/aura-assets.ts +3 -0
  74. package/templates/three-compat-asset-inspector/aura.assets.json +7 -0
  75. package/templates/three-compat-asset-inspector/package.json +1 -1
  76. package/templates/three-compat-asset-inspector/src/aura-assets.ts +3 -0
  77. package/templates/three-compat-character-viewer/aura.assets.json +7 -0
  78. package/templates/three-compat-character-viewer/package.json +1 -1
  79. package/templates/three-compat-character-viewer/src/aura-assets.ts +3 -0
  80. package/templates/three-compat-custom-threejs-migration/aura.assets.json +7 -0
  81. package/templates/three-compat-custom-threejs-migration/package.json +1 -1
  82. package/templates/three-compat-custom-threejs-migration/src/aura-assets.ts +3 -0
  83. package/templates/three-compat-large-scene/aura.assets.json +7 -0
  84. package/templates/three-compat-large-scene/package.json +1 -1
  85. package/templates/three-compat-large-scene/src/aura-assets.ts +3 -0
  86. package/templates/three-compat-material-authoring/aura.assets.json +7 -0
  87. package/templates/three-compat-material-authoring/package.json +1 -1
  88. package/templates/three-compat-material-authoring/src/aura-assets.ts +3 -0
  89. package/templates/three-compat-postprocess-scene/aura.assets.json +7 -0
  90. package/templates/three-compat-postprocess-scene/package.json +1 -1
  91. package/templates/three-compat-postprocess-scene/src/aura-assets.ts +3 -0
  92. package/templates/three-compat-premium-product-viewer/aura.assets.json +7 -0
  93. package/templates/three-compat-premium-product-viewer/package.json +1 -1
  94. package/templates/three-compat-premium-product-viewer/src/aura-assets.ts +3 -0
@@ -0,0 +1,7 @@
1
+ {
2
+ "schema": "aura3d.assets/1.0",
3
+ "assetBasePath": "/aura-assets/",
4
+ "outputDir": "public/aura-assets",
5
+ "typegen": "src/aura-assets.ts",
6
+ "assets": []
7
+ }
@@ -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.4.5"
14
+ "@aura3d/engine": "2.0.0"
15
15
  },
16
16
  "devDependencies": {
17
17
  "@playwright/test": "^1.52.0",
@@ -151,14 +151,12 @@ if (!input) throw new Error("create-aura3d fighting-game template failed to crea
151
151
  const replayInput = game.input({ ...inputOptions, autoListen: false, gamepad: false });
152
152
  const openingReplay = game.inputReplay(
153
153
  [
154
- { frame: 2, time: 2 / 60, type: "press", binding: "KeyD" },
155
- { frame: 16, time: 16 / 60, type: "press", binding: "KeyL" },
156
- { frame: 20, time: 20 / 60, type: "release", binding: "KeyL" },
157
- { frame: 32, time: 32 / 60, type: "release", binding: "KeyD" },
158
- { frame: 44, time: 44 / 60, type: "press", binding: "KeyJ" },
159
- { frame: 48, time: 48 / 60, type: "release", binding: "KeyJ" }
154
+ { frame: 1, time: 1 / 60, type: "press", binding: "KeyL" },
155
+ { frame: 8, time: 8 / 60, type: "release", binding: "KeyL" },
156
+ { frame: 12, time: 12 / 60, type: "press", binding: "KeyJ" },
157
+ { frame: 16, time: 16 / 60, type: "release", binding: "KeyJ" }
160
158
  ],
161
- { label: "starter approach-special replay", fps: 60, seed: 105 }
159
+ { label: "starter close-range special replay", fps: 60, seed: 105 }
162
160
  );
163
161
  const replayDriver = game.inputReplayDriver(replayInput, openingReplay);
164
162
 
@@ -1,8 +1,11 @@
1
1
  import { expect, test } from "@playwright/test";
2
2
 
3
+ test.setTimeout(120_000);
4
+
3
5
  test("input replay produces runtime evidence and a hit declaration", async ({ page }) => {
4
6
  await page.goto("/");
5
- await page.click("#hud-replay-button");
7
+ await page.waitForFunction(() => Boolean((window as any).__AURA3D_GAME_SOURCE__?.readiness));
8
+ await page.getByRole("button", { name: "Run replay" }).click();
6
9
  await page.waitForFunction(() => Boolean((window as any).__AURA3D_GAME_EVIDENCE__?.systems?.inputPlan));
7
10
  await page.waitForFunction(() => Boolean((window as any).__AURA3D_GAME_RUNTIME__?.kind === "aura-game-app-runtime-evidence"));
8
11
  await page.waitForFunction(() => ((window as any).__AURA3D_GAME_REPLAY__?.hitCount ?? 0) > 0);
@@ -1,10 +1,16 @@
1
1
  import { expect, test } from "@playwright/test";
2
2
 
3
3
  test("fighting-game route loads", async ({ page }) => {
4
+ test.setTimeout(90_000);
4
5
  await page.goto("/");
5
6
  await expect(page.locator("#app")).toBeVisible();
6
- await expect(page.locator("#hud")).toContainText("Aura3D Fighting Game Runtime");
7
- await page.waitForFunction(() => Boolean((window as any).__AURA3D_GAME_SOURCE__?.readiness));
7
+ await page.waitForFunction(() =>
8
+ Boolean((window as any).__AURA3D_GAME_SOURCE__?.readiness) &&
9
+ document.querySelector("#hud h1")?.textContent === "Aura3D Fighting Game Runtime",
10
+ undefined,
11
+ { polling: 100, timeout: 60_000 }
12
+ );
13
+ await expect(page.locator("#hud h1")).toHaveText("Aura3D Fighting Game Runtime");
8
14
  const source = await page.evaluate(() => (window as any).__AURA3D_GAME_SOURCE__);
9
15
  expect(source.readiness.route).toBe("/");
10
16
  expect(source.lifecycle).toMatchObject({
@@ -6,8 +6,9 @@ health checks.
6
6
  Status: playable starter, not a production game claim. This template uses a
7
7
  typed player asset, the public `game.platformer(...)` kit, runtime nodes, HUD
8
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,
9
+ starter boots and responds to keyboard input through `@aura3d/lean/game`'s
10
+ solver-free deterministic arcade API; it does not install or claim a physical
11
+ simulation solver and does not prove production art direction, skinned animation,
11
12
  advanced PBR, WebGPU, postprocess, or commercial game polish.
12
13
 
13
14
  Before presenting a route from this template as production-quality, replace any
@@ -0,0 +1,31 @@
1
+ {
2
+ "schema": "aura3d.assets/1.0",
3
+ "assetBasePath": "/aura-assets/",
4
+ "outputDir": "public/aura-assets",
5
+ "typegen": "src/aura-assets.ts",
6
+ "assets": [
7
+ {
8
+ "id": "playerModel",
9
+ "type": "model",
10
+ "format": "glb",
11
+ "source": "public/aura-assets/player-fixture.glb",
12
+ "outputPath": "public/aura-assets/player-fixture.glb",
13
+ "url": "/aura-assets/player-fixture.glb",
14
+ "hash": "sha256-047f5e5fb3bb6d378bd1df16ca6137f2a596c99b3a1b5690b4020c05aaf6f319",
15
+ "sizeBytes": 463988,
16
+ "bounds": [
17
+ 1.2,
18
+ 1.9,
19
+ 0.8
20
+ ],
21
+ "materials": [
22
+ "painted robot body",
23
+ "black joints",
24
+ "white armor"
25
+ ],
26
+ "animations": [],
27
+ "textures": [],
28
+ "thumbnailUrl": "/aura-assets/player.thumb.svg"
29
+ }
30
+ ]
31
+ }
@@ -11,7 +11,7 @@
11
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.4.5"
14
+ "@aura3d/lean": "2.0.0"
15
15
  },
16
16
  "devDependencies": {
17
17
  "@playwright/test": "^1.52.0",
@@ -1,4 +1,4 @@
1
- import { defineAuraAssets } from "@aura3d/engine";
1
+ import { defineAuraAssets } from "@aura3d/lean/game";
2
2
 
3
3
  export const assets = defineAuraAssets({
4
4
  playerModel: {
@@ -2,19 +2,17 @@ import {
2
2
  camera,
3
3
  createAuraApp,
4
4
  game,
5
- lights,
6
5
  material,
7
6
  model,
8
7
  primitives,
9
8
  scene,
10
- type AuraNodeInput
11
- } from "@aura3d/engine";
9
+ type AuraLeanNodeBuilder,
10
+ type LeanPlatformerEvent
11
+ } from "@aura3d/lean/game";
12
12
  import { assets } from "./aura-assets";
13
13
 
14
14
  declare global {
15
- interface Window {
16
- __AURA3D_MINI_GAME__?: MiniGameEvidence;
17
- }
15
+ interface Window { __AURA3D_MINI_GAME__?: MiniGameEvidence }
18
16
  }
19
17
 
20
18
  interface MiniGameEvidence {
@@ -24,13 +22,9 @@ interface MiniGameEvidence {
24
22
  readonly deaths: number;
25
23
  readonly checkpointId: string;
26
24
  readonly collected: readonly string[];
27
- readonly player: {
28
- readonly x: number;
29
- readonly y: number;
30
- readonly grounded: boolean;
31
- };
25
+ readonly player: { readonly x: number; readonly y: number; readonly grounded: boolean };
32
26
  readonly events: readonly string[];
33
- readonly evidence: unknown;
27
+ readonly evidence: { readonly entry: string; readonly physics: string; readonly typedAssets: number };
34
28
  }
35
29
 
36
30
  const level = {
@@ -49,12 +43,8 @@ const level = {
49
43
  { id: "coin-02", x: 4.8, y: 1.45, value: 75, radius: 0.55 },
50
44
  { id: "coin-03", x: 9.8, y: 1.05, value: 100, radius: 0.55 }
51
45
  ],
52
- hazards: [
53
- { id: "spikes", x: 7.7, y: 0.42, width: 0.85, height: 0.24 }
54
- ],
55
- checkpoints: [
56
- { id: "mid", x: 5.2, y: 0.92, radius: 0.8 }
57
- ],
46
+ hazards: [{ id: "spikes", x: 7.7, y: 0.42, width: 0.85, height: 0.24 }],
47
+ checkpoints: [{ id: "mid", x: 5.2, y: 0.92, radius: 0.8 }],
58
48
  lowerBound: -2.4,
59
49
  moveSpeed: 5.2,
60
50
  jumpVelocity: 8,
@@ -70,119 +60,97 @@ const input = game.input({
70
60
  down: ["KeyS", "ArrowDown"],
71
61
  reset: ["KeyR"]
72
62
  },
73
- axes: {
74
- moveX: { negative: "left", positive: "right" }
75
- },
63
+ axes: { moveX: { negative: "left", positive: "right" } },
76
64
  bufferMs: 140
77
65
  });
78
-
79
66
  const platformer = game.platformer(level);
80
- const routeEvents = game.eventLog({ label: "mini game route events", maxEvents: 16 });
81
- const hud = game.hud.bindings([
82
- game.hud.score({ valuePath: "appState.score" }),
83
- game.hud.lives({ valuePath: "appState.lives" }),
84
- game.hud.objective({ valuePath: "appState.objective" }),
85
- game.hud.checkpoint({ valuePath: "appState.checkpoint" }),
86
- game.hud.eventLog({ valuePath: "appState.events" })
87
- ]);
88
-
89
- const app = createAuraApp("#app", {
90
- diagnostics: { overlay: true, performancePanel: true },
91
- scene: buildScene()
92
- });
93
-
67
+ const routeEvents: string[] = [];
68
+ const app = createAuraApp("#app", { scene: buildScene() });
94
69
  const player = app.nodes.require("mini-player");
95
70
  const lift = app.nodes.require("platform-lift");
96
71
  const coins = level.collectibles.map((coin) => [coin.id, app.nodes.require(`coin-${coin.id}`)] as const);
97
72
  const checkpoint = app.nodes.require("checkpoint-mid");
98
73
  const goal = app.nodes.require("goal");
99
74
  const hudRoot = createHud();
100
-
101
75
  let objective = "Collect coins, avoid spikes, reach the goal";
102
76
 
103
- app.onFrame(({ dt }: { readonly dt: number }) => {
77
+ app.onFrame((dt) => {
104
78
  input.update(dt);
105
79
  if (input.pressed("reset")) {
106
80
  platformer.reset();
107
- routeEvents.push({ type: "reset", label: "Route reset" });
81
+ routeEvents.push("reset:Route reset");
108
82
  objective = "Collect coins, avoid spikes, reach the goal";
109
83
  }
110
-
111
- const state = platformer.step(dt, {
112
- moveX: input.axis("moveX"),
113
- jumpPressed: input.buffered("jump"),
114
- jumpHeld: input.held("jump"),
115
- dashPressed: input.pressed("dash"),
116
- fastFall: input.held("down")
117
- });
118
-
119
- for (const event of state.events) {
120
- routeEvents.push({
121
- type: event.type,
122
- label: event.id ? `${event.type}:${event.id}` : event.type,
123
- targetId: event.id,
124
- severity: event.type === "complete" || event.type === "collect" ? "success" : event.type === "hazard" || event.type === "fall" ? "warning" : "info",
125
- frame: event.frame,
126
- time: event.time
84
+ const moveX = input.axis("moveX");
85
+ const jumpPressed = input.buffered("jump");
86
+ const dashPressed = input.pressed("dash");
87
+ let remaining = Math.min(0.25, Math.max(0, dt));
88
+ let firstSubstep = true;
89
+ let state = platformer.snapshot();
90
+ do {
91
+ const substep = Math.min(0.05, remaining || 1 / 60);
92
+ state = platformer.step(substep, {
93
+ moveX,
94
+ jumpPressed: firstSubstep && jumpPressed,
95
+ jumpHeld: input.held("jump"),
96
+ dashPressed: firstSubstep && dashPressed,
97
+ fastFall: input.held("down")
127
98
  });
128
- if (event.type === "checkpoint") objective = "Checkpoint reached. Finish the route.";
129
- if (event.type === "complete") objective = "Finished. Press R to replay.";
130
- if (event.type === "respawn") objective = "Respawned. Take the safer route.";
131
- }
99
+ for (const event of state.events) updateObjective(event);
100
+ remaining -= substep;
101
+ firstSubstep = false;
102
+ } while (remaining > 0.000_001);
132
103
 
133
- player.setPosition(state.player.x, state.player.y + 0.54, 0);
104
+ player.setPosition(state.player.x + 0.38, state.player.y + 0.5, 0);
134
105
  lift.setPosition(level.movingPlatforms[0].x + level.movingPlatforms[0].width / 2, movingLiftY(state.time), 0);
135
106
  for (const [id, node] of coins) node.setVisible(!state.collected.includes(id));
136
107
  checkpoint.setVisible(!state.activatedCheckpoints.includes("mid"));
137
108
  goal.setScale(state.status === "completed" ? [0.28, 1.08, 0.18] : [0.22, 0.92, 0.16]);
138
-
139
109
  renderHud(state);
140
110
  publishEvidence(state);
141
111
  });
142
112
 
143
113
  publishEvidence(platformer.snapshot());
144
114
  renderHud(platformer.snapshot());
115
+ void app.ready().then(() => {
116
+ const diagnostics = app.diagnostics();
117
+ document.body.dataset.aura3dReady = "true";
118
+ document.body.dataset.aura3dRuntimeBackend = diagnostics.runtimeBackend;
119
+ document.body.dataset.aura3dDrawCalls = String(diagnostics.drawCalls);
120
+ (window as unknown as { __AURA3D_ROUTE_READY__?: unknown }).__AURA3D_ROUTE_READY__ = { ready: true, diagnostics };
121
+ }).catch((error: unknown) => {
122
+ document.body.dataset.aura3dError = error instanceof Error ? error.message : String(error);
123
+ });
145
124
 
146
125
  function buildScene() {
147
- const nodes: AuraNodeInput[] = [
148
- model(assets.playerModel, { name: "typed mini-game player", castShadow: true })
149
- .position(level.start.x, level.start.y + 0.54, 0)
150
- .scale(0.42)
151
- .runtime(game.runtimeNode("mini-player", { tags: ["player", "typed-asset", "runtime"] })),
152
- ...level.platforms.map((platform) =>
153
- primitives.box({ name: `${platform.id} platform`, material: material.neon({ color: "#62d8ef", emissive: "#62d8ef", emissiveIntensity: 0.18 }) })
154
- .position(platform.x + platform.width / 2, platform.y + platform.height / 2, -0.05)
155
- .scale([platform.width, platform.height, 0.22])
156
- ),
157
- primitives.box({ name: "moving lift platform", material: material.neon({ color: "#b5f77d", emissive: "#b5f77d", emissiveIntensity: 0.35 }) })
126
+ const nodes: AuraLeanNodeBuilder[] = [
127
+ ...level.platforms.map((platform) => primitives.box({ name: `${platform.id} platform`, material: material.pbr({ color: "#62d8ef", roughness: 0.28 }) })
128
+ .position(platform.x + platform.width / 2, platform.y + platform.height / 2, -0.05).scale([platform.width, platform.height, 0.22])),
129
+ primitives.box({ name: "moving lift platform", material: material.pbr({ color: "#b5f77d", roughness: 0.24 }) })
158
130
  .position(level.movingPlatforms[0].x + level.movingPlatforms[0].width / 2, movingLiftY(0), 0)
159
- .scale([level.movingPlatforms[0].width, level.movingPlatforms[0].height, 0.22])
160
- .runtime(game.runtimeNode("platform-lift", { tags: ["moving-platform", "runtime"] })),
161
- ...level.collectibles.map((coin) =>
162
- primitives.sphere({ name: `${coin.id} collectible`, material: material.neon({ color: "#f7d76b", emissive: "#f7d76b", emissiveIntensity: 0.7 }) })
163
- .position(coin.x, coin.y, 0.02)
164
- .scale(0.18)
165
- .runtime(game.runtimeNode(`coin-${coin.id}`, { tags: ["coin", "runtime"] }))
166
- ),
167
- primitives.box({ name: "hazard spikes", material: material.neon({ color: "#f06b7a", emissive: "#f06b7a", emissiveIntensity: 0.7 }) })
131
+ .scale([level.movingPlatforms[0].width, level.movingPlatforms[0].height, 0.22]).runtime("platform-lift"),
132
+ ...level.collectibles.map((coin) => primitives.sphere({ name: `${coin.id} collectible`, material: material.pbr({ color: "#f7d76b", metallic: 0.35, roughness: 0.22 }) })
133
+ .position(coin.x, coin.y, 0.02).scale(0.18).runtime(`coin-${coin.id}`)),
134
+ primitives.box({ name: "hazard spikes", material: material.pbr({ color: "#f06b7a", roughness: 0.2 }) })
168
135
  .position(level.hazards[0].x + level.hazards[0].width / 2, level.hazards[0].y + level.hazards[0].height / 2, 0.05)
169
136
  .scale([level.hazards[0].width, level.hazards[0].height, 0.2]),
170
- primitives.box({ name: "checkpoint marker", material: material.neon({ color: "#9df59e", emissive: "#9df59e", emissiveIntensity: 0.65 }) })
171
- .position(level.checkpoints[0].x, level.checkpoints[0].y + 0.42, 0.08)
172
- .scale([0.14, 0.85, 0.14])
173
- .runtime(game.runtimeNode("checkpoint-mid", { tags: ["checkpoint", "runtime"] })),
174
- primitives.box({ name: "finish portal", material: material.neon({ color: "#ffad57", emissive: "#ffad57", emissiveIntensity: 0.82 }) })
175
- .position(level.finish.x, level.finish.y + 0.7, 0.08)
176
- .scale([0.22, 0.92, 0.16])
177
- .runtime(game.runtimeNode("goal", { tags: ["finish", "runtime"] }))
137
+ primitives.box({ name: "checkpoint marker", material: material.pbr({ color: "#9df59e", roughness: 0.2 }) })
138
+ .position(level.checkpoints[0].x, level.checkpoints[0].y + 0.42, 0.08).scale([0.14, 0.85, 0.14]).runtime("checkpoint-mid"),
139
+ primitives.box({ name: "finish portal", material: material.pbr({ color: "#ffad57", metallic: 0.2, roughness: 0.18 }) })
140
+ .position(level.finish.x, level.finish.y + 0.7, 0.08).scale([0.22, 0.92, 0.16]).runtime("goal")
178
141
  ];
179
-
180
- return scene()
181
- .background("#071015")
142
+ return scene().background("#071015")
143
+ .add(model(assets.playerModel, { name: "typed mini-game player" }).position(level.start.x + 0.38, level.start.y + 0.5, 0).scale(0.28).runtime("mini-player"))
182
144
  .addMany(nodes)
183
- .add(lights.ambient({ name: "game ambient", intensity: 0.38, color: "#d9f8ff" }))
184
- .add(lights.directional({ name: "game key", position: [4, 6, 5], intensity: 1.2, color: "#ffffff" }))
185
- .camera(camera.perspective({ position: [5.8, 4.3, 8.5], target: [5.8, 0.9, 0], fov: 44 }));
145
+ .camera(camera.perspective({ position: [6.1, 3.8, 10.4], target: [6.1, 0.82, 0], fov: 46 }));
146
+ }
147
+
148
+ function updateObjective(event: LeanPlatformerEvent): void {
149
+ routeEvents.push(event.id ? `${event.type}:${event.id}` : event.type);
150
+ if (routeEvents.length > 16) routeEvents.shift();
151
+ if (event.type === "checkpoint") objective = "Checkpoint reached. Finish the route.";
152
+ if (event.type === "complete") objective = "Finished. Press R to replay.";
153
+ if (event.type === "respawn") objective = "Respawned. Take the safer route.";
186
154
  }
187
155
 
188
156
  function movingLiftY(time: number): number {
@@ -193,52 +161,16 @@ function movingLiftY(time: number): number {
193
161
  function createHud(): HTMLElement {
194
162
  const root = document.createElement("aside");
195
163
  root.id = "mini-game-hud";
196
- root.style.cssText = [
197
- "position:absolute",
198
- "left:16px",
199
- "top:16px",
200
- "z-index:5",
201
- "min-width:260px",
202
- "font:600 13px/1.35 Inter, system-ui, sans-serif",
203
- "color:#f5fbff",
204
- "background:rgba(3,9,14,0.78)",
205
- "border:1px solid rgba(125,220,235,0.34)",
206
- "border-radius:8px",
207
- "padding:12px",
208
- "pointer-events:none"
209
- ].join(";");
164
+ root.style.cssText = "position:absolute;left:16px;top:16px;z-index:5;min-width:260px;font:600 13px/1.35 Inter,system-ui,sans-serif;color:#f5fbff;background:rgba(3,9,14,.78);border:1px solid rgba(125,220,235,.34);border-radius:8px;padding:12px;pointer-events:none";
210
165
  document.body.append(root);
211
166
  return root;
212
167
  }
213
168
 
214
169
  function renderHud(state: ReturnType<typeof platformer.snapshot>): void {
215
- hudRoot.innerHTML = [
216
- `<strong>Aura3D Mini Game</strong>`,
217
- `<div>Score ${state.score} | Lives ${state.lives} | Deaths ${state.deaths}</div>`,
218
- `<div>Checkpoint ${state.checkpointId}</div>`,
219
- `<div>${objective}</div>`,
220
- `<div>Move A/D or arrows. Jump Space. Dash Shift. Reset R.</div>`
221
- ].join("");
170
+ hudRoot.innerHTML = `<strong>Aura3D Mini Game</strong><div>Score ${state.score} | Lives ${state.lives} | Deaths ${state.deaths}</div><div>Checkpoint ${state.checkpointId}</div><div>${objective}</div><div>Move A/D or arrows. Jump Space. Dash Shift. Reset R.</div>`;
222
171
  }
223
172
 
224
173
  function publishEvidence(state: ReturnType<typeof platformer.snapshot>): void {
225
- const evidence = app.evidence({
226
- input,
227
- events: routeEvents,
228
- hud,
229
- appState: {
230
- score: state.score,
231
- lives: state.lives,
232
- objective,
233
- checkpoint: state.checkpointId,
234
- events: routeEvents.events().map((event: { readonly label: string }) => event.label)
235
- },
236
- assets: {
237
- typedAssets: Object.keys(assets).length,
238
- missingAssets: []
239
- },
240
- source: { expectsGame: true }
241
- });
242
174
  window.__AURA3D_MINI_GAME__ = {
243
175
  status: state.status,
244
176
  frame: state.frame,
@@ -246,12 +178,8 @@ function publishEvidence(state: ReturnType<typeof platformer.snapshot>): void {
246
178
  deaths: state.deaths,
247
179
  checkpointId: state.checkpointId,
248
180
  collected: state.collected,
249
- player: {
250
- x: state.player.x,
251
- y: state.player.y,
252
- grounded: state.player.grounded
253
- },
254
- events: routeEvents.events().map((event: { readonly label: string }) => event.label),
255
- evidence
181
+ player: { x: state.player.x, y: state.player.y, grounded: state.player.grounded },
182
+ events: [...routeEvents],
183
+ evidence: { entry: "@aura3d/lean/game", physics: "solver-free deterministic arcade", typedAssets: Object.keys(assets).length }
256
184
  };
257
185
  }
@@ -1,6 +1,7 @@
1
1
  # Aura3D Product Viewer
2
2
 
3
- Agent-friendly product scene starter using the public `@aura3d/engine` API.
3
+ Agent-friendly product scene starter using the public `@aura3d/lean/product`
4
+ API.
4
5
 
5
6
  ```bash
6
7
  npm install
@@ -9,8 +10,9 @@ npx @aura3d/cli@latest assets add ./assets/product.glb --name product
9
10
  npm run test
10
11
  ```
11
12
 
12
- Edit `src/main.ts` to change camera, material, lights, and diagnostics. Do not
13
+ Edit `src/main.ts` to change the camera, material, studio environment, plinth,
14
+ typed product, and diagnostics. Do not
13
15
  invent asset paths; after `assets add`, use `assets.product` from
14
- `src/aura-assets.ts`. The default scene uses `prefabs.productViewer(assets.product)`
15
- so a replacement product keeps the plinth, contact shadow, softboxes, and
16
- turntable/orbit evidence together.
16
+ `src/aura-assets.ts`. The default scene composes the product, plinth, floor,
17
+ studio environment, and orbit interaction directly through the lean product
18
+ entry. It does not install physics, navigation, editor, or Node-media systems.
@@ -0,0 +1,34 @@
1
+ {
2
+ "schema": "aura3d.assets/1.0",
3
+ "assetBasePath": "/aura-assets/",
4
+ "outputDir": "public/aura-assets",
5
+ "typegen": "src/aura-assets.ts",
6
+ "assets": [
7
+ {
8
+ "id": "product",
9
+ "type": "model",
10
+ "format": "glb",
11
+ "source": "public/aura-assets/product-fixture.glb",
12
+ "outputPath": "public/aura-assets/product-fixture.glb",
13
+ "url": "/aura-assets/product-fixture.glb",
14
+ "hash": "sha256-5613d5ad4ddc538b02c147faf2f87777bfd5a79580297cc6a103fdb7556580d3",
15
+ "sizeBytes": 48612,
16
+ "bounds": [
17
+ 1.9,
18
+ 2.9,
19
+ 1.1
20
+ ],
21
+ "materials": [
22
+ "satin cabinet",
23
+ "patterned grille",
24
+ "rubber cone",
25
+ "metallic knobs"
26
+ ],
27
+ "animations": [],
28
+ "textures": [
29
+ "embedded product material swatches"
30
+ ],
31
+ "thumbnailUrl": "/aura-assets/product.thumb.svg"
32
+ }
33
+ ]
34
+ }
@@ -11,6 +11,6 @@
11
11
  </head>
12
12
  <body>
13
13
  <div id="app"></div>
14
- <script type="module" src="/src/main.ts"></script>
14
+ <script type="module" src="./src/main.ts"></script>
15
15
  </body>
16
16
  </html>
@@ -11,7 +11,7 @@
11
11
  "test": "playwright test tests/route-health.spec.ts tests/screenshot.spec.ts --workers=1"
12
12
  },
13
13
  "dependencies": {
14
- "@aura3d/engine": "1.4.5"
14
+ "@aura3d/lean": "2.0.0"
15
15
  },
16
16
  "devDependencies": {
17
17
  "@playwright/test": "^1.52.0",
@@ -1,4 +1,4 @@
1
- import { defineAuraAssets } from "@aura3d/engine";
1
+ import { defineAuraAssets } from "@aura3d/lean/product";
2
2
 
3
3
  export const assets = defineAuraAssets({
4
4
  product: {
@@ -1,27 +1,33 @@
1
- import { createAuraApp, definePromptPlan, promptPlanToScene } from "@aura3d/engine";
1
+ import {
2
+ camera,
3
+ createAuraApp,
4
+ environments,
5
+ interactions,
6
+ material,
7
+ model,
8
+ primitives,
9
+ scene
10
+ } from "@aura3d/lean/product";
2
11
  import { assets } from "./aura-assets";
3
12
 
4
- const plan = definePromptPlan({
5
- sceneType: "product-viewer",
6
- subject: { asset: assets.product, label: "studio product" },
7
- style: "premium studio product inspection with visible fit and turntable cues",
8
- environment: "studio sweep, round plinth, contact shadow, softboxes, reflection cards",
9
- camera: { preset: "product-orbit" },
10
- lighting: { preset: "studio-softbox" },
11
- effects: ["bloom"],
12
- interaction: "orbit",
13
- acceptanceCriteria: [
14
- "product is centered, normalized, and seated on the plinth",
15
- "softboxes, reflection cards, and contact shadow shape the asset",
16
- "turntable and orbit cues are visible without reading diagnostics"
17
- ],
18
- negativeCriteria: [
19
- "Do not use string asset ids or invented product URLs",
20
- "Do not ship a lone GLB without plinth, contact, fit, and rotation evidence"
21
- ]
22
- } as const);
13
+ const productScene = scene()
14
+ .background("#071018")
15
+ .add(primitives.plane({ name: "studio floor", material: material.pbr({ color: "#22313b", roughness: 0.32, metallic: 0.08 }) })
16
+ .position(0, -0.05, -0.62).scale([6.2, 1, 5.2]))
17
+ .add(primitives.box({ name: "product plinth", material: material.clearcoatPaint({ color: "#dce7ed", roughness: 0.2, metallic: 0.18 }) })
18
+ .position(0, 0.06, -0.62).scale([1.82, 0.18, 1.4]))
19
+ .add(model(assets.product, { name: "typed studio product" }).position(0, 1.08, -0.62).scale(0.66))
20
+ .add(environments.studio())
21
+ .add(interactions.orbit())
22
+ .camera(camera.perspective({ position: [2.65, 2.05, 4.55], target: [0, 1.02, -0.62], fov: 32 }));
23
23
 
24
- createAuraApp("#app", {
25
- diagnostics: { overlay: true, assetPanel: true, performancePanel: true },
26
- scene: promptPlanToScene(plan)
24
+ const app = createAuraApp("#app", { scene: productScene });
25
+ void app.ready().then(() => {
26
+ const diagnostics = app.diagnostics();
27
+ document.body.dataset.aura3dReady = "true";
28
+ document.body.dataset.aura3dRuntimeBackend = diagnostics.runtimeBackend;
29
+ document.body.dataset.aura3dDrawCalls = String(diagnostics.drawCalls);
30
+ (window as unknown as { __AURA3D_ROUTE_READY__?: unknown }).__AURA3D_ROUTE_READY__ = { ready: true, diagnostics };
31
+ }).catch((error: unknown) => {
32
+ document.body.dataset.aura3dError = error instanceof Error ? error.message : String(error);
27
33
  });
@@ -2,11 +2,11 @@ import { mkdirSync, writeFileSync } from "node:fs";
2
2
  import { resolve } from "node:path";
3
3
  import { expect, test } from "@playwright/test";
4
4
 
5
- test.setTimeout(60_000);
5
+ test.setTimeout(120_000);
6
6
 
7
7
  test("Aura3D product viewer reaches ready state", async ({ page }) => {
8
8
  await page.goto("/");
9
- await expect.poll(() => page.locator("body").getAttribute("data-aura3d-ready"), { timeout: 45_000 }).toBe("true");
9
+ await expect.poll(() => page.locator("body").getAttribute("data-aura3d-ready"), { timeout: 90_000 }).toBe("true");
10
10
  const drawCalls = Number(await page.locator("body").getAttribute("data-aura3d-draw-calls"));
11
11
  const diagnostics = await page.evaluate(() => (window as unknown as { __AURA3D_ROUTE_READY__?: { diagnostics?: { backend?: string } } }).__AURA3D_ROUTE_READY__?.diagnostics);
12
12
  expect(diagnostics?.backend).toBe("webgl2");
@@ -2,11 +2,11 @@ import { mkdirSync, writeFileSync } from "node:fs";
2
2
  import { resolve } from "node:path";
3
3
  import { expect, test } from "@playwright/test";
4
4
 
5
- test.setTimeout(60_000);
5
+ test.setTimeout(120_000);
6
6
 
7
7
  test("Aura3D product viewer screenshot shows a prompt-aligned studio product", async ({ page }) => {
8
8
  await page.goto("/");
9
- await expect.poll(() => page.locator("body").getAttribute("data-aura3d-ready"), { timeout: 45_000 }).toBe("true");
9
+ await expect.poll(() => page.locator("body").getAttribute("data-aura3d-ready"), { timeout: 90_000 }).toBe("true");
10
10
  const canvas = page.locator("canvas");
11
11
  await expect(canvas).toBeVisible();
12
12
  const profile = await canvas.evaluate((element) => {
@@ -0,0 +1,7 @@
1
+ {
2
+ "schema": "aura3d.assets/1.0",
3
+ "assetBasePath": "/aura-assets/",
4
+ "outputDir": "public/aura-assets",
5
+ "typegen": "src/aura-assets.ts",
6
+ "assets": []
7
+ }
@@ -11,7 +11,7 @@
11
11
  "test": "playwright test tests/route-health.spec.ts tests/storyboard-playback.spec.ts --workers=1"
12
12
  },
13
13
  "dependencies": {
14
- "@aura3d/engine": "1.4.5"
14
+ "@aura3d/engine": "2.0.0"
15
15
  },
16
16
  "devDependencies": {
17
17
  "@playwright/test": "^1.52.0",