format-reaction-duet — a real mom's post on top, a mom who recognizes it below
1. Read first
knowledge/ad-formats/_FORMAT-CORE.md(doctrine, lane limits, gates, launch) andknowledge/ad-formats/_MODEL-REGISTRY.md(roles → today's models). Binding.references/beat-sheet.md(skeleton, line rules, top-track cut list),references/sources.md(the davidaistar duet short, the editor-composite method, what we refuse from it).skills/dawn-statics/SKILL.mdlock 3 + Fish 9-30 rulings: quotes verbatim, never dressed as buyers, ≤ ~15 words per card. Lock 3's forum credit line is superseded: Fish 10-03, "never put 'a wife on Reddit' / 'post-purchase survey' on an ad" (memorycorrections-archive-2026-10-03.md).- Quote sources: the raw threads under
knowledge/brands/dawnbands/research/(reddit-mornings-, reddit-teen-mornings-, voc-blame-2026-10-04), the stake bankoutput/creative-strategy-cooper-2026-10-05/dawn-stakes.md(reddit post IDs per row),knowledge/brands/dawnbands/research/customer-evidence-2026-09-15.md. Check every card line withscripts/statics/voc_check.py. output/dawn-combo-map-2026-10-05/MAP.md§2 (testimonial devices lose: frame the reaction as recognition/vindication, never as a review).- Reactor lanes:
skills/format-podcast/SKILL.md§7 (no-lip-sync staging) and, for the photoreal reactor (Lane B),skills/ugc-omni/SKILL.md+scripts/ugc_omni/omni.py, THE photoreal engine (Fish 10-06; another session owns them: read, never edit); job shapereferences/omni-job.example.json(illustrative). Product facts:knowledge/brands/dawnbands/product-truth.md.
2. When to use / when not
- Use for a cheap, fast recognition video: the top panel does the "it's not just me" work with a REAL mom's words, the reactor turns it into vindication ("you're not failing, here's why"). It's the video cousin of the stakes-callout statics (MAP.md §3 recipe B) and costs a fraction of a song.
- Evidence status: untested on Dawn. Reference = davidaistar short sU3r8z-MRZA (a fabricated local-news "breaking report" posted as a duet for organic reach; we keep the split-screen mechanic only) and his editor-first split-screen method (HBIENgQIQVU: "podcast style top/bottom … built entirely in the editor"). No Dawn data on split screens. Testimonial devices are the 5 weakest pairings (1.21-1.31): the reactor never reviews the product.
- Don't use to imitate news: no anchor, no lower-third "BREAKING", no outlet, no "as seen on", no real person's face, name or clip. Don't use for recipe-A long stories (format-spoken-drama / format-song) or when no verbatim source line carries the stake.
3. The format
- 9:16, 20-60 s (target 35-45 s ≈ 120-150 words at ~3.4 wps). Composite at 720×1280 (cartoon) or 1080×1920 (photoreal): top panel 720×640, bottom panel 720×640, 4 px light seam.
- Top = the source panel, one of three kinds (pick one per ad):
- Q — quote card (default): a real public forum post, verbatim (trim only), rendered on a plain brand card as text, with NO source line on screen (Fish 10-03; the source ID lives in the pack's top track). Whether MOM may say "A mom posted this." aloud is Fish's call at gate 1 (it is the format's premise, and it is an attribution). No username, no avatar, no vote counts, no platform logo or UI chrome (feedback_native_image_scrape_first: reject usernames/UI chrome).
- E — original explainer card: our own illustrated "why teens sleep through alarms" card (sleeping-brain picture + one plain sentence). Brand-neutral and obviously ours. Never styled as a broadcast, a PSA from a real agency, or a publication.
- R — real footage with rights: only clips we own or hold a written license for (our b-roll creators in
clip-library/raw/dawn-broll-2026-09-10/), license note saved next to the pack. A platform's duet/stitch permission does NOT license a third-party clip for a Meta ad. - Bottom = the reactor: an ordinary American mom 35-54 (the avatar). Animated by default (Lane A). She watches, reacts, then talks to the poster and the viewer.
- Captions from the script on every word.
DuetStack(built 10-06) renders them once in the Reels safe box (block bottom at 64 % of the composite); the manual ffmpeg block keeps the reactor's burned captions, which land ~63-68 % of the composite since the 10-06 caption move (partly inside the bottom 35 % UI band, see §10). Motion end card (optional,--end-card; off by default per Fish 10-06) over both halves at the end.
4. Why it sells
- Recognition first (recipe B). The top card is her daily burden in another mom's real words: problem-aware by construction, line 1 names the symptom. The reactor's "that's my house" is the viewer's own reaction, said for her.
- Vindication, not review (MAP.md §2). The turn is the reactor clearing the poster (and the viewer): "You're not failing. Sound doesn't reach a deep-sleeping brain." Authority = science (the ruled root-cause line, optionally an E card), not a person vouching for a product.
- Stake on HER, caused by the mornings: the post carries it (truancy letter in her name, CPS warning, the fight), sourced from the stake bank. Escalation across two cards is allowed and good (corrections 10-05).
- Why a real post beats a made-up one: it's VOC on screen (concept formula slot 2) and it's true. A fabricated "news report" would be both a policy risk and a lie; we never need it.
5. Beat sheet (full rules + cut list in references/beat-sheet.md)
| Beat | % runtime | Top panel | Bottom panel | Must contain |
|---|---|---|---|---|
| 1 Source hook | 0-15 | Q card 1 (the stake/symptom line) | Mom stops scrolling, reads, freezes | Card line verbatim, sourced, read aloud within 1.5 s; names her symptom |
| 2 Recognition | 15-35 | Q card 1 held, key phrase highlighted | Literal inserts of HER morning | "That's my house" + her ritual (counts/times from VOC); generic failed solutions |
| 3 Stake | 35-50 | Q card 2 (escalation, same post or a second sourced post) | Mom's face listening, then her reaction | Cost lands on the mom; the mornings caused it |
| 4 Vindication turn | 50-65 | E card (sleeping brain) or Q card held | Mom leaning in to the camera side-on / OTS | Ruled root-cause line verbatim; "it's not you" to the poster |
| 5 Answer | 65-85 | Band footage (real R clip, or a ugc-omni b-roll that passed pick --product) |
Kid wakes to the buzz (wrist rule; Lane B: the gated broll:band line, top holds the E/Q card) |
Touch gets through; silent to everyone else; keeps going until he turns it off; product named once |
| 6 Payoff + CTA | 85-100 | End card (optional, --end-card; off by default per Fish 10-06) spans both halves |
Calm kitchen, kid already up | Independence/calm on HER; 60-night trial spoken; end card |
6. Pack spec
- Step 0 — combo plan (feedback loop,
workflow-combo-loop):python3 scripts/combo_loop/plan.py --format format-reaction-duet --brand dawn→ pick one combo (a paying parent + ONE changed dimension) and paste its block into the pack front matter / job or spec JSON ("combo": {...}; no file →tags.py register --prefix):combo_avatarcombo_anglecombo_povcombo_authoritycombo_stakecombo_stake_oncombo_emotioncombo_root_causecombo_mechanismcombo_payoffcombo_devicecombo_formatcombo_parentcombo_variablecombo_ad_prefix(= the launched Meta ad-name prefix).python3 scripts/combo_loop/tags.py check <pack>must PASS before concept approval; the weekly refresh reads results back by that prefix.
Two artefacts per ad: the reactor pack (cartoon-h3, carries all audio) and the top track (cards + clips, no audio of its own).
- Reactor pack output/concept-packs/<slug>.md, core §5 front matter plus: format: cartoon-h3, format_skill: format-reaction-duet, parent: (the paying static/callout whose stake this carries, with CPA), iteration: (the ONE variable, e.g. "stakes static #5 → 40 s reaction duet, same stake"), voices: MOM=<id> (+ CARD=<id> only if an E card speaks; a calm neutral reader, never an anchor read), overlay: = the hook text (it is drawn on the top card, see §7), end_card, end_card_secs: 2.5, body_start (3 hooks × 1 body: each hook = its own card 1 + its own line 1-2). Dialogue pace keys if CARD is used: vo_tempo: 1.08, gap_same: 0.15, gap_turn: 0.3, max_gap: 0.22, seam_gap: 0.25 (10-01 values that passed vo_check).
- ## VO Script: N. [MOM] "line" / N. [CARD] "line", one sentence per line, 5-12 words. The quote card is read aloud by MOM, introduced once ("A mom posted this.") and then read verbatim.
- ## Scene visual intents = the BOTTOM panel only: N. **role** — <env>: <who, doing what, framing>, face in the lower-middle of the frame, plain wall above her head (the composite keeps rows 560-1200 of her 1280-high frame, §7).
- ## Top track (pack.py ignores extra sections; don't start its title with "Hook"): a table card | kind Q/E/R | first line N | last line N | source id | text or file. Every Q row carries the source ID (reddit post id / survey #) and the thread file it came from.
- Cast: MOM (+ KID for the inserts and the answer beat). Envs: 3-5 (kitchen table with phone, teen bedroom door, hallway, kid's bed, calm kitchen).
- New slug per run (session freshness).
7. Visual grammar
- Top panel: Q cards = plain brand card, one sentence of real text, large (TikTok Sans 700 via
scripts/cartoon_h3/title.pymake_title), no credit line (Fish 10-03). One highlighted phrase at most. E card = one still from the Image-gen role (registry: GPT Image 2.5 viascripts/cartoon_h3/sheet.py) + one sentence of exact text drawn by PIL (registry "Exact text": deterministic PIL beats any model). Optional slow push-in (ffmpeg zoompan); real motion on an E card = registry "Cheap simple-motion i2v" role, one-unit proof first. R = real clip, crop-filled. - Hook overlay lives on top card 1 (the reactor's
title:render at 30 % of her frame is cropped away by the composite). - Bottom panel, no lip-sync (core §3). The format suits the limit: while the card is read or the CARD voice speaks, she LISTENS (eyes on the phone, hand to mouth, slow nod). On her own lines: profile or over-the-shoulder on the phone, back to camera at the stove, or a literal insert of what she says (fever-dream literal). Never a close-up of her face mouthing her own line.
- Framing for the crop: her face sits between ~50 % and ~75 % of her own frame height (medium-wide, camera slightly above, table/lap below). Check this on the first canvas sheet before any
--spend. - Product: only in beat 5+, top = real band footage (
dawn band vibrating broll.h264-clean.mp4class clips), bottom = wrist resting on the duvet, band side-on, display DARK, buzz shown by the kid's eyes opening and small motion lines. Never a raised wrist, never a lit face. Photoreal (Lane B) band shots are generated only through ugc-omni's product gate (pick --product≥7/10 vs the real pack, display-off wording, worn or resting across a palm, never pinched upright); real footage is the fallback. - No readable text in generated frames (clock hands only, letters = a shape with a red stamp). The only text on screen: our cards, captions, end card (optional,
--end-card; off by default per Fish 10-06).
8. Audio
- One audio clock: the reactor run's ElevenLabs VO (registry TTS role,
scripts/cartoon_h3/vo.py) carries every word, including the read-out quote. The top track is silent. - MOM = warm, slightly tired, 35-54; reads the quote a touch slower and flatter than her own lines (said-out-loud test still applies to her lines, not to the verbatim quote). CARD (optional) = calm, plain; never broadcast cadence.
- Narration ≥3.4 wps (vo_check). Never fix a line by speeding it: split or re-take.
- One ducked bed (assemble default) or
--musica quieter bed; reaction videos run light on music. End card (optional,--end-card; off by default per Fish 10-06) swoosh from endcard.py.
9. Production (free until canvases; composite is free)
P=output/concept-packs/<slug>.md; R=output/cartoon-h3/<slug>
python3 scripts/statics/voc_check.py "<card 1 text>" "<card 2 text>" # 0 hits = not her words: fix
A=(${(z)"$(python3 -m scripts.cartoon_h3.packargs $P --storyboard)"}); A=(${(Q)A})
python3 -m scripts.cartoon_h3.run $A --stage vo # EL VO + vo_check (EL quota)
python3 -m scripts.cartoon_h3.run $A --stage storyboard # free; Fish reviews storyboard.md + top track
touch $R/storyboard.approved # Fish only
A=(${(z)"$(python3 -m scripts.cartoon_h3.packargs $P)"}); A=(${(Q)A})
python3 -m scripts.cartoon_h3.run $A --stage canvas # paid ~$0.20/batch; check crop framing here
python3 -m scripts.cartoon_h3.run $A --stage gen --prompts-only # Fish reviews motion prompts
python3 -m scripts.cartoon_h3.run $A --stage gen --spend --backend fal --model h3-max --candidates 1
python3 -m scripts.cartoon_h3.run $A --stage assemble --no-end-card # card goes on the composite, not here
Then the top track and composite (references/beat-sheet.md §Composite has the full, tested ffmpeg block): cards from make_title → 720×640 PNGs; per-card segments timed from $R/lines.json (start/end per line); real band clip segment; concat; vstack with crop=720:640:0:560 of $R/<slug>-final.mp4; then
python3 -m scripts.cartoon_h3.endcard $R/split.mp4 $R/split-ec.mp4 --secs 2.5 --text "<end_card>"
python3 scripts/meta_preflight.py $R/split-ec.mp4
- BUILT 10-06 (Builder C): the
DuetStackRemotion composition (1080×1920 @ 30: top track of cards/clips, bottom reactor, 4 px seam, one caption track in the safe box). Props:top= [{src, start, end}] from## Top track+ lines.json starts,bottom=<run>/video-vo.mp4(the UNcaptioned VO cut, so captions aren't doubled),words=<run>/caption-words.json.python3 scripts/remotion_stitch.py comp DuetStack <props.json> <run>/split.mp4, then master (assemble.master_no_bed, ormaster_mixfor the bed) andendcard.py. Verified 10-06 on a real run (5 s cut, -14.1 LUFS after master). The manual ffmpeg block in references/beat-sheet.md stays as the fallback. Still NOT BUILT (needs Fish's go): asplitmodule that writes those props from the pack and runs render → master → end card (optional,--end-card; off by default per Fish 10-06) with a receipt. Also not built: a run.py flag to drop thetitle:render (harmless: it's cropped away). - Shot tiers (core §2b): tag each reactor batch in
storyboard.md. A = hook first 3 s, the answer beat (kid's wrist, band on), CTA; B = MOM listening/reacting, profile/OTS; C = literal inserts with no person (letter on the table, alarms on the nightstand). run.py auto-tags tiers into<run>/tiers.md+plan.json(tiers.py; re-tag if the word match is wrong); per-tier models via--tier-models(fal), default all H3-max. The top track is $0 (cards + owned clips). - Draft → final (core gate 6b): free drafts = storyboard, canvases (check the crop framing here),
--prompts-only. A paid H3 draft isrun.py --draft(480P at $0.05/s vs $0.08/s, own<slug>-draftdir, prints the native re-render command); finals re-render natively (a 480P→--hdupscale lost to native on Fish's 10-04 eye test), so draft only when staging needs iteration; otherwise one candidate at final quality,--regen Nper failed batch. - Cost: 40 s ≈ 3-4 H3 batches (h3-max 768P $1.20 per 15 s + ~$0.10-0.16 reference surcharge per batch; h3.py fixed 10-06,
tiers.mdper batch) ≈ $3.5-5.5 video + ~$2-4 sheets/canvases + EL quota; Q cards and composite $0; an E card still ~$0.15-0.30. Check fal, Higgsfield and kie balances before promising. - Lane B (photoreal reactor, ugc-omni; dry run until
--spend): one job (references/omni-job.example.json):style: photoreal,mode: cuts,aroll_model: flash,resolution: 1080p,image_arms: ["gpt2", "sd5"],product_refs: ["@dawn"], display-offproduct_truth,label: "Dramatization";lines[]= MOM's lines in order (visual: "AI UGC", she reads the quote aloud in her own voice), the answer line =visual: "broll:band"(product: true,avatar: false, gated); hooks carryend_frame: "avatar";prompt.sceneframes her low (face at 50-75 % of the frame) for the crop. Sequence: G0omni.py board --job $J→ G1refscan --src <real mom-at-table video>,ref,avatar --n 3 --arms gpt2,sd5,pick --name avatar→ G2lock --line H1-1 --n 3(same take good 3×) →--approve→ G3gen --lines all,verify,broll --id band --stage frame→pick --name broll_band --product→--stage anim(fallback: real clip tobroll/band.mp4) → G4assemble --hook H1, then the composite withcrop=1080:960:0:840and 1080×960 cards (omni's captions sit at 68 % of her frame, inside the crop). Omni buckets $0.315 / $0.42 / $0.525 / $0.63 per 4 / 6 / 8 / 10 s, images ~$0.05-0.075: a 40 s reactor ≈ $6-9. No draft step (the G2 lock is the proof). Feeding DuetStack an uncaptioned bottom needs a words file from the per-linearoll/<id>.verify.json(NOT BUILT); until then use the captioned omni cut in the manual ffmpeg block. Talking animated reactor on camera = registry Lip-sync role candidatefal-ai/sync-lipsync/v3/image-to-video(sync-3), untested: one-unit proof ≤$2, Fish's eye, before any lane depends on it.
10. Gates & QA checklist
- [ ] Parent named with CPA; ONE variable stated.
- [ ] Source truth: every Q card is a real public post, verbatim (trim only),
voc_checkhit ≥1 AND the file it prints for the hit is a forum thread file labelledforum(since 10-06 it prints each hit's file;surveyhits are buyers, not public posts; competitor/Okendo hits print as NOT COUNTED), source ID + thread file in the top track; no source line on screen; no username, avatar, logo, UI chrome or vote counts. - [ ] No fake news: no anchor, outlet, network, publication, "breaking", chyron, "as seen on", real person's name/face/voice, or recast of anyone's clip. E cards are visibly ours. R clips have a license note.
- [ ] Stake lands on the mom and the mornings caused it; no college/kid-money stake. Root-cause + mechanism lines verbatim (Fish's ruled lines); product facts per product-truth.md; red-team done.
- [ ] Reactor never reviews the product; product named once, beat 5+; ≤25 % of runtime.
- [ ] Bottom intents: listener/profile/OTS/insert staging on every MOM line; wrist rule; face framed for the 560-1200 crop (checked on the canvas).
- [ ] Lane B only = ugc-omni G0-G4, every gate Fish's: G0 script +
board→ G1 real ref + avatar (not the ref person, real clothes, bare wrists) → G2 the hook line good 3× → G3 every line watched (verify), the band b-roll passedpick --productor is real footage → G4 the composite by eye and ear, "Dramatization" label. - [ ] Lane A: vo_check PASS; Fish listened. Storyboard approved with a tier per batch; motion prompts reviewed; balances checked before
--spend. - [ ] Placement preview (starpop facebook-instagram-ad-sizes-and-safe-zones, how-to-preview-a-facebook-ad-before-publishing): scrub to the hook frame, the reveal frame and the end card (optional,
--end-card; off by default per Fish 10-06) on a 9:16 overlay; header, captions, product and offer sit inside Meta Reels' safe box (top 14 %, bottom 35 %, sides 6 % are UI = keep content in y 14-65 %) and survive the Feed 4:5 centre crop (~15 % lost top and bottom). Remotiontiktok/tiktokbox/dragoncaptions centre at ~68-74 % = inside the Reels bottom band on Meta: flag it to Fish, never re-burn on your own. - [ ] Composite: cards switch on the right lines, seam clean, captions readable (DuetStack: block bottom 64 %, inside the Reels safe box; manual block: ~63-68 %, preview and flag to Fish), end card (optional,
--end-card; off by default per Fish 10-06) over both halves, freezes 0, dark 0; meta_preflight run; Fish watched it (eyes, not Gemini scores).
11. Variants & test plan
- Test 1: Q-card duet carrying a paying stakes static's stake (stake bank #6 truancy letter, reddit 1rfciee, or #7 CPS, reddit 199olsy; #5 is a private survey answer, never a Q card), cartoon reactor, 35-45 s. Own ad set in Testing CBO; read at 7 days on CPA vs the parent static, plus 3-s hold and lifetime frequency. Never pause; reduce.
- 3 hooks per body (shared beats 2-6): (a) card 1 = the stake line ("…you can get put in jail" class), (b) card 1 = the symptom line (alarms going off, he sleeps), (c) reactor-first: MOM's one-line reaction over the card already on screen ("A mom posted this and I had to sit down.") — only if line 2 names the symptom.
- Then one variable per ad set: source kind (Q vs E), the post (stake row), reactor lane (A vs B; B = ugc-omni G0-G4), runtime (25 vs 50 s), split vs full-frame (same script, no top panel).
- Graduation per
feedback_graduation_rule; launch memory per core §6. - Upload hygiene (starpop are-ai-generated-ads-allowed-on-meta): Advantage+ creative enhancements stay OFF (Meta's tools have added music and altered products on finished ads). The launchers copy
degrees_of_freedom_specfrom a model ad and silently drop it if that post fails, which falls back to Meta's defaults: check the created creative. Day 1: open the live preview in Feed AND Reels for added music, re-crops or altered frames.
12. Failure modes
- Reads as fake news / impersonation → policy and trust risk; strip every broadcast cue, replace with a Q card. Non-negotiable.
- Reads as a testimonial (reactor praising the band) → cut to one product line; the turn is about HER being right.
- Quote paraphrased or merged → it's no longer hers;
voc_check0 hits; pick a shorter verbatim line. - Username / subreddit UI in a screenshot → never screenshot; render text on our card.
- Her face cropped off by the composite → re-intent framing low (face 50-75 %), regen that batch; or shift the crop
yper segment in the manual block. - Captions over her face → manual block: keep the 560 offset; never re-burn captions on a final. Under the Reels UI: fixed 10-06 by
DuetStack(captions rendered once at ≤64 % over the uncaptionedvideo-vo.mp4); the manual block still lands ~63-68 % (Fish approved the 10-06 caption move to ≤62 % centre). - Lit band / smartwatch → wrist rule, regen with wrists down.
- Overbuilding (animated cards, multiple overlays, recasts) before the plain Q-card version reads → corrections 10-05: simplest recipe first.
13. Sources
~/research/davidaistar/txt/sU3r8z-MRZA.txt, analysis/guide_copy_images_shorts.md (shorts table row sU3r8z-MRZA), analysis/gemini_HBIENgQIQVU.md (multi-clip composition, editor-first), analysis/diff_cloning.md (organic hijack vs competitor mirror); skills/dawn-statics/SKILL.md; skills/format-podcast/SKILL.md, skills/format-street-interview/SKILL.md; scripts/cartoon_h3/{assemble.py,endcard.py,title.py,run.py,pack.py}; MAP.md §2; stake bank; memory corrections-hot.md, feedback_native_image_scrape_first.md.
Not built yet (needs Fish's go)
- BUILT 10-06 (Builder C): the DuetStack Remotion composition (1080×1920 @ 30: top track of cards/clips, bottom reactor, 4 px seam, one caption track in the safe box). Props: top = [{src, start, end}] from Top track + lines.json starts, botto
- Lane B (photoreal reactor, ugc-omni; dry run until --spend): one job (references/omni-job.example.json): style: photoreal, mode: cuts, aroll_model: flash, resolution: 1080p, image_arms: ["gpt2", "sd5"], product_refs: ["@dawn"], display-off
- Composite (preferred since 10-06: Remotion DuetStack, SKILL §9; the manual ffmpeg below is the fallback; split.py NOT BUILT)