create-aura3d 1.3.3 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +10 -0
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/showcase-spec-artifacts.d.ts +104 -0
- package/dist/showcase-spec-artifacts.d.ts.map +1 -0
- package/dist/showcase-spec-artifacts.js +142 -0
- package/dist/showcase-spec-artifacts.js.map +1 -0
- package/dist/showcase-spec-category-plan.d.ts +15 -0
- package/dist/showcase-spec-category-plan.d.ts.map +1 -0
- package/dist/showcase-spec-category-plan.js +45 -0
- package/dist/showcase-spec-category-plan.js.map +1 -0
- package/dist/showcase-spec-compiler.d.ts +5 -0
- package/dist/showcase-spec-compiler.d.ts.map +1 -0
- package/dist/showcase-spec-compiler.js +240 -0
- package/dist/showcase-spec-compiler.js.map +1 -0
- package/dist/showcase-spec-evidence.d.ts +7 -0
- package/dist/showcase-spec-evidence.d.ts.map +1 -0
- package/dist/showcase-spec-evidence.js +815 -0
- package/dist/showcase-spec-evidence.js.map +1 -0
- package/dist/showcase-spec-game-geometry-extractor.d.ts +21 -0
- package/dist/showcase-spec-game-geometry-extractor.d.ts.map +1 -0
- package/dist/showcase-spec-game-geometry-extractor.js +725 -0
- package/dist/showcase-spec-game-geometry-extractor.js.map +1 -0
- package/dist/showcase-spec-game-replacement-evidence.d.ts +14 -0
- package/dist/showcase-spec-game-replacement-evidence.d.ts.map +1 -0
- package/dist/showcase-spec-game-replacement-evidence.js +219 -0
- package/dist/showcase-spec-game-replacement-evidence.js.map +1 -0
- package/dist/showcase-spec-game-template-evidence.d.ts +72 -0
- package/dist/showcase-spec-game-template-evidence.d.ts.map +1 -0
- package/dist/showcase-spec-game-template-evidence.js +435 -0
- package/dist/showcase-spec-game-template-evidence.js.map +1 -0
- package/dist/showcase-spec-gameplay-evidence.d.ts +3 -0
- package/dist/showcase-spec-gameplay-evidence.d.ts.map +1 -0
- package/dist/showcase-spec-gameplay-evidence.js +108 -0
- package/dist/showcase-spec-gameplay-evidence.js.map +1 -0
- package/dist/showcase-spec-parser.d.ts +3 -0
- package/dist/showcase-spec-parser.d.ts.map +1 -0
- package/dist/showcase-spec-parser.js +662 -0
- package/dist/showcase-spec-parser.js.map +1 -0
- package/dist/showcase-spec-platformer-artifacts.d.ts +3 -0
- package/dist/showcase-spec-platformer-artifacts.d.ts.map +1 -0
- package/dist/showcase-spec-platformer-artifacts.js +276 -0
- package/dist/showcase-spec-platformer-artifacts.js.map +1 -0
- package/dist/showcase-spec-racing-artifacts.d.ts +3 -0
- package/dist/showcase-spec-racing-artifacts.d.ts.map +1 -0
- package/dist/showcase-spec-racing-artifacts.js +273 -0
- package/dist/showcase-spec-racing-artifacts.js.map +1 -0
- package/dist/showcase-spec-replacement-apply.d.ts +10 -0
- package/dist/showcase-spec-replacement-apply.d.ts.map +1 -0
- package/dist/showcase-spec-replacement-apply.js +166 -0
- package/dist/showcase-spec-replacement-apply.js.map +1 -0
- package/dist/showcase-spec-replacement-candidates.d.ts +16 -0
- package/dist/showcase-spec-replacement-candidates.d.ts.map +1 -0
- package/dist/showcase-spec-replacement-candidates.js +244 -0
- package/dist/showcase-spec-replacement-candidates.js.map +1 -0
- package/dist/showcase-spec-replacement-game-geometry.d.ts +4 -0
- package/dist/showcase-spec-replacement-game-geometry.d.ts.map +1 -0
- package/dist/showcase-spec-replacement-game-geometry.js +175 -0
- package/dist/showcase-spec-replacement-game-geometry.js.map +1 -0
- package/dist/showcase-spec-replacement-manifest.d.ts +41 -0
- package/dist/showcase-spec-replacement-manifest.d.ts.map +1 -0
- package/dist/showcase-spec-replacement-manifest.js +62 -0
- package/dist/showcase-spec-replacement-manifest.js.map +1 -0
- package/dist/showcase-spec-replacement-values.d.ts +9 -0
- package/dist/showcase-spec-replacement-values.d.ts.map +1 -0
- package/dist/showcase-spec-replacement-values.js +41 -0
- package/dist/showcase-spec-replacement-values.js.map +1 -0
- package/dist/showcase-spec-replacement.d.ts +13 -0
- package/dist/showcase-spec-replacement.d.ts.map +1 -0
- package/dist/showcase-spec-replacement.js +144 -0
- package/dist/showcase-spec-replacement.js.map +1 -0
- package/dist/showcase-spec-types.d.ts +304 -0
- package/dist/showcase-spec-types.d.ts.map +1 -0
- package/dist/showcase-spec-types.js +2 -0
- package/dist/showcase-spec-types.js.map +1 -0
- package/package.json +13 -2
- package/templates/animation-studio/scripts/animation-scene.ts +1 -1
- package/templates/animation-studio/scripts/author-office-scene.ts +1 -1
- package/templates/animation-studio/scripts/build-characters.ts +1 -1
- package/templates/animation-studio/src/director/director-heuristics.ts +2 -2
- package/templates/animation-studio/src/director/{prompt-to-scene.ts → prompt-scene.ts} +1 -1
- package/templates/cinematic-scene/tests/screenshot.spec.ts +2 -4
- package/templates/falling-blocks-starter/README.md +18 -0
- package/templates/falling-blocks-starter/index.html +23 -0
- package/templates/falling-blocks-starter/package.json +21 -0
- package/templates/falling-blocks-starter/playwright.config.ts +12 -0
- package/templates/falling-blocks-starter/public/aura-assets/showcaseBlockfallCabinet.679d52fe.glb +0 -0
- package/templates/falling-blocks-starter/public/aura-assets/showcaseBlockfallCabinet.thumb.svg +1 -0
- package/templates/falling-blocks-starter/src/aura-assets.ts +20 -0
- package/templates/falling-blocks-starter/src/main.ts +358 -0
- package/templates/falling-blocks-starter/tests/playable.spec.ts +85 -0
- package/templates/falling-blocks-starter/tests/route-health.spec.ts +34 -0
- package/templates/falling-blocks-starter/tsconfig.json +13 -0
- package/templates/mini-game/README.md +13 -0
- package/templates/mini-game/index.html +1 -1
- package/templates/mini-game/package.json +1 -1
- package/templates/mini-game/src/main.ts +251 -21
- package/templates/mini-game/tests/playable.spec.ts +49 -0
- package/templates/mini-game/tests/screenshot.spec.ts +37 -32
- package/templates/product-viewer/tests/screenshot.spec.ts +3 -5
- package/templates/racing-starter/README.md +27 -0
- package/templates/racing-starter/index.html +23 -0
- package/templates/racing-starter/package.json +21 -0
- package/templates/racing-starter/playwright.config.ts +12 -0
- package/templates/racing-starter/public/aura-assets/showcaseCleanSportsCar.1c69283f.glb +0 -0
- package/templates/racing-starter/public/aura-assets/showcaseCleanSportsCar.thumb.svg +1 -0
- package/templates/racing-starter/public/aura-assets/showcaseReadableKartCircuit.5cbb912e.glb +0 -0
- package/templates/racing-starter/public/aura-assets/showcaseReadableKartCircuit.thumb.svg +1 -0
- package/templates/racing-starter/src/aura-assets.ts +36 -0
- package/templates/racing-starter/src/main.ts +280 -0
- package/templates/racing-starter/tests/playable.spec.ts +93 -0
- package/templates/racing-starter/tests/route-health.spec.ts +40 -0
- package/templates/racing-starter/tsconfig.json +13 -0
- package/templates/animation-channel/public/aura-assets/moon-garden-feature-frame.png +0 -0
- package/templates/animation-channel/tests/reports/prompt-animation/cartoon-sample-episode.png +0 -0
- package/templates/animation-studio/dist/episodes/prompt-to-scene-proof/two-robots-fixing-a-car.document.json +0 -680
- package/templates/animation-studio/dist/episodes/scene/episode-3d.webm +0 -0
- package/templates/animation-studio/dist/episodes/scene/frames/action.png +0 -0
- package/templates/animation-studio/dist/episodes/scene/frames/dialogue.png +0 -0
- package/templates/animation-studio/dist/episodes/scene/frames/final.png +0 -0
- package/templates/animation-studio/dist/episodes/scene/frames/first.png +0 -0
- package/templates/animation-studio/dist/episodes/scene/frames/mouth-closed.png +0 -0
- package/templates/animation-studio/dist/episodes/scene/frames/mouth-open.png +0 -0
- package/templates/animation-studio/dist/episodes/scene/render-live-summary.json +0 -9763
- package/templates/animation-studio/dist/episodes/scene/skeleton-overlays/broken.png +0 -0
- package/templates/animation-studio/dist/episodes/scene/skeleton-overlays/chefs.png +0 -0
- package/templates/animation-studio/dist/episodes/scene/skeleton-overlays/customer.png +0 -0
- package/templates/animation-studio/dist/episodes/scene/skeleton-overlays/friend-1.png +0 -0
- package/templates/animation-studio/dist/episodes/scene/skeleton-overlays/friend-2.png +0 -0
- package/templates/animation-studio/dist/episodes/scene/skeleton-overlays/worker-1.png +0 -0
- package/templates/animation-studio/dist/episodes/scene/skeleton-overlays/worker-2.png +0 -0
- package/templates/animation-studio/dist/generated/second-scene.document.json +0 -1210
- package/templates/animation-studio/dist/generated/studio-edited.document.json +0 -1196
- package/templates/animation-studio/dist/scene/inshader-accept.document.json +0 -1
- package/templates/animation-studio/dist/scene/inshader-test.document.json +0 -1
- package/templates/animation-studio/dist/scene/integ.document.json +0 -1
- package/templates/animation-studio/dist/scene/working.document.json +0 -673
- package/templates/animation-studio/dist/scene/working.history.json +0 -1
- package/templates/animation-studio/studio/dist/assets/index-C7f8tHfi.css +0 -1
- package/templates/animation-studio/studio/dist/assets/index-CLo3GXyN.js +0 -43
- package/templates/animation-studio/studio/dist/index.html +0 -19
- package/templates/animation-studio/tests/reports/animation-studio/hifi-cast-validation.json +0 -938
|
@@ -1,27 +1,257 @@
|
|
|
1
|
-
import {
|
|
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";
|
|
2
12
|
import { assets } from "./aura-assets";
|
|
3
13
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
declare global {
|
|
15
|
+
interface Window {
|
|
16
|
+
__AURA3D_MINI_GAME__?: MiniGameEvidence;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface MiniGameEvidence {
|
|
21
|
+
readonly status: string;
|
|
22
|
+
readonly frame: number;
|
|
23
|
+
readonly score: number;
|
|
24
|
+
readonly deaths: number;
|
|
25
|
+
readonly checkpointId: string;
|
|
26
|
+
readonly collected: readonly string[];
|
|
27
|
+
readonly player: {
|
|
28
|
+
readonly x: number;
|
|
29
|
+
readonly y: number;
|
|
30
|
+
readonly grounded: boolean;
|
|
31
|
+
};
|
|
32
|
+
readonly events: readonly string[];
|
|
33
|
+
readonly evidence: unknown;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const level = {
|
|
37
|
+
start: { x: 0, y: 0.35 },
|
|
38
|
+
finish: { x: 12.4, y: 0.35 },
|
|
39
|
+
platforms: [
|
|
40
|
+
{ id: "launch", x: -0.6, y: 0, width: 4.2, height: 0.35 },
|
|
41
|
+
{ id: "middle", x: 3.8, y: 0.62, width: 3.2, height: 0.3 },
|
|
42
|
+
{ id: "finish", x: 8.2, y: 0.18, width: 4.8, height: 0.35 }
|
|
43
|
+
],
|
|
44
|
+
movingPlatforms: [
|
|
45
|
+
{ id: "lift", x: 6.9, y: 1.05, width: 1.35, height: 0.22, axis: "y" as const, amplitude: 0.42, period: 2.4 }
|
|
46
|
+
],
|
|
47
|
+
collectibles: [
|
|
48
|
+
{ id: "coin-01", x: 1.4, y: 1.15, value: 50, radius: 0.55 },
|
|
49
|
+
{ id: "coin-02", x: 4.8, y: 1.45, value: 75, radius: 0.55 },
|
|
50
|
+
{ id: "coin-03", x: 9.8, y: 1.05, value: 100, radius: 0.55 }
|
|
51
|
+
],
|
|
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 }
|
|
17
57
|
],
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
58
|
+
lowerBound: -2.4,
|
|
59
|
+
moveSpeed: 5.2,
|
|
60
|
+
jumpVelocity: 8,
|
|
61
|
+
dashSpeed: 9.5
|
|
62
|
+
};
|
|
23
63
|
|
|
24
|
-
|
|
64
|
+
const input = game.input({
|
|
65
|
+
actions: {
|
|
66
|
+
left: ["KeyA", "ArrowLeft"],
|
|
67
|
+
right: ["KeyD", "ArrowRight"],
|
|
68
|
+
jump: ["Space", "KeyW", "ArrowUp"],
|
|
69
|
+
dash: ["ShiftLeft", "ShiftRight"],
|
|
70
|
+
down: ["KeyS", "ArrowDown"],
|
|
71
|
+
reset: ["KeyR"]
|
|
72
|
+
},
|
|
73
|
+
axes: {
|
|
74
|
+
moveX: { negative: "left", positive: "right" }
|
|
75
|
+
},
|
|
76
|
+
bufferMs: 140
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
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", {
|
|
25
90
|
diagnostics: { overlay: true, performancePanel: true },
|
|
26
|
-
scene:
|
|
91
|
+
scene: buildScene()
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const player = app.nodes.require("mini-player");
|
|
95
|
+
const lift = app.nodes.require("platform-lift");
|
|
96
|
+
const coins = level.collectibles.map((coin) => [coin.id, app.nodes.require(`coin-${coin.id}`)] as const);
|
|
97
|
+
const checkpoint = app.nodes.require("checkpoint-mid");
|
|
98
|
+
const goal = app.nodes.require("goal");
|
|
99
|
+
const hudRoot = createHud();
|
|
100
|
+
|
|
101
|
+
let objective = "Collect coins, avoid spikes, reach the goal";
|
|
102
|
+
|
|
103
|
+
app.onFrame(({ dt }: { readonly dt: number }) => {
|
|
104
|
+
input.update(dt);
|
|
105
|
+
if (input.pressed("reset")) {
|
|
106
|
+
platformer.reset();
|
|
107
|
+
routeEvents.push({ type: "reset", label: "Route reset" });
|
|
108
|
+
objective = "Collect coins, avoid spikes, reach the goal";
|
|
109
|
+
}
|
|
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
|
|
127
|
+
});
|
|
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
|
+
}
|
|
132
|
+
|
|
133
|
+
player.setPosition(state.player.x, state.player.y + 0.54, 0);
|
|
134
|
+
lift.setPosition(level.movingPlatforms[0].x + level.movingPlatforms[0].width / 2, movingLiftY(state.time), 0);
|
|
135
|
+
for (const [id, node] of coins) node.setVisible(!state.collected.includes(id));
|
|
136
|
+
checkpoint.setVisible(!state.activatedCheckpoints.includes("mid"));
|
|
137
|
+
goal.setScale(state.status === "completed" ? [0.28, 1.08, 0.18] : [0.22, 0.92, 0.16]);
|
|
138
|
+
|
|
139
|
+
renderHud(state);
|
|
140
|
+
publishEvidence(state);
|
|
27
141
|
});
|
|
142
|
+
|
|
143
|
+
publishEvidence(platformer.snapshot());
|
|
144
|
+
renderHud(platformer.snapshot());
|
|
145
|
+
|
|
146
|
+
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 }) })
|
|
158
|
+
.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 }) })
|
|
168
|
+
.position(level.hazards[0].x + level.hazards[0].width / 2, level.hazards[0].y + level.hazards[0].height / 2, 0.05)
|
|
169
|
+
.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"] }))
|
|
178
|
+
];
|
|
179
|
+
|
|
180
|
+
return scene()
|
|
181
|
+
.background("#071015")
|
|
182
|
+
.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 }));
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function movingLiftY(time: number): number {
|
|
189
|
+
const platform = level.movingPlatforms[0];
|
|
190
|
+
return platform.y + platform.height / 2 + Math.sin((time / platform.period) * Math.PI * 2) * platform.amplitude;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function createHud(): HTMLElement {
|
|
194
|
+
const root = document.createElement("aside");
|
|
195
|
+
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(";");
|
|
210
|
+
document.body.append(root);
|
|
211
|
+
return root;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
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("");
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
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
|
+
window.__AURA3D_MINI_GAME__ = {
|
|
243
|
+
status: state.status,
|
|
244
|
+
frame: state.frame,
|
|
245
|
+
score: state.score,
|
|
246
|
+
deaths: state.deaths,
|
|
247
|
+
checkpointId: state.checkpointId,
|
|
248
|
+
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
|
|
256
|
+
};
|
|
257
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { expect, test } from "@playwright/test";
|
|
2
|
+
|
|
3
|
+
test.setTimeout(60_000);
|
|
4
|
+
|
|
5
|
+
type MiniGameState = {
|
|
6
|
+
readonly score: number;
|
|
7
|
+
readonly events: readonly string[];
|
|
8
|
+
readonly player: {
|
|
9
|
+
readonly x: number;
|
|
10
|
+
readonly y: number;
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
function readMiniGameState(): MiniGameState | undefined {
|
|
15
|
+
return (window as unknown as { readonly __AURA3D_MINI_GAME__?: MiniGameState }).__AURA3D_MINI_GAME__;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
test("Aura3D mini game responds to keyboard input, scoring, and reset", async ({ page }) => {
|
|
19
|
+
await page.goto("/");
|
|
20
|
+
await expect.poll(() => page.locator("body").getAttribute("data-aura3d-ready"), { timeout: 45_000 }).toBe("true");
|
|
21
|
+
|
|
22
|
+
const initial = await page.evaluate(readMiniGameState);
|
|
23
|
+
expect(initial?.player.x).toBeLessThan(0.2);
|
|
24
|
+
|
|
25
|
+
await page.keyboard.down("ArrowRight");
|
|
26
|
+
await page.waitForTimeout(650);
|
|
27
|
+
await page.keyboard.up("ArrowRight");
|
|
28
|
+
|
|
29
|
+
const moved = await page.evaluate(readMiniGameState);
|
|
30
|
+
expect(moved?.player.x ?? 0).toBeGreaterThan((initial?.player.x ?? 0) + 1.2);
|
|
31
|
+
|
|
32
|
+
await page.keyboard.press("Space");
|
|
33
|
+
await page.waitForTimeout(180);
|
|
34
|
+
const jumped = await page.evaluate(readMiniGameState);
|
|
35
|
+
expect(jumped?.player.y ?? 0).toBeGreaterThan(moved?.player.y ?? 0);
|
|
36
|
+
|
|
37
|
+
await page.keyboard.down("ArrowRight");
|
|
38
|
+
await page.waitForTimeout(1600);
|
|
39
|
+
await page.keyboard.up("ArrowRight");
|
|
40
|
+
const progressed = await page.evaluate(readMiniGameState);
|
|
41
|
+
expect(progressed?.score ?? 0).toBeGreaterThanOrEqual(50);
|
|
42
|
+
expect(progressed?.events.length ?? 0).toBeGreaterThan(0);
|
|
43
|
+
|
|
44
|
+
await page.keyboard.press("KeyR");
|
|
45
|
+
await page.waitForTimeout(120);
|
|
46
|
+
const reset = await page.evaluate(readMiniGameState);
|
|
47
|
+
expect(reset?.player.x ?? 99).toBeLessThan(0.3);
|
|
48
|
+
expect(reset?.events.some((event) => event.includes("reset"))).toBe(true);
|
|
49
|
+
});
|
|
@@ -4,60 +4,65 @@ import { expect, test } from "@playwright/test";
|
|
|
4
4
|
|
|
5
5
|
test.setTimeout(60_000);
|
|
6
6
|
|
|
7
|
-
test("Aura3D mini game screenshot shows a playable
|
|
7
|
+
test("Aura3D mini game screenshot shows a readable playable scene", async ({ page }) => {
|
|
8
8
|
await page.goto("/");
|
|
9
9
|
await expect.poll(() => page.locator("body").getAttribute("data-aura3d-ready"), { timeout: 45_000 }).toBe("true");
|
|
10
|
+
|
|
11
|
+
await page.keyboard.down("ArrowRight");
|
|
12
|
+
await page.waitForTimeout(550);
|
|
13
|
+
await page.keyboard.up("ArrowRight");
|
|
14
|
+
|
|
15
|
+
const state = await page.evaluate(() =>
|
|
16
|
+
(window as unknown as {
|
|
17
|
+
readonly __AURA3D_MINI_GAME__?: {
|
|
18
|
+
readonly score: number;
|
|
19
|
+
readonly player: { readonly x: number };
|
|
20
|
+
};
|
|
21
|
+
}).__AURA3D_MINI_GAME__
|
|
22
|
+
);
|
|
23
|
+
expect(state?.player.x ?? 0).toBeGreaterThan(0.8);
|
|
24
|
+
|
|
10
25
|
const canvas = page.locator("canvas");
|
|
11
26
|
const profile = await canvas.evaluate((element) => {
|
|
12
27
|
const target = element as HTMLCanvasElement;
|
|
13
28
|
const gl = target.getContext("webgl2", { preserveDrawingBuffer: true });
|
|
14
|
-
if (!gl) return { error: "missing-webgl2",
|
|
29
|
+
if (!gl) return { error: "missing-webgl2", brightPixels: 0, warmPixels: 0, cyanPixels: 0, redPixels: 0, uniqueBuckets: 0 };
|
|
15
30
|
const pixels = new Uint8Array(target.width * target.height * 4);
|
|
16
31
|
gl.readPixels(0, 0, target.width, target.height, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
|
|
17
32
|
const buckets = new Set<string>();
|
|
18
|
-
let
|
|
19
|
-
let
|
|
20
|
-
let
|
|
21
|
-
let
|
|
22
|
-
let hazardPixels = 0;
|
|
23
|
-
let portalPixels = 0;
|
|
24
|
-
let cyanTrailPixels = 0;
|
|
25
|
-
let arenaPixels = 0;
|
|
33
|
+
let brightPixels = 0;
|
|
34
|
+
let warmPixels = 0;
|
|
35
|
+
let cyanPixels = 0;
|
|
36
|
+
let redPixels = 0;
|
|
26
37
|
for (let y = 0; y < target.height; y += 4) {
|
|
27
38
|
for (let x = 0; x < target.width; x += 4) {
|
|
28
|
-
if (x > target.width * 0.76 && y > target.height * 0.74) continue;
|
|
29
39
|
const offset = (y * target.width + x) * 4;
|
|
30
40
|
const r = pixels[offset] ?? 0;
|
|
31
41
|
const g = pixels[offset + 1] ?? 0;
|
|
32
42
|
const b = pixels[offset + 2] ?? 0;
|
|
33
43
|
const luminance = r * 0.2126 + g * 0.7152 + b * 0.0722;
|
|
34
|
-
if (luminance >
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if (
|
|
39
|
-
if (
|
|
40
|
-
if (r >
|
|
41
|
-
if (r > 165 && g > 95 && b < 125) portalPixels += 1;
|
|
42
|
-
if (g > 140 && b > 150 && r < 150) cyanTrailPixels += 1;
|
|
43
|
-
if (g > 42 && b > 50 && b > r * 1.08 && luminance > 24 && luminance < 100) arenaPixels += 1;
|
|
44
|
+
if (luminance > 30) {
|
|
45
|
+
brightPixels += 1;
|
|
46
|
+
buckets.add(`${r >> 5}-${g >> 5}-${b >> 5}`);
|
|
47
|
+
}
|
|
48
|
+
if (r > 130 && g > 105 && b < 155 && r >= g * 0.9) warmPixels += 1;
|
|
49
|
+
if (g > 115 && b > 105 && r < 190 && b >= r * 0.78) cyanPixels += 1;
|
|
50
|
+
if (r > 120 && g < 150 && b < 180 && r > g * 1.03) redPixels += 1;
|
|
44
51
|
}
|
|
45
52
|
}
|
|
46
|
-
return {
|
|
53
|
+
return { brightPixels, warmPixels, cyanPixels, redPixels, uniqueBuckets: buckets.size };
|
|
47
54
|
});
|
|
55
|
+
|
|
48
56
|
const screenshot = await page.screenshot({ fullPage: false });
|
|
49
57
|
mkdirSync(resolve("tests/reports"), { recursive: true });
|
|
50
58
|
writeFileSync(resolve("tests/reports/screenshot.png"), screenshot);
|
|
51
|
-
writeFileSync(resolve("tests/reports/screenshot.json"), `${JSON.stringify({ bytes: screenshot.byteLength, profile }, null, 2)}\n`);
|
|
59
|
+
writeFileSync(resolve("tests/reports/screenshot.json"), `${JSON.stringify({ bytes: screenshot.byteLength, profile, state }, null, 2)}\n`);
|
|
60
|
+
|
|
52
61
|
expect(profile.error).toBeUndefined();
|
|
53
|
-
expect(profile.
|
|
54
|
-
expect(profile.
|
|
55
|
-
expect(profile.
|
|
56
|
-
expect(profile.
|
|
57
|
-
expect(profile.
|
|
58
|
-
expect(profile.portalPixels).toBeGreaterThan(45);
|
|
59
|
-
expect(profile.cyanTrailPixels).toBeGreaterThan(90);
|
|
60
|
-
expect(profile.arenaPixels).toBeGreaterThan(600);
|
|
61
|
-
expect(profile.uniqueBuckets).toBeGreaterThan(22);
|
|
62
|
+
expect(profile.brightPixels).toBeGreaterThan(900);
|
|
63
|
+
expect(profile.cyanPixels).toBeGreaterThan(20);
|
|
64
|
+
expect(profile.warmPixels).toBeGreaterThan(10);
|
|
65
|
+
expect(profile.redPixels).toBeGreaterThan(5);
|
|
66
|
+
expect(profile.uniqueBuckets).toBeGreaterThan(10);
|
|
62
67
|
expect(screenshot.byteLength).toBeGreaterThan(1000);
|
|
63
68
|
});
|
|
@@ -31,8 +31,8 @@ test("Aura3D product viewer screenshot shows a prompt-aligned studio product", a
|
|
|
31
31
|
if (luminance > 32) buckets.add(`${r >> 5}-${g >> 5}-${b >> 5}`);
|
|
32
32
|
const inProductWindow = x > target.width * 0.34 && x < target.width * 0.68 && y > target.height * 0.24 && y < target.height * 0.78;
|
|
33
33
|
if (inProductWindow && r > 145 && g > 145 && b > 138 && Math.abs(r - g) < 32 && Math.abs(g - b) < 40) metalPixels += 1;
|
|
34
|
-
if (r >
|
|
35
|
-
if (Math.max(r, g, b) - Math.min(r, g, b) >
|
|
34
|
+
if (r > 70 && g > 45 && b < 120 && r >= g && g >= b * 0.75) warmAccentPixels += 1;
|
|
35
|
+
if (Math.max(r, g, b) - Math.min(r, g, b) > 32 && luminance > 38) saturatedStudioPixels += 1;
|
|
36
36
|
if (inProductWindow && luminance > 32) centerObjectPixels += 1;
|
|
37
37
|
}
|
|
38
38
|
}
|
|
@@ -44,11 +44,9 @@ test("Aura3D product viewer screenshot shows a prompt-aligned studio product", a
|
|
|
44
44
|
writeFileSync(resolve("tests/reports/screenshot.json"), `${JSON.stringify({ bytes: screenshot.byteLength, profile }, null, 2)}\n`);
|
|
45
45
|
expect(profile.error).toBeUndefined();
|
|
46
46
|
expect(profile.metalPixels).toBeGreaterThan(5);
|
|
47
|
-
// Measured 12 on 2026-06-10 after the engine's neutral-gray default-material
|
|
48
|
-
// fallback (was pure white) dimmed the warm accent contribution.
|
|
49
47
|
expect(profile.warmAccentPixels).toBeGreaterThan(8);
|
|
50
48
|
expect(profile.centerObjectPixels).toBeGreaterThan(650);
|
|
51
|
-
expect(profile.saturatedStudioPixels).toBeGreaterThan(
|
|
49
|
+
expect(profile.saturatedStudioPixels).toBeGreaterThan(800);
|
|
52
50
|
expect(profile.uniqueBuckets).toBeGreaterThan(18);
|
|
53
51
|
expect(screenshot.byteLength).toBeGreaterThan(1000);
|
|
54
52
|
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Aura3D Racing Starter
|
|
2
|
+
|
|
3
|
+
Keyboard-playable racing starter using only the public `@aura3d/engine` API.
|
|
4
|
+
|
|
5
|
+
Status: source-level prototype starter, not a public-quality racing game claim.
|
|
6
|
+
It proves input, route progress, checkpoints, lap/reset state, and deployable
|
|
7
|
+
source structure. It does not prove that an arbitrary track GLB has certified
|
|
8
|
+
road topology, car-to-road binding, camera-safe race composition, or public
|
|
9
|
+
visual quality.
|
|
10
|
+
|
|
11
|
+
- Typed vehicle and track assets are defined in `src/aura-assets.ts`.
|
|
12
|
+
- `game.racing(...)` owns route progress, throttle, steering, drift, checkpoint,
|
|
13
|
+
lap, reset, and camera proof state.
|
|
14
|
+
- `tests/playable.spec.ts` drives keyboard input and verifies checkpoint/lap
|
|
15
|
+
progression.
|
|
16
|
+
|
|
17
|
+
Run:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install
|
|
21
|
+
npm run dev
|
|
22
|
+
npm test
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Before presenting a generated racing route as a public example, add retained
|
|
26
|
+
racing topology evidence, prove the car is visibly bound to the road surface,
|
|
27
|
+
capture a readable public screenshot, and pass visual review.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Aura3D Racing Starter</title>
|
|
7
|
+
<style>
|
|
8
|
+
html,
|
|
9
|
+
body,
|
|
10
|
+
#app {
|
|
11
|
+
width: 100%;
|
|
12
|
+
height: 100%;
|
|
13
|
+
margin: 0;
|
|
14
|
+
overflow: hidden;
|
|
15
|
+
background: #05090c;
|
|
16
|
+
}
|
|
17
|
+
</style>
|
|
18
|
+
</head>
|
|
19
|
+
<body>
|
|
20
|
+
<div id="app"></div>
|
|
21
|
+
<script type="module" src="./src/main.ts"></script>
|
|
22
|
+
</body>
|
|
23
|
+
</html>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "aura3d-racing-starter",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite --host 127.0.0.1",
|
|
8
|
+
"typecheck": "tsc --noEmit",
|
|
9
|
+
"build": "npm run typecheck && vite build",
|
|
10
|
+
"preview": "vite preview --host 127.0.0.1",
|
|
11
|
+
"test": "playwright test tests/playable.spec.ts --workers=1"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@aura3d/engine": "1.0.0"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@playwright/test": "^1.52.0",
|
|
18
|
+
"typescript": "^5.8.3",
|
|
19
|
+
"vite": "^7.3.2"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { defineConfig } from "@playwright/test";
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
webServer: {
|
|
5
|
+
command: "npm run dev -- --port 5178",
|
|
6
|
+
url: "http://127.0.0.1:5178",
|
|
7
|
+
reuseExistingServer: !process.env.CI
|
|
8
|
+
},
|
|
9
|
+
use: {
|
|
10
|
+
baseURL: "http://127.0.0.1:5178"
|
|
11
|
+
}
|
|
12
|
+
});
|
|
Binary file
|
|
@@ -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">showcaseCleanSportsCar 3.455x3.428x2.206</text></svg>
|
|
Binary file
|
|
@@ -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">showcaseReadableKartCircuit 24.651x24.647x2.073</text></svg>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { defineAuraAssets } from "@aura3d/engine";
|
|
2
|
+
|
|
3
|
+
export const assets = defineAuraAssets({
|
|
4
|
+
carModel: {
|
|
5
|
+
type: "model",
|
|
6
|
+
format: "glb",
|
|
7
|
+
url: "/aura-assets/showcaseCleanSportsCar.1c69283f.glb",
|
|
8
|
+
bounds: [3.455, 3.428, 2.206],
|
|
9
|
+
hash: "sha256-1c69283fa2b3fb96b3dc3418f2d0dede842e6984f1776a8ca00b099836fc9c14",
|
|
10
|
+
metadata: {
|
|
11
|
+
materials: ["Material.001", "Material.002", "Material.003", "Material.004"],
|
|
12
|
+
animations: [],
|
|
13
|
+
textures: [],
|
|
14
|
+
license: "CC-BY-4.0",
|
|
15
|
+
author: "JUFF",
|
|
16
|
+
sourcePage: "https://huggingface.co/datasets/allenai/objaverse/blob/main/glbs/000-008/23dfdeb55dc24970b36065afaab7a8a5.glb",
|
|
17
|
+
thumbnailUrl: "/aura-assets/showcaseCleanSportsCar.thumb.svg"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
trackModel: {
|
|
21
|
+
type: "model",
|
|
22
|
+
format: "glb",
|
|
23
|
+
url: "/aura-assets/showcaseReadableKartCircuit.5cbb912e.glb",
|
|
24
|
+
bounds: [24.651, 24.647, 2.073],
|
|
25
|
+
hash: "sha256-5cbb912e511e5e82363c347210096d59dd3007db12c363fd0c9f07919f5ebbd0",
|
|
26
|
+
metadata: {
|
|
27
|
+
materials: ["LINEAS", "gris", "verde", "rojo", "azul", "amarillo"],
|
|
28
|
+
animations: [],
|
|
29
|
+
textures: [],
|
|
30
|
+
license: "CC-BY-4.0",
|
|
31
|
+
author: "Puendiz",
|
|
32
|
+
sourcePage: "https://huggingface.co/datasets/allenai/objaverse/blob/main/glbs/000-034/2fc89fcd8c034bc9ba05eac24c9bf537.glb",
|
|
33
|
+
thumbnailUrl: "/aura-assets/showcaseReadableKartCircuit.thumb.svg"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
} as const);
|