create-aura3d 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/dist/cli.d.ts +3 -0
  2. package/dist/cli.d.ts.map +1 -0
  3. package/dist/cli.js +27 -0
  4. package/dist/cli.js.map +1 -0
  5. package/dist/index.d.ts +16 -0
  6. package/dist/index.d.ts.map +1 -0
  7. package/dist/index.js +70 -0
  8. package/dist/index.js.map +1 -0
  9. package/package.json +55 -0
  10. package/templates/cartoon-channel/README.md +42 -0
  11. package/templates/cartoon-channel/index.html +13 -0
  12. package/templates/cartoon-channel/package.json +21 -0
  13. package/templates/cartoon-channel/playwright.config.ts +13 -0
  14. package/templates/cartoon-channel/src/aura-assets.ts +14 -0
  15. package/templates/cartoon-channel/src/characters.ts +28 -0
  16. package/templates/cartoon-channel/src/contract.ts +1 -0
  17. package/templates/cartoon-channel/src/episode.ts +300 -0
  18. package/templates/cartoon-channel/src/main.ts +322 -0
  19. package/templates/cartoon-channel/src/render-plan.ts +405 -0
  20. package/templates/cartoon-channel/src/sets.ts +14 -0
  21. package/templates/cartoon-channel/tests/route-health.spec.ts +7 -0
  22. package/templates/cartoon-channel/tests/storyboard-playback.spec.ts +74 -0
  23. package/templates/cartoon-channel/tsconfig.json +12 -0
  24. package/templates/cinematic-scene/README.md +11 -0
  25. package/templates/cinematic-scene/index.html +13 -0
  26. package/templates/cinematic-scene/package.json +21 -0
  27. package/templates/cinematic-scene/playwright.config.ts +14 -0
  28. package/templates/cinematic-scene/public/aura-assets/hero-fixture.glb +0 -0
  29. package/templates/cinematic-scene/public/aura-assets/hero.thumb.svg +1 -0
  30. package/templates/cinematic-scene/src/aura-assets.ts +17 -0
  31. package/templates/cinematic-scene/src/main.ts +27 -0
  32. package/templates/cinematic-scene/tests/route-health.spec.ts +16 -0
  33. package/templates/cinematic-scene/tests/screenshot.spec.ts +56 -0
  34. package/templates/cinematic-scene/tsconfig.json +15 -0
  35. package/templates/fighting-game/README.md +64 -0
  36. package/templates/fighting-game/index.html +13 -0
  37. package/templates/fighting-game/package.json +21 -0
  38. package/templates/fighting-game/playwright.config.ts +12 -0
  39. package/templates/fighting-game/src/aura-assets.ts +11 -0
  40. package/templates/fighting-game/src/game/fighters.ts +152 -0
  41. package/templates/fighting-game/src/game/moves.ts +70 -0
  42. package/templates/fighting-game/src/game/stage.ts +98 -0
  43. package/templates/fighting-game/src/main.ts +417 -0
  44. package/templates/fighting-game/src/styles.css +124 -0
  45. package/templates/fighting-game/tests/gameplay-smoke.spec.ts +23 -0
  46. package/templates/fighting-game/tests/route-health.spec.ts +11 -0
  47. package/templates/fighting-game/tsconfig.json +19 -0
  48. package/templates/mini-game/README.md +10 -0
  49. package/templates/mini-game/index.html +13 -0
  50. package/templates/mini-game/package.json +21 -0
  51. package/templates/mini-game/playwright.config.ts +14 -0
  52. package/templates/mini-game/public/aura-assets/player-fixture.glb +0 -0
  53. package/templates/mini-game/public/aura-assets/player.thumb.svg +8 -0
  54. package/templates/mini-game/src/aura-assets.ts +17 -0
  55. package/templates/mini-game/src/main.ts +27 -0
  56. package/templates/mini-game/tests/route-health.spec.ts +16 -0
  57. package/templates/mini-game/tests/screenshot.spec.ts +63 -0
  58. package/templates/mini-game/tsconfig.json +15 -0
  59. package/templates/product-viewer/README.md +16 -0
  60. package/templates/product-viewer/index.html +16 -0
  61. package/templates/product-viewer/package.json +21 -0
  62. package/templates/product-viewer/playwright.config.ts +14 -0
  63. package/templates/product-viewer/public/aura-assets/product-fixture.glb +0 -0
  64. package/templates/product-viewer/public/aura-assets/product.thumb.svg +1 -0
  65. package/templates/product-viewer/src/aura-assets.ts +17 -0
  66. package/templates/product-viewer/src/main.ts +27 -0
  67. package/templates/product-viewer/tests/route-health.spec.ts +16 -0
  68. package/templates/product-viewer/tests/screenshot.spec.ts +60 -0
  69. package/templates/product-viewer/tsconfig.json +15 -0
  70. package/templates/prompt-cartoon-channel/README.md +42 -0
  71. package/templates/prompt-cartoon-channel/index.html +13 -0
  72. package/templates/prompt-cartoon-channel/package.json +21 -0
  73. package/templates/prompt-cartoon-channel/playwright.config.ts +13 -0
  74. package/templates/prompt-cartoon-channel/src/aura-assets.ts +14 -0
  75. package/templates/prompt-cartoon-channel/src/characters.ts +28 -0
  76. package/templates/prompt-cartoon-channel/src/contract.ts +1 -0
  77. package/templates/prompt-cartoon-channel/src/episode.ts +300 -0
  78. package/templates/prompt-cartoon-channel/src/main.ts +314 -0
  79. package/templates/prompt-cartoon-channel/src/render-plan.ts +405 -0
  80. package/templates/prompt-cartoon-channel/src/sets.ts +14 -0
  81. package/templates/prompt-cartoon-channel/tests/route-health.spec.ts +7 -0
  82. package/templates/prompt-cartoon-channel/tests/storyboard-playback.spec.ts +74 -0
  83. package/templates/prompt-cartoon-channel/tsconfig.json +12 -0
