create-aura3d 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +27 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +70 -0
- package/dist/index.js.map +1 -0
- package/package.json +55 -0
- package/templates/cartoon-channel/README.md +42 -0
- package/templates/cartoon-channel/index.html +13 -0
- package/templates/cartoon-channel/package.json +21 -0
- package/templates/cartoon-channel/playwright.config.ts +13 -0
- package/templates/cartoon-channel/src/aura-assets.ts +14 -0
- package/templates/cartoon-channel/src/characters.ts +28 -0
- package/templates/cartoon-channel/src/contract.ts +1 -0
- package/templates/cartoon-channel/src/episode.ts +300 -0
- package/templates/cartoon-channel/src/main.ts +322 -0
- package/templates/cartoon-channel/src/render-plan.ts +405 -0
- package/templates/cartoon-channel/src/sets.ts +14 -0
- package/templates/cartoon-channel/tests/route-health.spec.ts +7 -0
- package/templates/cartoon-channel/tests/storyboard-playback.spec.ts +74 -0
- package/templates/cartoon-channel/tsconfig.json +12 -0
- package/templates/cinematic-scene/README.md +11 -0
- package/templates/cinematic-scene/index.html +13 -0
- package/templates/cinematic-scene/package.json +21 -0
- package/templates/cinematic-scene/playwright.config.ts +14 -0
- package/templates/cinematic-scene/public/aura-assets/hero-fixture.glb +0 -0
- package/templates/cinematic-scene/public/aura-assets/hero.thumb.svg +1 -0
- package/templates/cinematic-scene/src/aura-assets.ts +17 -0
- package/templates/cinematic-scene/src/main.ts +27 -0
- package/templates/cinematic-scene/tests/route-health.spec.ts +16 -0
- package/templates/cinematic-scene/tests/screenshot.spec.ts +56 -0
- package/templates/cinematic-scene/tsconfig.json +15 -0
- package/templates/fighting-game/README.md +64 -0
- package/templates/fighting-game/index.html +13 -0
- package/templates/fighting-game/package.json +21 -0
- package/templates/fighting-game/playwright.config.ts +12 -0
- package/templates/fighting-game/src/aura-assets.ts +11 -0
- package/templates/fighting-game/src/game/fighters.ts +152 -0
- package/templates/fighting-game/src/game/moves.ts +70 -0
- package/templates/fighting-game/src/game/stage.ts +98 -0
- package/templates/fighting-game/src/main.ts +417 -0
- package/templates/fighting-game/src/styles.css +124 -0
- package/templates/fighting-game/tests/gameplay-smoke.spec.ts +23 -0
- package/templates/fighting-game/tests/route-health.spec.ts +11 -0
- package/templates/fighting-game/tsconfig.json +19 -0
- package/templates/mini-game/README.md +10 -0
- package/templates/mini-game/index.html +13 -0
- package/templates/mini-game/package.json +21 -0
- package/templates/mini-game/playwright.config.ts +14 -0
- package/templates/mini-game/public/aura-assets/player-fixture.glb +0 -0
- package/templates/mini-game/public/aura-assets/player.thumb.svg +8 -0
- package/templates/mini-game/src/aura-assets.ts +17 -0
- package/templates/mini-game/src/main.ts +27 -0
- package/templates/mini-game/tests/route-health.spec.ts +16 -0
- package/templates/mini-game/tests/screenshot.spec.ts +63 -0
- package/templates/mini-game/tsconfig.json +15 -0
- package/templates/product-viewer/README.md +16 -0
- package/templates/product-viewer/index.html +16 -0
- package/templates/product-viewer/package.json +21 -0
- package/templates/product-viewer/playwright.config.ts +14 -0
- package/templates/product-viewer/public/aura-assets/product-fixture.glb +0 -0
- package/templates/product-viewer/public/aura-assets/product.thumb.svg +1 -0
- package/templates/product-viewer/src/aura-assets.ts +17 -0
- package/templates/product-viewer/src/main.ts +27 -0
- package/templates/product-viewer/tests/route-health.spec.ts +16 -0
- package/templates/product-viewer/tests/screenshot.spec.ts +60 -0
- package/templates/product-viewer/tsconfig.json +15 -0
- package/templates/prompt-cartoon-channel/README.md +42 -0
- package/templates/prompt-cartoon-channel/index.html +13 -0
- package/templates/prompt-cartoon-channel/package.json +21 -0
- package/templates/prompt-cartoon-channel/playwright.config.ts +13 -0
- package/templates/prompt-cartoon-channel/src/aura-assets.ts +14 -0
- package/templates/prompt-cartoon-channel/src/characters.ts +28 -0
- package/templates/prompt-cartoon-channel/src/contract.ts +1 -0
- package/templates/prompt-cartoon-channel/src/episode.ts +300 -0
- package/templates/prompt-cartoon-channel/src/main.ts +314 -0
- package/templates/prompt-cartoon-channel/src/render-plan.ts +405 -0
- package/templates/prompt-cartoon-channel/src/sets.ts +14 -0
- package/templates/prompt-cartoon-channel/tests/route-health.spec.ts +7 -0
- package/templates/prompt-cartoon-channel/tests/storyboard-playback.spec.ts +74 -0
- package/templates/prompt-cartoon-channel/tsconfig.json +12 -0
|
@@ -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",
|
|
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
|
+
];
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { expect, test } from "@playwright/test";
|
|
2
|
+
|
|
3
|
+
test("cartoon channel storyboard caption renders", async ({ page }) => {
|
|
4
|
+
await page.goto("/");
|
|
5
|
+
await expect(page.getByText(/Aura3D cartoon channel|moon|robot/i)).toBeVisible();
|
|
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 template = (window as unknown as {
|
|
15
|
+
__AURA3D_CARTOON_TEMPLATE__?: {
|
|
16
|
+
shotIds: readonly string[];
|
|
17
|
+
captionIds: readonly string[];
|
|
18
|
+
storyBible?: unknown;
|
|
19
|
+
playbackProbeSamples: readonly unknown[];
|
|
20
|
+
sampleAt(time: number): unknown;
|
|
21
|
+
};
|
|
22
|
+
}).__AURA3D_CARTOON_TEMPLATE__;
|
|
23
|
+
return {
|
|
24
|
+
shotIds: template?.shotIds ?? [],
|
|
25
|
+
captionIds: template?.captionIds ?? [],
|
|
26
|
+
storyBible: template?.storyBible,
|
|
27
|
+
samples: [1, 21, 43].map((time) => template?.sampleAt(time)),
|
|
28
|
+
playbackProbeSamples: template?.playbackProbeSamples ?? [],
|
|
29
|
+
bodyShotCount: document.body.dataset.cartoonShotCount,
|
|
30
|
+
bodyCaptionCount: document.body.dataset.cartoonCaptionCount
|
|
31
|
+
};
|
|
32
|
+
})) as {
|
|
33
|
+
shotIds: string[];
|
|
34
|
+
captionIds: string[];
|
|
35
|
+
storyBible?: {
|
|
36
|
+
props?: unknown[];
|
|
37
|
+
styleGuide?: { visualStyle?: string };
|
|
38
|
+
shotList?: unknown[];
|
|
39
|
+
};
|
|
40
|
+
samples: Array<{
|
|
41
|
+
shotId?: string;
|
|
42
|
+
captionId?: string;
|
|
43
|
+
captionText?: string;
|
|
44
|
+
cameraMove?: string;
|
|
45
|
+
transitionOut?: string;
|
|
46
|
+
nodeUpdates?: Array<{ characterId?: string; action?: string; emotion?: string }>;
|
|
47
|
+
}>;
|
|
48
|
+
playbackProbeSamples: unknown[];
|
|
49
|
+
bodyShotCount?: string;
|
|
50
|
+
bodyCaptionCount?: string;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
expect(routeProof.shotIds).toEqual([
|
|
54
|
+
"shot-moon-garden-open",
|
|
55
|
+
"shot-glow-stone-teamwork",
|
|
56
|
+
"shot-moon-garden-finish"
|
|
57
|
+
]);
|
|
58
|
+
expect(new Set(routeProof.samples.map((sample) => sample.shotId)).size).toBe(3);
|
|
59
|
+
expect(routeProof.captionIds).toHaveLength(6);
|
|
60
|
+
expect(routeProof.samples.every((sample) => sample.captionId && sample.captionText)).toBe(true);
|
|
61
|
+
expect(routeProof.samples.some((sample) => sample.cameraMove === "push-in" || sample.transitionOut === "cut")).toBe(true);
|
|
62
|
+
expect(routeProof.samples.flatMap((sample) => sample.nodeUpdates ?? []).some((update) => update.action === "speak")).toBe(true);
|
|
63
|
+
expect(routeProof.samples.flatMap((sample) => sample.nodeUpdates ?? []).some((update) => update.characterId === "miko")).toBe(true);
|
|
64
|
+
expect(routeProof.samples.flatMap((sample) => sample.nodeUpdates ?? []).some((update) => update.characterId === "luma")).toBe(true);
|
|
65
|
+
expect(routeProof.storyBible?.props?.length).toBeGreaterThanOrEqual(3);
|
|
66
|
+
expect(routeProof.storyBible?.styleGuide?.visualStyle).toMatch(/cartoon/i);
|
|
67
|
+
expect(routeProof.storyBible?.shotList).toHaveLength(3);
|
|
68
|
+
expect(routeProof.playbackProbeSamples).toHaveLength(3);
|
|
69
|
+
expect(routeProof.bodyShotCount).toBe("3");
|
|
70
|
+
expect(routeProof.bodyCaptionCount).toBe("6");
|
|
71
|
+
|
|
72
|
+
const screenshot = await page.screenshot();
|
|
73
|
+
expect(screenshot.byteLength).toBeGreaterThan(2048);
|
|
74
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Aura3D Cinematic Scene
|
|
2
|
+
|
|
3
|
+
Short source-code cinematic starter with typed assets, dolly camera, studio
|
|
4
|
+
lights, fog, rain, bloom, timeline, diagnostics, and screenshot tests.
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
npm install
|
|
8
|
+
npm run dev
|
|
9
|
+
npx @aura3d/cli@latest assets add ./assets/hero.glb --name hero
|
|
10
|
+
npm run test
|
|
11
|
+
```
|
|
@@ -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 Cinematic Scene</title>
|
|
7
|
+
<style>html, body, #app { margin: 0; width: 100%; height: 100%; background: #05070d; }</style>
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div id="app"></div>
|
|
11
|
+
<script type="module" src="/src/main.ts"></script>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "aura3d-cinematic-scene",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite --host 127.0.0.1",
|
|
8
|
+
"typecheck": "tsc --noEmit",
|
|
9
|
+
"build": "npm run typecheck && vite build",
|
|
10
|
+
"preview": "vite preview --host 127.0.0.1",
|
|
11
|
+
"test": "playwright test tests/route-health.spec.ts tests/screenshot.spec.ts --workers=1"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@aura3d/engine": "1.0.5"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@playwright/test": "^1.52.0",
|
|
18
|
+
"typescript": "^5.8.3",
|
|
19
|
+
"vite": "^7.3.2"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { defineConfig } from "@playwright/test";
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
testDir: "./tests",
|
|
5
|
+
use: {
|
|
6
|
+
baseURL: "http://127.0.0.1:4173"
|
|
7
|
+
},
|
|
8
|
+
webServer: {
|
|
9
|
+
command: "npm exec vite -- --host 127.0.0.1 --port 4173 --strictPort",
|
|
10
|
+
url: "http://127.0.0.1:4173",
|
|
11
|
+
reuseExistingServer: !process.env.CI,
|
|
12
|
+
timeout: 120_000
|
|
13
|
+
}
|
|
14
|
+
});
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180"><rect width="320" height="180" fill="#05070d"/><path d="M160 38 212 136H108z" fill="#38d6ff"/><text x="160" y="160" text-anchor="middle" font-family="Arial" font-size="18" fill="#f4f7fb">hero</text></svg>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { defineAuraAssets } from "@aura3d/engine";
|
|
2
|
+
|
|
3
|
+
export const assets = defineAuraAssets({
|
|
4
|
+
hero: {
|
|
5
|
+
type: "model",
|
|
6
|
+
format: "glb",
|
|
7
|
+
url: "/aura-assets/hero-fixture.glb",
|
|
8
|
+
bounds: [2.2, 1.6, 1.8],
|
|
9
|
+
hash: "sha256-4028ccbce11eb924936dad9bfee2af2aecf4b72203feb8a6b9dfdc458093e656",
|
|
10
|
+
metadata: {
|
|
11
|
+
materials: ["weathered metal", "visor glass", "scratched paint"],
|
|
12
|
+
animations: [],
|
|
13
|
+
textures: [],
|
|
14
|
+
thumbnailUrl: "/aura-assets/hero.thumb.svg"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
} as const);
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { createAuraApp, definePromptPlan, promptPlanToScene } from "@aura3d/engine";
|
|
2
|
+
import { assets } from "./aura-assets";
|
|
3
|
+
|
|
4
|
+
const plan = definePromptPlan({
|
|
5
|
+
sceneType: "cinematic-scene",
|
|
6
|
+
subject: { asset: assets.hero, label: "rain hero asset" },
|
|
7
|
+
style: "rainy neon alley hero shot",
|
|
8
|
+
environment: "wet asphalt, alley walls, cyan and amber practical lights",
|
|
9
|
+
camera: { preset: "cinematic-dolly" },
|
|
10
|
+
lighting: { preset: "neon-practicals" },
|
|
11
|
+
effects: ["rain", "fog", "bloom", "wet-reflection"],
|
|
12
|
+
interaction: "orbit",
|
|
13
|
+
acceptanceCriteria: [
|
|
14
|
+
"hero asset is framed in a rainy alley",
|
|
15
|
+
"rain, fog, neon practicals, and wet reflections are visible",
|
|
16
|
+
"camera uses a slow dolly toward the subject"
|
|
17
|
+
],
|
|
18
|
+
negativeCriteria: [
|
|
19
|
+
"do not accept a single model with only rain-line decoration",
|
|
20
|
+
"do not accept a flat grid without alley depth or wet surface cues"
|
|
21
|
+
]
|
|
22
|
+
} as const);
|
|
23
|
+
|
|
24
|
+
createAuraApp("#app", {
|
|
25
|
+
diagnostics: { overlay: true, assetPanel: true, performancePanel: true },
|
|
26
|
+
scene: promptPlanToScene(plan)
|
|
27
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { expect, test } from "@playwright/test";
|
|
4
|
+
|
|
5
|
+
test.setTimeout(60_000);
|
|
6
|
+
|
|
7
|
+
test("Aura3D cinematic scene reaches ready state", async ({ page }) => {
|
|
8
|
+
await page.goto("/");
|
|
9
|
+
await expect.poll(() => page.locator("body").getAttribute("data-aura3d-ready"), { timeout: 45_000 }).toBe("true");
|
|
10
|
+
const drawCalls = Number(await page.locator("body").getAttribute("data-aura3d-draw-calls"));
|
|
11
|
+
const diagnostics = await page.evaluate(() => (window as unknown as { __AURA3D_ROUTE_READY__?: { diagnostics?: { backend?: string } } }).__AURA3D_ROUTE_READY__?.diagnostics);
|
|
12
|
+
expect(diagnostics?.backend).toBe("webgl2");
|
|
13
|
+
expect(drawCalls).toBeGreaterThan(0);
|
|
14
|
+
mkdirSync(resolve("tests/reports"), { recursive: true });
|
|
15
|
+
writeFileSync(resolve("tests/reports/route-health.json"), `${JSON.stringify({ ready: true, backend: diagnostics?.backend, drawCalls }, null, 2)}\n`);
|
|
16
|
+
});
|