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.
- package/README.md +9 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -4
- package/dist/index.js.map +1 -1
- package/dist/showcase-spec-evidence.js +5 -1
- package/dist/showcase-spec-evidence.js.map +1 -1
- package/dist/showcase-spec-game-geometry-extractor.d.ts +8 -1
- package/dist/showcase-spec-game-geometry-extractor.d.ts.map +1 -1
- package/dist/showcase-spec-game-geometry-extractor.js +191 -31
- package/dist/showcase-spec-game-geometry-extractor.js.map +1 -1
- package/dist/showcase-spec-game-template-evidence.d.ts.map +1 -1
- package/dist/showcase-spec-game-template-evidence.js +7 -0
- package/dist/showcase-spec-game-template-evidence.js.map +1 -1
- package/dist/showcase-spec-parser.js +1 -1
- package/dist/showcase-spec-parser.js.map +1 -1
- package/dist/showcase-spec-types.d.ts +50 -0
- package/dist/showcase-spec-types.d.ts.map +1 -1
- package/package.json +2 -2
- package/templates/animation-channel/package.json +1 -1
- package/templates/animation-channel/src/experimental/README.md +1 -1
- package/templates/animation-channel/tests/storyboard-playback.spec.ts +2 -0
- package/templates/animation-studio/package.json +2 -5
- package/templates/animation-studio/scripts/asset-motion-probe.ts +1 -1
- package/templates/animation-studio/scripts/build-characters.ts +1 -1
- package/templates/animation-studio/scripts/motion-evidence.ts +1 -1
- package/templates/animation-studio/scripts/render-core.ts +4 -2
- package/templates/animation-studio/scripts/render-live.ts +3 -1
- package/templates/animation-studio/scripts/validate-characters.ts +0 -0
- package/templates/animation-studio/src/scene-player.ts +2 -2
- package/templates/character-controller/README.md +15 -2
- package/templates/character-controller/aura.assets.json +7 -0
- package/templates/character-controller/package.json +3 -3
- package/templates/character-controller/src/aura-assets.ts +3 -0
- package/templates/cinematic-scene/aura.assets.json +31 -0
- package/templates/cinematic-scene/package.json +1 -1
- package/templates/episode-builder/aura.assets.json +7 -0
- package/templates/episode-builder/package.json +1 -1
- package/templates/episode-builder/tests/route-health.spec.ts +2 -2
- package/templates/falling-blocks-starter/aura.assets.json +37 -0
- package/templates/falling-blocks-starter/package.json +2 -1
- package/templates/falling-blocks-starter/tests/playable.spec.ts +2 -3
- package/templates/falling-blocks-starter/tsconfig.json +1 -1
- package/templates/fighting-game/aura.assets.json +7 -0
- package/templates/fighting-game/package.json +1 -1
- package/templates/fighting-game/src/main.ts +5 -7
- package/templates/fighting-game/tests/gameplay-smoke.spec.ts +4 -1
- package/templates/fighting-game/tests/route-health.spec.ts +8 -2
- package/templates/mini-game/README.md +3 -2
- package/templates/mini-game/aura.assets.json +31 -0
- package/templates/mini-game/package.json +1 -1
- package/templates/mini-game/src/aura-assets.ts +1 -1
- package/templates/mini-game/src/main.ts +69 -141
- package/templates/product-viewer/README.md +7 -5
- package/templates/product-viewer/aura.assets.json +34 -0
- package/templates/product-viewer/index.html +1 -1
- package/templates/product-viewer/package.json +1 -1
- package/templates/product-viewer/src/aura-assets.ts +1 -1
- package/templates/product-viewer/src/main.ts +29 -23
- package/templates/product-viewer/tests/route-health.spec.ts +2 -2
- package/templates/product-viewer/tests/screenshot.spec.ts +2 -2
- package/templates/prompt-animation-channel/aura.assets.json +7 -0
- package/templates/prompt-animation-channel/package.json +1 -1
- package/templates/prompt-animation-channel/src/main.ts +4 -66
- package/templates/prompt-animation-channel/src/render-plan.ts +1 -1
- package/templates/prompt-animation-channel/tests/route-health.spec.ts +4 -2
- package/templates/prompt-animation-channel/tests/storyboard-playback.spec.ts +13 -2
- package/templates/racing-starter/aura.assets.json +64 -0
- package/templates/racing-starter/package.json +2 -1
- package/templates/racing-starter/src/main.ts +2 -2
- package/templates/racing-starter/tsconfig.json +1 -1
- package/templates/three-compat-architecture-interior/aura.assets.json +7 -0
- package/templates/three-compat-architecture-interior/package.json +1 -1
- package/templates/three-compat-architecture-interior/src/aura-assets.ts +3 -0
- package/templates/three-compat-asset-inspector/aura.assets.json +7 -0
- package/templates/three-compat-asset-inspector/package.json +1 -1
- package/templates/three-compat-asset-inspector/src/aura-assets.ts +3 -0
- package/templates/three-compat-character-viewer/aura.assets.json +7 -0
- package/templates/three-compat-character-viewer/package.json +1 -1
- package/templates/three-compat-character-viewer/src/aura-assets.ts +3 -0
- package/templates/three-compat-custom-threejs-migration/aura.assets.json +7 -0
- package/templates/three-compat-custom-threejs-migration/package.json +1 -1
- package/templates/three-compat-custom-threejs-migration/src/aura-assets.ts +3 -0
- package/templates/three-compat-large-scene/aura.assets.json +7 -0
- package/templates/three-compat-large-scene/package.json +1 -1
- package/templates/three-compat-large-scene/src/aura-assets.ts +3 -0
- package/templates/three-compat-material-authoring/aura.assets.json +7 -0
- package/templates/three-compat-material-authoring/package.json +1 -1
- package/templates/three-compat-material-authoring/src/aura-assets.ts +3 -0
- package/templates/three-compat-postprocess-scene/aura.assets.json +7 -0
- package/templates/three-compat-postprocess-scene/package.json +1 -1
- package/templates/three-compat-postprocess-scene/src/aura-assets.ts +3 -0
- package/templates/three-compat-premium-product-viewer/aura.assets.json +7 -0
- package/templates/three-compat-premium-product-viewer/package.json +1 -1
- package/templates/three-compat-premium-product-viewer/src/aura-assets.ts +3 -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:
|
|
155
|
-
{ frame:
|
|
156
|
-
{ frame:
|
|
157
|
-
{ frame:
|
|
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
|
|
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.
|
|
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
|
|
7
|
-
|
|
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
|
|
10
|
-
|
|
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/
|
|
14
|
+
"@aura3d/lean": "2.0.0"
|
|
15
15
|
},
|
|
16
16
|
"devDependencies": {
|
|
17
17
|
"@playwright/test": "^1.52.0",
|
|
@@ -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
|
|
11
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
|
81
|
-
const
|
|
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((
|
|
77
|
+
app.onFrame((dt) => {
|
|
104
78
|
input.update(dt);
|
|
105
79
|
if (input.pressed("reset")) {
|
|
106
80
|
platformer.reset();
|
|
107
|
-
routeEvents.push(
|
|
81
|
+
routeEvents.push("reset:Route reset");
|
|
108
82
|
objective = "Collect coins, avoid spikes, reach the goal";
|
|
109
83
|
}
|
|
110
|
-
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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.
|
|
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:
|
|
148
|
-
|
|
149
|
-
.position(
|
|
150
|
-
|
|
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
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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.
|
|
171
|
-
.position(level.checkpoints[0].x, level.checkpoints[0].y + 0.42, 0.08)
|
|
172
|
-
|
|
173
|
-
.
|
|
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
|
-
|
|
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
|
-
.
|
|
184
|
-
|
|
185
|
-
|
|
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
|
-
|
|
251
|
-
|
|
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/
|
|
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,
|
|
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
|
|
15
|
-
|
|
16
|
-
|
|
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
|
+
}
|
|
@@ -1,27 +1,33 @@
|
|
|
1
|
-
import {
|
|
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
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
26
|
-
|
|
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(
|
|
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:
|
|
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(
|
|
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:
|
|
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) => {
|