@@ -0,0 +1,17 @@
1
+ import { defineAuraAssets } from "@aura3d/engine";
2
+
3
+ export const assets = defineAuraAssets({
4
+ playerModel: {
5
+ type: "model",
6
+ format: "glb",
7
+ url: "/aura-assets/player-fixture.glb",
8
+ bounds: [1.2, 1.9, 0.8],
9
+ hash: "sha256-047f5e5fb3bb6d378bd1df16ca6137f2a596c99b3a1b5690b4020c05aaf6f319",
10
+ metadata: {
11
+ materials: ["painted robot body", "black joints", "white armor"],
12
+ animations: [],
13
+ textures: [],
14
+ thumbnailUrl: "/aura-assets/player.thumb.svg"
15
+ }
16
+ }
17
+ } as const);
@@ -0,0 +1,27 @@
1
+ import { createAuraApp, definePromptPlan, promptPlanToScene } from "@aura3d/engine";
2
+ import { assets } from "./aura-assets";
3
+
4
+ const plan = definePromptPlan({
5
+ sceneType: "mini-game",
6
+ subject: { asset: assets.playerModel, label: "player" },
7
+ style: "readable neon collect-and-dodge arena",
8
+ environment: "bounded board with rails, lane markings, coins, hazards, and portal goal",
9
+ camera: { preset: "game-board" },
10
+ lighting: { preset: "game-readable" },
11
+ effects: ["motion-trail", "hud", "bloom"],
12
+ interaction: "keyboard",
13
+ acceptanceCriteria: [
14
+ "player, coins, hazards, and portal are all visible",
15
+ "arena boundaries and lane direction are readable",
16
+ "motion trail and shield communicate active game state"
17
+ ],
18
+ negativeCriteria: [
19
+ "do not accept random primitives without readable game state",
20
+ "do not accept a character on an empty grid as a mini-game"
21
+ ]
22
+ } as const);
23
+
24
+ createAuraApp("#app", {
25
+ diagnostics: { overlay: true, performancePanel: true },
26
+ scene: promptPlanToScene(plan)
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 mini game 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,63 @@
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 mini game screenshot shows a playable collect-and-dodge scene", async ({ page }) => {
8
+ await page.goto("/");
9
+ await expect.poll(() => page.locator("body").getAttribute("data-aura3d-ready"), { timeout: 45_000 }).toBe("true");
10
+ const canvas = page.locator("canvas");
11
+ const profile = await canvas.evaluate((element) => {
12
+ const target = element as HTMLCanvasElement;
13
+ const gl = target.getContext("webgl2", { preserveDrawingBuffer: true });
14
+ if (!gl) return { error: "missing-webgl2", robotArmorPixels: 0, robotJointPixels: 0, boostPixels: 0, coinPixels: 0, hazardPixels: 0, portalPixels: 0, cyanTrailPixels: 0, arenaPixels: 0, uniqueBuckets: 0 };
15
+ const pixels = new Uint8Array(target.width * target.height * 4);
16
+ gl.readPixels(0, 0, target.width, target.height, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
17
+ const buckets = new Set<string>();
18
+ let robotArmorPixels = 0;
19
+ let robotJointPixels = 0;
20
+ let boostPixels = 0;
21
+ let coinPixels = 0;
22
+ let hazardPixels = 0;
23
+ let portalPixels = 0;
24
+ let cyanTrailPixels = 0;
25
+ let arenaPixels = 0;
26
+ for (let y = 0; y < target.height; y += 4) {
27
+ for (let x = 0; x < target.width; x += 4) {
28
+ if (x > target.width * 0.76 && y > target.height * 0.74) continue;
29
+ const offset = (y * target.width + x) * 4;
30
+ const r = pixels[offset] ?? 0;
31
+ const g = pixels[offset + 1] ?? 0;
32
+ const b = pixels[offset + 2] ?? 0;
33
+ const luminance = r * 0.2126 + g * 0.7152 + b * 0.0722;
34
+ if (luminance > 32) buckets.add(`${r >> 5}-${g >> 5}-${b >> 5}`);
35
+ const inPlayerWindow = x > target.width * 0.2 && x < target.width * 0.42 && y > target.height * 0.34 && y < target.height * 0.7;
36
+ if (inPlayerWindow && r > 120 && g > 118 && b > 86 && Math.abs(r - g) < 70) robotArmorPixels += 1;
37
+ if (inPlayerWindow && r < 95 && g < 115 && b < 125 && luminance > 18) robotJointPixels += 1;
38
+ if (inPlayerWindow && r > 150 && g > 80 && b < 82) boostPixels += 1;
39
+ if (r > 170 && g > 130 && b < 110) coinPixels += 1;
40
+ if (r > 165 && g < 125 && b < 145) hazardPixels += 1;
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
+ }
45
+ }
46
+ return { robotArmorPixels, robotJointPixels, boostPixels, coinPixels, hazardPixels, portalPixels, cyanTrailPixels, arenaPixels, uniqueBuckets: buckets.size };
47
+ });
48
+ const screenshot = await page.screenshot({ fullPage: false });
49
+ mkdirSync(resolve("tests/reports"), { recursive: true });
50
+ writeFileSync(resolve("tests/reports/screenshot.png"), screenshot);
51
+ writeFileSync(resolve("tests/reports/screenshot.json"), `${JSON.stringify({ bytes: screenshot.byteLength, profile }, null, 2)}\n`);
52
+ expect(profile.error).toBeUndefined();
53
+ expect(profile.robotArmorPixels).toBeGreaterThan(90);
54
+ expect(profile.robotJointPixels).toBeGreaterThan(18);
55
+ expect(profile.boostPixels).toBeGreaterThan(8);
56
+ expect(profile.coinPixels).toBeGreaterThan(35);
57
+ expect(profile.hazardPixels).toBeGreaterThan(45);
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(screenshot.byteLength).toBeGreaterThan(1000);
63
+ });
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "useDefineForClassFields": true,
5
+ "module": "ESNext",
6
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
7
+ "skipLibCheck": true,
8
+ "moduleResolution": "Bundler",
9
+ "allowImportingTsExtensions": true,
10
+ "isolatedModules": true,
11
+ "noEmit": true,
12
+ "strict": true
13
+ },
14
+ "include": ["src", "tests", "playwright.config.ts"]
15
+ }
@@ -0,0 +1,16 @@
1
+ # Aura3D Product Viewer
2
+
3
+ Agent-friendly product scene starter using the public `@aura3d/engine` API.
4
+
5
+ ```bash
6
+ npm install
7
+ npm run dev
8
+ npx @aura3d/cli@latest assets add ./assets/product.glb --name product
9
+ npm run test
10
+ ```
11
+
12
+ Edit `src/main.ts` to change camera, material, lights, and diagnostics. Do not
13
+ invent asset paths; after `assets add`, use `assets.product` from
14
+ `src/aura-assets.ts`. The default scene uses `prefabs.productViewer(assets.product)`
15
+ so a replacement product keeps the plinth, contact shadow, softboxes, and
16
+ turntable/orbit evidence together.
@@ -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 Product Viewer</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-product-viewer",
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.0.5"
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:4173"
7
+ },
8
+ webServer: {
9
+ command: "npm exec vite -- --host 127.0.0.1 --port 4173 --strictPort",
10
+ url: "http://127.0.0.1:4173",
11
+ reuseExistingServer: !process.env.CI,
12
+ timeout: 120_000
13
+ }
14
+ });
@@ -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="#8fb4ff"/><text x="160" y="160" text-anchor="middle" font-family="Arial" font-size="18" fill="#f4f7fb">product</text></svg>
@@ -0,0 +1,17 @@
1
+ import { defineAuraAssets } from "@aura3d/engine";
2
+
3
+ export const assets = defineAuraAssets({
4
+ product: {
5
+ type: "model",
6
+ format: "glb",
7
+ url: "/aura-assets/product-fixture.glb",
8
+ bounds: [1.9, 2.9, 1.1],
9
+ hash: "sha256-5613d5ad4ddc538b02c147faf2f87777bfd5a79580297cc6a103fdb7556580d3",
10
+ metadata: {
11
+ materials: ["satin cabinet", "patterned grille", "rubber cone", "metallic knobs"],
12
+ animations: [],
13
+ textures: ["embedded product material swatches"],
14
+ thumbnailUrl: "/aura-assets/product.thumb.svg"
15
+ }
16
+ }
17
+ } as const);
@@ -0,0 +1,27 @@
1
+ import { createAuraApp, definePromptPlan, promptPlanToScene } from "@aura3d/engine";
2
+ import { assets } from "./aura-assets";
3
+
4
+ const plan = definePromptPlan({
5
+ sceneType: "product-viewer",
6
+ subject: { asset: assets.product, label: "studio product" },
7
+ style: "premium studio product inspection with visible fit and turntable cues",
8
+ environment: "studio sweep, round plinth, contact shadow, softboxes, reflection cards",
9
+ camera: { preset: "product-orbit" },
10
+ lighting: { preset: "studio-softbox" },
11
+ effects: ["bloom"],
12
+ interaction: "orbit",
13
+ acceptanceCriteria: [
14
+ "product is centered, normalized, and seated on the plinth",
15
+ "softboxes, reflection cards, and contact shadow shape the asset",
16
+ "turntable and orbit cues are visible without reading diagnostics"
17
+ ],
18
+ negativeCriteria: [
19
+ "Do not use string asset ids or invented product URLs",
20
+ "Do not ship a lone GLB without plinth, contact, fit, and rotation evidence"
21
+ ]
22
+ } as const);
23
+
24
+ createAuraApp("#app", {
25
+ diagnostics: { overlay: true, assetPanel: true, performancePanel: true },
26
+ scene: promptPlanToScene(plan)
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 product viewer 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,60 @@
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 product viewer screenshot shows a prompt-aligned studio product", async ({ page }) => {
8
+ await page.goto("/");
9
+ await expect.poll(() => page.locator("body").getAttribute("data-aura3d-ready"), { timeout: 45_000 }).toBe("true");
10
+ const canvas = page.locator("canvas");
11
+ await expect(canvas).toBeVisible();
12
+ const profile = await canvas.evaluate((element) => {
13
+ const target = element as HTMLCanvasElement;
14
+ const gl = target.getContext("webgl2", { preserveDrawingBuffer: true });
15
+ if (!gl) return { error: "missing-webgl2", cabinetPixels: 0, grillePixels: 0, metalPixels: 0, softboxPixels: 0, warmReflectionPixels: 0, centerObjectPixels: 0, uniqueBuckets: 0 };
16
+ const pixels = new Uint8Array(target.width * target.height * 4);
17
+ gl.readPixels(0, 0, target.width, target.height, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
18
+ const buckets = new Set<string>();
19
+ let cabinetPixels = 0;
20
+ let grillePixels = 0;
21
+ let metalPixels = 0;
22
+ let softboxPixels = 0;
23
+ let warmReflectionPixels = 0;
24
+ let centerObjectPixels = 0;
25
+ for (let y = 0; y < target.height; y += 4) {
26
+ for (let x = 0; x < target.width; x += 4) {
27
+ if (x > target.width * 0.76 && y > target.height * 0.74) continue;
28
+ const offset = (y * target.width + x) * 4;
29
+ const r = pixels[offset] ?? 0;
30
+ const g = pixels[offset + 1] ?? 0;
31
+ const b = pixels[offset + 2] ?? 0;
32
+ const luminance = r * 0.2126 + g * 0.7152 + b * 0.0722;
33
+ if (luminance > 32) buckets.add(`${r >> 5}-${g >> 5}-${b >> 5}`);
34
+ const inProductWindow = x > target.width * 0.34 && x < target.width * 0.68 && y > target.height * 0.24 && y < target.height * 0.78;
35
+ const warmCabinetEdge = r > 105 && g > 72 && b > 48 && r > g * 1.08 && g > b * 1.08;
36
+ const darkCabinetBody = luminance > 10 && luminance < 62 && r >= b * 0.78 && g >= b * 0.64;
37
+ if (inProductWindow && (warmCabinetEdge || darkCabinetBody)) cabinetPixels += 1;
38
+ if (inProductWindow && r < 48 && g < 58 && b < 70 && luminance > 18) grillePixels += 1;
39
+ if (inProductWindow && r > 145 && g > 145 && b > 138 && Math.abs(r - g) < 32 && Math.abs(g - b) < 40) metalPixels += 1;
40
+ if (r > 175 && g > 180 && b > 185 && Math.abs(r - g) < 45 && Math.abs(g - b) < 48) softboxPixels += 1;
41
+ if (r > 95 && g > 62 && b < 78 && r > g * 1.12) warmReflectionPixels += 1;
42
+ if (inProductWindow && luminance > 32) centerObjectPixels += 1;
43
+ }
44
+ }
45
+ return { cabinetPixels, grillePixels, metalPixels, softboxPixels, warmReflectionPixels, centerObjectPixels, uniqueBuckets: buckets.size };
46
+ });
47
+ const screenshot = await page.screenshot({ fullPage: false });
48
+ mkdirSync(resolve("tests/reports"), { recursive: true });
49
+ writeFileSync(resolve("tests/reports/screenshot.png"), screenshot);
50
+ writeFileSync(resolve("tests/reports/screenshot.json"), `${JSON.stringify({ bytes: screenshot.byteLength, profile }, null, 2)}\n`);
51
+ expect(profile.error).toBeUndefined();
52
+ expect(profile.cabinetPixels).toBeGreaterThan(900);
53
+ expect(profile.grillePixels).toBeGreaterThan(60);
54
+ expect(profile.metalPixels).toBeGreaterThan(5);
55
+ expect(profile.softboxPixels).toBeGreaterThan(180);
56
+ expect(profile.warmReflectionPixels).toBeGreaterThan(20);
57
+ expect(profile.centerObjectPixels).toBeGreaterThan(650);
58
+ expect(profile.uniqueBuckets).toBeGreaterThan(18);
59
+ expect(screenshot.byteLength).toBeGreaterThan(1000);
60
+ });
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "useDefineForClassFields": true,
5
+ "module": "ESNext",
6
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
7
+ "skipLibCheck": true,
8
+ "moduleResolution": "Bundler",
9
+ "allowImportingTsExtensions": true,
10
+ "isolatedModules": true,
11
+ "noEmit": true,
12
+ "strict": true
13
+ },
14
+ "include": ["src", "tests", "playwright.config.ts"]
15
+ }
@@ -0,0 +1,42 @@
1
+ # Aura3D Cartoon Channel Template
2
+
3
+ This template demonstrates the AuraVoice-to-Aura3D prompt-animation contract.
4
+
5
+ It uses:
6
+
7
+ - `compilePromptEpisodePlan(...)` for prompt-to-episode planning.
8
+ - Optional typed GLB characters from `./src/aura-assets` with primitive runtime fallbacks until assets are added.
9
+ - `createShotPlaybackPlan(...)` plus `installShotPlayback(...)` so `app.onFrame` updates nodes without recreating the app.
10
+ - `createAuraVoiceBridgePackage(...)`, `validateAuraVoiceBridgePackage(...)`, `collectPromptAnimationEvidence(...)`, and `evaluatePromptAnimationPublishReadiness(...)` for source-level AuraVoice/Aura3D handoff declarations.
11
+ - Caption HUD and caption timing proof metadata.
12
+ - Render queue metadata and three deterministic screenshot fixture records.
13
+ - Child-safe, reduced-motion, and high-contrast accessibility proof defaults.
14
+ - Primitive-mouth and typed-GLB viseme examples.
15
+ - Phoneme/viseme/dub source proof metadata for stable shot, storyboard, caption, line, and word timing ids.
16
+
17
+ Replace primitive characters with typed GLB assets by running:
18
+
19
+ ```bash
20
+ npx @aura3d/cli@latest assets add ./assets/miko.glb --name miko
21
+ npx @aura3d/cli@latest assets add ./assets/luma.glb --name luma
22
+ npx @aura3d/cli@latest assets validate-cartoon
23
+ ```
24
+
25
+ Then import `assets.miko` and `assets.luma` from `src/aura-assets.ts` and pass them to `model(assets.miko)` and `model(assets.luma)`.
26
+
27
+ The GLB path must keep typed assets:
28
+
29
+ ```ts
30
+ import { model } from "@aura3d/engine";
31
+ import { assets } from "./aura-assets";
32
+
33
+ model(assets.miko);
34
+ ```
35
+
36
+ Do not replace this with a string asset id or a raw loader.
37
+
38
+ This scaffold intentionally does not claim publish readiness from source alone.
39
+ Before closing build, route, asset, screenshot, render, or visual-quality gates,
40
+ archive the matching `npm run build`, `assets validate-cartoon`, browser
41
+ evidence, screenshot hashes, render outputs, and human or automated review
42
+ artifacts.
@@ -0,0 +1,13 @@
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 Cartoon Channel</title>
7
+ </head>
8
+ <body>
9
+ <div id="app"></div>
10
+ <script type="module" src="/src/main.ts"></script>
11
+ </body>
12
+ </html>
13
+
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "aura3d-cartoon-channel",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite --host 0.0.0.0",
8
+ "typecheck": "tsc --noEmit",
9
+ "build": "npm run typecheck && vite build",
10
+ "preview": "vite preview --host 0.0.0.0",
11
+ "test": "playwright test tests/route-health.spec.ts tests/storyboard-playback.spec.ts --workers=1"
12
+ },
13
+ "dependencies": {
14
+ "@aura3d/engine": "1.0.5"
15
+ },
16
+ "devDependencies": {
17
+ "@playwright/test": "^1.52.0",
18
+ "typescript": "^5.9.3",
19
+ "vite": "^7.2.4"
20
+ }
21
+ }
@@ -0,0 +1,13 @@
1
+ import { defineConfig } from "@playwright/test";
2
+
3
+ export default defineConfig({
4
+ webServer: {
5
+ command: "npm run dev -- --port 4178",
6
+ url: "http://127.0.0.1:4178",
7
+ reuseExistingServer: !process.env.CI
8
+ },
9
+ use: {
10
+ baseURL: "http://127.0.0.1:4178"
11
+ }
12
+ });
13
+
@@ -0,0 +1,14 @@
1
+ import { defineAuraAssets } from "@aura3d/engine";
2
+ import { AURAVOICE_AURA3D_PROMPT_ANIMATION_CONTRACT_ID } from "./contract";
3
+
4
+ export const assetContractId = AURAVOICE_AURA3D_PROMPT_ANIMATION_CONTRACT_ID;
5
+
6
+ export const assets = defineAuraAssets({
7
+ /*
8
+ * Add character GLBs with the Aura3D CLI, then keep these generated keys:
9
+ *
10
+ * npx @aura3d/cli@latest assets add ./assets/miko.glb --name miko
11
+ * npx @aura3d/cli@latest assets add ./assets/luma.glb --name luma
12
+ * npx @aura3d/cli@latest assets validate-cartoon
13
+ */
14
+ });
@@ -0,0 +1,28 @@
1
+ import { AURAVOICE_AURA3D_PROMPT_ANIMATION_CONTRACT_ID } from "./contract";
2
+
3
+ export const characterContractId = AURAVOICE_AURA3D_PROMPT_ANIMATION_CONTRACT_ID;
4
+
5
+ export const characters = [
6
+ {
7
+ id: "miko",
8
+ name: "Miko",
9
+ palette: ["#7de2ff", "#f7ffe8"],
10
+ primitiveFallback: "round robot with antenna",
11
+ typedAssetKey: "miko",
12
+ runtimeNodeId: "miko",
13
+ primitiveMouthNodeId: "miko:mouth",
14
+ performanceChannels: ["body", "facial", "gesture", "blocking", "gaze"],
15
+ glbUpgrade: "After assets add, use model(assets.miko) and keep viseme blendshape metadata."
16
+ },
17
+ {
18
+ id: "luma",
19
+ name: "Luma",
20
+ palette: ["#ffe18e", "#40ffbf"],
21
+ primitiveFallback: "small moon-garden helper",
22
+ typedAssetKey: "luma",
23
+ runtimeNodeId: "luma",
24
+ primitiveMouthNodeId: "luma:mouth",
25
+ performanceChannels: ["body", "facial", "gesture", "blocking", "gaze"],
26
+ glbUpgrade: "After assets add, use model(assets.luma) and keep viseme blendshape metadata."
27
+ }
28
+ ];
@@ -0,0 +1 @@
1
+ export const AURAVOICE_AURA3D_PROMPT_ANIMATION_CONTRACT_ID = "auravoice-aura3d-prompt-animation/v1" as const;