Purpose
Author the scene manifest — the text-based shot list that drives downstream prompt assembly and per-scene rendering.
Replaces the role that visual storyboarding played in the older NB2 + i2v multi-keyframe architecture. With Seedance 2.0 reference-to-video, scene-by-scene image generation is no longer required — but a structured plan IS still required, because:
- Multi-scene ads have global decisions that don't exist inside any single prompt (product ordering, setting variety, wardrobe rotation, lighting continuity).
- Cost gating — at $5-10 per Seedance scene, a human needs to approve the plan before firing 4-6 scenes.
- Global-rule enforcement — e.g. "wardrobe must vary across scenes" (per
create-ugc-style-videoDecision Rule 5). Each Seedance call has no memory of other calls; the author enforces variation in the manifest.
Use this atom when:
- The video has ≥2 distinct scenes (hook + product scenes, multi-product review, multi-setting GRWM, before/after, etc.)
- The architecture is Seedance 2.0 reference-to-video (one prompt per scene with internal sub-beats) — OR the legacy NB2 + i2v multi-keyframe flow (one keyframe per beat)
- A pre-render human gate is needed
Skip the manifest (or collapse to one row) for single-scene 15s ads with one product and no setting change.
Inputs
Required:
script.md— the locked VO/dialogue script with per-scene linesidea-brief.mdor equivalent — angle, format archetype, funnel stage, target platform- Product reference image paths (
source/products/*.pngor similar) - Avatar persona spec (from upstream casting step) including locked portrait reference path
Optional:
targeting-spec.md— for tone/voice continuitybrand-voice.md— for wardrobe/aesthetic palette- Reference ads — for pacing benchmarks
Output
Primary artifact: scene-manifest.yml (or .json) at the project's working/ path.
Schema:
project: <slug> # e.g. "video-01-three-product-grwm"
brand: <brand-name>
total_duration_seconds: <int> # sum of all scene durations
architecture: seedance-ref # or "nb-i2v" for legacy multi-keyframe
aspect_ratio: 9:16
target_platform: meta-paid # | tiktok-organic | both | youtube-shorts | pinterest
funnel_stage: TOF # | MOF | BOF
avatar:
persona_id: <slug> # link to brands/<brand>/avatars/<persona>/
portrait_ref: assets/avatar/portrait-hero.png
voice_character: "warm conversational American female, early-20s creator"
products:
- id: <product-slug>
label: "<exact brand name on package>"
ref_image: source/products/<slug>.png
visual_description: "powder-blue soft squeeze tube with white cap"
common_confusions: ["NOT a moisturizer", "NOT a body wash", "NOT a serum dropper"]
scenes:
- id: cold-open # ⭐ MANDATORY for UGC ads — 1s scroll-stopper teaser
order: 0
duration: 1 # 0.8-1.2s; ffmpeg-trim to exact target in composite
script: "Okay yeah, that's plumped." # lifted VO OR 1-word interjection ("Wait —" / "Okay so —")
source_strategy: lift_from_scene # | standalone_render
lift_source: # populated if source_strategy=lift_from_scene
scene_id: product-1
start_time_sec: 12.4 # absolute timestamp in source scene
duration_sec: 1.0
setting: { description: "inherits from lift source" }
wardrobe: { description: "inherits from lift source" }
framing: "extreme close-up of payoff state (e.g. post-application lips, surprised face, product mid-squeeze)"
on_screen_ratio: 0 # not counted toward face-share budget
sub_beats: []
product_refs: [] # the lift carries product context; no separate refs
continuity:
prev_state: null
end_state: "viewer registers a 'wait — what just happened?' moment, primed to keep watching"
next_compatible: [hook]
notes: |
Scroll-stopper cold open. Show the PAYOFF state before the SETUP state.
Default to lifting a 0.8-1.2s slice from a later scene (free, no drift).
Only set source_strategy=standalone_render if the lifted slice would spoil the body
(e.g. shows product name before the reveal). Memory: feedback_ugc_scroll_stopper_cold_open.md.
- id: hook
order: 1
duration: 8 # seconds
script: "Okay so… top 3 products I'm honestly obsessed with."
setting:
description: "casual bedroom corner with wooden dresser, cream wall, terracotta pot"
lighting: "soft warm morning daylight from window on right"
wardrobe:
description: "cream linen tank, gold hoops, honey-blonde beachy waves"
negations: ["NOT white-white", "NOT grey", "NOT silk", "NOT patterned"]
framing: "wide selfie, mid-thigh to head, all 3 products visible on dresser"
on_screen_ratio: 100 # percent — creator fully on-screen
sub_beats: [] # hook = single continuous shot, no internal cuts
product_refs: [face-wash, deodorant, self-tanner]
continuity:
prev_state: null
end_state: "hand rests on dresser, satisfied smile, eyes on camera"
next_compatible: [product-1] # downstream scene IDs that can follow
notes: "Single take. All 3 products visible throughout. NO internal cuts."
- id: product-1
order: 2
duration: 15
script: "First — the face wash. Plant-powered, no harsh chemicals."
setting:
description: "casual American home bathroom, stone counter, eucalyptus stem in glass"
lighting: "soft natural morning daylight from window above"
wardrobe:
description: "cream waffle robe, gold hoops, hair in soft messy top-knot"
negations: ["NOT white-white", "NOT terry-towel", "NOT strapless"]
framing: "mix wide / medium chest-up / close-up (varies per sub-beat)"
on_screen_ratio: 60
sub_beats:
- { time: "0-3s", type: WIDE_HOOK, framing: "wide selfie", action: "holds product up beside face, gentle gesture" }
- { time: "3-7s", type: PRODUCT_HERO, framing: "focus pull face→product", action: "rotates product, label visible" }
- { time: "7-11s", type: SIDESTEP, framing: "medium-close", action: "pumps gel onto fingertips, shows to camera. NO water, NO lather, NO application to face." }
- { time: "11-15s", type: REACTION_CTA, framing: "medium-to-wide", action: "caps product, satisfied smile, head nod" }
product_refs: [face-wash]
continuity:
prev_state: "from hook's bedroom dresser"
end_state: "satisfied smile, product capped and held beside face"
next_compatible: [product-2]
notes: "NSFW sidestep — fingertips show only. Lips closed between beats."
# ... product-2, product-3, etc.
- id: endcard
order: 99
duration: 4
script: null # endcard is visual-only, audio = L-cut from prior scene
setting:
description: "HTML hyperframe — brand wordmark + tagline + CTA + cert footer over warm flat-lay BG"
lighting: "n/a — HTML render"
wardrobe: null # no avatar
framing: "9:16 1080×1920, brand wordmark centered upper, CTA centered lower"
on_screen_ratio: 0
sub_beats: []
product_refs: [face-wash, deodorant, self-tanner]
continuity:
prev_state: "L-cut audio from last product scene's CTA"
end_state: "holds silent for 2s after audio ends"
notes: "Render via HTML hyperframe atom, not Seedance. See create-end-card-html."
global_rules:
- "Avatar identity holds via `portrait_ref` across every Seedance scene"
- "Wardrobe MUST vary across consecutive scenes"
- "Setting SHOULD vary across consecutive scenes"
- "Lips remain closed between dialogue beats (no idle mouth movement)"
- "No NSFW visual: no water, no lather, no actual body application"
- "Each product appears in exactly one product scene (hook is the exception)"
cost_estimate:
nb2_avatar: 0.08
seedance_scenes: <sum> # sum across scenes from PRICE_PER_SEC × duration
endcard_bg: 0.08
music: 0.40
total_usd: <sum>
Workflow
- Read inputs. Load
script.md,idea-brief.md, avatar persona, product refs. - Decompose the script into scenes. For UGC review format: hook (8s) + N product scenes (15s each) + endcard (4s).
- For each scene, populate:
- Setting + lighting that varies from neighbors (per global rule)
- Wardrobe with explicit negations of common drift
- Framing strategy (wide/medium/close mix vs single focal length)
- On-screen ratio (typically 100% for hook, 50-60% for product scenes, 0% for endcard)
- For Seedance scenes: 4 sub-beats (WIDE_HOOK / PRODUCT_HERO / SIDESTEP / REACTION_CTA) inside the 15s window
- Continuity links to prior and next scenes
- Run the continuity audit (per memory
feedback_continuity_audit_pass.md):- Walk through
scenes[]in order - For each scene N+1, check that
scenes[N].continuity.end_stateis a reachable starting point forscenes[N+1].continuity.prev_state - Flag any time-jumps, setting jumps without explicit transition, wardrobe collisions
- Walk through
- Compute cost estimate from scene durations × resolution-keyed per-second cost.
- Write
working/scene-manifest.ymlto the project folder. - Surface to operator for approval — this is the cheap pre-render gate.
Decision Rules
- Wardrobe must vary across consecutive scenes. Same identity portrait + different wardrobe per scene gives the ad visual rhythm. Avatar identity holds; wardrobe creates variety.
- Setting SHOULD vary across consecutive scenes for multi-product reviews (bathroom → bedroom → kitchen → outdoor). For single-setting ads (e.g. mirror-only GRWM), state this explicitly in
global_rules. - One product per scene (except the hook). Multi-product scenes inside a single Seedance call degrade label fidelity. The hook scene shows all products together but doesn't go macro on any single label.
- NSFW sidestep for body-application products. Reframe scenes that involve water/lather/face/body-application as: pump-onto-fingertips, twist-up-and-smell-test, or hand-only POV placement. Encode the sidestep explicitly in the scene's
actionfield. - On-screen ratio target. UGC review format aggregates ~50-60% creator on-screen. Hook = 100%, product macros (PRODUCT_HERO sub-beat) = 30%, B-roll = 0%. Sum across scenes shouldn't exceed 70% (over-talking-head reads as documentary, not UGC).
- Continuity audit before approval. Sequential walk per memory
feedback_continuity_audit_pass.md. Catches VO/visual timing inversions, off-screen transitions, time jumps. - Scene 0 (
cold-open) MUST exist for UGC ads. A 0.8-1.2s scroll-stopper teasing the payoff state before the verbal hook.source_strategy=lift_from_sceneis the default (free, identity/wardrobe lock for free);source_strategy=standalone_renderonly when the lifted slice spoils the body of the ad. The verbal hook in scene 1 (hook) is NOT a substitute — the cold open is visual scroll-stop, the verbal hook is intent framing. Memory:feedback_ugc_scroll_stopper_cold_open.md.
Quality Checks
- For UGC ads:
scenes[0].id == "cold-open"withdurationin [0.8, 1.2] and a validsource_strategy. Iflift_from_scene,lift_source.scene_idmatches a later scene'sid. - Every scene has a non-empty
script,setting,wardrobe,framing,duration(except endcard which may havescript: null, andcold-openwhich may inherit setting/wardrobe from its lift source). sum(scenes[].duration)matchestotal_duration_seconds.- For ≥2 consecutive scenes: wardrobe text differs.
- Every product in
products[]appears in exactly one scene'sproduct_refs(excluding the hook). continuity.end_stateof scene N is reachable fromcontinuity.prev_stateof scene N+1.cost_estimate.total_usdmatches the sum of per-scene costs.
Failure Modes
| Failure | Cause | Recovery |
|---|---|---|
| Identical wardrobe across scenes | Skipped Decision Rule 1 | Author wardrobe variations before approval gate. |
| Sub-beats sum > 15s | Time accounting drift | Tighten each sub-beat's time window; 0-3 / 3-7 / 7-11 / 11-15 is the proven split. |
| NSFW sidestep not encoded | Author defaulted to "apply to face" | Add explicit negations in scene's action: "NO water, NO lather, NO actual face washing motion." |
| End-state of scene N doesn't reach start of N+1 | Skipped continuity audit | Run sequential audit (Decision Rule 6). Insert a transitional beat or rewrite the gap. |
| Total duration doesn't sum to plan | Hook/endcard durations forgotten | Verify sum(scenes[].duration) == total_duration_seconds. |
References
- Composing molecule:
skills/molecules/ugc-ad/create-ugc-style-video(Phase 4 scene breakdown defines the structure) - Downstream prompt template:
prompt-example.mdat repo root - Downstream render atom:
skills/atoms/video-generation/create-video-seedance-2-fal - Continuity audit pattern: memory
feedback_continuity_audit_pass.md - Reference run:
beauty-by-earth/video-01-three-product-grwm/working/— seescene-{hook,facewash,deo,tanner}.jsonfor per-scene specs