create-aura3d 1.0.10 → 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/package.json +1 -1
- package/templates/cartoon-channel/README.md +38 -0
- package/templates/cartoon-channel/aura.assets.json +1364 -0
- package/templates/cartoon-channel/package.json +2 -1
- 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 +36 -0
- package/templates/cartoon-studio/aura.assets.json +157 -0
- package/templates/cartoon-studio/package.json +12 -1
- 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 +55 -8
- package/templates/cartoon-studio/src/characters.ts +38 -1
- package/templates/cartoon-studio/src/episode-renderer.ts +605 -0
- package/templates/cartoon-studio/src/episode.ts +12 -6
- package/templates/cartoon-studio/src/main.ts +109 -41
- package/templates/cartoon-studio/src/render-plan.ts +162 -0
- package/templates/cartoon-studio/src/review.ts +99 -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/storyboard-playback.spec.ts +18 -10
- package/templates/cartoon-studio/tests/visual-review.spec.ts +21 -0
- package/templates/product-viewer/tests/screenshot.spec.ts +8 -16
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
createShotPlaybackPlan,
|
|
6
6
|
effects,
|
|
7
7
|
game,
|
|
8
|
+
group,
|
|
8
9
|
installShotPlayback,
|
|
9
10
|
labels,
|
|
10
11
|
lights,
|
|
@@ -13,8 +14,7 @@ import {
|
|
|
13
14
|
primitives,
|
|
14
15
|
sampleAuraVoiceBridgeAtTime,
|
|
15
16
|
sampleShotPlaybackPlan,
|
|
16
|
-
scene
|
|
17
|
-
type AuraAssetRef
|
|
17
|
+
scene
|
|
18
18
|
} from "@aura3d/engine";
|
|
19
19
|
import { assets } from "./aura-assets";
|
|
20
20
|
import {
|
|
@@ -23,8 +23,7 @@ import {
|
|
|
23
23
|
missingCartoonCharacterAssets,
|
|
24
24
|
publicCartoonAssetInstructions,
|
|
25
25
|
requiredCartoonCharacterAssets,
|
|
26
|
-
typedCartoonAssetSummary
|
|
27
|
-
type CartoonAssetKey
|
|
26
|
+
typedCartoonAssetSummary
|
|
28
27
|
} from "./episode";
|
|
29
28
|
import {
|
|
30
29
|
accessibilityProofMetadata,
|
|
@@ -49,15 +48,31 @@ import { cartoonStudioSupport, installCartoonStudioPanel } from "./studio";
|
|
|
49
48
|
|
|
50
49
|
declare global {
|
|
51
50
|
interface Window {
|
|
52
|
-
|
|
51
|
+
__AURA3D_CARTOON_EPISODE_SEEK__?: (time: number) => void;
|
|
52
|
+
__AURA3D_CARTOON_EPISODE_PROOF__?: CartoonEpisodeRouteProof;
|
|
53
|
+
__AURA3D_CARTOON_TEMPLATE__?: CartoonEpisodeRouteProof;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
interface CartoonEpisodeRouteProof {
|
|
58
|
+
readonly proofKind: "aura3d-cartoon-episode-proof";
|
|
53
59
|
readonly contractId: string;
|
|
54
60
|
readonly template: string;
|
|
55
61
|
readonly storyBible: unknown;
|
|
62
|
+
readonly shots: readonly unknown[];
|
|
63
|
+
readonly captions: readonly unknown[];
|
|
64
|
+
readonly visemes: readonly unknown[];
|
|
65
|
+
readonly gestures: readonly unknown[];
|
|
66
|
+
readonly assets: unknown;
|
|
67
|
+
readonly renderStatus: unknown;
|
|
68
|
+
readonly errors: readonly string[];
|
|
69
|
+
readonly sourceOnlyAcceptedAsPublishProof: false;
|
|
56
70
|
readonly shotIds: readonly string[];
|
|
57
71
|
readonly captionIds: readonly string[];
|
|
58
72
|
readonly playbackProbeTimes: readonly number[];
|
|
59
73
|
readonly playbackProbeSamples: readonly unknown[];
|
|
60
74
|
readonly renderQueueItems: number;
|
|
75
|
+
readonly renderPreset: unknown;
|
|
61
76
|
readonly typedAssets: unknown;
|
|
62
77
|
readonly requiredTypedAssets: readonly string[];
|
|
63
78
|
readonly missingTypedAssets: readonly string[];
|
|
@@ -69,8 +84,6 @@ declare global {
|
|
|
69
84
|
readonly sourceProofs: unknown;
|
|
70
85
|
readonly sampleRenderSourceWorkflow: unknown;
|
|
71
86
|
sampleAt(time: number): unknown;
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
87
|
}
|
|
75
88
|
|
|
76
89
|
const firstStoryboardShot = episode.storyboard.scenes[0]?.shots[0];
|
|
@@ -78,13 +91,21 @@ const firstCaption = episode.captionTrack.cues[0];
|
|
|
78
91
|
const thumbnailCapture = renderOutputPackage.thumbnailCapture;
|
|
79
92
|
const thumbnailCaption = captionCueAtTime(episode.captionTrack, thumbnailCapture.time);
|
|
80
93
|
const storyBible = episode.storyBible;
|
|
81
|
-
const
|
|
94
|
+
const captureMode = new URLSearchParams(window.location.search).get("capture") === "thumbnail";
|
|
95
|
+
const cartoonRenderPreset = createCartoonStudioRouteRenderPreset({
|
|
96
|
+
name: "moon-garden-cartoon-studio",
|
|
97
|
+
resolution: episode.episodePlan.runtime.resolution,
|
|
98
|
+
materialStyle: { treatment: "cel" },
|
|
99
|
+
reducedMotion: episode.episodePlan.runtime.reducedMotion,
|
|
100
|
+
reducedFlash: true
|
|
101
|
+
});
|
|
82
102
|
|
|
83
103
|
const captionOverlay = document.createElement("div");
|
|
84
104
|
captionOverlay.id = "caption-overlay";
|
|
85
105
|
captionOverlay.textContent = firstCaption?.text ?? firstStoryboardShot?.storyBeat ?? "Aura3D cartoon studio";
|
|
86
106
|
captionOverlay.style.cssText = [
|
|
87
107
|
"position:fixed",
|
|
108
|
+
captureMode ? "display:none" : "",
|
|
88
109
|
"left:50%",
|
|
89
110
|
"bottom:28px",
|
|
90
111
|
"transform:translateX(-50%)",
|
|
@@ -121,14 +142,30 @@ document.body.dataset.cartoonShotCount = String(episode.shotTimeline.shots.lengt
|
|
|
121
142
|
document.body.dataset.cartoonCaptionCount = String(episode.captionTrack.cues.length);
|
|
122
143
|
document.body.dataset.cartoonStoryBibleProps = String(storyBible.props.length);
|
|
123
144
|
document.body.dataset.cartoonStudioPanels = cartoonStudioSupport.panels.join(",");
|
|
124
|
-
installCartoonStudioPanel(document.body);
|
|
145
|
+
if (!captureMode) installCartoonStudioPanel(document.body);
|
|
125
146
|
|
|
126
147
|
const app = createAuraApp("#app", {
|
|
127
148
|
scene: scene()
|
|
128
149
|
.background("#081b2a")
|
|
129
|
-
.add(
|
|
150
|
+
.add(
|
|
151
|
+
model(assets.moonGarden, { name: "Moon Garden typed set asset" })
|
|
152
|
+
.position(0, 0, -0.02)
|
|
153
|
+
.scale(1)
|
|
154
|
+
.runtime(game.runtimeNode("moonGarden", { tags: ["set", "typed-asset", "moon-garden"] }))
|
|
155
|
+
)
|
|
156
|
+
.add(
|
|
157
|
+
model(assets.miko, { name: "Miko typed cartoon character" })
|
|
158
|
+
.position(-0.8, 0.75, 0)
|
|
159
|
+
.scale(0.72)
|
|
160
|
+
.runtime(game.runtimeNode("miko", { tags: ["character", "miko", "typed-asset"] }))
|
|
161
|
+
)
|
|
130
162
|
.add(createMouthCard("miko:mouth", "Miko", [-0.8, 0.8, 0.32], "#f8fff2"))
|
|
131
|
-
.add(
|
|
163
|
+
.add(
|
|
164
|
+
model(assets.luma, { name: "Luma typed cartoon character" })
|
|
165
|
+
.position(0.8, 0.72, 0)
|
|
166
|
+
.scale(0.68)
|
|
167
|
+
.runtime(game.runtimeNode("luma", { tags: ["character", "luma", "typed-asset"] }))
|
|
168
|
+
)
|
|
132
169
|
.add(createMouthCard("luma:mouth", "Luma", [0.8, 0.77, 0.32], "#40ffbf"))
|
|
133
170
|
.add(
|
|
134
171
|
primitives
|
|
@@ -148,22 +185,40 @@ const app = createAuraApp("#app", {
|
|
|
148
185
|
.position(-0.38, 0.2, 0.18)
|
|
149
186
|
.scale([0.12, 0.24, 0.04])
|
|
150
187
|
)
|
|
151
|
-
.add(labels.hud(firstCaption?.text ?? firstStoryboardShot?.storyBeat ?? "Aura3D cartoon studio"))
|
|
188
|
+
.add(captureMode ? group("thumbnail capture label placeholder") : labels.hud(firstCaption?.text ?? firstStoryboardShot?.storyBeat ?? "Aura3D cartoon studio"))
|
|
152
189
|
.add(lights.studio({ intensity: 1.2 }))
|
|
153
190
|
.add(effects.bloom({ intensity: 0.18, color: "#7de2ff" }))
|
|
154
191
|
.camera(camera.perspective({ position: [0, 1.4, 4.2], target: [0, 0.7, 0], fov: 42 })),
|
|
155
|
-
diagnostics:
|
|
192
|
+
diagnostics: !captureMode
|
|
156
193
|
});
|
|
157
194
|
|
|
158
|
-
|
|
195
|
+
const cartoonEpisodeProof: CartoonEpisodeRouteProof = {
|
|
196
|
+
proofKind: "aura3d-cartoon-episode-proof",
|
|
159
197
|
contractId: episodeContractId,
|
|
160
198
|
template: "cartoon-studio",
|
|
161
199
|
storyBible,
|
|
200
|
+
shots: episode.shotTimeline.shots,
|
|
201
|
+
captions: episode.captionTrack.cues,
|
|
202
|
+
visemes: visemeTrack.cues,
|
|
203
|
+
gestures: episode.performance.cues,
|
|
204
|
+
assets: typedCartoonAssetSummary,
|
|
205
|
+
renderStatus: {
|
|
206
|
+
route: renderPlan.route,
|
|
207
|
+
renderQueueItems: renderPlan.items.length,
|
|
208
|
+
outputs: renderPlan.outputs.map((output) => output.kind),
|
|
209
|
+
preset: cartoonRenderPreset,
|
|
210
|
+
sourceOnlyAcceptedAsPublishProof: false,
|
|
211
|
+
publishReadyFromCurrentEvidence: publishReadiness.ready,
|
|
212
|
+
issueCount: publishReadiness.issues.length
|
|
213
|
+
},
|
|
214
|
+
errors: bridgeIssues.map((issue) => String((issue as { message?: unknown }).message ?? issue)),
|
|
215
|
+
sourceOnlyAcceptedAsPublishProof: false,
|
|
162
216
|
shotIds: episode.shotTimeline.shots.map((shot) => shot.shotId),
|
|
163
217
|
captionIds: episode.captionTrack.cues.map((caption) => caption.captionId),
|
|
164
218
|
playbackProbeTimes,
|
|
165
219
|
playbackProbeSamples,
|
|
166
220
|
renderQueueItems: renderPlan.items.length,
|
|
221
|
+
renderPreset: cartoonRenderPreset,
|
|
167
222
|
typedAssets: typedCartoonAssetSummary,
|
|
168
223
|
requiredTypedAssets: requiredCartoonCharacterAssets,
|
|
169
224
|
missingTypedAssets: missingCartoonCharacterAssets,
|
|
@@ -182,6 +237,14 @@ window.__AURA3D_CARTOON_TEMPLATE__ = {
|
|
|
182
237
|
return sampleCartoonRouteAt(time);
|
|
183
238
|
}
|
|
184
239
|
};
|
|
240
|
+
window.__AURA3D_CARTOON_EPISODE_PROOF__ = cartoonEpisodeProof;
|
|
241
|
+
window.__AURA3D_CARTOON_TEMPLATE__ = cartoonEpisodeProof;
|
|
242
|
+
window.__AURA3D_CARTOON_EPISODE_SEEK__ = (time: number) => {
|
|
243
|
+
const sample = sampleCartoonRouteAt(time);
|
|
244
|
+
captionOverlay.textContent = sample.captionText ?? firstCaption?.text ?? "";
|
|
245
|
+
captionOverlay.dataset.shotId = sample.shotId ?? "";
|
|
246
|
+
captionOverlay.dataset.captionId = sample.captionId ?? "";
|
|
247
|
+
};
|
|
185
248
|
|
|
186
249
|
installShotPlayback(app, shotPlaybackPlan, {
|
|
187
250
|
primitiveMouthNodeByCharacterId: {
|
|
@@ -195,33 +258,6 @@ installShotPlayback(app, shotPlaybackPlan, {
|
|
|
195
258
|
}
|
|
196
259
|
});
|
|
197
260
|
|
|
198
|
-
function createCharacterBody(
|
|
199
|
-
id: "miko" | "luma",
|
|
200
|
-
name: string,
|
|
201
|
-
assetKey: CartoonAssetKey,
|
|
202
|
-
position: readonly [number, number, number],
|
|
203
|
-
fallbackScale: readonly [number, number, number],
|
|
204
|
-
modelScale: number,
|
|
205
|
-
color: string
|
|
206
|
-
) {
|
|
207
|
-
const asset = typedCartoonAssets[assetKey];
|
|
208
|
-
const runtime = game.runtimeNode(id, { tags: ["character", id, asset ? "typed-asset" : "primitive-fallback"] });
|
|
209
|
-
if (asset) {
|
|
210
|
-
return model(asset, { name: `${name} typed character asset` })
|
|
211
|
-
.position(position[0], position[1], position[2])
|
|
212
|
-
.scale(modelScale)
|
|
213
|
-
.runtime(runtime);
|
|
214
|
-
}
|
|
215
|
-
return primitives
|
|
216
|
-
.sphere({
|
|
217
|
-
name: `${name} primitive fallback body - add ${assetKey} with the Aura3D CLI`,
|
|
218
|
-
material: material.pbr({ color, roughness: 0.54 })
|
|
219
|
-
})
|
|
220
|
-
.position(position[0], position[1], position[2])
|
|
221
|
-
.scale(fallbackScale)
|
|
222
|
-
.runtime(runtime);
|
|
223
|
-
}
|
|
224
|
-
|
|
225
261
|
function createMouthCard(id: "miko:mouth" | "luma:mouth", name: string, position: readonly [number, number, number], color: string) {
|
|
226
262
|
return primitives
|
|
227
263
|
.box({
|
|
@@ -233,6 +269,38 @@ function createMouthCard(id: "miko:mouth" | "luma:mouth", name: string, position
|
|
|
233
269
|
.runtime(game.runtimeNode(id, { tags: ["mouth", "primitive", name.toLowerCase()] }));
|
|
234
270
|
}
|
|
235
271
|
|
|
272
|
+
function createCartoonStudioRouteRenderPreset(input: {
|
|
273
|
+
readonly name: string;
|
|
274
|
+
readonly resolution: { readonly width: number; readonly height: number };
|
|
275
|
+
readonly materialStyle: { readonly treatment: "cel" | "preserve-pbr" };
|
|
276
|
+
readonly reducedMotion?: boolean | undefined;
|
|
277
|
+
readonly reducedFlash?: boolean | undefined;
|
|
278
|
+
}) {
|
|
279
|
+
return {
|
|
280
|
+
kind: "cartoon-render-preset-evidence",
|
|
281
|
+
name: input.name,
|
|
282
|
+
resolution: input.resolution,
|
|
283
|
+
lights: ["soft-key", "cool-rim", "set-fill", "emissive-practicals"],
|
|
284
|
+
shadows: { soft: true, contact: true },
|
|
285
|
+
postprocess: {
|
|
286
|
+
bloom: input.reducedFlash ? 0.08 : 0.18,
|
|
287
|
+
colorGrade: "storybook-night",
|
|
288
|
+
fogDepthCue: true
|
|
289
|
+
},
|
|
290
|
+
materialStyle: {
|
|
291
|
+
treatment: input.materialStyle.treatment,
|
|
292
|
+
outline: input.materialStyle.treatment === "cel",
|
|
293
|
+
rampSteps: input.materialStyle.treatment === "cel" ? 4 : 7,
|
|
294
|
+
saturation: 1.08,
|
|
295
|
+
blackLineWeight: input.materialStyle.treatment === "cel" ? 0.9 : 0
|
|
296
|
+
},
|
|
297
|
+
frameBudgetMs: 16.7,
|
|
298
|
+
debugOverlaysAllowedInExport: false,
|
|
299
|
+
reducedMotion: input.reducedMotion ?? false,
|
|
300
|
+
reducedFlash: input.reducedFlash ?? false
|
|
301
|
+
} as const;
|
|
302
|
+
}
|
|
303
|
+
|
|
236
304
|
function sampleCartoonRouteAt(time: number) {
|
|
237
305
|
const bridgeSample = sampleAuraVoiceBridgeAtTime(auraVoicePackage, time);
|
|
238
306
|
const playbackSample = sampleShotPlaybackPlan(shotPlaybackPlan, time);
|
|
@@ -221,6 +221,87 @@ export const spanishDubMap = defineDubMap({
|
|
|
221
221
|
generatedAt: episode.episodePlan.generatedAt
|
|
222
222
|
});
|
|
223
223
|
|
|
224
|
+
const spanishLineTextBySourceText = new Map<string, string>([
|
|
225
|
+
["Luma, the moon lilies are losing their sparkle.", "Luma, los lirios lunares estan perdiendo su brillo."],
|
|
226
|
+
["Then we sweep softly and wake the glow stones.", "Entonces barremos con suavidad y despertamos las piedras brillantes."],
|
|
227
|
+
["I will polish the blue stones one tiny circle at a time.", "Pulire las piedras azules con circulos pequenitos."],
|
|
228
|
+
["And I will hum the garden's sleepy cleanup song.", "Y yo tarareare la cancion tranquila del jardin."],
|
|
229
|
+
["The moon lilies are twinkling again.", "Los lirios lunares vuelven a centellear."],
|
|
230
|
+
["Goodnight, little garden. Keep glowing.", "Buenas noches, jardin pequeno. Sigue brillando."]
|
|
231
|
+
]);
|
|
232
|
+
|
|
233
|
+
function spanishTextForLine(text: string): string {
|
|
234
|
+
return spanishLineTextBySourceText.get(text) ?? text;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export const spanishDubCaptionMetadata = {
|
|
238
|
+
artifact: "alternate-language-caption-metadata",
|
|
239
|
+
schema: "aura3d-cartoon-studio-alternate-language-captions/v1",
|
|
240
|
+
contractId: renderContractId,
|
|
241
|
+
episodeId: episode.episodePlan.episodeId,
|
|
242
|
+
sourceLanguage: episode.dialogueTrack.language,
|
|
243
|
+
targetLanguage: spanishDubMap.targetLanguage,
|
|
244
|
+
cues: episode.captionTrack.cues.map((cue) => {
|
|
245
|
+
const sourceLine = episode.dialogueTrack.lines.find((line) => line.lineId === cue.lineId);
|
|
246
|
+
return {
|
|
247
|
+
captionId: cue.captionId,
|
|
248
|
+
lineId: cue.lineId,
|
|
249
|
+
shotId: cue.shotId,
|
|
250
|
+
sceneId: cue.sceneId,
|
|
251
|
+
speakerId: cue.speakerId,
|
|
252
|
+
sourceText: cue.text,
|
|
253
|
+
text: spanishTextForLine(sourceLine?.text ?? cue.text),
|
|
254
|
+
language: "es",
|
|
255
|
+
startTime: cue.startTime,
|
|
256
|
+
endTime: cue.endTime,
|
|
257
|
+
lineSafe: cue.lineSafe
|
|
258
|
+
};
|
|
259
|
+
}),
|
|
260
|
+
generatedAt: episode.episodePlan.generatedAt
|
|
261
|
+
} as const;
|
|
262
|
+
|
|
263
|
+
export const alternateLanguageDubMetadata = {
|
|
264
|
+
artifact: "cartoon-alternate-language-dub-proof",
|
|
265
|
+
schema: "aura3d-cartoon-studio-alternate-language-dub-proof/v1",
|
|
266
|
+
contractId: renderContractId,
|
|
267
|
+
episodeId: episode.episodePlan.episodeId,
|
|
268
|
+
sourceLanguage: episode.dialogueTrack.language,
|
|
269
|
+
alternateLanguageCount: 1,
|
|
270
|
+
alternateLanguages: [
|
|
271
|
+
{
|
|
272
|
+
language: spanishDubMap.targetLanguage,
|
|
273
|
+
label: "Spanish",
|
|
274
|
+
title: "Limpieza del Jardin Lunar",
|
|
275
|
+
dubMapArtifact: spanishDubMap.artifact,
|
|
276
|
+
lineCount: spanishDubMap.entries.length,
|
|
277
|
+
captionCueCount: spanishDubCaptionMetadata.cues.length,
|
|
278
|
+
captionMetadata: spanishDubCaptionMetadata,
|
|
279
|
+
audioStemSlots: episode.dialogueTrack.lines.map((line) => ({
|
|
280
|
+
id: `dub-slot:es:${line.lineId}`,
|
|
281
|
+
lineId: line.lineId,
|
|
282
|
+
speakerId: line.speakerId,
|
|
283
|
+
language: "es",
|
|
284
|
+
assetRequiredBeforePublish: true
|
|
285
|
+
})),
|
|
286
|
+
stableIds: {
|
|
287
|
+
shotIds: spanishDubMap.entries.every((entry) => entry.originalShotId === entry.dubbedShotId),
|
|
288
|
+
storyboardIds: spanishDubMap.entries.every((entry) => entry.originalStoryboardId === entry.dubbedStoryboardId),
|
|
289
|
+
sceneIds: spanishDubMap.entries.every((entry) => entry.originalSceneId === entry.dubbedSceneId),
|
|
290
|
+
lineIds: spanishDubMap.entries.every((entry) => entry.originalLineId === entry.dubbedLineId),
|
|
291
|
+
captionIds: spanishDubMap.entries.every((entry) => entry.originalCaptionId === entry.dubbedCaptionId),
|
|
292
|
+
characterIds: spanishDubMap.entries.every((entry) => entry.originalCharacterId === entry.dubbedCharacterId)
|
|
293
|
+
},
|
|
294
|
+
publishBoundary: "Metadata-only alternate-language proof. Add licensed/typed Spanish dialogue stems before claiming a rendered Spanish dub."
|
|
295
|
+
}
|
|
296
|
+
],
|
|
297
|
+
assetPolicy: {
|
|
298
|
+
inventsAudioAssets: false,
|
|
299
|
+
inventsModelAssets: false,
|
|
300
|
+
typedAssetSource: "./src/aura-assets"
|
|
301
|
+
},
|
|
302
|
+
generatedAt: episode.episodePlan.generatedAt
|
|
303
|
+
} as const;
|
|
304
|
+
|
|
224
305
|
export const phonemeVisemeDubSyncSourceProof = {
|
|
225
306
|
contractId: renderContractId,
|
|
226
307
|
sourceOnly: true,
|
|
@@ -255,6 +336,59 @@ export const phonemeVisemeDubSyncSourceProof = {
|
|
|
255
336
|
executionBoundary: "Source harness only; actual phoneme, viseme, and dub playback proof requires rendered frame/video evidence."
|
|
256
337
|
} as const;
|
|
257
338
|
|
|
339
|
+
export const cartoonStudioShowBibleBatchRenderPlan = {
|
|
340
|
+
artifact: "cartoon-show-bible-batch-render-plan",
|
|
341
|
+
schema: "aura3d-cartoon-studio-show-bible-batch-render-plan/v1",
|
|
342
|
+
contractId: renderContractId,
|
|
343
|
+
sourceStoryBible: {
|
|
344
|
+
episodeId: episode.storyBible.episodeId,
|
|
345
|
+
title: episode.storyBible.title,
|
|
346
|
+
characterIds: episode.storyBible.characters.map((character) => character.id),
|
|
347
|
+
locationIds: episode.storyBible.locations.map((location) => location.id),
|
|
348
|
+
propIds: episode.storyBible.props.map((prop) => prop.id),
|
|
349
|
+
continuityRules: episode.storyBible.continuityRules ?? []
|
|
350
|
+
},
|
|
351
|
+
renderJobs: [
|
|
352
|
+
{
|
|
353
|
+
id: "batch:moon-garden-cleanup-001",
|
|
354
|
+
episodeId: episode.episodePlan.episodeId,
|
|
355
|
+
title: episode.episodePlan.title,
|
|
356
|
+
sourceStoryBibleEpisodeId: episode.storyBible.episodeId,
|
|
357
|
+
route: renderPlan.route,
|
|
358
|
+
outputDirectory: "dist/episodes/moon-garden-001",
|
|
359
|
+
language: episode.episodePlan.language,
|
|
360
|
+
shotIds: episode.shotTimeline.shots.map((shot) => shot.shotId),
|
|
361
|
+
requiredTypedAssetKeys: typedCartoonAssetSummary.requiredCharacterAssets,
|
|
362
|
+
command: "npm run episode:render -- --episode moon-garden-cleanup-001"
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
id: "batch:moon-garden-glow-check-002",
|
|
366
|
+
episodeId: "moon-garden-glow-check-002",
|
|
367
|
+
title: "Moon Garden Glow Check",
|
|
368
|
+
sourceStoryBibleEpisodeId: episode.storyBible.episodeId,
|
|
369
|
+
route: renderPlan.route,
|
|
370
|
+
outputDirectory: "dist/episodes/moon-garden-002",
|
|
371
|
+
language: episode.episodePlan.language,
|
|
372
|
+
reusedCharacterIds: episode.storyBible.characters.map((character) => character.id),
|
|
373
|
+
reusedLocationIds: episode.storyBible.locations.map((location) => location.id),
|
|
374
|
+
reusedPropIds: episode.storyBible.props.map((prop) => prop.id),
|
|
375
|
+
requiredTypedAssetKeys: typedCartoonAssetSummary.requiredCharacterAssets,
|
|
376
|
+
command: "npm run episode:render -- --episode moon-garden-glow-check-002",
|
|
377
|
+
metadataOnly: true,
|
|
378
|
+
publishBoundary: "Second episode job reuses the same show bible and asset slots; rendered media is produced only when the template runner is executed with real typed assets."
|
|
379
|
+
}
|
|
380
|
+
],
|
|
381
|
+
guarantees: {
|
|
382
|
+
oneShowBible: true,
|
|
383
|
+
multipleEpisodes: true,
|
|
384
|
+
noInventedAssets: true,
|
|
385
|
+
stableCharacterIds: true,
|
|
386
|
+
stableLocationIds: true,
|
|
387
|
+
stablePropIds: true
|
|
388
|
+
},
|
|
389
|
+
generatedAt: episode.episodePlan.generatedAt
|
|
390
|
+
} as const;
|
|
391
|
+
|
|
258
392
|
export const performanceCoverage = createCartoonPerformanceCoverage(episode.performance, episode.dialogueTrack);
|
|
259
393
|
|
|
260
394
|
export const reviewPackagePaths = renderOutputPackage.reviewPackagePaths;
|
|
@@ -365,6 +499,34 @@ export const sampleRenderSourceWorkflow = {
|
|
|
365
499
|
]
|
|
366
500
|
} as const;
|
|
367
501
|
|
|
502
|
+
export const templateEpisodePackageWorkflow = {
|
|
503
|
+
contractId: renderContractId,
|
|
504
|
+
sourceOnly: false,
|
|
505
|
+
status: "template-package-runner-ready",
|
|
506
|
+
packageDirectory: "dist/episodes/moon-garden-001",
|
|
507
|
+
commands: [
|
|
508
|
+
"npm run episode:plan",
|
|
509
|
+
"npm run episode:preview",
|
|
510
|
+
"npm run episode:render",
|
|
511
|
+
"npm run episode:package",
|
|
512
|
+
"npm run episode:review",
|
|
513
|
+
"npm run episode:verify"
|
|
514
|
+
],
|
|
515
|
+
requiredArtifacts: [
|
|
516
|
+
"thumbnail.png",
|
|
517
|
+
"captions.vtt",
|
|
518
|
+
"captions.srt",
|
|
519
|
+
"metadata.json",
|
|
520
|
+
"route-proof.json",
|
|
521
|
+
"asset-provenance.json",
|
|
522
|
+
"render-manifest.json",
|
|
523
|
+
"visual-acceptance.json",
|
|
524
|
+
"review-package.md"
|
|
525
|
+
],
|
|
526
|
+
renderOutputMode: "episode.webm-or-png-sequence-fallback-marker",
|
|
527
|
+
publishBoundary: "PNG sequence fallback proves template packaging only; publish-ready claims require a real encoded WebM or MP4 plus human review."
|
|
528
|
+
} as const;
|
|
529
|
+
|
|
368
530
|
export const auraVoicePackage = createAuraVoiceBridgePackage({
|
|
369
531
|
episodePlan: episode.episodePlan,
|
|
370
532
|
storyboard: episode.storyboard,
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { captionTimingProof, renderOutputPackage, visemeFrameSyncSourceProof } from "./render-plan";
|
|
2
|
+
import { episode, missingCartoonCharacterAssets, publicCartoonAssetInstructions, typedCartoonAssetSummary, youtubeDraftMetadata } from "./episode";
|
|
3
|
+
|
|
4
|
+
export const cartoonEpisodePackageDirectory = "dist/episodes/moon-garden-001" as const;
|
|
5
|
+
|
|
6
|
+
export const requiredCartoonEpisodePackageFiles = [
|
|
7
|
+
"episode.webm",
|
|
8
|
+
"thumbnail.png",
|
|
9
|
+
"captions.vtt",
|
|
10
|
+
"captions.srt",
|
|
11
|
+
"metadata.json",
|
|
12
|
+
"prompt-animation-evidence.json",
|
|
13
|
+
"route-proof.json",
|
|
14
|
+
"asset-provenance.json",
|
|
15
|
+
"render-manifest.json",
|
|
16
|
+
"dub-metadata.json",
|
|
17
|
+
"batch-render-plan.json",
|
|
18
|
+
"motion-quality.json",
|
|
19
|
+
"visual-acceptance.json",
|
|
20
|
+
"review-package.md"
|
|
21
|
+
] as const;
|
|
22
|
+
|
|
23
|
+
export const optionalCartoonEpisodePackageFiles = [
|
|
24
|
+
"episode.mp4",
|
|
25
|
+
"episode.png-sequence-fallback.json"
|
|
26
|
+
] as const;
|
|
27
|
+
|
|
28
|
+
export interface CartoonEpisodeReviewInput {
|
|
29
|
+
readonly packageDirectory?: string;
|
|
30
|
+
readonly hasWebm: boolean;
|
|
31
|
+
readonly hasPngSequenceFallback: boolean;
|
|
32
|
+
readonly generatedAt: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function createCartoonEpisodeReviewPackage(input: CartoonEpisodeReviewInput): string {
|
|
36
|
+
const packageDirectory = input.packageDirectory ?? cartoonEpisodePackageDirectory;
|
|
37
|
+
const outputMode = input.hasWebm ? "encoded-webm" : input.hasPngSequenceFallback ? "png-sequence-fallback" : "missing-render-output";
|
|
38
|
+
const typedAssetStatus = missingCartoonCharacterAssets.length === 0 ? "typed character assets present" : "starter fallback mode";
|
|
39
|
+
const reviewStatus = input.hasWebm && missingCartoonCharacterAssets.length === 0 ? "ready-for-human-review" : "needs-render-upgrade-before-publish";
|
|
40
|
+
|
|
41
|
+
return [
|
|
42
|
+
"# Moon Garden 001 Review Package",
|
|
43
|
+
"",
|
|
44
|
+
`Generated: ${input.generatedAt}`,
|
|
45
|
+
`Episode: ${episode.episodePlan.title}`,
|
|
46
|
+
`Package: \`${packageDirectory}\``,
|
|
47
|
+
`Output mode: \`${outputMode}\``,
|
|
48
|
+
`Review status: \`${reviewStatus}\``,
|
|
49
|
+
"",
|
|
50
|
+
"## Required Files",
|
|
51
|
+
"",
|
|
52
|
+
...requiredCartoonEpisodePackageFiles.map((file) => `- [x] \`${file}\``),
|
|
53
|
+
input.hasPngSequenceFallback ? "- [x] `episode.png-sequence-fallback.json`" : "- [ ] `episode.png-sequence-fallback.json`",
|
|
54
|
+
"",
|
|
55
|
+
"## Episode Summary",
|
|
56
|
+
"",
|
|
57
|
+
`- Runtime: ${episode.episodePlan.runtime.duration}s at ${episode.episodePlan.runtime.frameRate}fps`,
|
|
58
|
+
`- Resolution: ${episode.episodePlan.runtime.resolution.width}x${episode.episodePlan.runtime.resolution.height}`,
|
|
59
|
+
`- Shots: ${episode.shotTimeline.shots.length}`,
|
|
60
|
+
`- Captions: ${episode.captionTrack.cues.length}`,
|
|
61
|
+
`- Characters: ${episode.episodePlan.characters.map((character) => character.name).join(", ")}`,
|
|
62
|
+
`- YouTube draft title: ${youtubeDraftMetadata.title}`,
|
|
63
|
+
"",
|
|
64
|
+
"## Asset Status",
|
|
65
|
+
"",
|
|
66
|
+
`- Status: ${typedAssetStatus}`,
|
|
67
|
+
`- Required typed assets: ${typedCartoonAssetSummary.requiredCharacterAssets.join(", ")}`,
|
|
68
|
+
`- Missing typed assets: ${missingCartoonCharacterAssets.length ? missingCartoonCharacterAssets.join(", ") : "none"}`,
|
|
69
|
+
"",
|
|
70
|
+
"Typed asset commands:",
|
|
71
|
+
"",
|
|
72
|
+
"```bash",
|
|
73
|
+
...publicCartoonAssetInstructions,
|
|
74
|
+
"```",
|
|
75
|
+
"",
|
|
76
|
+
"## Timing Proof",
|
|
77
|
+
"",
|
|
78
|
+
`- Caption timing: ${captionTimingProof.status}`,
|
|
79
|
+
`- Max caption drift frames: ${captionTimingProof.maxDriftFrames}`,
|
|
80
|
+
`- Mouth source sync: ${visemeFrameSyncSourceProof.mouthMovementWithinOneFrame ? "pass" : "fail"}`,
|
|
81
|
+
"",
|
|
82
|
+
"## Human Review Checklist",
|
|
83
|
+
"",
|
|
84
|
+
"- [ ] Both characters are readable in representative frames.",
|
|
85
|
+
"- [ ] Captions do not cover important action.",
|
|
86
|
+
"- [ ] Mouth movement is visible during dialogue in the browser route.",
|
|
87
|
+
"- [ ] Character body motion is more than still-image wobble.",
|
|
88
|
+
"- [ ] No route chrome, proof panels, or debug overlays appear in encoded output.",
|
|
89
|
+
"- [ ] Reviewer name and approval date are recorded before publish-ready claims.",
|
|
90
|
+
"",
|
|
91
|
+
"## Boundary",
|
|
92
|
+
"",
|
|
93
|
+
input.hasWebm
|
|
94
|
+
? "This package includes an encoded WebM output. Human review and asset readiness are still required before public publish-ready claims."
|
|
95
|
+
: "This package uses the PNG-sequence fallback marker because no real browser video encoder was attached. It is useful for package workflow proof, but it is not publish-ready video evidence.",
|
|
96
|
+
"",
|
|
97
|
+
`Render package metadata source: \`${renderOutputPackage.packageId}\``
|
|
98
|
+
].join("\n");
|
|
99
|
+
}
|
|
@@ -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
|
+
});
|