create-aura3d 2.0.3 → 3.0.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/README.md +1 -1
- package/dist/index.js +1 -1
- package/dist/showcase-spec-game-geometry-extractor.d.ts.map +1 -1
- package/dist/showcase-spec-game-geometry-extractor.js +86 -4
- package/dist/showcase-spec-game-geometry-extractor.js.map +1 -1
- package/package.json +2 -2
- package/templates/animation-channel/package.json +2 -1
- package/templates/animation-studio/package.json +3 -2
- package/templates/animation-studio/src/scene-player.ts +28 -1
- package/templates/character-controller/README.md +1 -1
- package/templates/character-controller/aura.assets.json +31 -1
- package/templates/character-controller/package.json +5 -4
- package/templates/character-controller/public/aura-assets/showcaseWalkAnimatedGirl.93872fc2.glb +0 -0
- package/templates/character-controller/public/aura-assets/showcaseWalkAnimatedGirl.thumb.svg +1 -0
- package/templates/character-controller/src/aura-assets.ts +15 -1
- package/templates/character-controller/src/main.ts +32 -8
- package/templates/character-controller/tests/certified-rig.spec.ts +52 -0
- package/templates/cinematic-scene/package.json +2 -1
- package/templates/episode-builder/package.json +2 -1
- package/templates/falling-blocks-starter/package.json +2 -1
- package/templates/fighting-game/README.md +8 -7
- package/templates/fighting-game/aura.assets.json +62 -1
- package/templates/fighting-game/package.json +3 -2
- package/templates/fighting-game/public/aura-assets/showcaseRunnerRobot.252b3a16.glb +0 -0
- package/templates/fighting-game/public/aura-assets/showcaseRunnerRobot.thumb.svg +1 -0
- package/templates/fighting-game/public/aura-assets/showcaseWalkAnimatedGirl.93872fc2.glb +0 -0
- package/templates/fighting-game/public/aura-assets/showcaseWalkAnimatedGirl.thumb.svg +1 -0
- package/templates/fighting-game/src/aura-assets.ts +36 -5
- package/templates/fighting-game/src/game/fighters.ts +23 -8
- package/templates/fighting-game/src/main.ts +21 -4
- package/templates/fighting-game/tests/certified-rig.spec.ts +53 -0
- package/templates/fighting-game/tests/gameplay-smoke.spec.ts +6 -1
- package/templates/mini-game/aura.assets.json +38 -14
- package/templates/mini-game/package.json +2 -2
- package/templates/mini-game/public/aura-assets/showcaseKenneyOobiPlatformerHero.3f821141.glb +0 -0
- package/templates/mini-game/public/aura-assets/showcaseKenneyOobiPlatformerHero.thumb.svg +1 -0
- package/templates/mini-game/src/aura-assets.ts +33 -7
- package/templates/mini-game/src/main.ts +40 -5
- package/templates/mini-game/tests/certified-rig.spec.ts +40 -0
- package/templates/product-viewer/package.json +1 -1
- package/templates/product-viewer/src/main.ts +9 -2
- package/templates/prompt-animation-channel/package.json +2 -1
- package/templates/racing-starter/package.json +2 -1
- package/templates/racing-starter/src/main.ts +31 -0
- package/templates/three-compat-architecture-interior/package.json +2 -1
- package/templates/three-compat-asset-inspector/package.json +2 -1
- package/templates/three-compat-character-viewer/package.json +2 -1
- package/templates/three-compat-custom-threejs-migration/package.json +2 -1
- package/templates/three-compat-large-scene/package.json +2 -1
- package/templates/three-compat-material-authoring/package.json +2 -1
- package/templates/three-compat-postprocess-scene/package.json +2 -1
- package/templates/three-compat-premium-product-viewer/package.json +2 -1
- package/templates/mini-game/public/aura-assets/player-fixture.glb +0 -0
- package/templates/mini-game/public/aura-assets/player.thumb.svg +0 -8
|
@@ -4,10 +4,13 @@ import {
|
|
|
4
4
|
animationLayer,
|
|
5
5
|
createFighterAnimationController,
|
|
6
6
|
createFighterNode,
|
|
7
|
+
FIGHTER_CERTIFIED_CLIP,
|
|
7
8
|
isLoopingFighterClip,
|
|
9
|
+
PLAYER_FIGHTER_ASSET,
|
|
8
10
|
publicAssetInstructions,
|
|
9
11
|
REQUIRED_FIGHTER_ASSETS,
|
|
10
12
|
resolveTypedFighterAssets,
|
|
13
|
+
RIVAL_FIGHTER_ASSET,
|
|
11
14
|
type FighterClip
|
|
12
15
|
} from "./game/fighters";
|
|
13
16
|
import { heavyMove, lightMove, specialMove } from "./game/moves";
|
|
@@ -129,8 +132,10 @@ const inputOptions = {
|
|
|
129
132
|
const arena = scene()
|
|
130
133
|
.background("#10071c")
|
|
131
134
|
.addMany(fightingStage.nodes)
|
|
132
|
-
|
|
133
|
-
|
|
135
|
+
// The creature rival renders roughly twice the player height at scale 1, so
|
|
136
|
+
// it mounts slightly under scale to share the frame with the humanoid player.
|
|
137
|
+
.add(createFighterNode("player", PLAYER_FIGHTER_ASSET, "Player fighter", playerStart, 1, "#45f5bb", typedFighterAssets, 1))
|
|
138
|
+
.add(createFighterNode("rival", RIVAL_FIGHTER_ASSET, "Rival fighter", rivalStart, -1, "#ffca5f", typedFighterAssets, 0.75))
|
|
134
139
|
.addMany([
|
|
135
140
|
effects.bloom({ intensity: 0.32 }),
|
|
136
141
|
lights.studio({ intensity: 1.15 }),
|
|
@@ -183,8 +188,8 @@ const director = game.cameraDirector({
|
|
|
183
188
|
});
|
|
184
189
|
const playerNode = app.nodes.require("player");
|
|
185
190
|
const rivalNode = app.nodes.require("rival");
|
|
186
|
-
const playerAnimation = createFighterAnimationController("player", typedFighterAssets
|
|
187
|
-
const rivalAnimation = createFighterAnimationController("rival", typedFighterAssets
|
|
191
|
+
const playerAnimation = createFighterAnimationController("player", typedFighterAssets[PLAYER_FIGHTER_ASSET]);
|
|
192
|
+
const rivalAnimation = createFighterAnimationController("rival", typedFighterAssets[RIVAL_FIGHTER_ASSET]);
|
|
188
193
|
|
|
189
194
|
playerAnimation.bindRuntimeNode(playerNode, { defaultClipId: "idle", fallbackClipId: "idle" });
|
|
190
195
|
rivalAnimation.bindRuntimeNode(rivalNode, { defaultClipId: "idle", fallbackClipId: "idle" });
|
|
@@ -207,6 +212,18 @@ gameWindow.__AURA3D_GAME_SOURCE__ = {
|
|
|
207
212
|
typedAssetPattern: "src/aura-assets.ts",
|
|
208
213
|
typedAssetKeys: REQUIRED_FIGHTER_ASSETS,
|
|
209
214
|
missingAssets: missingFighterAssets,
|
|
215
|
+
heroAssets: {
|
|
216
|
+
player: {
|
|
217
|
+
assetId: assets[PLAYER_FIGHTER_ASSET]?.id ?? PLAYER_FIGHTER_ASSET,
|
|
218
|
+
url: assets[PLAYER_FIGHTER_ASSET]?.url,
|
|
219
|
+
certifiedClip: FIGHTER_CERTIFIED_CLIP.player
|
|
220
|
+
},
|
|
221
|
+
rival: {
|
|
222
|
+
assetId: assets[RIVAL_FIGHTER_ASSET]?.id ?? RIVAL_FIGHTER_ASSET,
|
|
223
|
+
url: assets[RIVAL_FIGHTER_ASSET]?.url,
|
|
224
|
+
certifiedClip: FIGHTER_CERTIFIED_CLIP.rival
|
|
225
|
+
}
|
|
226
|
+
},
|
|
210
227
|
addAssets: publicAssetInstructions,
|
|
211
228
|
touchControls: touchLayout,
|
|
212
229
|
readiness: routeReadiness
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { expect, test } from "@playwright/test";
|
|
5
|
+
|
|
6
|
+
test.setTimeout(90_000);
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* E1 certified-rig mount proof for the fighting-game template.
|
|
10
|
+
*
|
|
11
|
+
* Both fighters must be E1-certified hero rigs
|
|
12
|
+
* (docs/rendering/skinning-and-morphs.md "Certified hero rigs"):
|
|
13
|
+
* - player: showcaseWalkAnimatedGirl (humanoid-a) / "Take 001"
|
|
14
|
+
* - rival: showcaseRunnerRobot (creature) / "WALK"
|
|
15
|
+
* The mounted asset ids AND urls assert against that roster; a placeholder or
|
|
16
|
+
* fixture swap regresses this spec instead of silently shipping uncertified
|
|
17
|
+
* heroes.
|
|
18
|
+
*/
|
|
19
|
+
/*
|
|
20
|
+
* No raw GLB URL literal lives in this file (public-example-boundary): expected
|
|
21
|
+
* urls are read from the template's own generated typed assets. A placeholder
|
|
22
|
+
* or fixture swap (roster id gone from aura-assets.ts) throws here instead of
|
|
23
|
+
* silently shipping uncertified heroes.
|
|
24
|
+
*/
|
|
25
|
+
const CERTIFIED_ROSTER = {
|
|
26
|
+
player: { assetId: "showcaseWalkAnimatedGirl", certifiedClip: "Take 001" },
|
|
27
|
+
rival: { assetId: "showcaseRunnerRobot", certifiedClip: "WALK" }
|
|
28
|
+
} as const;
|
|
29
|
+
function heroUrlFromTypedAssets(assetId: string): string {
|
|
30
|
+
const source = readFileSync(join(dirname(fileURLToPath(import.meta.url)), "..", "src", "aura-assets.ts"), "utf8");
|
|
31
|
+
const pattern = new RegExp(`${assetId}:([\\s\\S]*?)\\n \\},?\\n`);
|
|
32
|
+
const block = pattern.exec(source)?.[1] ?? "";
|
|
33
|
+
const url = /url:\s*"([^"]+)"/.exec(block)?.[1];
|
|
34
|
+
if (!url) throw new Error(`certified-roster hero ${assetId} missing from the template typed assets`);
|
|
35
|
+
return url;
|
|
36
|
+
}
|
|
37
|
+
const CERTIFIED_HEROES = {
|
|
38
|
+
player: { ...CERTIFIED_ROSTER.player, url: heroUrlFromTypedAssets(CERTIFIED_ROSTER.player.assetId) },
|
|
39
|
+
rival: { ...CERTIFIED_ROSTER.rival, url: heroUrlFromTypedAssets(CERTIFIED_ROSTER.rival.assetId) }
|
|
40
|
+
} as const;
|
|
41
|
+
|
|
42
|
+
test("fighting-game fighters mount certified hero rigs", async ({ page }) => {
|
|
43
|
+
await page.goto("/");
|
|
44
|
+
await page.waitForFunction(() => Boolean((window as any).__AURA3D_GAME_SOURCE__?.readiness), undefined, {
|
|
45
|
+
polling: 100,
|
|
46
|
+
timeout: 60_000
|
|
47
|
+
});
|
|
48
|
+
const source = await page.evaluate(() => (window as any).__AURA3D_GAME_SOURCE__);
|
|
49
|
+
expect(source?.heroAssets?.player).toMatchObject(CERTIFIED_HEROES.player);
|
|
50
|
+
expect(source?.heroAssets?.rival).toMatchObject(CERTIFIED_HEROES.rival);
|
|
51
|
+
expect(source?.missingAssets ?? []).toEqual([]);
|
|
52
|
+
expect(source?.readiness?.proofMode).toBe("typed-assets");
|
|
53
|
+
});
|
|
@@ -33,6 +33,11 @@ test("input replay produces runtime evidence and a hit declaration", async ({ pa
|
|
|
33
33
|
expect(evidence.systems.cameraPlan).toBeTruthy();
|
|
34
34
|
expect(evidence.systems.stagePlan).toBeTruthy();
|
|
35
35
|
expect(replay.hitCount).toBeGreaterThan(0);
|
|
36
|
-
|
|
36
|
+
// Certified heroes ship as typed fighter assets (humanoid-a + creature), so
|
|
37
|
+
// the route runs in typed-assets proof mode instead of source placeholders.
|
|
38
|
+
expect(source.readiness.sourceOnly).toBe(false);
|
|
39
|
+
expect(source.readiness.placeholderMode).toBe(false);
|
|
40
|
+
expect(source.readiness.proofMode).toBe("typed-assets");
|
|
41
|
+
expect(source.readiness.missingTypedAssets ?? []).toEqual([]);
|
|
37
42
|
expect(source.readiness.publicEngineApis).toContain("games.fighting.stagePreset");
|
|
38
43
|
});
|
|
@@ -5,27 +5,51 @@
|
|
|
5
5
|
"typegen": "src/aura-assets.ts",
|
|
6
6
|
"assets": [
|
|
7
7
|
{
|
|
8
|
-
"id": "
|
|
8
|
+
"id": "showcaseKenneyOobiPlatformerHero",
|
|
9
9
|
"type": "model",
|
|
10
10
|
"format": "glb",
|
|
11
|
-
"source": "public/aura-assets/
|
|
12
|
-
"outputPath": "public/aura-assets/
|
|
13
|
-
"url": "/aura-assets/
|
|
14
|
-
"hash": "sha256-
|
|
15
|
-
"sizeBytes":
|
|
11
|
+
"source": "public/aura-assets/showcaseKenneyOobiPlatformerHero.3f821141.glb",
|
|
12
|
+
"outputPath": "public/aura-assets/showcaseKenneyOobiPlatformerHero.3f821141.glb",
|
|
13
|
+
"url": "/aura-assets/showcaseKenneyOobiPlatformerHero.3f821141.glb",
|
|
14
|
+
"hash": "sha256-3f82114135cdf4b627d463901308eb0dcf4bbbb10f1958f044eaa42160ad5df5",
|
|
15
|
+
"sizeBytes": 208028,
|
|
16
16
|
"bounds": [
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
0.
|
|
17
|
+
0.868,
|
|
18
|
+
0.907,
|
|
19
|
+
0.603
|
|
20
20
|
],
|
|
21
21
|
"materials": [
|
|
22
|
-
"
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
"colormap"
|
|
23
|
+
],
|
|
24
|
+
"animations": [
|
|
25
|
+
"attack-kick-left",
|
|
26
|
+
"attack-kick-right",
|
|
27
|
+
"attack-melee-left",
|
|
28
|
+
"attack-melee-right",
|
|
29
|
+
"crouch",
|
|
30
|
+
"die",
|
|
31
|
+
"drive",
|
|
32
|
+
"emote-no",
|
|
33
|
+
"emote-yes",
|
|
34
|
+
"fall",
|
|
35
|
+
"holding-both",
|
|
36
|
+
"holding-both-shoot",
|
|
37
|
+
"holding-left",
|
|
38
|
+
"holding-left-shoot",
|
|
39
|
+
"holding-right",
|
|
40
|
+
"holding-right-shoot",
|
|
41
|
+
"idle",
|
|
42
|
+
"interact-left",
|
|
43
|
+
"interact-right",
|
|
44
|
+
"jump",
|
|
45
|
+
"pick-up",
|
|
46
|
+
"sit",
|
|
47
|
+
"sprint",
|
|
48
|
+
"static",
|
|
49
|
+
"walk"
|
|
25
50
|
],
|
|
26
|
-
"animations": [],
|
|
27
51
|
"textures": [],
|
|
28
|
-
"thumbnailUrl": "/aura-assets/
|
|
52
|
+
"thumbnailUrl": "/aura-assets/showcaseKenneyOobiPlatformerHero.thumb.svg"
|
|
29
53
|
}
|
|
30
54
|
]
|
|
31
55
|
}
|
|
@@ -8,10 +8,10 @@
|
|
|
8
8
|
"typecheck": "tsc --noEmit",
|
|
9
9
|
"build": "npm run typecheck && vite build",
|
|
10
10
|
"preview": "vite preview --host 127.0.0.1",
|
|
11
|
-
"test": "playwright test tests/route-health.spec.ts tests/playable.spec.ts tests/screenshot.spec.ts --workers=1"
|
|
11
|
+
"test": "playwright test tests/route-health.spec.ts tests/playable.spec.ts tests/screenshot.spec.ts tests/certified-rig.spec.ts --workers=1"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@aura3d/lean": "
|
|
14
|
+
"@aura3d/lean": "3.0.0"
|
|
15
15
|
},
|
|
16
16
|
"devDependencies": {
|
|
17
17
|
"@playwright/test": "^1.52.0",
|
|
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">showcaseKenneyOobiPlatformerHero 0.868x0.907x0.603</text></svg>
|
|
@@ -1,17 +1,43 @@
|
|
|
1
1
|
import { defineAuraAssets } from "@aura3d/lean/game";
|
|
2
2
|
|
|
3
3
|
export const assets = defineAuraAssets({
|
|
4
|
-
|
|
4
|
+
showcaseKenneyOobiPlatformerHero: {
|
|
5
5
|
type: "model",
|
|
6
6
|
format: "glb",
|
|
7
|
-
url: "/aura-assets/
|
|
8
|
-
bounds: [
|
|
9
|
-
hash: "sha256-
|
|
7
|
+
url: "/aura-assets/showcaseKenneyOobiPlatformerHero.3f821141.glb",
|
|
8
|
+
bounds: [0.868, 0.907, 0.603],
|
|
9
|
+
hash: "sha256-3f82114135cdf4b627d463901308eb0dcf4bbbb10f1958f044eaa42160ad5df5",
|
|
10
10
|
metadata: {
|
|
11
|
-
materials: ["
|
|
12
|
-
animations: [
|
|
11
|
+
materials: ["colormap"],
|
|
12
|
+
animations: [
|
|
13
|
+
"attack-kick-left",
|
|
14
|
+
"attack-kick-right",
|
|
15
|
+
"attack-melee-left",
|
|
16
|
+
"attack-melee-right",
|
|
17
|
+
"crouch",
|
|
18
|
+
"die",
|
|
19
|
+
"drive",
|
|
20
|
+
"emote-no",
|
|
21
|
+
"emote-yes",
|
|
22
|
+
"fall",
|
|
23
|
+
"holding-both",
|
|
24
|
+
"holding-both-shoot",
|
|
25
|
+
"holding-left",
|
|
26
|
+
"holding-left-shoot",
|
|
27
|
+
"holding-right",
|
|
28
|
+
"holding-right-shoot",
|
|
29
|
+
"idle",
|
|
30
|
+
"interact-left",
|
|
31
|
+
"interact-right",
|
|
32
|
+
"jump",
|
|
33
|
+
"pick-up",
|
|
34
|
+
"sit",
|
|
35
|
+
"sprint",
|
|
36
|
+
"static",
|
|
37
|
+
"walk"
|
|
38
|
+
],
|
|
13
39
|
textures: [],
|
|
14
|
-
thumbnailUrl: "/aura-assets/
|
|
40
|
+
thumbnailUrl: "/aura-assets/showcaseKenneyOobiPlatformerHero.thumb.svg"
|
|
15
41
|
}
|
|
16
42
|
}
|
|
17
43
|
} as const);
|
|
@@ -22,8 +22,13 @@ interface MiniGameEvidence {
|
|
|
22
22
|
readonly deaths: number;
|
|
23
23
|
readonly checkpointId: string;
|
|
24
24
|
readonly collected: readonly string[];
|
|
25
|
+
readonly hero: { readonly assetId: string; readonly url: string };
|
|
25
26
|
readonly player: { readonly x: number; readonly y: number; readonly grounded: boolean };
|
|
26
27
|
readonly events: readonly string[];
|
|
28
|
+
readonly cameraRig: { readonly kind: string; readonly position: readonly [number, number, number] };
|
|
29
|
+
readonly gameFeel: { readonly trauma: number; readonly frozen: boolean };
|
|
30
|
+
readonly debugDraw: boolean;
|
|
31
|
+
readonly governor: { readonly resolutionScale: number; readonly particleScale: number };
|
|
27
32
|
readonly evidence: { readonly entry: string; readonly physics: string; readonly typedAssets: number };
|
|
28
33
|
}
|
|
29
34
|
|
|
@@ -58,12 +63,20 @@ const input = game.input({
|
|
|
58
63
|
jump: ["Space", "KeyW", "ArrowUp"],
|
|
59
64
|
dash: ["ShiftLeft", "ShiftRight"],
|
|
60
65
|
down: ["KeyS", "ArrowDown"],
|
|
61
|
-
reset: ["KeyR"]
|
|
66
|
+
reset: ["KeyR"],
|
|
67
|
+
debug: ["KeyT"]
|
|
62
68
|
},
|
|
63
69
|
axes: { moveX: { negative: "left", positive: "right" } },
|
|
64
70
|
bufferMs: 140
|
|
65
71
|
});
|
|
66
72
|
const platformer = game.platformer(level);
|
|
73
|
+
// J3 scaffold surface: camera rig frames the opening shot, game feel adds
|
|
74
|
+
// trauma/shake on impacts, the debug toggle gates overlays, and the lean perf
|
|
75
|
+
// governor degrades resolution/particle scale when frames run long.
|
|
76
|
+
const cameraRig = game.cameraRig({ kind: "side-view-follow", offset: [0, 2.98, 10.4] });
|
|
77
|
+
const gameFeel = game.gameFeel();
|
|
78
|
+
const debugDraw = game.debugDraw();
|
|
79
|
+
const governor = game.performanceGovernor("conservative");
|
|
67
80
|
const routeEvents: string[] = [];
|
|
68
81
|
const app = createAuraApp("#app", { scene: buildScene() });
|
|
69
82
|
const player = app.nodes.require("mini-player");
|
|
@@ -76,6 +89,9 @@ let objective = "Collect coins, avoid spikes, reach the goal";
|
|
|
76
89
|
|
|
77
90
|
app.onFrame((dt) => {
|
|
78
91
|
input.update(dt);
|
|
92
|
+
governor.step(Math.max(0, dt) * 1000);
|
|
93
|
+
gameFeel.update(Math.max(0, dt));
|
|
94
|
+
if (input.pressed("debug")) debugDraw.toggle();
|
|
79
95
|
if (input.pressed("reset")) {
|
|
80
96
|
platformer.reset();
|
|
81
97
|
routeEvents.push("reset:Route reset");
|
|
@@ -96,12 +112,22 @@ app.onFrame((dt) => {
|
|
|
96
112
|
dashPressed: firstSubstep && dashPressed,
|
|
97
113
|
fastFall: input.held("down")
|
|
98
114
|
});
|
|
99
|
-
for (const event of state.events)
|
|
115
|
+
for (const event of state.events) {
|
|
116
|
+
updateObjective(event);
|
|
117
|
+
if (event.type === "collect") gameFeel.addTrauma(0.25);
|
|
118
|
+
if (event.type === "hazard" || event.type === "fall") {
|
|
119
|
+
gameFeel.addTrauma(0.7);
|
|
120
|
+
gameFeel.hitStop();
|
|
121
|
+
}
|
|
122
|
+
if (event.type === "land") gameFeel.addTrauma(0.12);
|
|
123
|
+
}
|
|
100
124
|
remaining -= substep;
|
|
101
125
|
firstSubstep = false;
|
|
102
126
|
} while (remaining > 0.000_001);
|
|
103
127
|
|
|
104
|
-
|
|
128
|
+
// The certified Oobi hero is feet-origin (GLB min y = 0), so the visual
|
|
129
|
+
// rides at the physics point instead of the old fixture's +0.5 lift.
|
|
130
|
+
player.setPosition(state.player.x + 0.38, state.player.y + 0.01, 0);
|
|
105
131
|
lift.setPosition(level.movingPlatforms[0].x + level.movingPlatforms[0].width / 2, movingLiftY(state.time), 0);
|
|
106
132
|
for (const [id, node] of coins) node.setVisible(!state.collected.includes(id));
|
|
107
133
|
checkpoint.setVisible(!state.activatedCheckpoints.includes("mid"));
|
|
@@ -139,10 +165,14 @@ function buildScene() {
|
|
|
139
165
|
primitives.box({ name: "finish portal", material: material.pbr({ color: "#ffad57", metallic: 0.2, roughness: 0.18 }) })
|
|
140
166
|
.position(level.finish.x, level.finish.y + 0.7, 0.08).scale([0.22, 0.92, 0.16]).runtime("goal")
|
|
141
167
|
];
|
|
168
|
+
// Opening shot is framed by the side-view camera rig. The offset reproduces
|
|
169
|
+
// the certified framing exactly (position [6.1, 3.8, 10.4], target
|
|
170
|
+
// [6.1, 0.82, 0]) so screenshot baselines do not shift.
|
|
171
|
+
const opening = cameraRig.snap([6.1, 0.82, 0]);
|
|
142
172
|
return scene().background("#071015")
|
|
143
|
-
.add(model(assets.
|
|
173
|
+
.add(model(assets.showcaseKenneyOobiPlatformerHero, { name: "certified hero vehicle-driver" }).position(level.start.x + 0.38, level.start.y + 0.01, 0).scale(1).runtime("mini-player"))
|
|
144
174
|
.addMany(nodes)
|
|
145
|
-
.camera(camera.perspective({ position: [
|
|
175
|
+
.camera(camera.perspective({ position: [opening[0], opening[1], opening[2]], target: [6.1, 0.82, 0], fov: 46 }));
|
|
146
176
|
}
|
|
147
177
|
|
|
148
178
|
function updateObjective(event: LeanPlatformerEvent): void {
|
|
@@ -178,8 +208,13 @@ function publishEvidence(state: ReturnType<typeof platformer.snapshot>): void {
|
|
|
178
208
|
deaths: state.deaths,
|
|
179
209
|
checkpointId: state.checkpointId,
|
|
180
210
|
collected: state.collected,
|
|
211
|
+
hero: { assetId: assets.showcaseKenneyOobiPlatformerHero.id, url: assets.showcaseKenneyOobiPlatformerHero.url },
|
|
181
212
|
player: { x: state.player.x, y: state.player.y, grounded: state.player.grounded },
|
|
182
213
|
events: [...routeEvents],
|
|
214
|
+
cameraRig: { kind: cameraRig.kind, position: cameraRig.follow([state.player.x, state.player.y, 0], 1 / 60) },
|
|
215
|
+
gameFeel: gameFeel.snapshot(),
|
|
216
|
+
debugDraw: debugDraw.enabled,
|
|
217
|
+
governor: { ...governor.settings },
|
|
183
218
|
evidence: { entry: "@aura3d/lean/game", physics: "solver-free deterministic arcade", typedAssets: Object.keys(assets).length }
|
|
184
219
|
};
|
|
185
220
|
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { expect, test } from "@playwright/test";
|
|
5
|
+
|
|
6
|
+
test.setTimeout(60_000);
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* E1 certified-rig mount proof for the mini-game template.
|
|
10
|
+
*
|
|
11
|
+
* The template hero must be one of the E1-certified hero rigs
|
|
12
|
+
* (docs/rendering/skinning-and-morphs.md "Certified hero rigs") — currently
|
|
13
|
+
* the vehicle-driver slot: showcaseKenneyOobiPlatformerHero / "walk".
|
|
14
|
+
* The mounted asset id AND url assert against that roster; a fixture swap
|
|
15
|
+
* regresses this spec instead of silently shipping an uncertified hero.
|
|
16
|
+
*
|
|
17
|
+
* No raw GLB URL literal lives in this file (public-example-boundary): the
|
|
18
|
+
* expected url is read from the template's own generated typed assets.
|
|
19
|
+
*/
|
|
20
|
+
const CERTIFIED_ROSTER = ["showcaseKenneyOobiPlatformerHero"] as const;
|
|
21
|
+
function certifiedHeroFromTypedAssets(): { assetId: string; url: string } {
|
|
22
|
+
const source = readFileSync(join(dirname(fileURLToPath(import.meta.url)), "..", "src", "aura-assets.ts"), "utf8");
|
|
23
|
+
for (const assetId of CERTIFIED_ROSTER) {
|
|
24
|
+
const pattern = new RegExp(`${assetId}:[\\s\\S]*?url:\\s*"([^"]+)"`);
|
|
25
|
+
const match = pattern.exec(source);
|
|
26
|
+
if (match?.[1]) return { assetId, url: match[1] };
|
|
27
|
+
}
|
|
28
|
+
throw new Error(`no certified-roster hero declared in the template typed assets (roster: ${CERTIFIED_ROSTER.join(", ")})`);
|
|
29
|
+
}
|
|
30
|
+
const CERTIFIED_HERO = certifiedHeroFromTypedAssets();
|
|
31
|
+
|
|
32
|
+
test("mini-game hero mounts the certified vehicle-driver rig", async ({ page }) => {
|
|
33
|
+
await page.goto("/");
|
|
34
|
+
await expect.poll(() => page.locator("body").getAttribute("data-aura3d-ready"), { timeout: 45_000 }).toBe("true");
|
|
35
|
+
const hero = await page.evaluate(
|
|
36
|
+
() => (window as unknown as { readonly __AURA3D_MINI_GAME__?: { readonly hero?: { readonly assetId?: string; readonly url?: string } } }).__AURA3D_MINI_GAME__?.hero
|
|
37
|
+
);
|
|
38
|
+
expect(hero?.assetId).toBe(CERTIFIED_HERO.assetId);
|
|
39
|
+
expect(hero?.url).toBe(CERTIFIED_HERO.url);
|
|
40
|
+
});
|
|
@@ -10,11 +10,18 @@ import {
|
|
|
10
10
|
} from "@aura3d/lean/product";
|
|
11
11
|
import { assets } from "./aura-assets";
|
|
12
12
|
|
|
13
|
+
// PART C2 game-ready adoption: the plinth wears the @aura3d/materials
|
|
14
|
+
// "carPaint" preset shell (clearcoat 1 + flake normal scale) in a pearl studio
|
|
15
|
+
// base, and the floor carries the "glassThin" preset color/roughness values as
|
|
16
|
+
// a glass display deck (transmission itself lives in the full engine API).
|
|
17
|
+
const carPaintPlinth = material.clearcoatPaint({ color: "#d7dce2", roughness: 0.3, metallic: 0.55, clearcoat: 1 });
|
|
18
|
+
const glassThinDeck = material.pbr({ color: "#9fc6d4", roughness: 0.08, metallic: 0 });
|
|
19
|
+
|
|
13
20
|
const productScene = scene()
|
|
14
21
|
.background("#071018")
|
|
15
|
-
.add(primitives.plane({ name: "
|
|
22
|
+
.add(primitives.plane({ name: "glass deck floor", material: glassThinDeck })
|
|
16
23
|
.position(0, -0.05, -0.62).scale([6.2, 1, 5.2]))
|
|
17
|
-
.add(primitives.box({ name: "product plinth", material:
|
|
24
|
+
.add(primitives.box({ name: "carPaint product plinth", material: carPaintPlinth })
|
|
18
25
|
.position(0, 0.06, -0.62).scale([1.82, 0.18, 1.4]))
|
|
19
26
|
.add(model(assets.product, { name: "typed studio product" }).position(0, 1.08, -0.62).scale(0.66))
|
|
20
27
|
.add(environments.studio())
|
|
@@ -11,7 +11,8 @@
|
|
|
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": "
|
|
14
|
+
"@aura3d/engine": "3.0.0",
|
|
15
|
+
"@aura3d/navigation-recast": "3.0.0"
|
|
15
16
|
},
|
|
16
17
|
"devDependencies": {
|
|
17
18
|
"@playwright/test": "^1.52.0",
|
|
@@ -174,6 +174,7 @@ function buildScene() {
|
|
|
174
174
|
.scale(0.18)
|
|
175
175
|
.runtime(game.runtimeNode("race-car", { tags: ["player", "vehicle", "typed-asset", "runtime"] })),
|
|
176
176
|
...routeRibbonNodes(),
|
|
177
|
+
...paintAndGlassGantryNodes(),
|
|
177
178
|
primitives.box({ name: "checkpoint marker", material: material.neon({ color: "#7ff0c5", emissive: "#7ff0c5", emissiveIntensity: 0.7 }) })
|
|
178
179
|
.position(0.06, 0.18, 0.2)
|
|
179
180
|
.scale([0.16, 0.28, 0.95])
|
|
@@ -199,6 +200,36 @@ function routeRibbonNodes(): AuraNodeInput[] {
|
|
|
199
200
|
);
|
|
200
201
|
}
|
|
201
202
|
|
|
203
|
+
// PART C2 game-ready adoption: a static paint-and-glass gantry south of the
|
|
204
|
+
// start straight wearing the @aura3d/materials "carPaint" preset (clearcoat
|
|
205
|
+
// shell + flake normal) on the showcase shell and the "glassThin" preset
|
|
206
|
+
// (thin-walled transmission) on the canopy panel.
|
|
207
|
+
function paintAndGlassGantryNodes(): AuraNodeInput[] {
|
|
208
|
+
const carPaintShell = material.clearcoatPaint({
|
|
209
|
+
color: "#c1121f",
|
|
210
|
+
roughness: 0.32,
|
|
211
|
+
metallic: 0.65,
|
|
212
|
+
clearcoat: 1,
|
|
213
|
+
clearcoatRoughness: 0.06,
|
|
214
|
+
normalScale: 0.35,
|
|
215
|
+
envMapIntensity: 1.4
|
|
216
|
+
});
|
|
217
|
+
const glassThinCanopy = material.glass({
|
|
218
|
+
color: "#bfe6f2",
|
|
219
|
+
opacity: 0.35,
|
|
220
|
+
transmission: 0.9,
|
|
221
|
+
thickness: 0.02,
|
|
222
|
+
roughness: 0.05
|
|
223
|
+
});
|
|
224
|
+
const post = material.pbr({ color: "#1c2530", roughness: 0.6, metallic: 0.4 });
|
|
225
|
+
return [
|
|
226
|
+
primitives.box({ name: "carPaint showcase shell", material: carPaintShell }).position(2.7, 0.3, -1.7).scale([1.5, 0.55, 0.75]),
|
|
227
|
+
primitives.box({ name: "glassThin canopy", material: glassThinCanopy }).position(2.7, 1.5, -1.7).scale([1.7, 0.06, 0.95]),
|
|
228
|
+
primitives.box({ name: "gantry post west", material: post }).position(1.95, 0.75, -1.7).scale([0.08, 1.5, 0.08]),
|
|
229
|
+
primitives.box({ name: "gantry post east", material: post }).position(3.45, 0.75, -1.7).scale([0.08, 1.5, 0.08])
|
|
230
|
+
];
|
|
231
|
+
}
|
|
232
|
+
|
|
202
233
|
function createHud(): HTMLElement {
|
|
203
234
|
const root = document.createElement("aside");
|
|
204
235
|
root.id = "racing-starter-hud";
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
"test": "playwright test tests/route-health.spec.ts tests/screenshot.spec.ts --workers=1"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@aura3d/engine": "
|
|
14
|
+
"@aura3d/engine": "3.0.0",
|
|
15
|
+
"@aura3d/navigation-recast": "3.0.0"
|
|
15
16
|
},
|
|
16
17
|
"devDependencies": {
|
|
17
18
|
"@playwright/test": "^1.52.0",
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
"test": "playwright test tests/route-health.spec.ts tests/screenshot.spec.ts --workers=1"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@aura3d/engine": "
|
|
14
|
+
"@aura3d/engine": "3.0.0",
|
|
15
|
+
"@aura3d/navigation-recast": "3.0.0"
|
|
15
16
|
},
|
|
16
17
|
"devDependencies": {
|
|
17
18
|
"@playwright/test": "^1.52.0",
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
"test": "playwright test tests/route-health.spec.ts tests/screenshot.spec.ts --workers=1"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@aura3d/engine": "
|
|
14
|
+
"@aura3d/engine": "3.0.0",
|
|
15
|
+
"@aura3d/navigation-recast": "3.0.0"
|
|
15
16
|
},
|
|
16
17
|
"devDependencies": {
|
|
17
18
|
"@playwright/test": "^1.52.0",
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
"test": "playwright test tests/route-health.spec.ts tests/screenshot.spec.ts --workers=1"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@aura3d/engine": "
|
|
14
|
+
"@aura3d/engine": "3.0.0",
|
|
15
|
+
"@aura3d/navigation-recast": "3.0.0"
|
|
15
16
|
},
|
|
16
17
|
"devDependencies": {
|
|
17
18
|
"@playwright/test": "^1.52.0",
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
"test": "playwright test tests/route-health.spec.ts tests/screenshot.spec.ts --workers=1"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@aura3d/engine": "
|
|
14
|
+
"@aura3d/engine": "3.0.0",
|
|
15
|
+
"@aura3d/navigation-recast": "3.0.0"
|
|
15
16
|
},
|
|
16
17
|
"devDependencies": {
|
|
17
18
|
"@playwright/test": "^1.52.0",
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
"test": "playwright test tests/route-health.spec.ts tests/screenshot.spec.ts --workers=1"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@aura3d/engine": "
|
|
14
|
+
"@aura3d/engine": "3.0.0",
|
|
15
|
+
"@aura3d/navigation-recast": "3.0.0"
|
|
15
16
|
},
|
|
16
17
|
"devDependencies": {
|
|
17
18
|
"@playwright/test": "^1.52.0",
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
"test": "playwright test tests/route-health.spec.ts tests/screenshot.spec.ts --workers=1"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@aura3d/engine": "
|
|
14
|
+
"@aura3d/engine": "3.0.0",
|
|
15
|
+
"@aura3d/navigation-recast": "3.0.0"
|
|
15
16
|
},
|
|
16
17
|
"devDependencies": {
|
|
17
18
|
"@playwright/test": "^1.52.0",
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
"test": "playwright test tests/route-health.spec.ts tests/screenshot.spec.ts --workers=1"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@aura3d/engine": "
|
|
14
|
+
"@aura3d/engine": "3.0.0",
|
|
15
|
+
"@aura3d/navigation-recast": "3.0.0"
|
|
15
16
|
},
|
|
16
17
|
"devDependencies": {
|
|
17
18
|
"@playwright/test": "^1.52.0",
|
|
Binary file
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 160 120">
|
|
2
|
-
<rect width="160" height="120" rx="16" fill="#07111d"/>
|
|
3
|
-
<circle cx="74" cy="62" r="29" fill="#ffd84a"/>
|
|
4
|
-
<circle cx="97" cy="50" r="19" fill="#ffe680"/>
|
|
5
|
-
<path d="M112 55h22l-20 12z" fill="#ff8a4c"/>
|
|
6
|
-
<circle cx="101" cy="45" r="3" fill="#111827"/>
|
|
7
|
-
<path d="M40 91c27 12 62 12 91 0" fill="none" stroke="#55e7ff" stroke-width="6" stroke-linecap="round"/>
|
|
8
|
-
</svg>
|