create-aura3d 1.3.1 → 1.3.3
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 +0 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -2
- package/dist/index.js.map +1 -1
- package/package.json +11 -3
- package/templates/animation-channel/package.json +1 -1
- package/templates/animation-channel/tests/screenshot.spec.ts +16 -0
- package/templates/animation-studio/dist/episodes/prompt-to-scene-proof/two-robots-fixing-a-car.document.json +680 -0
- 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 +2021 -2542
- 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/scene/working.document.json +219 -269
- package/templates/animation-studio/index.html +12 -0
- package/templates/animation-studio/node_modules.publish-aside/.vite/deps/_metadata.json +8 -0
- package/templates/animation-studio/node_modules.publish-aside/.vite/deps/package.json +3 -0
- package/templates/animation-studio/package-lock.json +1307 -55
- package/templates/animation-studio/package.json +11 -5
- package/templates/animation-studio/scripts/asset-motion-probe.ts +7 -1
- package/templates/animation-studio/scripts/render-live.ts +24 -0
- package/templates/animation-studio/src/director/prompt-to-scene.ts +73 -5
- package/templates/animation-studio/studio/dist/assets/index-C7f8tHfi.css +1 -0
- package/templates/animation-studio/studio/dist/assets/index-CLo3GXyN.js +43 -0
- package/templates/animation-studio/studio/dist/index.html +19 -0
- package/templates/animation-studio/studio/index.html +18 -0
- package/templates/animation-studio/studio/src/App.tsx +498 -0
- package/templates/animation-studio/studio/src/components/Console.tsx +372 -0
- package/templates/animation-studio/studio/src/components/Icon.tsx +90 -0
- package/templates/animation-studio/studio/src/components/Inspector.tsx +265 -0
- package/templates/animation-studio/studio/src/components/Outliner.tsx +210 -0
- package/templates/animation-studio/studio/src/components/Palette.tsx +204 -0
- package/templates/animation-studio/studio/src/components/Stage.tsx +344 -0
- package/templates/animation-studio/studio/src/components/Timeline.tsx +230 -0
- package/templates/animation-studio/studio/src/components/Topbar.tsx +66 -0
- package/templates/animation-studio/studio/src/main.tsx +10 -0
- package/templates/animation-studio/studio/src/state/backend.ts +108 -0
- package/templates/animation-studio/studio/src/state/fidelity.ts +110 -0
- package/templates/animation-studio/studio/src/state/mapDocument.ts +430 -0
- package/templates/animation-studio/studio/src/state/sceneTool.ts +66 -0
- package/templates/animation-studio/studio/src/state/types.ts +173 -0
- package/templates/animation-studio/studio/src/state/util.ts +46 -0
- package/templates/animation-studio/studio/src/styles.css +528 -0
- package/templates/animation-studio/studio/vite.config.ts +175 -0
- package/templates/animation-studio/tests/route-health.spec.ts +33 -0
- package/templates/animation-studio/tests/screenshot.spec.ts +30 -0
- package/templates/character-controller/package.json +3 -3
- package/templates/character-controller/src/main.ts +18 -1
- package/templates/cinematic-scene/package.json +1 -1
- package/templates/cinematic-scene/tests/screenshot.spec.ts +3 -1
- package/templates/episode-builder/package.json +1 -1
- package/templates/episode-builder/tests/screenshot.spec.ts +15 -0
- package/templates/fighting-game/package.json +1 -1
- package/templates/fighting-game/tests/screenshot.spec.ts +15 -0
- package/templates/mini-game/package.json +1 -1
- package/templates/product-viewer/package.json +1 -1
- package/templates/product-viewer/tests/screenshot.spec.ts +3 -1
- package/templates/prompt-animation-channel/package.json +1 -1
- package/templates/prompt-animation-channel/tests/screenshot.spec.ts +15 -0
- package/templates/three-compat-architecture-interior/index.html +16 -0
- package/templates/three-compat-architecture-interior/package.json +21 -0
- package/templates/three-compat-architecture-interior/playwright.config.ts +14 -0
- package/templates/three-compat-architecture-interior/src/main.ts +27 -0
- package/templates/three-compat-architecture-interior/tests/route-health.spec.ts +16 -0
- package/templates/three-compat-architecture-interior/tests/screenshot.spec.ts +15 -0
- package/templates/three-compat-architecture-interior/tsconfig.json +19 -0
- package/templates/three-compat-asset-inspector/index.html +16 -0
- package/templates/three-compat-asset-inspector/package.json +21 -0
- package/templates/three-compat-asset-inspector/playwright.config.ts +14 -0
- package/templates/three-compat-asset-inspector/src/main.ts +17 -0
- package/templates/three-compat-asset-inspector/tests/route-health.spec.ts +16 -0
- package/templates/three-compat-asset-inspector/tests/screenshot.spec.ts +15 -0
- package/templates/three-compat-asset-inspector/tsconfig.json +19 -0
- package/templates/three-compat-character-viewer/index.html +16 -0
- package/templates/three-compat-character-viewer/package.json +21 -0
- package/templates/three-compat-character-viewer/playwright.config.ts +14 -0
- package/templates/three-compat-character-viewer/src/main.ts +17 -0
- package/templates/three-compat-character-viewer/tests/route-health.spec.ts +16 -0
- package/templates/three-compat-character-viewer/tests/screenshot.spec.ts +15 -0
- package/templates/three-compat-character-viewer/tsconfig.json +19 -0
- package/templates/three-compat-custom-threejs-migration/index.html +16 -0
- package/templates/three-compat-custom-threejs-migration/package.json +21 -0
- package/templates/three-compat-custom-threejs-migration/playwright.config.ts +14 -0
- package/templates/three-compat-custom-threejs-migration/src/main.ts +16 -0
- package/templates/three-compat-custom-threejs-migration/tests/route-health.spec.ts +16 -0
- package/templates/three-compat-custom-threejs-migration/tests/screenshot.spec.ts +15 -0
- package/templates/three-compat-custom-threejs-migration/tsconfig.json +19 -0
- package/templates/three-compat-large-scene/index.html +16 -0
- package/templates/three-compat-large-scene/package.json +21 -0
- package/templates/three-compat-large-scene/playwright.config.ts +14 -0
- package/templates/three-compat-large-scene/src/main.ts +31 -0
- package/templates/three-compat-large-scene/tests/route-health.spec.ts +16 -0
- package/templates/three-compat-large-scene/tests/screenshot.spec.ts +15 -0
- package/templates/three-compat-large-scene/tsconfig.json +19 -0
- package/templates/three-compat-material-authoring/index.html +16 -0
- package/templates/three-compat-material-authoring/package.json +21 -0
- package/templates/three-compat-material-authoring/playwright.config.ts +14 -0
- package/templates/three-compat-material-authoring/src/main.ts +33 -0
- package/templates/three-compat-material-authoring/tests/route-health.spec.ts +16 -0
- package/templates/three-compat-material-authoring/tests/screenshot.spec.ts +15 -0
- package/templates/three-compat-material-authoring/tsconfig.json +19 -0
- package/templates/three-compat-postprocess-scene/index.html +16 -0
- package/templates/three-compat-postprocess-scene/package.json +21 -0
- package/templates/three-compat-postprocess-scene/playwright.config.ts +14 -0
- package/templates/three-compat-postprocess-scene/src/main.ts +17 -0
- package/templates/three-compat-postprocess-scene/tests/route-health.spec.ts +16 -0
- package/templates/three-compat-postprocess-scene/tests/screenshot.spec.ts +15 -0
- package/templates/three-compat-postprocess-scene/tsconfig.json +19 -0
- package/templates/three-compat-premium-product-viewer/index.html +16 -0
- package/templates/three-compat-premium-product-viewer/package.json +21 -0
- package/templates/three-compat-premium-product-viewer/playwright.config.ts +14 -0
- package/templates/three-compat-premium-product-viewer/src/main.ts +18 -0
- package/templates/three-compat-premium-product-viewer/tests/route-health.spec.ts +16 -0
- package/templates/three-compat-premium-product-viewer/tests/screenshot.spec.ts +15 -0
- package/templates/three-compat-premium-product-viewer/tsconfig.json +19 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { createReadStream, existsSync, readFileSync, statSync } from "node:fs";
|
|
3
|
+
import { dirname, extname, resolve } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { defineConfig, type Plugin, type ViteDevServer } from "vite";
|
|
6
|
+
import react from "@vitejs/plugin-react";
|
|
7
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
8
|
+
|
|
9
|
+
// Aura3D Animation Studio — bundled inside the animation-studio template.
|
|
10
|
+
// Adapts the monorepo paths (REPO_ROOT, tsconfig.base.json) to the template's
|
|
11
|
+
// standalone project layout so `npm run studio` works in a scaffolded project.
|
|
12
|
+
|
|
13
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
// The template root (parent of this studio/ directory).
|
|
15
|
+
const TEMPLATE_DIR = resolve(__dirname, "..");
|
|
16
|
+
// The Scene-Tool CLI lives inside the template's scripts/ folder.
|
|
17
|
+
const SCENE_CLI = resolve(TEMPLATE_DIR, "scripts", "animation-scene.ts");
|
|
18
|
+
// Persisted working document the Scene-Tool CLI mutates.
|
|
19
|
+
const WORKING_DOC = resolve(TEMPLATE_DIR, "dist", "scene", "working.document.json");
|
|
20
|
+
// Command/result history alongside the working document.
|
|
21
|
+
const WORKING_HISTORY = resolve(TEMPLATE_DIR, "dist", "scene", "working.history.json");
|
|
22
|
+
// Where `animation-scene render` writes frames + episode-3d.webm.
|
|
23
|
+
const RENDER_OUT_DIR = resolve(TEMPLATE_DIR, "dist", "episodes", "scene");
|
|
24
|
+
|
|
25
|
+
const MIME: Record<string, string> = {
|
|
26
|
+
".webm": "video/webm",
|
|
27
|
+
".png": "image/png",
|
|
28
|
+
".jpg": "image/jpeg",
|
|
29
|
+
".json": "application/json"
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
function readBody(req: IncomingMessage): Promise<string> {
|
|
33
|
+
return new Promise((res, rej) => {
|
|
34
|
+
const chunks: Buffer[] = [];
|
|
35
|
+
req.on("data", (c: Buffer) => chunks.push(c));
|
|
36
|
+
req.on("end", () => res(Buffer.concat(chunks).toString("utf8")));
|
|
37
|
+
req.on("error", rej);
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function json(res: ServerResponse, code: number, body: unknown): void {
|
|
42
|
+
res.statusCode = code;
|
|
43
|
+
res.setHeader("content-type", "application/json");
|
|
44
|
+
res.end(JSON.stringify(body));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function tokenize(command: string): string[] {
|
|
48
|
+
const out: string[] = [];
|
|
49
|
+
const re = /"([^"]*)"|'([^']*)'|(\S+)/g;
|
|
50
|
+
let m: RegExpExecArray | null;
|
|
51
|
+
while ((m = re.exec(command))) out.push(m[1] ?? m[2] ?? m[3] ?? "");
|
|
52
|
+
return out;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Spawn the Scene-Tool CLI from the template root using npx tsx. */
|
|
56
|
+
function runCli(args: string[], extraEnv: Record<string, string> = {}): Promise<{ code: number; out: string }> {
|
|
57
|
+
return new Promise((res) => {
|
|
58
|
+
const child = spawn(
|
|
59
|
+
"npx",
|
|
60
|
+
["tsx", SCENE_CLI, ...args],
|
|
61
|
+
{ cwd: TEMPLATE_DIR, env: { ...process.env, ...extraEnv } }
|
|
62
|
+
);
|
|
63
|
+
let out = "";
|
|
64
|
+
child.stdout.on("data", (d: Buffer) => (out += d.toString("utf8")));
|
|
65
|
+
child.stderr.on("data", (d: Buffer) => (out += d.toString("utf8")));
|
|
66
|
+
child.on("error", (e) => res({ code: 1, out: out + String(e) }));
|
|
67
|
+
child.on("close", (code) => res({ code: code ?? 0, out: out.trim() }));
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function docHash(): string {
|
|
72
|
+
if (!existsSync(WORKING_DOC)) return "—";
|
|
73
|
+
let h = 5381;
|
|
74
|
+
const buf = readFileSync(WORKING_DOC);
|
|
75
|
+
for (let i = 0; i < buf.length; i++) h = ((h << 5) + h + buf[i]!) >>> 0;
|
|
76
|
+
return h.toString(16).slice(-3);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function auraBackend(): Plugin {
|
|
80
|
+
return {
|
|
81
|
+
name: "aura-studio-backend",
|
|
82
|
+
apply: "serve",
|
|
83
|
+
configureServer(server: ViteDevServer) {
|
|
84
|
+
server.middlewares.use("/api/document", (req, res, next) => {
|
|
85
|
+
if (req.method !== "GET") return next();
|
|
86
|
+
try {
|
|
87
|
+
if (!existsSync(WORKING_DOC)) return json(res, 200, { exists: false });
|
|
88
|
+
const doc = JSON.parse(readFileSync(WORKING_DOC, "utf8")) as unknown;
|
|
89
|
+
json(res, 200, doc);
|
|
90
|
+
} catch (e) {
|
|
91
|
+
json(res, 500, { ok: false, error: e instanceof Error ? e.message : String(e) });
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
server.middlewares.use("/api/history", (req, res, next) => {
|
|
96
|
+
if (req.method !== "GET") return next();
|
|
97
|
+
try {
|
|
98
|
+
if (!existsSync(WORKING_HISTORY)) return json(res, 200, []);
|
|
99
|
+
const hist = JSON.parse(readFileSync(WORKING_HISTORY, "utf8")) as unknown;
|
|
100
|
+
json(res, 200, Array.isArray(hist) ? hist : []);
|
|
101
|
+
} catch (e) {
|
|
102
|
+
json(res, 500, { ok: false, error: e instanceof Error ? e.message : String(e) });
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
server.middlewares.use("/api/scene", (req, res, next) => {
|
|
107
|
+
if (req.method !== "POST") return next();
|
|
108
|
+
void (async () => {
|
|
109
|
+
const started = Date.now();
|
|
110
|
+
try {
|
|
111
|
+
const { command } = JSON.parse((await readBody(req)) || "{}") as { command?: string };
|
|
112
|
+
if (!command || !command.trim()) return json(res, 400, { ok: false, error: "empty command" });
|
|
113
|
+
const { code, out } = await runCli(tokenize(command.trim()));
|
|
114
|
+
const ok = code === 0;
|
|
115
|
+
json(res, 200, { ok, output: out, rejected: !ok, ms: Date.now() - started, hash: docHash() });
|
|
116
|
+
} catch (e) {
|
|
117
|
+
json(res, 500, { ok: false, error: e instanceof Error ? e.message : String(e) });
|
|
118
|
+
}
|
|
119
|
+
})();
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
server.middlewares.use("/api/render", (req, res, next) => {
|
|
123
|
+
if (req.method === "GET") {
|
|
124
|
+
const webm = resolve(RENDER_OUT_DIR, "episode-3d.webm");
|
|
125
|
+
const poster = resolve(RENDER_OUT_DIR, "frames", "first.png");
|
|
126
|
+
const hasVideo = existsSync(webm);
|
|
127
|
+
json(res, 200, {
|
|
128
|
+
ok: true, exists: hasVideo,
|
|
129
|
+
video: hasVideo ? "/preview/episode-3d.webm" : null,
|
|
130
|
+
poster: existsSync(poster) ? "/preview/frames/first.png" : null,
|
|
131
|
+
hash: docHash()
|
|
132
|
+
});
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
if (req.method !== "POST") return next();
|
|
136
|
+
void (async () => {
|
|
137
|
+
const started = Date.now();
|
|
138
|
+
try {
|
|
139
|
+
const body = JSON.parse((await readBody(req)) || "{}") as { lowFi?: boolean; range?: string };
|
|
140
|
+
const args = ["render"];
|
|
141
|
+
if (body.range) args.push("--range", body.range);
|
|
142
|
+
const env: Record<string, string> = { AURA_LOW_FIDELITY: body.lowFi === false ? "0" : "1" };
|
|
143
|
+
const { code, out } = await runCli(args, env);
|
|
144
|
+
if (code !== 0) return json(res, 500, { ok: false, output: out });
|
|
145
|
+
const webm = resolve(RENDER_OUT_DIR, "episode-3d.webm");
|
|
146
|
+
const poster = resolve(RENDER_OUT_DIR, "frames", "first.png");
|
|
147
|
+
json(res, 200, {
|
|
148
|
+
ok: true, output: out, ms: Date.now() - started,
|
|
149
|
+
video: existsSync(webm) ? "/preview/episode-3d.webm" : null,
|
|
150
|
+
poster: existsSync(poster) ? "/preview/frames/first.png" : null,
|
|
151
|
+
hash: docHash()
|
|
152
|
+
});
|
|
153
|
+
} catch (e) {
|
|
154
|
+
json(res, 500, { ok: false, error: e instanceof Error ? e.message : String(e) });
|
|
155
|
+
}
|
|
156
|
+
})();
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
server.middlewares.use("/preview", (req, res, next) => {
|
|
160
|
+
const rel = decodeURIComponent((req.url ?? "/").split("?")[0]!).replace(/^\/+/, "");
|
|
161
|
+
const file = resolve(RENDER_OUT_DIR, rel);
|
|
162
|
+
if (!file.startsWith(RENDER_OUT_DIR) || !existsSync(file) || !statSync(file).isFile()) return next();
|
|
163
|
+
res.setHeader("content-type", MIME[extname(file)] ?? "application/octet-stream");
|
|
164
|
+
res.setHeader("cache-control", "no-store");
|
|
165
|
+
createReadStream(file).pipe(res);
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export default defineConfig({
|
|
172
|
+
plugins: [react(), auraBackend()],
|
|
173
|
+
server: { host: "127.0.0.1", port: 5188 },
|
|
174
|
+
build: { outDir: "dist", emptyOutDir: true }
|
|
175
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { expect, test } from "@playwright/test";
|
|
4
|
+
|
|
5
|
+
test.setTimeout(60_000);
|
|
6
|
+
|
|
7
|
+
// The animation-studio route is the generic scene player (render-live-route.ts ->
|
|
8
|
+
// scene-player.ts). Its readiness contract is window.__AURA_LIVE_ROUTE_READY__ /
|
|
9
|
+
// __AURA_LIVE_ROUTE_ERROR__, not the createAuraApp data-aura3d-ready attribute.
|
|
10
|
+
test("Aura3D animation studio live route reaches ready state", async ({ page }) => {
|
|
11
|
+
await page.goto("/");
|
|
12
|
+
await expect
|
|
13
|
+
.poll(
|
|
14
|
+
() =>
|
|
15
|
+
page.evaluate(() => {
|
|
16
|
+
const w = window as unknown as {
|
|
17
|
+
__AURA_LIVE_ROUTE_READY__?: { ready?: boolean; backend?: string };
|
|
18
|
+
__AURA_LIVE_ROUTE_ERROR__?: string;
|
|
19
|
+
};
|
|
20
|
+
if (w.__AURA_LIVE_ROUTE_ERROR__) return `error: ${w.__AURA_LIVE_ROUTE_ERROR__}`;
|
|
21
|
+
return w.__AURA_LIVE_ROUTE_READY__?.ready === true ? "ready" : "pending";
|
|
22
|
+
}),
|
|
23
|
+
{ timeout: 45_000 }
|
|
24
|
+
)
|
|
25
|
+
.toBe("ready");
|
|
26
|
+
const proof = await page.evaluate(
|
|
27
|
+
() => (window as unknown as { __AURA_LIVE_ROUTE_READY__?: { ready?: boolean; backend?: string } }).__AURA_LIVE_ROUTE_READY__
|
|
28
|
+
);
|
|
29
|
+
expect(proof?.ready).toBe(true);
|
|
30
|
+
expect(typeof proof?.backend).toBe("string");
|
|
31
|
+
mkdirSync(resolve("tests/reports"), { recursive: true });
|
|
32
|
+
writeFileSync(resolve("tests/reports/route-health.json"), `${JSON.stringify({ ready: true, backend: proof?.backend }, null, 2)}\n`);
|
|
33
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { expect, test } from "@playwright/test";
|
|
4
|
+
|
|
5
|
+
test.setTimeout(60_000);
|
|
6
|
+
|
|
7
|
+
// Waits on the scene player's real readiness contract (__AURA_LIVE_ROUTE_READY__),
|
|
8
|
+
// then captures the rendered route as visual evidence.
|
|
9
|
+
test("Aura3D animation studio screenshot shows content", async ({ page }) => {
|
|
10
|
+
await page.goto("/");
|
|
11
|
+
await expect
|
|
12
|
+
.poll(
|
|
13
|
+
() =>
|
|
14
|
+
page.evaluate(() => {
|
|
15
|
+
const w = window as unknown as {
|
|
16
|
+
__AURA_LIVE_ROUTE_READY__?: { ready?: boolean };
|
|
17
|
+
__AURA_LIVE_ROUTE_ERROR__?: string;
|
|
18
|
+
};
|
|
19
|
+
if (w.__AURA_LIVE_ROUTE_ERROR__) return `error: ${w.__AURA_LIVE_ROUTE_ERROR__}`;
|
|
20
|
+
return w.__AURA_LIVE_ROUTE_READY__?.ready === true ? "ready" : "pending";
|
|
21
|
+
}),
|
|
22
|
+
{ timeout: 45_000 }
|
|
23
|
+
)
|
|
24
|
+
.toBe("ready");
|
|
25
|
+
const screenshot = await page.screenshot({ fullPage: false });
|
|
26
|
+
mkdirSync(resolve("tests/reports"), { recursive: true });
|
|
27
|
+
writeFileSync(resolve("tests/reports/screenshot.png"), screenshot);
|
|
28
|
+
writeFileSync(resolve("tests/reports/screenshot.json"), `${JSON.stringify({ bytes: screenshot.byteLength }, null, 2)}\n`);
|
|
29
|
+
expect(screenshot.byteLength).toBeGreaterThan(1000);
|
|
30
|
+
});
|
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
"test": "playwright test tests/route-health.spec.ts tests/screenshot.spec.ts --workers=1"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@aura3d/animation": "1.3.
|
|
15
|
-
"@aura3d/engine": "1.3.
|
|
16
|
-
"@aura3d/physics": "1.3.
|
|
14
|
+
"@aura3d/animation": "1.3.2",
|
|
15
|
+
"@aura3d/engine": "1.3.3",
|
|
16
|
+
"@aura3d/physics": "1.3.2"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
19
|
"@playwright/test": "^1.52.0",
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Character controller preview: keyboard input -> kinematic speed -> @aura3d/animation locomotion
|
|
2
2
|
// kit (idle/walk/run blended by speed) -> live proof object. Hold a direction key (W/A/S/D or
|
|
3
|
-
// arrows) to walk; hold Shift to run.
|
|
3
|
+
// arrows) to walk; hold Shift to run. A minimal Aura3D stage renders beneath the HUD.
|
|
4
|
+
import { camera, createAuraApp, lights, material, primitives, scene } from "@aura3d/engine";
|
|
4
5
|
import { createLocomotionKit } from "@aura3d/animation";
|
|
5
6
|
import { defaultCharacterControllerTuning, stepCharacterSpeed, type CharacterControllerState } from "./controller.js";
|
|
6
7
|
|
|
@@ -32,6 +33,22 @@ hud.id = "character-controller-hud";
|
|
|
32
33
|
hud.style.cssText = "font:14px monospace;padding:16px;color:#6cf;background:#0c0f16;min-height:140px";
|
|
33
34
|
root.appendChild(hud);
|
|
34
35
|
|
|
36
|
+
// Minimal visual stage: a capsule stand-in on a floor so the route renders a real
|
|
37
|
+
// Aura3D scene alongside the locomotion HUD proof.
|
|
38
|
+
const stage = document.createElement("div");
|
|
39
|
+
stage.id = "character-controller-stage";
|
|
40
|
+
stage.style.cssText = "height:320px";
|
|
41
|
+
root.appendChild(stage);
|
|
42
|
+
createAuraApp(stage, {
|
|
43
|
+
scene: scene()
|
|
44
|
+
.background("#0c0f16")
|
|
45
|
+
.camera(camera.orbit({ target: [0, 1, 0], distance: 5 }))
|
|
46
|
+
.add(primitives.box({ name: "floor", size: [6, 0.1, 6], position: [0, -0.05, 0], material: material.pbr({ color: "#151b27", roughness: 0.9 }), receiveShadow: true }))
|
|
47
|
+
.add(primitives.capsule({ name: "character stand-in", size: [0.7, 1.7, 0.7], position: [0, 1.2, 0], material: material.pbr({ color: "#6cb1f0", roughness: 0.5 }), castShadow: true }))
|
|
48
|
+
.add(lights.ambient({ intensity: 0.35 }))
|
|
49
|
+
.add(lights.directional({ name: "key", position: [3, 5, 2], intensity: 1.4 }))
|
|
50
|
+
});
|
|
51
|
+
|
|
35
52
|
let state: CharacterControllerState = { speed: 0 };
|
|
36
53
|
let last = 0;
|
|
37
54
|
|
|
@@ -50,7 +50,9 @@ test("Aura3D cinematic scene screenshot shows the rainy neon hero prompt", async
|
|
|
50
50
|
expect(profile.rainPixels).toBeGreaterThan(90);
|
|
51
51
|
expect(profile.wetReflectionPixels).toBeGreaterThan(60);
|
|
52
52
|
expect(profile.centerHeroPixels).toBeGreaterThan(600);
|
|
53
|
-
|
|
53
|
+
// Measured 174 on 2026-06-10 after engine lighting/material updates (neutral-gray
|
|
54
|
+
// default fallback, bloom retune) brightened the alley floor slightly.
|
|
55
|
+
expect(profile.darkAlleyPixels).toBeGreaterThan(150);
|
|
54
56
|
expect(profile.uniqueBuckets).toBeGreaterThan(22);
|
|
55
57
|
expect(screenshot.byteLength).toBeGreaterThan(1000);
|
|
56
58
|
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { expect, test } from "@playwright/test";
|
|
4
|
+
|
|
5
|
+
test.setTimeout(60_000);
|
|
6
|
+
|
|
7
|
+
test("Aura3D episode builder screenshot shows content", async ({ page }) => {
|
|
8
|
+
await page.goto("/");
|
|
9
|
+
await expect.poll(() => page.locator("body").getAttribute("data-aura3d-ready"), { timeout: 45_000 }).toBe("true");
|
|
10
|
+
const screenshot = await page.screenshot({ fullPage: false });
|
|
11
|
+
mkdirSync(resolve("tests/reports"), { recursive: true });
|
|
12
|
+
writeFileSync(resolve("tests/reports/screenshot.png"), screenshot);
|
|
13
|
+
writeFileSync(resolve("tests/reports/screenshot.json"), `${JSON.stringify({ bytes: screenshot.byteLength }, null, 2)}\n`);
|
|
14
|
+
expect(screenshot.byteLength).toBeGreaterThan(1000);
|
|
15
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { expect, test } from "@playwright/test";
|
|
4
|
+
|
|
5
|
+
test.setTimeout(60_000);
|
|
6
|
+
|
|
7
|
+
test("Aura3D fighting game screenshot shows content", async ({ page }) => {
|
|
8
|
+
await page.goto("/");
|
|
9
|
+
await expect.poll(() => page.locator("body").getAttribute("data-aura3d-ready"), { timeout: 45_000 }).toBe("true");
|
|
10
|
+
const screenshot = await page.screenshot({ fullPage: false });
|
|
11
|
+
mkdirSync(resolve("tests/reports"), { recursive: true });
|
|
12
|
+
writeFileSync(resolve("tests/reports/screenshot.png"), screenshot);
|
|
13
|
+
writeFileSync(resolve("tests/reports/screenshot.json"), `${JSON.stringify({ bytes: screenshot.byteLength }, null, 2)}\n`);
|
|
14
|
+
expect(screenshot.byteLength).toBeGreaterThan(1000);
|
|
15
|
+
});
|
|
@@ -44,7 +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
|
-
|
|
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
|
+
expect(profile.warmAccentPixels).toBeGreaterThan(8);
|
|
48
50
|
expect(profile.centerObjectPixels).toBeGreaterThan(650);
|
|
49
51
|
expect(profile.saturatedStudioPixels).toBeGreaterThan(900);
|
|
50
52
|
expect(profile.uniqueBuckets).toBeGreaterThan(18);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { expect, test } from "@playwright/test";
|
|
4
|
+
|
|
5
|
+
test.setTimeout(60_000);
|
|
6
|
+
|
|
7
|
+
test("Aura3D prompt animation channel screenshot shows content", async ({ page }) => {
|
|
8
|
+
await page.goto("/");
|
|
9
|
+
await expect.poll(() => page.locator("body").getAttribute("data-aura3d-ready"), { timeout: 45_000 }).toBe("true");
|
|
10
|
+
const screenshot = await page.screenshot({ fullPage: false });
|
|
11
|
+
mkdirSync(resolve("tests/reports"), { recursive: true });
|
|
12
|
+
writeFileSync(resolve("tests/reports/screenshot.png"), screenshot);
|
|
13
|
+
writeFileSync(resolve("tests/reports/screenshot.json"), `${JSON.stringify({ bytes: screenshot.byteLength }, null, 2)}\n`);
|
|
14
|
+
expect(screenshot.byteLength).toBeGreaterThan(1000);
|
|
15
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
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 Three-Compat Architecture Interior</title>
|
|
7
|
+
<style>
|
|
8
|
+
html, body, #app { margin: 0; width: 100%; height: 100%; background: #080d15; }
|
|
9
|
+
body { font-family: Inter, ui-sans-serif, system-ui, sans-serif; }
|
|
10
|
+
</style>
|
|
11
|
+
</head>
|
|
12
|
+
<body>
|
|
13
|
+
<div id="app"></div>
|
|
14
|
+
<script type="module" src="/src/main.ts"></script>
|
|
15
|
+
</body>
|
|
16
|
+
</html>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "aura3d-three-compat-architecture-interior",
|
|
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/route-health.spec.ts tests/screenshot.spec.ts --workers=1"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@aura3d/engine": "1.3.3"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@playwright/test": "^1.52.0",
|
|
18
|
+
"typescript": "^5.8.3",
|
|
19
|
+
"vite": "^7.3.2"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { defineConfig } from "@playwright/test";
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
testDir: "./tests",
|
|
5
|
+
use: {
|
|
6
|
+
baseURL: "http://127.0.0.1:4191"
|
|
7
|
+
},
|
|
8
|
+
webServer: {
|
|
9
|
+
command: "npm exec vite -- --host 127.0.0.1 --port 4191 --strictPort",
|
|
10
|
+
url: "http://127.0.0.1:4191",
|
|
11
|
+
reuseExistingServer: !process.env.CI,
|
|
12
|
+
timeout: 120_000
|
|
13
|
+
}
|
|
14
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Three-compat architecture interior: a small room built from boxes,
|
|
2
|
+
// authored entirely through the public @aura3d/engine scene API.
|
|
3
|
+
import { camera, createAuraApp, lights, material, primitives, scene } from "@aura3d/engine";
|
|
4
|
+
|
|
5
|
+
const plaster = material.pbr({ color: "#cfd6e2", roughness: 0.85 });
|
|
6
|
+
const oakFloor = material.pbr({ color: "#8a6f52", roughness: 0.6 });
|
|
7
|
+
const walnut = material.pbr({ color: "#5b4231", roughness: 0.5 });
|
|
8
|
+
|
|
9
|
+
createAuraApp("#app", {
|
|
10
|
+
scene: scene()
|
|
11
|
+
.background("#10141c")
|
|
12
|
+
.camera(camera.orbit({ target: [0, 1.1, 0], distance: 7 }))
|
|
13
|
+
.add(primitives.box({ name: "floor slab", size: [6, 0.12, 6], position: [0, -0.06, 0], material: oakFloor, receiveShadow: true }))
|
|
14
|
+
.add(primitives.box({ name: "back wall", size: [6, 2.8, 0.12], position: [0, 1.4, -3], material: plaster, receiveShadow: true }))
|
|
15
|
+
.add(primitives.box({ name: "side wall", size: [0.12, 2.8, 6], position: [-3, 1.4, 0], material: plaster, receiveShadow: true }))
|
|
16
|
+
.add(primitives.box({ name: "window header", size: [2.4, 0.5, 0.12], position: [1.2, 2.55, -3.01], material: plaster }))
|
|
17
|
+
.add(primitives.box({ name: "table top", size: [1.8, 0.08, 0.9], position: [0.5, 0.74, -0.8], material: walnut, castShadow: true }))
|
|
18
|
+
.add(primitives.box({ name: "table leg front", size: [0.08, 0.7, 0.08], position: [-0.3, 0.35, -0.45], material: walnut, castShadow: true }))
|
|
19
|
+
.add(primitives.box({ name: "table leg back", size: [0.08, 0.7, 0.08], position: [1.3, 0.35, -1.15], material: walnut, castShadow: true }))
|
|
20
|
+
.add(primitives.box({ name: "sofa seat", size: [2, 0.45, 0.9], position: [-1.8, 0.23, 1.3], material: material.pbr({ color: "#41506b", roughness: 0.78 }), castShadow: true }))
|
|
21
|
+
.add(primitives.box({ name: "sofa backrest", size: [2, 0.6, 0.18], position: [-1.8, 0.75, 1.66], material: material.pbr({ color: "#36435a", roughness: 0.78 }), castShadow: true }))
|
|
22
|
+
.add(primitives.cylinder({ name: "floor lamp pole", size: [0.06, 1.6, 0.06], position: [2.3, 0.8, 1.8], material: material.metal({ color: "#9aa3ad" }), castShadow: true }))
|
|
23
|
+
.add(primitives.sphere({ name: "floor lamp shade", size: 0.32, position: [2.3, 1.7, 1.8], material: material.emissive({ color: "#ffe7b8", emissive: "#ffd28a", emissiveIntensity: 0.8 }) }))
|
|
24
|
+
.add(lights.ambient({ intensity: 0.35 }))
|
|
25
|
+
.add(lights.directional({ name: "window sun", position: [4, 5, 3], intensity: 1.4 }))
|
|
26
|
+
.add(lights.point({ name: "lamp fill", position: [2.3, 1.7, 1.8], intensity: 1.1, color: "#ffd9a0" }))
|
|
27
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { expect, test } from "@playwright/test";
|
|
4
|
+
|
|
5
|
+
test.setTimeout(60_000);
|
|
6
|
+
|
|
7
|
+
test("Aura3D three-compat template reaches ready state", async ({ page }) => {
|
|
8
|
+
await page.goto("/");
|
|
9
|
+
await expect.poll(() => page.locator("body").getAttribute("data-aura3d-ready"), { timeout: 45_000 }).toBe("true");
|
|
10
|
+
const drawCalls = Number(await page.locator("body").getAttribute("data-aura3d-draw-calls"));
|
|
11
|
+
const diagnostics = await page.evaluate(() => (window as unknown as { __AURA3D_ROUTE_READY__?: { diagnostics?: { backend?: string } } }).__AURA3D_ROUTE_READY__?.diagnostics);
|
|
12
|
+
expect(diagnostics?.backend).toBe("webgl2");
|
|
13
|
+
expect(drawCalls).toBeGreaterThan(0);
|
|
14
|
+
mkdirSync(resolve("tests/reports"), { recursive: true });
|
|
15
|
+
writeFileSync(resolve("tests/reports/route-health.json"), `${JSON.stringify({ ready: true, backend: diagnostics?.backend, drawCalls }, null, 2)}\n`);
|
|
16
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { expect, test } from "@playwright/test";
|
|
4
|
+
|
|
5
|
+
test.setTimeout(60_000);
|
|
6
|
+
|
|
7
|
+
test("Aura3D three-compat template screenshot shows content", async ({ page }) => {
|
|
8
|
+
await page.goto("/");
|
|
9
|
+
await expect.poll(() => page.locator("body").getAttribute("data-aura3d-ready"), { timeout: 45_000 }).toBe("true");
|
|
10
|
+
const screenshot = await page.screenshot({ fullPage: false });
|
|
11
|
+
mkdirSync(resolve("tests/reports"), { recursive: true });
|
|
12
|
+
writeFileSync(resolve("tests/reports/screenshot.png"), screenshot);
|
|
13
|
+
writeFileSync(resolve("tests/reports/screenshot.json"), `${JSON.stringify({ bytes: screenshot.byteLength }, null, 2)}\n`);
|
|
14
|
+
expect(screenshot.byteLength).toBeGreaterThan(1000);
|
|
15
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"useDefineForClassFields": true,
|
|
5
|
+
"module": "ESNext",
|
|
6
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
7
|
+
"allowJs": false,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"allowSyntheticDefaultImports": true,
|
|
11
|
+
"strict": true,
|
|
12
|
+
"forceConsistentCasingInFileNames": true,
|
|
13
|
+
"moduleResolution": "Bundler",
|
|
14
|
+
"resolveJsonModule": true,
|
|
15
|
+
"isolatedModules": true,
|
|
16
|
+
"noEmit": true
|
|
17
|
+
},
|
|
18
|
+
"include": ["src", "tests"]
|
|
19
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
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 Three-Compat Asset Inspector</title>
|
|
7
|
+
<style>
|
|
8
|
+
html, body, #app { margin: 0; width: 100%; height: 100%; background: #080d15; }
|
|
9
|
+
body { font-family: Inter, ui-sans-serif, system-ui, sans-serif; }
|
|
10
|
+
</style>
|
|
11
|
+
</head>
|
|
12
|
+
<body>
|
|
13
|
+
<div id="app"></div>
|
|
14
|
+
<script type="module" src="/src/main.ts"></script>
|
|
15
|
+
</body>
|
|
16
|
+
</html>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "aura3d-three-compat-asset-inspector",
|
|
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/route-health.spec.ts tests/screenshot.spec.ts --workers=1"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@aura3d/engine": "1.3.3"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@playwright/test": "^1.52.0",
|
|
18
|
+
"typescript": "^5.8.3",
|
|
19
|
+
"vite": "^7.3.2"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { defineConfig } from "@playwright/test";
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
testDir: "./tests",
|
|
5
|
+
use: {
|
|
6
|
+
baseURL: "http://127.0.0.1:4192"
|
|
7
|
+
},
|
|
8
|
+
webServer: {
|
|
9
|
+
command: "npm exec vite -- --host 127.0.0.1 --port 4192 --strictPort",
|
|
10
|
+
url: "http://127.0.0.1:4192",
|
|
11
|
+
reuseExistingServer: !process.env.CI,
|
|
12
|
+
timeout: 120_000
|
|
13
|
+
}
|
|
14
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Three-compat asset inspector: one centered primitive on a neutral stage
|
|
2
|
+
// with inspection lighting, authored through the public @aura3d/engine API.
|
|
3
|
+
// Swap the torus for `model(assets.yourAsset)` once you add typed assets.
|
|
4
|
+
import { camera, createAuraApp, lights, material, primitives, scene } from "@aura3d/engine";
|
|
5
|
+
|
|
6
|
+
createAuraApp("#app", {
|
|
7
|
+
diagnostics: { overlay: true },
|
|
8
|
+
scene: scene()
|
|
9
|
+
.background("#0b0f16")
|
|
10
|
+
.camera(camera.orbit({ target: [0, 0.9, 0], distance: 4.2 }))
|
|
11
|
+
.add(primitives.box({ name: "inspection stage", size: [4, 0.1, 4], position: [0, -0.05, 0], material: material.pbr({ color: "#1d2533", roughness: 0.9 }), receiveShadow: true }))
|
|
12
|
+
.add(primitives.torus({ name: "inspected asset", size: 1.1, position: [0, 0.95, 0], rotation: [0.5, 0.35, 0], material: material.metal({ color: "#c9d4e4", roughness: 0.22 }), castShadow: true }))
|
|
13
|
+
.add(primitives.box({ name: "scale reference cube", size: 0.25, position: [1.4, 0.125, 0.9], material: material.pbr({ color: "#38d6ff" }), castShadow: true }))
|
|
14
|
+
.add(lights.ambient({ intensity: 0.3 }))
|
|
15
|
+
.add(lights.directional({ name: "key", position: [3, 4, 3], intensity: 1.5 }))
|
|
16
|
+
.add(lights.point({ name: "rim", position: [-2.5, 2, -2], intensity: 1.4, color: "#9fc4ff" }))
|
|
17
|
+
});
|