create-aura3d 1.0.8 → 1.0.10
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/package.json +1 -1
- package/templates/cartoon-studio/README.md +46 -0
- package/templates/cartoon-studio/index.html +13 -0
- package/templates/cartoon-studio/package.json +21 -0
- package/templates/cartoon-studio/playwright.config.ts +13 -0
- package/templates/cartoon-studio/src/aura-assets.ts +14 -0
- package/templates/cartoon-studio/src/characters.ts +28 -0
- package/templates/cartoon-studio/src/contract.ts +1 -0
- package/templates/cartoon-studio/src/episode.ts +300 -0
- package/templates/cartoon-studio/src/main.ts +265 -0
- package/templates/cartoon-studio/src/render-plan.ts +405 -0
- package/templates/cartoon-studio/src/sets.ts +14 -0
- package/templates/cartoon-studio/src/studio.ts +101 -0
- package/templates/cartoon-studio/tests/route-health.spec.ts +7 -0
- package/templates/cartoon-studio/tests/storyboard-playback.spec.ts +91 -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/prompt-cartoon-channel/package.json +1 -1
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import {
|
|
2
|
+
camera,
|
|
3
|
+
captionCueAtTime,
|
|
4
|
+
createAuraApp,
|
|
5
|
+
createShotPlaybackPlan,
|
|
6
|
+
effects,
|
|
7
|
+
game,
|
|
8
|
+
installShotPlayback,
|
|
9
|
+
labels,
|
|
10
|
+
lights,
|
|
11
|
+
material,
|
|
12
|
+
model,
|
|
13
|
+
primitives,
|
|
14
|
+
sampleAuraVoiceBridgeAtTime,
|
|
15
|
+
sampleShotPlaybackPlan,
|
|
16
|
+
scene,
|
|
17
|
+
type AuraAssetRef
|
|
18
|
+
} from "@aura3d/engine";
|
|
19
|
+
import { assets } from "./aura-assets";
|
|
20
|
+
import {
|
|
21
|
+
episode,
|
|
22
|
+
episodeContractId,
|
|
23
|
+
missingCartoonCharacterAssets,
|
|
24
|
+
publicCartoonAssetInstructions,
|
|
25
|
+
requiredCartoonCharacterAssets,
|
|
26
|
+
typedCartoonAssetSummary,
|
|
27
|
+
type CartoonAssetKey
|
|
28
|
+
} from "./episode";
|
|
29
|
+
import {
|
|
30
|
+
accessibilityProofMetadata,
|
|
31
|
+
auraVoicePackage,
|
|
32
|
+
bridgeIssues,
|
|
33
|
+
bridgeSampleAtThumbnail,
|
|
34
|
+
captionFrameSyncSourceProof,
|
|
35
|
+
captionTimingProof,
|
|
36
|
+
deterministicScreenshotFixtures,
|
|
37
|
+
glbVisemeRuntimeExample,
|
|
38
|
+
promptAnimationEvidence,
|
|
39
|
+
primitiveMouthRuntimeExample,
|
|
40
|
+
phonemeVisemeDubSyncSourceProof,
|
|
41
|
+
publishReadiness,
|
|
42
|
+
renderOutputPackage,
|
|
43
|
+
renderPlan,
|
|
44
|
+
sampleRenderSourceWorkflow,
|
|
45
|
+
visemeFrameSyncSourceProof,
|
|
46
|
+
visemeTrack
|
|
47
|
+
} from "./render-plan";
|
|
48
|
+
import { cartoonStudioSupport, installCartoonStudioPanel } from "./studio";
|
|
49
|
+
|
|
50
|
+
declare global {
|
|
51
|
+
interface Window {
|
|
52
|
+
__AURA3D_CARTOON_TEMPLATE__?: {
|
|
53
|
+
readonly contractId: string;
|
|
54
|
+
readonly template: string;
|
|
55
|
+
readonly storyBible: unknown;
|
|
56
|
+
readonly shotIds: readonly string[];
|
|
57
|
+
readonly captionIds: readonly string[];
|
|
58
|
+
readonly playbackProbeTimes: readonly number[];
|
|
59
|
+
readonly playbackProbeSamples: readonly unknown[];
|
|
60
|
+
readonly renderQueueItems: number;
|
|
61
|
+
readonly typedAssets: unknown;
|
|
62
|
+
readonly requiredTypedAssets: readonly string[];
|
|
63
|
+
readonly missingTypedAssets: readonly string[];
|
|
64
|
+
readonly assetCommands: readonly string[];
|
|
65
|
+
readonly bridgeIssues: readonly unknown[];
|
|
66
|
+
readonly promptAnimationEvidence: unknown;
|
|
67
|
+
readonly publishReadiness: unknown;
|
|
68
|
+
readonly studio: unknown;
|
|
69
|
+
readonly sourceProofs: unknown;
|
|
70
|
+
readonly sampleRenderSourceWorkflow: unknown;
|
|
71
|
+
sampleAt(time: number): unknown;
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const firstStoryboardShot = episode.storyboard.scenes[0]?.shots[0];
|
|
77
|
+
const firstCaption = episode.captionTrack.cues[0];
|
|
78
|
+
const thumbnailCapture = renderOutputPackage.thumbnailCapture;
|
|
79
|
+
const thumbnailCaption = captionCueAtTime(episode.captionTrack, thumbnailCapture.time);
|
|
80
|
+
const storyBible = episode.storyBible;
|
|
81
|
+
const typedCartoonAssets = assets as Partial<Record<CartoonAssetKey, AuraAssetRef<"model">>>;
|
|
82
|
+
|
|
83
|
+
const captionOverlay = document.createElement("div");
|
|
84
|
+
captionOverlay.id = "caption-overlay";
|
|
85
|
+
captionOverlay.textContent = firstCaption?.text ?? firstStoryboardShot?.storyBeat ?? "Aura3D cartoon studio";
|
|
86
|
+
captionOverlay.style.cssText = [
|
|
87
|
+
"position:fixed",
|
|
88
|
+
"left:50%",
|
|
89
|
+
"bottom:28px",
|
|
90
|
+
"transform:translateX(-50%)",
|
|
91
|
+
"max-width:min(840px,calc(100vw - 32px))",
|
|
92
|
+
"padding:12px 18px",
|
|
93
|
+
"border-radius:14px",
|
|
94
|
+
"background:rgba(3,12,20,0.88)",
|
|
95
|
+
"color:#f8fff2",
|
|
96
|
+
"font:700 18px/1.35 ui-rounded, Trebuchet MS, sans-serif",
|
|
97
|
+
"letter-spacing:0.01em",
|
|
98
|
+
"text-align:center",
|
|
99
|
+
"box-shadow:0 10px 30px rgba(0,0,0,0.28)",
|
|
100
|
+
"z-index:10"
|
|
101
|
+
].join(";");
|
|
102
|
+
document.body.appendChild(captionOverlay);
|
|
103
|
+
|
|
104
|
+
const shotPlaybackPlan = createShotPlaybackPlan({
|
|
105
|
+
timeline: episode.shotTimeline,
|
|
106
|
+
performance: episode.performance,
|
|
107
|
+
captions: episode.captionTrack,
|
|
108
|
+
visemes: visemeTrack,
|
|
109
|
+
runtimeNodeByCharacterId: {
|
|
110
|
+
miko: "miko",
|
|
111
|
+
luma: "luma"
|
|
112
|
+
},
|
|
113
|
+
loop: true
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
const playbackProbeTimes = [1, 21, 43] as const;
|
|
117
|
+
const playbackProbeSamples = playbackProbeTimes.map((time) => sampleCartoonRouteAt(time));
|
|
118
|
+
captionOverlay.dataset.shotId = playbackProbeSamples[0]?.shotId ?? "";
|
|
119
|
+
captionOverlay.dataset.captionId = playbackProbeSamples[0]?.captionId ?? firstCaption?.captionId ?? "";
|
|
120
|
+
document.body.dataset.cartoonShotCount = String(episode.shotTimeline.shots.length);
|
|
121
|
+
document.body.dataset.cartoonCaptionCount = String(episode.captionTrack.cues.length);
|
|
122
|
+
document.body.dataset.cartoonStoryBibleProps = String(storyBible.props.length);
|
|
123
|
+
document.body.dataset.cartoonStudioPanels = cartoonStudioSupport.panels.join(",");
|
|
124
|
+
installCartoonStudioPanel(document.body);
|
|
125
|
+
|
|
126
|
+
const app = createAuraApp("#app", {
|
|
127
|
+
scene: scene()
|
|
128
|
+
.background("#081b2a")
|
|
129
|
+
.add(createCharacterBody("miko", "Miko", "miko", [-0.8, 0.75, 0], [0.32, 0.42, 0.32], 0.72, "#7de2ff"))
|
|
130
|
+
.add(createMouthCard("miko:mouth", "Miko", [-0.8, 0.8, 0.32], "#f8fff2"))
|
|
131
|
+
.add(createCharacterBody("luma", "Luma", "luma", [0.8, 0.72, 0], [0.3, 0.38, 0.3], 0.68, "#ffe18e"))
|
|
132
|
+
.add(createMouthCard("luma:mouth", "Luma", [0.8, 0.77, 0.32], "#40ffbf"))
|
|
133
|
+
.add(
|
|
134
|
+
primitives
|
|
135
|
+
.box({ name: "moon garden path", material: material.pbr({ color: "#182d3f", roughness: 0.78 }) })
|
|
136
|
+
.position(0, 0.05, 0)
|
|
137
|
+
.scale([2.8, 0.04, 0.8])
|
|
138
|
+
)
|
|
139
|
+
.add(
|
|
140
|
+
primitives
|
|
141
|
+
.sphere({ name: "glow stones primitive fallback", material: material.emissive({ color: "#ffe18e", emissive: "#ffe18e", emissiveIntensity: 0.42 }) })
|
|
142
|
+
.position(0.18, 0.16, 0.28)
|
|
143
|
+
.scale([0.16, 0.08, 0.16])
|
|
144
|
+
)
|
|
145
|
+
.add(
|
|
146
|
+
primitives
|
|
147
|
+
.box({ name: "moon lilies primitive fallback", material: material.pbr({ color: "#f8fff2", roughness: 0.5 }) })
|
|
148
|
+
.position(-0.38, 0.2, 0.18)
|
|
149
|
+
.scale([0.12, 0.24, 0.04])
|
|
150
|
+
)
|
|
151
|
+
.add(labels.hud(firstCaption?.text ?? firstStoryboardShot?.storyBeat ?? "Aura3D cartoon studio"))
|
|
152
|
+
.add(lights.studio({ intensity: 1.2 }))
|
|
153
|
+
.add(effects.bloom({ intensity: 0.18, color: "#7de2ff" }))
|
|
154
|
+
.camera(camera.perspective({ position: [0, 1.4, 4.2], target: [0, 0.7, 0], fov: 42 })),
|
|
155
|
+
diagnostics: true
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
window.__AURA3D_CARTOON_TEMPLATE__ = {
|
|
159
|
+
contractId: episodeContractId,
|
|
160
|
+
template: "cartoon-studio",
|
|
161
|
+
storyBible,
|
|
162
|
+
shotIds: episode.shotTimeline.shots.map((shot) => shot.shotId),
|
|
163
|
+
captionIds: episode.captionTrack.cues.map((caption) => caption.captionId),
|
|
164
|
+
playbackProbeTimes,
|
|
165
|
+
playbackProbeSamples,
|
|
166
|
+
renderQueueItems: renderPlan.items.length,
|
|
167
|
+
typedAssets: typedCartoonAssetSummary,
|
|
168
|
+
requiredTypedAssets: requiredCartoonCharacterAssets,
|
|
169
|
+
missingTypedAssets: missingCartoonCharacterAssets,
|
|
170
|
+
assetCommands: publicCartoonAssetInstructions,
|
|
171
|
+
bridgeIssues,
|
|
172
|
+
promptAnimationEvidence,
|
|
173
|
+
publishReadiness,
|
|
174
|
+
studio: cartoonStudioSupport,
|
|
175
|
+
sourceProofs: {
|
|
176
|
+
captionFrameSyncSourceProof,
|
|
177
|
+
visemeFrameSyncSourceProof,
|
|
178
|
+
phonemeVisemeDubSyncSourceProof
|
|
179
|
+
},
|
|
180
|
+
sampleRenderSourceWorkflow,
|
|
181
|
+
sampleAt(time: number) {
|
|
182
|
+
return sampleCartoonRouteAt(time);
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
installShotPlayback(app, shotPlaybackPlan, {
|
|
187
|
+
primitiveMouthNodeByCharacterId: {
|
|
188
|
+
miko: "miko:mouth",
|
|
189
|
+
luma: "luma:mouth"
|
|
190
|
+
},
|
|
191
|
+
onCaption(caption, framePlan) {
|
|
192
|
+
captionOverlay.textContent = caption?.text ?? firstCaption?.text ?? "";
|
|
193
|
+
captionOverlay.dataset.shotId = framePlan.shotId ?? "";
|
|
194
|
+
captionOverlay.dataset.captionId = caption?.captionId ?? "";
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
|
|
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
|
+
function createMouthCard(id: "miko:mouth" | "luma:mouth", name: string, position: readonly [number, number, number], color: string) {
|
|
226
|
+
return primitives
|
|
227
|
+
.box({
|
|
228
|
+
name: `${name} primitive mouth card`,
|
|
229
|
+
material: material.emissive({ color, emissive: color, emissiveIntensity: 0.32 })
|
|
230
|
+
})
|
|
231
|
+
.position(position[0], position[1], position[2])
|
|
232
|
+
.scale([0.22, 0.035, 0.025])
|
|
233
|
+
.runtime(game.runtimeNode(id, { tags: ["mouth", "primitive", name.toLowerCase()] }));
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function sampleCartoonRouteAt(time: number) {
|
|
237
|
+
const bridgeSample = sampleAuraVoiceBridgeAtTime(auraVoicePackage, time);
|
|
238
|
+
const playbackSample = sampleShotPlaybackPlan(shotPlaybackPlan, time);
|
|
239
|
+
const caption = captionCueAtTime(episode.captionTrack, time);
|
|
240
|
+
const speakingCharacterId = bridgeSample.dialogue?.speakerId;
|
|
241
|
+
return {
|
|
242
|
+
time,
|
|
243
|
+
frame: bridgeSample.frame,
|
|
244
|
+
shotId: bridgeSample.shot?.shotId,
|
|
245
|
+
sceneId: bridgeSample.shot?.sceneId,
|
|
246
|
+
captionId: caption?.captionId,
|
|
247
|
+
captionText: caption?.text,
|
|
248
|
+
cameraMove: bridgeSample.shot?.camera.move,
|
|
249
|
+
transitionOut: bridgeSample.shot?.transitionOut,
|
|
250
|
+
dialogueLineId: bridgeSample.dialogue?.lineId,
|
|
251
|
+
visemeId: bridgeSample.viseme?.primaryVisemeId,
|
|
252
|
+
nodeUpdates: playbackSample.nodeUpdates.map((update) => ({
|
|
253
|
+
characterId: update.characterId,
|
|
254
|
+
action: speakingCharacterId === update.characterId ? "speak" : update.action,
|
|
255
|
+
emotion: update.emotion,
|
|
256
|
+
position: update.position
|
|
257
|
+
})),
|
|
258
|
+
sourceShotCharacters: (bridgeSample.shot?.characters ?? []).map((character) => ({
|
|
259
|
+
characterId: character.characterId,
|
|
260
|
+
action: speakingCharacterId === character.characterId ? "speak" : character.action,
|
|
261
|
+
emotion: character.emotion,
|
|
262
|
+
position: character.position
|
|
263
|
+
}))
|
|
264
|
+
};
|
|
265
|
+
}
|
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
import {
|
|
2
|
+
collectPromptAnimationEvidence,
|
|
3
|
+
createAudioStemManifest,
|
|
4
|
+
createAuraVoiceBridgePackage,
|
|
5
|
+
createAuraVoiceVisemeTrack,
|
|
6
|
+
createCaptionTimingProof,
|
|
7
|
+
createGlbBlendshapeVisemeCue,
|
|
8
|
+
createCartoonPerformanceCoverage,
|
|
9
|
+
createCartoonRenderOutputPackageMetadata,
|
|
10
|
+
createPrimitiveMouthVisemeCues,
|
|
11
|
+
createPromptAnimationDeterministicScreenshotFixtureMetadata,
|
|
12
|
+
evaluatePromptAnimationPublishReadiness,
|
|
13
|
+
defineDubMap,
|
|
14
|
+
glbVisemeBlendshapeExample,
|
|
15
|
+
primitiveMouthVisemeExample,
|
|
16
|
+
sampleAuraVoiceBridgeAtTime,
|
|
17
|
+
sampleVisemeTrack,
|
|
18
|
+
validateAuraVoiceBridgePackage
|
|
19
|
+
} from "@aura3d/engine";
|
|
20
|
+
import {
|
|
21
|
+
episode,
|
|
22
|
+
episodeAudioCues,
|
|
23
|
+
episodeContractId,
|
|
24
|
+
missingCartoonCharacterAssets,
|
|
25
|
+
publicCartoonAssetInstructions,
|
|
26
|
+
requiredCartoonCharacterAssets,
|
|
27
|
+
typedCartoonAssetSummary,
|
|
28
|
+
youtubeDraftMetadata
|
|
29
|
+
} from "./episode";
|
|
30
|
+
|
|
31
|
+
const frameRate = episode.episodePlan.runtime.frameRate;
|
|
32
|
+
|
|
33
|
+
export const renderContractId = episodeContractId;
|
|
34
|
+
|
|
35
|
+
export const renderPlan = episode.renderQueue;
|
|
36
|
+
|
|
37
|
+
export const renderOutputPackage = createCartoonRenderOutputPackageMetadata({
|
|
38
|
+
episodePlan: episode.episodePlan,
|
|
39
|
+
shotTimeline: episode.shotTimeline,
|
|
40
|
+
renderQueue: renderPlan,
|
|
41
|
+
youtube: youtubeDraftMetadata,
|
|
42
|
+
generatedAt: episode.episodePlan.generatedAt
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
export const audioStemManifest = createAudioStemManifest({
|
|
46
|
+
episodeId: episode.episodePlan.episodeId,
|
|
47
|
+
duration: episode.dialogueTrack.duration,
|
|
48
|
+
stems: [
|
|
49
|
+
...episode.dialogueTrack.lines.map((line) => ({
|
|
50
|
+
id: `audio:${line.lineId}`,
|
|
51
|
+
role: "dialogue" as const,
|
|
52
|
+
path: line.audioFile ?? `assets/audio/en/${line.lineId}.wav`,
|
|
53
|
+
startTime: line.startTime,
|
|
54
|
+
duration: line.endTime - line.startTime,
|
|
55
|
+
language: line.language
|
|
56
|
+
})),
|
|
57
|
+
...episodeAudioCues
|
|
58
|
+
],
|
|
59
|
+
generatedAt: episode.episodePlan.generatedAt
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
export const visemeTrack = createAuraVoiceVisemeTrack({
|
|
63
|
+
episodeId: episode.episodePlan.episodeId,
|
|
64
|
+
language: episode.episodePlan.language,
|
|
65
|
+
frameRate,
|
|
66
|
+
cues: episode.dialogueTrack.lines.flatMap((line) =>
|
|
67
|
+
createPrimitiveMouthVisemeCues({
|
|
68
|
+
characterId: line.speakerId,
|
|
69
|
+
speakerId: line.speakerId,
|
|
70
|
+
lineId: line.lineId,
|
|
71
|
+
startTime: line.startTime,
|
|
72
|
+
endTime: line.endTime
|
|
73
|
+
}).map((cue, cueIndex) => {
|
|
74
|
+
const words = line.text.split(/\s+/).filter(Boolean);
|
|
75
|
+
const wordIndex = Math.min(cueIndex, Math.max(0, words.length - 1));
|
|
76
|
+
return createGlbBlendshapeVisemeCue({
|
|
77
|
+
...cue,
|
|
78
|
+
phoneme: cue.visemeId === "m" ? "M" : "AH",
|
|
79
|
+
phonemeId: `${line.lineId}:phoneme-${cueIndex + 1}`,
|
|
80
|
+
word: words[wordIndex] ?? line.text,
|
|
81
|
+
wordIndex,
|
|
82
|
+
wordStartTime: cue.startTime,
|
|
83
|
+
wordEndTime: cue.endTime
|
|
84
|
+
});
|
|
85
|
+
})
|
|
86
|
+
),
|
|
87
|
+
generatedAt: episode.episodePlan.generatedAt
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
export const primitiveMouthRuntimeExample = {
|
|
91
|
+
...primitiveMouthVisemeExample,
|
|
92
|
+
runtimeNodes: {
|
|
93
|
+
miko: "miko:mouth",
|
|
94
|
+
luma: "luma:mouth"
|
|
95
|
+
}
|
|
96
|
+
} as const;
|
|
97
|
+
|
|
98
|
+
export const glbVisemeRuntimeExample = {
|
|
99
|
+
...glbVisemeBlendshapeExample,
|
|
100
|
+
typedAssetExample: "import { assets } from './aura-assets'; model(assets.character)",
|
|
101
|
+
sampleBlendshapeNames: Object.values(glbVisemeBlendshapeExample.blendshapeMap).slice(0, 6)
|
|
102
|
+
} as const;
|
|
103
|
+
|
|
104
|
+
export const captionTimingProof = createCaptionTimingProof(episode.dialogueTrack, episode.captionTrack, {
|
|
105
|
+
frameRate,
|
|
106
|
+
maxAllowedDriftFrames: 1
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
function frameDriftFrames(actual: number, expected: number) {
|
|
110
|
+
return Math.round(Math.abs(actual - expected) * frameRate);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const visemeLineSyncChecks = episode.dialogueTrack.lines.map((line) => {
|
|
114
|
+
const cues = visemeTrack.cues.filter((cue) => cue.lineId === line.lineId && cue.speakerId === line.speakerId);
|
|
115
|
+
const firstCue = cues[0];
|
|
116
|
+
const lastCue = cues[cues.length - 1];
|
|
117
|
+
const startDriftFrames = firstCue ? frameDriftFrames(firstCue.startTime, line.startTime) : 999;
|
|
118
|
+
const endDriftFrames = lastCue ? frameDriftFrames(lastCue.endTime, line.endTime) : 999;
|
|
119
|
+
const sampleTime = Math.min(line.endTime - 1 / frameRate, line.startTime + 1 / frameRate);
|
|
120
|
+
const sampledMouth = sampleVisemeTrack(visemeTrack, sampleTime, line.speakerId);
|
|
121
|
+
return {
|
|
122
|
+
lineId: line.lineId,
|
|
123
|
+
speakerId: line.speakerId,
|
|
124
|
+
characterId: line.speakerId,
|
|
125
|
+
cueCount: cues.length,
|
|
126
|
+
startDriftFrames,
|
|
127
|
+
endDriftFrames,
|
|
128
|
+
maxDriftFrames: Math.max(startDriftFrames, endDriftFrames),
|
|
129
|
+
sampledMouthState: {
|
|
130
|
+
time: sampleTime,
|
|
131
|
+
frame: Math.round(sampleTime * frameRate),
|
|
132
|
+
visemeId: sampledMouth.visemeId,
|
|
133
|
+
mouthOpenness: sampledMouth.mouthOpenness,
|
|
134
|
+
primitiveMouthCard: sampledMouth.primitiveMouthCard,
|
|
135
|
+
blendshapeWeights: sampledMouth.blendshapeWeights
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
const maxVisemeLineDriftFrames = Math.max(0, ...visemeLineSyncChecks.map((check) => check.maxDriftFrames));
|
|
141
|
+
const missingVisemeLineIds = visemeLineSyncChecks
|
|
142
|
+
.filter((check) => check.cueCount === 0 || check.maxDriftFrames > 1)
|
|
143
|
+
.map((check) => check.lineId);
|
|
144
|
+
|
|
145
|
+
export const captionFrameSyncSourceProof = {
|
|
146
|
+
contractId: renderContractId,
|
|
147
|
+
sourceOnly: true,
|
|
148
|
+
proofType: "caption-display-one-frame-source-harness",
|
|
149
|
+
frameRate,
|
|
150
|
+
allowedDriftFrames: 1,
|
|
151
|
+
allowedFrameDurationSeconds: 1 / frameRate,
|
|
152
|
+
captionDisplayWithinOneFrame: captionTimingProof.status === "pass" && captionTimingProof.maxDriftFrames <= 1,
|
|
153
|
+
maxObservedDriftFrames: captionTimingProof.maxDriftFrames,
|
|
154
|
+
coveredLineIds: captionTimingProof.coveredLineIds,
|
|
155
|
+
missingLineIds: captionTimingProof.missingLineIds,
|
|
156
|
+
sampledCaptionCues: captionTimingProof.lines.map((line) => ({
|
|
157
|
+
lineId: line.lineId,
|
|
158
|
+
captionCueId: line.captionId,
|
|
159
|
+
startDriftFrames: line.startDriftFrames,
|
|
160
|
+
endDriftFrames: line.endDriftFrames,
|
|
161
|
+
maxDriftFrames: line.maxDriftFrames
|
|
162
|
+
})),
|
|
163
|
+
executionBoundary: "Source harness only; actual caption display proof requires rendered frame/video evidence."
|
|
164
|
+
} as const;
|
|
165
|
+
|
|
166
|
+
export const visemeFrameSyncSourceProof = {
|
|
167
|
+
contractId: renderContractId,
|
|
168
|
+
sourceOnly: true,
|
|
169
|
+
proofType: "mouth-movement-one-frame-source-harness",
|
|
170
|
+
frameRate,
|
|
171
|
+
allowedDriftFrames: 1,
|
|
172
|
+
allowedFrameDurationSeconds: 1 / frameRate,
|
|
173
|
+
mouthMovementWithinOneFrame: missingVisemeLineIds.length === 0 && maxVisemeLineDriftFrames <= 1,
|
|
174
|
+
maxObservedDriftFrames: maxVisemeLineDriftFrames,
|
|
175
|
+
coveredLineIds: visemeLineSyncChecks.filter((check) => check.cueCount > 0).map((check) => check.lineId),
|
|
176
|
+
missingLineIds: missingVisemeLineIds,
|
|
177
|
+
sampledMouthStates: visemeLineSyncChecks.map((check) => ({
|
|
178
|
+
lineId: check.lineId,
|
|
179
|
+
speakerId: check.speakerId,
|
|
180
|
+
characterId: check.characterId,
|
|
181
|
+
...check.sampledMouthState
|
|
182
|
+
})),
|
|
183
|
+
primitiveMouthRuntimeNodeIds: primitiveMouthRuntimeExample.runtimeNodes,
|
|
184
|
+
glbBlendshapeNames: glbVisemeRuntimeExample.sampleBlendshapeNames,
|
|
185
|
+
executionBoundary: "Source harness only; actual mouth movement proof requires rendered frame/video evidence."
|
|
186
|
+
} as const;
|
|
187
|
+
|
|
188
|
+
function shotForLine(lineId: string) {
|
|
189
|
+
return episode.shotTimeline.shots.find((shot) => lineId.startsWith(`${shot.shotId}:line-`));
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export const spanishDubMap = defineDubMap({
|
|
193
|
+
artifact: "dub-map",
|
|
194
|
+
contractId: episode.dialogueTrack.contractId,
|
|
195
|
+
episodeId: episode.episodePlan.episodeId,
|
|
196
|
+
sourceLanguage: "en",
|
|
197
|
+
targetLanguage: "es",
|
|
198
|
+
entries: episode.dialogueTrack.lines.map((line) => {
|
|
199
|
+
const shot = shotForLine(line.lineId);
|
|
200
|
+
return {
|
|
201
|
+
originalStoryboardId: shot?.sceneId,
|
|
202
|
+
dubbedStoryboardId: shot?.sceneId,
|
|
203
|
+
originalStoryboardSceneId: shot?.sceneId,
|
|
204
|
+
dubbedStoryboardSceneId: shot?.sceneId,
|
|
205
|
+
originalLineId: line.lineId,
|
|
206
|
+
dubbedLineId: line.lineId,
|
|
207
|
+
originalCaptionId: `${line.lineId}:caption`,
|
|
208
|
+
dubbedCaptionId: `${line.lineId}:caption`,
|
|
209
|
+
originalCharacterId: line.speakerId,
|
|
210
|
+
dubbedCharacterId: line.speakerId,
|
|
211
|
+
originalSpeakerId: line.speakerId,
|
|
212
|
+
dubbedSpeakerId: line.speakerId,
|
|
213
|
+
originalLanguage: line.language,
|
|
214
|
+
dubbedLanguage: "es",
|
|
215
|
+
originalShotId: shot?.shotId,
|
|
216
|
+
dubbedShotId: shot?.shotId,
|
|
217
|
+
originalSceneId: shot?.sceneId,
|
|
218
|
+
dubbedSceneId: shot?.sceneId
|
|
219
|
+
};
|
|
220
|
+
}),
|
|
221
|
+
generatedAt: episode.episodePlan.generatedAt
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
export const phonemeVisemeDubSyncSourceProof = {
|
|
225
|
+
contractId: renderContractId,
|
|
226
|
+
sourceOnly: true,
|
|
227
|
+
proofType: "phoneme-viseme-dub-sync-source-harness",
|
|
228
|
+
visemeFormat: visemeTrack.format,
|
|
229
|
+
frameRate,
|
|
230
|
+
allowedDriftFrames: 1,
|
|
231
|
+
phonemeFields: ["phoneme", "phonemeId", "word", "wordIndex", "wordStartTime", "wordEndTime"],
|
|
232
|
+
sampledPhonemeVisemeCues: visemeTrack.cues.slice(0, 8).map((cue) => ({
|
|
233
|
+
cueId: cue.id,
|
|
234
|
+
lineId: cue.lineId,
|
|
235
|
+
speakerId: cue.speakerId,
|
|
236
|
+
characterId: cue.characterId,
|
|
237
|
+
visemeId: cue.visemeId,
|
|
238
|
+
phoneme: cue.phoneme,
|
|
239
|
+
phonemeId: cue.phonemeId,
|
|
240
|
+
word: cue.word,
|
|
241
|
+
wordIndex: cue.wordIndex,
|
|
242
|
+
wordStartTime: cue.wordStartTime,
|
|
243
|
+
wordEndTime: cue.wordEndTime,
|
|
244
|
+
startTime: cue.startTime,
|
|
245
|
+
endTime: cue.endTime
|
|
246
|
+
})),
|
|
247
|
+
dubContinuity: {
|
|
248
|
+
sourceLanguage: spanishDubMap.sourceLanguage,
|
|
249
|
+
targetLanguage: spanishDubMap.targetLanguage,
|
|
250
|
+
stableShotIds: spanishDubMap.entries.every((entry) => entry.originalShotId === entry.dubbedShotId),
|
|
251
|
+
stableStoryboardIds: spanishDubMap.entries.every((entry) => entry.originalStoryboardId === entry.dubbedStoryboardId),
|
|
252
|
+
stableCaptionIds: spanishDubMap.entries.every((entry) => entry.originalCaptionId === entry.dubbedCaptionId),
|
|
253
|
+
stableLineIds: spanishDubMap.entries.every((entry) => entry.originalLineId === entry.dubbedLineId)
|
|
254
|
+
},
|
|
255
|
+
executionBoundary: "Source harness only; actual phoneme, viseme, and dub playback proof requires rendered frame/video evidence."
|
|
256
|
+
} as const;
|
|
257
|
+
|
|
258
|
+
export const performanceCoverage = createCartoonPerformanceCoverage(episode.performance, episode.dialogueTrack);
|
|
259
|
+
|
|
260
|
+
export const reviewPackagePaths = renderOutputPackage.reviewPackagePaths;
|
|
261
|
+
|
|
262
|
+
export const thumbnailCapture = renderOutputPackage.thumbnailCapture;
|
|
263
|
+
|
|
264
|
+
export const plannedDeterministicCaptureSources = renderPlan.items.map((item) => ({
|
|
265
|
+
renderQueueItemId: item.id,
|
|
266
|
+
time: item.time,
|
|
267
|
+
frame: item.frame,
|
|
268
|
+
...(item.shotId ? { shotId: item.shotId } : {}),
|
|
269
|
+
sourceSceneStateId: item.sourceSceneState?.sceneStateId,
|
|
270
|
+
auraVoiceTimestamp: item.sourceSceneState?.auraVoiceTimestamp ?? item.time,
|
|
271
|
+
deterministicSeed: item.sourceSceneState?.deterministicSeed
|
|
272
|
+
}));
|
|
273
|
+
|
|
274
|
+
export const sampleRenderSourceWorkflow = {
|
|
275
|
+
contractId: renderContractId,
|
|
276
|
+
sourceOnly: true,
|
|
277
|
+
status: "source-workflow-ready",
|
|
278
|
+
promptToAudioToAura3DAnimation: true,
|
|
279
|
+
sampleEpisodeId: episode.episodePlan.episodeId,
|
|
280
|
+
runtimeSeconds: episode.episodePlan.runtime.duration,
|
|
281
|
+
minimumRuntimeSeconds: 60,
|
|
282
|
+
frameRate,
|
|
283
|
+
resolution: episode.episodePlan.runtime.resolution,
|
|
284
|
+
shotCount: episode.shotTimeline.shots.length,
|
|
285
|
+
speakingCharacterIds: Array.from(new Set(episode.dialogueTrack.lines.map((line) => line.speakerId))),
|
|
286
|
+
requiredTypedAssets: requiredCartoonCharacterAssets,
|
|
287
|
+
typedAssetSummary: typedCartoonAssetSummary,
|
|
288
|
+
missingTypedCharacterAssets: missingCartoonCharacterAssets,
|
|
289
|
+
assetCommands: publicCartoonAssetInstructions,
|
|
290
|
+
audioStemRoles: audioStemManifest.stems.map((stem) => stem.role),
|
|
291
|
+
requiresNoManualTimingEdits: true,
|
|
292
|
+
doesNotClaimRenderedArtifacts: true,
|
|
293
|
+
humanReviewRequired: true,
|
|
294
|
+
captionFrameSyncSourceProof,
|
|
295
|
+
visemeFrameSyncSourceProof,
|
|
296
|
+
phonemeVisemeDubSyncSourceProof,
|
|
297
|
+
artifactManifestExpectations: {
|
|
298
|
+
video: {
|
|
299
|
+
required: true,
|
|
300
|
+
outputId: renderOutputPackage.outputs.mp4?.id,
|
|
301
|
+
path: renderOutputPackage.outputs.mp4?.path,
|
|
302
|
+
evidenceJsonFields: ["path", "sha256", "byteSize", "duration", "frameRate", "resolution", "renderQueueId"]
|
|
303
|
+
},
|
|
304
|
+
thumbnail: {
|
|
305
|
+
required: true,
|
|
306
|
+
outputId: renderOutputPackage.outputs.thumbnail?.id,
|
|
307
|
+
path: renderOutputPackage.outputs.thumbnail?.path,
|
|
308
|
+
sourceSceneStateId: renderOutputPackage.thumbnailCapture.sourceSceneStateId,
|
|
309
|
+
evidenceJsonFields: ["path", "sha256", "byteSize", "width", "height", "captureTime", "shotId", "sourceSceneStateId"]
|
|
310
|
+
},
|
|
311
|
+
captions: {
|
|
312
|
+
required: true,
|
|
313
|
+
outputIds: renderOutputPackage.outputs.captions.map((output) => output.id),
|
|
314
|
+
paths: renderOutputPackage.outputs.captions.map((output) => output.path),
|
|
315
|
+
evidenceJsonFields: ["path", "sha256", "byteSize", "language", "cueCount", "captionTrackId"]
|
|
316
|
+
},
|
|
317
|
+
timeline: {
|
|
318
|
+
required: true,
|
|
319
|
+
outputId: renderOutputPackage.outputs.timelineJson?.id,
|
|
320
|
+
path: renderOutputPackage.outputs.timelineJson?.path,
|
|
321
|
+
evidenceJsonFields: ["path", "sha256", "byteSize", "shotTimelineId", "frameRate", "duration"]
|
|
322
|
+
},
|
|
323
|
+
audioStems: {
|
|
324
|
+
required: true,
|
|
325
|
+
artifact: audioStemManifest.artifact,
|
|
326
|
+
path: "dist/render/audio-stems.json",
|
|
327
|
+
stemCount: audioStemManifest.stems.length,
|
|
328
|
+
evidenceJsonFields: ["path", "sha256", "byteSize", "stemCount", "dialogueStemCount", "sfxStemCount"]
|
|
329
|
+
},
|
|
330
|
+
evidenceJson: {
|
|
331
|
+
required: true,
|
|
332
|
+
outputId: renderOutputPackage.outputs.evidenceJson?.id,
|
|
333
|
+
path: renderOutputPackage.outputs.evidenceJson?.path,
|
|
334
|
+
evidenceJsonFields: ["path", "sha256", "byteSize", "schema", "ok", "gates", "proofs"]
|
|
335
|
+
},
|
|
336
|
+
youtubeDraftMetadata: {
|
|
337
|
+
required: true,
|
|
338
|
+
outputId: renderOutputPackage.outputs.youtubeMetadata?.id,
|
|
339
|
+
path: renderOutputPackage.outputs.youtubeMetadata?.path,
|
|
340
|
+
evidenceJsonFields: ["title", "description", "language", "videoPath", "thumbnailPath", "captionsPath"]
|
|
341
|
+
}
|
|
342
|
+
},
|
|
343
|
+
requiredEvidenceJsonFields: [
|
|
344
|
+
"artifacts.video",
|
|
345
|
+
"artifacts.thumbnail",
|
|
346
|
+
"artifacts.captions",
|
|
347
|
+
"artifacts.timeline",
|
|
348
|
+
"artifacts.audioStems",
|
|
349
|
+
"artifacts.evidenceJson",
|
|
350
|
+
"captionFrameSyncSourceProof.captionDisplayWithinOneFrame",
|
|
351
|
+
"visemeFrameSyncSourceProof.mouthMovementWithinOneFrame",
|
|
352
|
+
"phonemeVisemeDubSyncSourceProof.sampledPhonemeVisemeCues",
|
|
353
|
+
"phonemeVisemeDubSyncSourceProof.dubContinuity.stableShotIds",
|
|
354
|
+
"humanReview.status"
|
|
355
|
+
],
|
|
356
|
+
remainingProofGates: [
|
|
357
|
+
"Fresh scaffold npm run build output",
|
|
358
|
+
"Browser route health and storyboard playback output",
|
|
359
|
+
"Typed character GLBs added through src/aura-assets.ts",
|
|
360
|
+
"npx @aura3d/cli@latest assets validate-cartoon output",
|
|
361
|
+
"Actual 60-second MP4/WebM render artifact",
|
|
362
|
+
"Actual thumbnail artifact captured from the same Aura3D scene state",
|
|
363
|
+
"Actual captions/timeline/audio-stems/evidence JSON files with hashes",
|
|
364
|
+
"Human visual review approval"
|
|
365
|
+
]
|
|
366
|
+
} as const;
|
|
367
|
+
|
|
368
|
+
export const auraVoicePackage = createAuraVoiceBridgePackage({
|
|
369
|
+
episodePlan: episode.episodePlan,
|
|
370
|
+
storyboard: episode.storyboard,
|
|
371
|
+
shotTimeline: episode.shotTimeline,
|
|
372
|
+
dialogueTrack: episode.dialogueTrack,
|
|
373
|
+
captionTrack: episode.captionTrack,
|
|
374
|
+
visemes: visemeTrack,
|
|
375
|
+
audioStems: audioStemManifest,
|
|
376
|
+
dubMap: spanishDubMap,
|
|
377
|
+
renderQueue: renderPlan,
|
|
378
|
+
renderOutputPackage
|
|
379
|
+
}, {
|
|
380
|
+
route: "/",
|
|
381
|
+
frameRate,
|
|
382
|
+
maxTimingDriftFrames: 1,
|
|
383
|
+
youtube: youtubeDraftMetadata
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
export const deterministicScreenshotFixtures = createPromptAnimationDeterministicScreenshotFixtureMetadata({
|
|
387
|
+
bridgePackage: auraVoicePackage,
|
|
388
|
+
count: 3,
|
|
389
|
+
pathPrefix: "artifacts/screenshots/moon-garden"
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
export const accessibilityProofMetadata = auraVoicePackage.artifacts.episodePlan.accessibilityProof;
|
|
393
|
+
|
|
394
|
+
export const bridgeIssues = validateAuraVoiceBridgePackage(auraVoicePackage);
|
|
395
|
+
|
|
396
|
+
export const bridgeSampleAtThumbnail = sampleAuraVoiceBridgeAtTime(auraVoicePackage, renderOutputPackage.thumbnailCapture.time);
|
|
397
|
+
|
|
398
|
+
export const promptAnimationEvidence = collectPromptAnimationEvidence({
|
|
399
|
+
bridgePackage: auraVoicePackage,
|
|
400
|
+
performanceCoverage,
|
|
401
|
+
routeHealth: { status: "missing" },
|
|
402
|
+
generatedAt: episode.episodePlan.generatedAt
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
export const publishReadiness = evaluatePromptAnimationPublishReadiness(promptAnimationEvidence);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { AURAVOICE_AURA3D_PROMPT_ANIMATION_CONTRACT_ID } from "./contract";
|
|
2
|
+
|
|
3
|
+
export const setContractId = AURAVOICE_AURA3D_PROMPT_ANIMATION_CONTRACT_ID;
|
|
4
|
+
|
|
5
|
+
export const sets = [
|
|
6
|
+
{
|
|
7
|
+
id: "moon-garden",
|
|
8
|
+
name: "Moon Garden",
|
|
9
|
+
layers: ["foreground flowers", "midground robot path", "background planet skyline"],
|
|
10
|
+
primitiveFallbackProps: ["glow-broom", "glow-stones", "moon-lilies"],
|
|
11
|
+
optionalTypedAssetKeys: ["glowBroom", "glowStones", "moonLilies"],
|
|
12
|
+
safety: "soft colors, readable captions, reduced flash by default"
|
|
13
|
+
}
|
|
14
|
+
];
|