create-aura3d 1.0.9 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +0 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/package.json +8 -4
- package/templates/cartoon-channel/README.md +38 -0
- package/templates/cartoon-channel/aura.assets.json +1364 -0
- package/templates/cartoon-channel/package.json +3 -2
- package/templates/cartoon-channel/public/aura-assets/luma.047f5e5f.glb +0 -0
- package/templates/cartoon-channel/public/aura-assets/luma.thumb.svg +1 -0
- package/templates/cartoon-channel/public/aura-assets/miko.047f5e5f.glb +0 -0
- package/templates/cartoon-channel/public/aura-assets/miko.thumb.svg +1 -0
- package/templates/cartoon-channel/public/aura-assets/moon-garden-feature-frame.png +0 -0
- package/templates/cartoon-channel/src/aura-assets.ts +5 -11
- package/templates/cartoon-channel/src/concept-episode-2-5d.css +159 -0
- package/templates/cartoon-channel/src/concept-episode-2-5d.ts +155 -0
- package/templates/cartoon-channel/src/image-puppet-episode.css +290 -0
- package/templates/cartoon-channel/src/image-puppet-episode.ts +139 -0
- package/templates/cartoon-channel/src/main.ts +89 -71
- package/templates/cartoon-channel/src/puppet-episode-2d.css +526 -0
- package/templates/cartoon-channel/src/puppet-episode-2d.ts +189 -0
- package/templates/cartoon-channel/src/render-plan.ts +1 -1
- package/templates/cartoon-channel/src/sample-episode-visual.css +457 -0
- package/templates/cartoon-channel/src/sample-episode-visual.ts +185 -0
- package/templates/cartoon-channel/test-results/.last-run.json +4 -0
- package/templates/cartoon-channel/tests/concept-2-5d-animation.spec.ts +18 -0
- package/templates/cartoon-channel/tests/concept-2-5d.spec.ts +30 -0
- package/templates/cartoon-channel/tests/image-puppet-animation.spec.ts +18 -0
- package/templates/cartoon-channel/tests/image-puppet.spec.ts +29 -0
- package/templates/cartoon-channel/tests/puppet-2d-animation.spec.ts +18 -0
- package/templates/cartoon-channel/tests/puppet-2d.spec.ts +26 -0
- package/templates/cartoon-channel/tests/reports/prompt-animation/cartoon-sample-episode.png +0 -0
- package/templates/cartoon-channel/tests/sample-episode-visual.spec.ts +45 -0
- package/templates/cartoon-channel/tests/storyboard-playback.spec.ts +1 -1
- package/templates/cartoon-studio/README.md +82 -0
- package/templates/cartoon-studio/aura.assets.json +157 -0
- package/templates/cartoon-studio/index.html +13 -0
- package/templates/cartoon-studio/package.json +32 -0
- package/templates/cartoon-studio/playwright.config.ts +13 -0
- package/templates/cartoon-studio/public/aura-assets/luma.047f5e5f.glb +0 -0
- package/templates/cartoon-studio/public/aura-assets/luma.humanoid-fixture.glb +0 -0
- package/templates/cartoon-studio/public/aura-assets/luma.thumb.svg +1 -0
- package/templates/cartoon-studio/public/aura-assets/miko.047f5e5f.glb +0 -0
- package/templates/cartoon-studio/public/aura-assets/miko.thumb.svg +1 -0
- package/templates/cartoon-studio/public/aura-assets/moonGarden.gltf +183 -0
- package/templates/cartoon-studio/scripts/episode.ts +276 -0
- package/templates/cartoon-studio/src/aura-assets.ts +61 -0
- package/templates/cartoon-studio/src/characters.ts +65 -0
- package/templates/cartoon-studio/src/contract.ts +1 -0
- package/templates/cartoon-studio/src/episode-renderer.ts +605 -0
- package/templates/cartoon-studio/src/episode.ts +306 -0
- package/templates/cartoon-studio/src/main.ts +333 -0
- package/templates/cartoon-studio/src/render-plan.ts +567 -0
- package/templates/cartoon-studio/src/review.ts +99 -0
- package/templates/cartoon-studio/src/sets.ts +14 -0
- package/templates/cartoon-studio/src/studio.ts +101 -0
- package/templates/cartoon-studio/test-results/.last-run.json +4 -0
- package/templates/cartoon-studio/tests/episode-render.spec.ts +44 -0
- package/templates/cartoon-studio/tests/motion-quality.spec.ts +45 -0
- package/templates/cartoon-studio/tests/route-health.spec.ts +7 -0
- package/templates/cartoon-studio/tests/storyboard-playback.spec.ts +99 -0
- package/templates/cartoon-studio/tests/visual-review.spec.ts +21 -0
- package/templates/cartoon-studio/tsconfig.json +12 -0
- package/templates/cinematic-scene/package.json +1 -1
- package/templates/episode-builder/README.md +46 -0
- package/templates/episode-builder/index.html +13 -0
- package/templates/episode-builder/package.json +21 -0
- package/templates/episode-builder/playwright.config.ts +13 -0
- package/templates/episode-builder/src/aura-assets.ts +14 -0
- package/templates/episode-builder/src/builder.ts +78 -0
- package/templates/episode-builder/src/characters.ts +28 -0
- package/templates/episode-builder/src/contract.ts +1 -0
- package/templates/episode-builder/src/episode.ts +300 -0
- package/templates/episode-builder/src/main.ts +265 -0
- package/templates/episode-builder/src/render-plan.ts +405 -0
- package/templates/episode-builder/src/sets.ts +14 -0
- package/templates/episode-builder/tests/route-health.spec.ts +7 -0
- package/templates/episode-builder/tests/storyboard-playback.spec.ts +98 -0
- package/templates/episode-builder/tsconfig.json +12 -0
- package/templates/fighting-game/package.json +1 -1
- package/templates/fighting-game/src/game/stage.ts +18 -4
- package/templates/fighting-game/src/main.ts +3 -2
- package/templates/mini-game/package.json +1 -1
- package/templates/product-viewer/package.json +1 -1
- package/templates/product-viewer/tests/screenshot.spec.ts +8 -16
- package/templates/prompt-cartoon-channel/package.json +1 -1
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { episode, publicCartoonAssetInstructions, typedCartoonAssetSummary } from "./episode";
|
|
2
|
+
import { publishReadiness, renderPlan } from "./render-plan";
|
|
3
|
+
|
|
4
|
+
export const cartoonStudioSupport = {
|
|
5
|
+
template: "cartoon-studio",
|
|
6
|
+
panels: ["timeline", "assets", "performance", "render"] as const,
|
|
7
|
+
timelineTracks: [
|
|
8
|
+
{
|
|
9
|
+
id: "shots",
|
|
10
|
+
label: "Shots",
|
|
11
|
+
clips: episode.shotTimeline.shots.map((shot) => ({
|
|
12
|
+
id: shot.shotId,
|
|
13
|
+
startTime: shot.startTime,
|
|
14
|
+
endTime: shot.endTime,
|
|
15
|
+
cameraMove: shot.camera.move,
|
|
16
|
+
transitionOut: shot.transitionOut ?? "cut"
|
|
17
|
+
}))
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
id: "dialogue",
|
|
21
|
+
label: "Dialogue",
|
|
22
|
+
clips: episode.dialogueTrack.lines.map((line) => ({
|
|
23
|
+
id: line.lineId,
|
|
24
|
+
speakerId: line.speakerId,
|
|
25
|
+
startTime: line.startTime,
|
|
26
|
+
endTime: line.endTime,
|
|
27
|
+
text: line.text
|
|
28
|
+
}))
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
id: "render",
|
|
32
|
+
label: "Render",
|
|
33
|
+
clips: renderPlan.items.map((item) => ({
|
|
34
|
+
id: item.id,
|
|
35
|
+
time: item.time,
|
|
36
|
+
outputIds: item.outputIds,
|
|
37
|
+
width: item.viewport.width,
|
|
38
|
+
height: item.viewport.height
|
|
39
|
+
}))
|
|
40
|
+
}
|
|
41
|
+
],
|
|
42
|
+
assetLibrary: {
|
|
43
|
+
requiredCharacters: typedCartoonAssetSummary.requiredCharacterAssets,
|
|
44
|
+
optionalProps: typedCartoonAssetSummary.optionalPropAssets,
|
|
45
|
+
missingCharacters: typedCartoonAssetSummary.missingCharacterAssets,
|
|
46
|
+
commands: publicCartoonAssetInstructions
|
|
47
|
+
},
|
|
48
|
+
renderPipeline: {
|
|
49
|
+
outputs: renderPlan.outputs.map((output) => output.kind),
|
|
50
|
+
itemCount: renderPlan.items.length,
|
|
51
|
+
publishReadyFromCurrentEvidence: publishReadiness.ready,
|
|
52
|
+
issueCount: publishReadiness.issues.length
|
|
53
|
+
}
|
|
54
|
+
} as const;
|
|
55
|
+
|
|
56
|
+
export function installCartoonStudioPanel(target: HTMLElement): void {
|
|
57
|
+
const panel = document.createElement("section");
|
|
58
|
+
panel.id = "cartoon-studio-panel";
|
|
59
|
+
panel.style.cssText = [
|
|
60
|
+
"position:fixed",
|
|
61
|
+
"left:18px",
|
|
62
|
+
"top:18px",
|
|
63
|
+
"width:min(360px,calc(100vw - 36px))",
|
|
64
|
+
"display:grid",
|
|
65
|
+
"grid-template-columns:repeat(4,1fr)",
|
|
66
|
+
"gap:6px",
|
|
67
|
+
"padding:10px",
|
|
68
|
+
"border:1px solid rgba(248,255,242,0.22)",
|
|
69
|
+
"border-radius:8px",
|
|
70
|
+
"background:rgba(3,12,20,0.78)",
|
|
71
|
+
"color:#f8fff2",
|
|
72
|
+
"font:600 12px/1.2 system-ui,sans-serif",
|
|
73
|
+
"z-index:9"
|
|
74
|
+
].join(";");
|
|
75
|
+
|
|
76
|
+
for (const name of cartoonStudioSupport.panels) {
|
|
77
|
+
const button = document.createElement("button");
|
|
78
|
+
button.type = "button";
|
|
79
|
+
button.textContent = name;
|
|
80
|
+
button.dataset.panel = name;
|
|
81
|
+
button.style.cssText = [
|
|
82
|
+
"min-width:0",
|
|
83
|
+
"height:30px",
|
|
84
|
+
"border:1px solid rgba(125,226,255,0.42)",
|
|
85
|
+
"border-radius:6px",
|
|
86
|
+
"background:rgba(24,45,63,0.84)",
|
|
87
|
+
"color:#f8fff2",
|
|
88
|
+
"font:inherit",
|
|
89
|
+
"text-transform:capitalize"
|
|
90
|
+
].join(";");
|
|
91
|
+
panel.appendChild(button);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const status = document.createElement("div");
|
|
95
|
+
status.id = "cartoon-studio-status";
|
|
96
|
+
status.textContent = `${episode.shotTimeline.shots.length} shots | ${episode.dialogueTrack.lines.length} lines | ${renderPlan.items.length} render cues`;
|
|
97
|
+
status.style.cssText = "grid-column:1 / -1;color:#7de2ff;white-space:normal";
|
|
98
|
+
panel.appendChild(status);
|
|
99
|
+
|
|
100
|
+
target.appendChild(panel);
|
|
101
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { expect, test } from "@playwright/test";
|
|
2
|
+
import {
|
|
3
|
+
createCartoonEpisodePackage,
|
|
4
|
+
createCartoonRenderManifestArtifact,
|
|
5
|
+
createRenderedPromptAnimationEvidenceArtifact
|
|
6
|
+
} from "../src/episode-renderer";
|
|
7
|
+
import { requiredCartoonEpisodePackageFiles } from "../src/review";
|
|
8
|
+
|
|
9
|
+
test("episode renderer package declares encoded video and required package files", () => {
|
|
10
|
+
const pkg = createCartoonEpisodePackage("package");
|
|
11
|
+
const filePaths = new Set(pkg.files.map((file) => file.path));
|
|
12
|
+
const manifest = createCartoonRenderManifestArtifact();
|
|
13
|
+
const evidence = createRenderedPromptAnimationEvidenceArtifact();
|
|
14
|
+
|
|
15
|
+
expect(pkg.packageDirectory).toBe("dist/episodes/moon-garden-001");
|
|
16
|
+
expect(pkg.hasWebm).toBe(true);
|
|
17
|
+
expect(pkg.hasPngSequenceFallback).toBe(false);
|
|
18
|
+
expect(pkg.requiredFiles).toEqual([...requiredCartoonEpisodePackageFiles]);
|
|
19
|
+
expect([...requiredCartoonEpisodePackageFiles]
|
|
20
|
+
.filter((file) => file !== "episode.webm" && file !== "thumbnail.png")
|
|
21
|
+
.every((file) => filePaths.has(file))).toBe(true);
|
|
22
|
+
expect(filePaths.has("episode.webm")).toBe(false);
|
|
23
|
+
expect(filePaths.has("thumbnail.png")).toBe(false);
|
|
24
|
+
expect(pkg.files.filter((file) => /^frames\/frame-\d+\.svg$/.test(file.path)).length).toBeGreaterThan(20);
|
|
25
|
+
|
|
26
|
+
expect(manifest).toMatchObject({
|
|
27
|
+
artifact: "cartoon-render-manifest",
|
|
28
|
+
outputMode: "encoded-webm",
|
|
29
|
+
hasEncodedVideo: true,
|
|
30
|
+
encodedVideo: {
|
|
31
|
+
path: "episode.webm",
|
|
32
|
+
codec: "vp9"
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
expect(evidence).toMatchObject({
|
|
36
|
+
artifact: "prompt-animation-evidence",
|
|
37
|
+
ok: true,
|
|
38
|
+
renderOutput: {
|
|
39
|
+
mode: "encoded-webm",
|
|
40
|
+
encodedVideoPresent: true,
|
|
41
|
+
pngSequenceFallbackPresent: false
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { expect, test } from "@playwright/test";
|
|
2
|
+
import { createCartoonMotionQualityArtifact, createCartoonVisualAcceptanceArtifact } from "../src/episode-renderer";
|
|
3
|
+
|
|
4
|
+
test("motion quality rejects still-image wobble and requires regional character motion", () => {
|
|
5
|
+
const motion = createCartoonMotionQualityArtifact();
|
|
6
|
+
|
|
7
|
+
expect(motion).toMatchObject({
|
|
8
|
+
artifact: "cartoon-motion-quality",
|
|
9
|
+
videoPath: "episode.webm",
|
|
10
|
+
globalOnlyMotion: false,
|
|
11
|
+
flatLayerMotion: false,
|
|
12
|
+
cameraMotionDeclared: true,
|
|
13
|
+
rejectedEvidence: {
|
|
14
|
+
stillImageWobbleAccepted: false,
|
|
15
|
+
subtitleOverStillAccepted: false,
|
|
16
|
+
sourceOnlyAccepted: false
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
expect(motion.independentRegionMotionSegments).toBeGreaterThanOrEqual(2);
|
|
20
|
+
expect(motion.characterRegionMotionSegments).toBeGreaterThanOrEqual(2);
|
|
21
|
+
expect(motion.bodyRegionMotionSegments).toBeGreaterThanOrEqual(2);
|
|
22
|
+
expect(motion.mouthMotionSegments).toBeGreaterThanOrEqual(2);
|
|
23
|
+
expect(motion.regions.some((region) => region.kind === "mouth" && region.movesDuringDialogue)).toBe(true);
|
|
24
|
+
expect(motion.regions.some((region) => region.kind === "limb" && region.movesDuringAction)).toBe(true);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("visual acceptance requires both characters and refuses source-only publish evidence", () => {
|
|
28
|
+
const visual = createCartoonVisualAcceptanceArtifact();
|
|
29
|
+
|
|
30
|
+
expect(visual).toMatchObject({
|
|
31
|
+
artifact: "cartoon-visual-acceptance",
|
|
32
|
+
ok: true,
|
|
33
|
+
visualOk: true,
|
|
34
|
+
encodedVideoPresent: true,
|
|
35
|
+
notTrue3DAccepted: false,
|
|
36
|
+
sourceOnlyAcceptedAsPublishProof: false,
|
|
37
|
+
requiredHumanReview: true,
|
|
38
|
+
visibleCharacterCount: 2,
|
|
39
|
+
globalOnlyMotion: false,
|
|
40
|
+
flatLayerMotion: false,
|
|
41
|
+
reviewerRequiredBeforePublish: true
|
|
42
|
+
});
|
|
43
|
+
expect(visual.frameHashChanges).toBeGreaterThan(1);
|
|
44
|
+
expect(visual.motionSegments.every((segment) => segment.independentRegionMotion)).toBe(true);
|
|
45
|
+
});
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { expect, test } from "@playwright/test";
|
|
2
|
+
|
|
3
|
+
test("cartoon studio storyboard caption renders", async ({ page }) => {
|
|
4
|
+
await page.goto("/");
|
|
5
|
+
await expect(page.locator("#caption-overlay")).toContainText(/moon|sparkle|garden/i);
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
test("storyboard playback, character performance, caption timing, cuts, and nonblank cartoon frames are sourced", async ({
|
|
9
|
+
page
|
|
10
|
+
}) => {
|
|
11
|
+
await page.goto("/");
|
|
12
|
+
|
|
13
|
+
const routeProof = (await page.evaluate(() => {
|
|
14
|
+
const proof = (window as unknown as {
|
|
15
|
+
__AURA3D_CARTOON_EPISODE_PROOF__?: {
|
|
16
|
+
proofKind: string;
|
|
17
|
+
shotIds: readonly string[];
|
|
18
|
+
captionIds: readonly string[];
|
|
19
|
+
storyBible?: unknown;
|
|
20
|
+
studio?: unknown;
|
|
21
|
+
sourceOnlyAcceptedAsPublishProof: boolean;
|
|
22
|
+
playbackProbeSamples: readonly unknown[];
|
|
23
|
+
sampleAt(time: number): unknown;
|
|
24
|
+
};
|
|
25
|
+
}).__AURA3D_CARTOON_EPISODE_PROOF__;
|
|
26
|
+
return {
|
|
27
|
+
proofKind: proof?.proofKind,
|
|
28
|
+
shotIds: proof?.shotIds ?? [],
|
|
29
|
+
captionIds: proof?.captionIds ?? [],
|
|
30
|
+
storyBible: proof?.storyBible,
|
|
31
|
+
studio: proof?.studio,
|
|
32
|
+
sourceOnlyAcceptedAsPublishProof: proof?.sourceOnlyAcceptedAsPublishProof,
|
|
33
|
+
samples: [1, 21, 43].map((time) => proof?.sampleAt(time)),
|
|
34
|
+
playbackProbeSamples: proof?.playbackProbeSamples ?? [],
|
|
35
|
+
bodyShotCount: document.body.dataset.cartoonShotCount,
|
|
36
|
+
bodyCaptionCount: document.body.dataset.cartoonCaptionCount,
|
|
37
|
+
bodyPanels: document.body.dataset.cartoonStudioPanels
|
|
38
|
+
};
|
|
39
|
+
})) as {
|
|
40
|
+
proofKind?: string;
|
|
41
|
+
shotIds: string[];
|
|
42
|
+
captionIds: string[];
|
|
43
|
+
storyBible?: {
|
|
44
|
+
props?: unknown[];
|
|
45
|
+
styleGuide?: { visualStyle?: string };
|
|
46
|
+
shotList?: unknown[];
|
|
47
|
+
};
|
|
48
|
+
studio?: {
|
|
49
|
+
panels?: readonly string[];
|
|
50
|
+
timelineTracks?: readonly { id: string; clips: readonly unknown[] }[];
|
|
51
|
+
assetLibrary?: { commands?: readonly string[] };
|
|
52
|
+
renderPipeline?: { itemCount?: number };
|
|
53
|
+
};
|
|
54
|
+
samples: Array<{
|
|
55
|
+
shotId?: string;
|
|
56
|
+
captionId?: string;
|
|
57
|
+
captionText?: string;
|
|
58
|
+
cameraMove?: string;
|
|
59
|
+
transitionOut?: string;
|
|
60
|
+
nodeUpdates?: Array<{ characterId?: string; action?: string; emotion?: string }>;
|
|
61
|
+
}>;
|
|
62
|
+
playbackProbeSamples: unknown[];
|
|
63
|
+
sourceOnlyAcceptedAsPublishProof?: boolean;
|
|
64
|
+
bodyShotCount?: string;
|
|
65
|
+
bodyCaptionCount?: string;
|
|
66
|
+
bodyPanels?: string;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
expect(routeProof.proofKind).toBe("aura3d-cartoon-episode-proof");
|
|
70
|
+
expect(routeProof.sourceOnlyAcceptedAsPublishProof).toBe(false);
|
|
71
|
+
expect(routeProof.shotIds).toEqual([
|
|
72
|
+
"shot-moon-garden-open",
|
|
73
|
+
"shot-glow-stone-teamwork",
|
|
74
|
+
"shot-moon-garden-finish"
|
|
75
|
+
]);
|
|
76
|
+
expect(new Set(routeProof.samples.map((sample) => sample.shotId)).size).toBe(3);
|
|
77
|
+
expect(routeProof.captionIds).toHaveLength(6);
|
|
78
|
+
expect(routeProof.samples.every((sample) => sample.captionId && sample.captionText)).toBe(true);
|
|
79
|
+
expect(routeProof.samples.some((sample) => sample.cameraMove === "push-in" || sample.transitionOut === "cut")).toBe(true);
|
|
80
|
+
expect(routeProof.samples.flatMap((sample) => sample.nodeUpdates ?? []).some((update) => update.action === "speak")).toBe(true);
|
|
81
|
+
expect(routeProof.samples.flatMap((sample) => sample.nodeUpdates ?? []).some((update) => update.characterId === "miko")).toBe(true);
|
|
82
|
+
expect(routeProof.samples.flatMap((sample) => sample.nodeUpdates ?? []).some((update) => update.characterId === "luma")).toBe(true);
|
|
83
|
+
expect(routeProof.storyBible?.props?.length).toBeGreaterThanOrEqual(3);
|
|
84
|
+
expect(routeProof.storyBible?.styleGuide?.visualStyle).toMatch(/cartoon/i);
|
|
85
|
+
expect(routeProof.storyBible?.shotList).toHaveLength(3);
|
|
86
|
+
expect(routeProof.playbackProbeSamples).toHaveLength(3);
|
|
87
|
+
expect(routeProof.bodyShotCount).toBe("3");
|
|
88
|
+
expect(routeProof.bodyCaptionCount).toBe("6");
|
|
89
|
+
expect(routeProof.studio?.panels).toEqual(["timeline", "assets", "performance", "render"]);
|
|
90
|
+
expect(routeProof.studio?.timelineTracks?.map((track) => track.id)).toEqual(["shots", "dialogue", "render"]);
|
|
91
|
+
expect(routeProof.studio?.timelineTracks?.every((track) => track.clips.length > 0)).toBe(true);
|
|
92
|
+
expect(routeProof.studio?.assetLibrary?.commands?.some((command) => command.includes("assets validate-cartoon"))).toBe(true);
|
|
93
|
+
expect(routeProof.studio?.renderPipeline?.itemCount).toBeGreaterThan(0);
|
|
94
|
+
expect(routeProof.bodyPanels).toBe("timeline,assets,performance,render");
|
|
95
|
+
await expect(page.locator("#cartoon-studio-panel")).toBeVisible();
|
|
96
|
+
|
|
97
|
+
const screenshot = await page.screenshot();
|
|
98
|
+
expect(screenshot.byteLength).toBeGreaterThan(2048);
|
|
99
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { expect, test } from "@playwright/test";
|
|
2
|
+
import { createCartoonEpisodeReviewPackage } from "../src/review";
|
|
3
|
+
|
|
4
|
+
test("review package includes human approval checklist and publish boundary", () => {
|
|
5
|
+
const review = createCartoonEpisodeReviewPackage({
|
|
6
|
+
hasWebm: true,
|
|
7
|
+
hasPngSequenceFallback: false,
|
|
8
|
+
generatedAt: "2026-06-06T00:00:00.000Z"
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
expect(review).toContain("# Moon Garden 001 Review Package");
|
|
12
|
+
expect(review).toContain("Output mode: `encoded-webm`");
|
|
13
|
+
expect(review).toContain("Review status: `ready-for-human-review`");
|
|
14
|
+
expect(review).toContain("- [ ] Both characters are readable in representative frames.");
|
|
15
|
+
expect(review).toContain("- [ ] Captions do not cover important action.");
|
|
16
|
+
expect(review).toContain("- [ ] Mouth movement is visible during dialogue in the browser route.");
|
|
17
|
+
expect(review).toContain("- [ ] Character body motion is more than still-image wobble.");
|
|
18
|
+
expect(review).toContain("- [ ] No route chrome, proof panels, or debug overlays appear in encoded output.");
|
|
19
|
+
expect(review).toContain("- [ ] Reviewer name and approval date are recorded before publish-ready claims.");
|
|
20
|
+
expect(review).toContain("Human review and asset readiness are still required before public publish-ready claims.");
|
|
21
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Aura3D Episode Builder Template
|
|
2
|
+
|
|
3
|
+
This template demonstrates a guided prompt-to-episode builder on top of the
|
|
4
|
+
AuraVoice-to-Aura3D prompt-animation contract.
|
|
5
|
+
|
|
6
|
+
It uses:
|
|
7
|
+
|
|
8
|
+
- `compilePromptEpisodePlan(...)` for prompt-to-episode planning.
|
|
9
|
+
- Optional typed GLB characters from `./src/aura-assets` with primitive runtime fallbacks until assets are added.
|
|
10
|
+
- `createShotPlaybackPlan(...)` plus `installShotPlayback(...)` so `app.onFrame` updates nodes without recreating the app.
|
|
11
|
+
- `createAuraVoiceBridgePackage(...)`, `validateAuraVoiceBridgePackage(...)`, `collectPromptAnimationEvidence(...)`, and `evaluatePromptAnimationPublishReadiness(...)` for source-level AuraVoice/Aura3D handoff declarations.
|
|
12
|
+
- Caption HUD and caption timing proof metadata.
|
|
13
|
+
- Render queue metadata and three deterministic screenshot fixture records.
|
|
14
|
+
- Child-safe, reduced-motion, and high-contrast accessibility proof defaults.
|
|
15
|
+
- Primitive-mouth and typed-GLB viseme examples.
|
|
16
|
+
- Phoneme/viseme/dub source proof metadata for stable shot, storyboard, caption, line, and word timing ids.
|
|
17
|
+
- Episode format choices for short-form, standard, pilot, educational, and music-video structures.
|
|
18
|
+
- Wizard state that points at the compiled episode plan, beats, characters, and publish-evidence status.
|
|
19
|
+
- A compact builder panel for route tests and agent inspection.
|
|
20
|
+
|
|
21
|
+
Replace primitive characters with typed GLB assets by running:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npx @aura3d/cli@latest assets add ./assets/miko.glb --name miko
|
|
25
|
+
npx @aura3d/cli@latest assets add ./assets/luma.glb --name luma
|
|
26
|
+
npx @aura3d/cli@latest assets validate-cartoon
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Then import `assets.miko` and `assets.luma` from `src/aura-assets.ts` and pass them to `model(assets.miko)` and `model(assets.luma)`.
|
|
30
|
+
|
|
31
|
+
The GLB path must keep typed assets:
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
import { model } from "@aura3d/engine";
|
|
35
|
+
import { assets } from "./aura-assets";
|
|
36
|
+
|
|
37
|
+
model(assets.miko);
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Do not replace this with a string asset id or a raw loader.
|
|
41
|
+
|
|
42
|
+
This scaffold intentionally does not claim publish readiness from source alone.
|
|
43
|
+
Before closing build, route, asset, screenshot, render, or visual-quality gates,
|
|
44
|
+
archive the matching `npm run build`, `assets validate-cartoon`, browser
|
|
45
|
+
evidence, screenshot hashes, render outputs, and human or automated review
|
|
46
|
+
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 Episode Builder</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-episode-builder",
|
|
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.10"
|
|
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 4181",
|
|
6
|
+
url: "http://127.0.0.1:4181",
|
|
7
|
+
reuseExistingServer: !process.env.CI
|
|
8
|
+
},
|
|
9
|
+
use: {
|
|
10
|
+
baseURL: "http://127.0.0.1:4181"
|
|
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,78 @@
|
|
|
1
|
+
import { episode, publicCartoonAssetInstructions, typedCartoonAssetSummary } from "./episode";
|
|
2
|
+
import { publishReadiness } from "./render-plan";
|
|
3
|
+
|
|
4
|
+
export const episodeBuilderSupport = {
|
|
5
|
+
template: "episode-builder",
|
|
6
|
+
formats: [
|
|
7
|
+
{ id: "short-form", label: "Short-form", targetMinutes: "1-3", beats: "3-5", locations: "single" },
|
|
8
|
+
{ id: "standard", label: "Standard", targetMinutes: "5-7", beats: "8-12", locations: "multi" },
|
|
9
|
+
{ id: "series-pilot", label: "Series pilot", targetMinutes: "10-15", beats: "12-20", locations: "multi" },
|
|
10
|
+
{ id: "educational", label: "Educational", targetMinutes: "3-5", beats: "topic segments", locations: "guided" },
|
|
11
|
+
{ id: "music-video", label: "Music video", targetMinutes: "2-4", beats: "song sections", locations: "choreographed" }
|
|
12
|
+
] as const,
|
|
13
|
+
wizardSteps: [
|
|
14
|
+
{ id: "prompt", label: "Prompt", complete: true },
|
|
15
|
+
{ id: "format", label: "Format", complete: true },
|
|
16
|
+
{ id: "characters", label: "Characters", complete: episode.episodePlan.characters.length >= 2 },
|
|
17
|
+
{ id: "beats", label: "Beats", complete: episode.shotTimeline.shots.length >= 3 },
|
|
18
|
+
{ id: "publish", label: "Publish", complete: publishReadiness.ready }
|
|
19
|
+
] as const,
|
|
20
|
+
compiledEpisode: {
|
|
21
|
+
episodeId: episode.episodePlan.episodeId,
|
|
22
|
+
title: episode.episodePlan.title,
|
|
23
|
+
sourcePrompt: episode.episodePlan.production.sourcePrompt,
|
|
24
|
+
shotCount: episode.shotTimeline.shots.length,
|
|
25
|
+
captionCount: episode.captionTrack.cues.length,
|
|
26
|
+
renderQueueItems: episode.renderQueue.items.length
|
|
27
|
+
},
|
|
28
|
+
typedAssets: {
|
|
29
|
+
requiredCharacters: typedCartoonAssetSummary.requiredCharacterAssets,
|
|
30
|
+
missingCharacters: typedCartoonAssetSummary.missingCharacterAssets,
|
|
31
|
+
commands: publicCartoonAssetInstructions
|
|
32
|
+
}
|
|
33
|
+
} as const;
|
|
34
|
+
|
|
35
|
+
export function installEpisodeBuilderPanel(target: HTMLElement): void {
|
|
36
|
+
const panel = document.createElement("section");
|
|
37
|
+
panel.id = "episode-builder-panel";
|
|
38
|
+
panel.style.cssText = [
|
|
39
|
+
"position:fixed",
|
|
40
|
+
"left:18px",
|
|
41
|
+
"top:18px",
|
|
42
|
+
"width:min(390px,calc(100vw - 36px))",
|
|
43
|
+
"display:grid",
|
|
44
|
+
"gap:8px",
|
|
45
|
+
"padding:12px",
|
|
46
|
+
"border:1px solid rgba(248,255,242,0.22)",
|
|
47
|
+
"border-radius:8px",
|
|
48
|
+
"background:rgba(3,12,20,0.78)",
|
|
49
|
+
"color:#f8fff2",
|
|
50
|
+
"font:600 12px/1.25 system-ui,sans-serif",
|
|
51
|
+
"z-index:9"
|
|
52
|
+
].join(";");
|
|
53
|
+
|
|
54
|
+
const format = document.createElement("select");
|
|
55
|
+
format.id = "episode-format";
|
|
56
|
+
format.style.cssText = "height:32px;border-radius:6px;background:#182d3f;color:#f8fff2;border:1px solid rgba(125,226,255,0.42)";
|
|
57
|
+
for (const option of episodeBuilderSupport.formats) {
|
|
58
|
+
const element = document.createElement("option");
|
|
59
|
+
element.value = option.id;
|
|
60
|
+
element.textContent = option.label;
|
|
61
|
+
format.appendChild(element);
|
|
62
|
+
}
|
|
63
|
+
panel.appendChild(format);
|
|
64
|
+
|
|
65
|
+
const steps = document.createElement("div");
|
|
66
|
+
steps.id = "episode-builder-steps";
|
|
67
|
+
steps.textContent = episodeBuilderSupport.wizardSteps.map((step) => `${step.label}:${step.complete ? "ready" : "needs evidence"}`).join(" | ");
|
|
68
|
+
steps.style.cssText = "color:#7de2ff;white-space:normal";
|
|
69
|
+
panel.appendChild(steps);
|
|
70
|
+
|
|
71
|
+
const compiled = document.createElement("div");
|
|
72
|
+
compiled.id = "episode-builder-compiled-plan";
|
|
73
|
+
compiled.textContent = `${episodeBuilderSupport.compiledEpisode.shotCount} shots | ${episodeBuilderSupport.compiledEpisode.captionCount} captions`;
|
|
74
|
+
compiled.style.cssText = "color:#f8fff2;white-space:normal";
|
|
75
|
+
panel.appendChild(compiled);
|
|
76
|
+
|
|
77
|
+
target.appendChild(panel);
|
|
78
|
+
}
|
|
@@ -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;
|