Format core — what every ad format inherits
Every format-* skill = this core + the format's own beat sheet, pack spec, visual grammar and test plan. Read this once per session before running any format.
0. Plan from the combo map (Step 0, every format; workflow-combo-loop)
- Before a pack exists:
python3 scripts/combo_loop/plan.py --format <format-skill> --brand dawnreadsoutput/combo-loop/dawn/latest/recipes.json+ the stake bank and proposes 3-5 combos. Each = a named PAYING parent (spend, ROAS, CPA) + ONE changed dimension (exploit / stake swap / explore). Pick with Fish; never two variables. - Paste the chosen block into the pack front matter (or job/spec JSON
"combo": {...}; no file →tags.py register --prefix):combo_avatar,combo_angle,combo_pov,combo_authority,combo_stake,combo_stake_on,combo_emotion,combo_root_cause,combo_mechanism,combo_payoff,combo_device,combo_format,combo_parent,combo_variable,combo_ad_prefix. Values only fromscripts/combo_loop/codebook_dawn.md(tags.py keys). - Gate 1 (concept approval) requires
python3 scripts/combo_loop/tags.py check <pack>PASS. - Thin families (video today) plan from the song recipe (story) or static recipe (callout) and say so; the result tests the format AND the variable.
1. Dawn doctrine (non-negotiable, all formats)
- Awareness gate. Problem-aware or warmer only (unaware lane parked, Fish 2026-08-31). Lines 1-2 name HER by an observable symptom. No pure mystery openers.
- Hook = Emotion + Curiosity gap + High stakes, starts in action, zero backstory (
feedback_hook_taxonomy). - The stake lands on HER and the mornings caused it (MAP.md §1). Escalating stakes is good, including family/relationship and legal stakes (corrections 10-05), sourced from real VOC. Do not use the kid's future money (college) or stakes the mornings didn't cause.
- Story formats: mom first-person narrator, an accuser close to her, a credible explainer INSIDE the story, a vindication payoff (MAP.md recipe A). Callout formats: brand voice straight to her, her daily burden, exhaustion→relief, kid independence payoff, authority = guarantee/science not a person (recipe B).
- Root cause + mechanism lines: sound doesn't reach a deep-sleeping brain; touch gets through; silent to everyone else and keeps going until they turn it off. Use Fish's ruled lines verbatim where the format has a mechanism beat.
- Brand files:
voc-*.md,root-cause.md,objection-map.mdare compatibility pointers since 9-15. Real sources:claims-register.md,product-truth.md, the VOC jsonl/banks they link, andoutput/dawn-combo-map-2026-10-05/stake bank. - Product reference images =
knowledge/brands/dawnbands/product-refs/(Fish 10-06, permanent: real frames of the real band from creator footage; read its README for defaults — photoreal/UGC =simple_{on,off}.jpg+pack_{on,off}.jpg, cartoon = the simple sheet only; display OFF unless the lit time is the point). Never PDP renders or generated images as product refs. - Product facts come from
knowledge/brands/dawnbands/product-truth.mdplus the live PDP (curl -sL https://dawnbands.com/products/dawn-wake-band.json): a plain ribbed silicone band (show the matte BLACK variant in creative; the store also sells blue, pink, orange), small flush LED time display that is DARK when idle, three alarms, no app, ships in a ziplock POUCH (never a box;reference_dawnbands_packaging), price = whatever the live PDP says that day (never hard-code it; product-truth's $39.99 is stale). Never a smartwatch, never a touchscreen, never a lit face in a scene. - Claims are unlocked (Fish 2026-10-02): ADHD, cortisol, 3x stronger, 10,000+ parents, percentages, ratings, guarantees, doctor/nurse authority. Product FACTS still must match product-truth.md.
- Register: literal and direct; figurative only if a 6th grader gets it instantly. Every first-person line passes the said-out-loud test (
feedback_written_vs_said_copy). Pronoun-first. - Copy routing: Dawn natives go through
dawn-native-brief→dawn-native-write. Video scripts go through the format skill's beat sheet, thendirect-response-copywriterdoctrine (Evolve rules:knowledge/evolve/copywriting-rules.md), thenhumanizer, then a red-team pass (song-ad-playbook §2 pattern: your logic pass → Opus red-team → fix → re-check). - Concept formula (binding): proven structure + verbatim customer line + a belief she holds + the buyer who converts (
feedback_ad_concept_formula). Every concept must fail the competitor-swap test (corrections 10-05: beat the leader, don't mirror it). - Don't build: college/kid-money stakes; stakes the mornings didn't cause; character-less explainer VO (0 purchases); testimonial-style statics; more dad-POV songs before the existing one reads.
1b. Fish rulings 10-06 (binding)
- Creator footage may be AI-edited / recast (Fish 10-06: "creator footage can ai edit").
- Photoreal people ads carry a "dramatization" label (on-screen or primary text); animated ads don't need one.
- vo-broll / b-roll generation: Higgsfield first, fal as fallback (Fish 10-06), an exception to fal-as-default.
- On-camera sung chorus: ONE test cell allowed (overrides the 9-19 no for a test only): same song, only chorus beats sung to camera via the proven cartoon lip-sync route (sync-3), one variable vs its parent.
- Square (1:1) text-thread statics: max 3 bubbles; longer threads use 4:5 or 9:16.
- No h3 vs h3-max test: all tiers stay on H3-max (Fish 10-06: "or need h3 max" — don't need the cheaper-model test). Nano Banana retired everywhere; image arm 2 = Seedream 5 Pro.
- End card: off by default (
--end-cardto opt in). Photoreal talking heads / UGC = ugc-omni (skills/ugc-omni/SKILL.md; Gemini Omni 1.1 Flash on kie, ~$0.06/s;mode: cuts= KJ UGC with b-roll,mode: oneshot= yapper single take). H3 native voice (yapper) is a side arm only. Photoreal image arms = GPT Image 2 + Seedream 5 Pro (--arms gpt2,sd5).
2. Production lanes (pick the one the format names)
Models are ROLES: look up today's model for each role in knowledge/ad-formats/_MODEL-REGISTRY.md; swap models there, not in skills.
| Lane | Entry | Use for |
|---|---|---|
| cartoon-h3 narrator | scripts/cartoon_h3/run.py (ElevenLabs VO is the clock) |
animated VO stories, explainers |
| cartoon-h3 dialogue | same; pack voices: + [SPEAKER] "line" VO lines |
animated multi-voice scenes (see §3 lip-sync limit) |
| cartoon-h3 song | song.py (Suno V5 on kie) → run.py --song |
sung stories (song-ad-playbook.md) |
| re-skin | scripts/cartoon_h3/reskin.py |
same audio + beats, new style (one variable) |
| ugc-omni | scripts/ugc_omni/omni.py (skill ugc-omni) |
THE photoreal engine: talking head + b-roll (mode: cuts) and single-take yapper (mode: oneshot); gates G0-G4 |
| statics | scripts/statics/gen_statics.py (skill dawn-statics) |
non-native image ads |
| natives | native-image-gen + dawn-native-write |
long copy + one image |
| yapper (H3 native voice) | scripts/yapper/yapper.py |
SIDE ARM only (Fish 10-06): one variable against a locked ugc-omni baseline. Photoreal talking heads and single takes run on ugc-omni (mode: oneshot); the keyframe chain stays closed (10-05) |
| vo-broll | scripts/vo_broll_runner.py (+ seedance_broll_runner.py, clip_library.py) |
VO over real/AI b-roll (format-warehouse, ugc batches) |
| hooks / readout | scripts.cartoon_h3.hooksplice, scripts.cartoon_h3.readout, scripts.cartoon_h3.matrix |
hook splices on a shared body; per-concept read |
2a. Editing = code, never CapCut (Fish 10-06: "we have remotion and stuff to cut for us instead of going into capcut")
Any source step that says CapCut/Premiere maps to our own stack, so edits are repeatable and batchable:
- Captions, title header, music bed, final render: scripts/remotion_stitch.py (Remotion project apps/orange-doc, compositions VideoAd 24 fps / VideoAd30; captionMode tiktok / dragon / tiktokbox; captions always from the script) — called by scripts/cartoon_h3/assemble.py; header via scripts/cartoon_h3/title.py; end card via scripts/cartoon_h3/endcard.py (OPT-IN --end-card since Fish 10-06: "we don't need that").
- Trims, speed fits, concat, mux, crop 16:9→9:16, intercut A/B speaker clips, split screen (vstack/hstack), cue-sheet SFX: ffmpeg (patterns in assemble.py fit_clip, master_mix in scripts/vo_audio_chain.py).
- Built 10-06: Remotion PodcastTwoShot, DuetStack, ScoreCard, ChapterCard (python3 scripts/remotion_stitch.py comp <Comp> <props.json> <out>); SFX cue sheets (scripts/cartoon_h3/sfx.py, H3_SFX_PLAN= or <run>/sfx-plan.json; real SFX files only — AI foley rejected 10-06); scripts/cartoon_h3/concat.py (chapters / hook prepend). Captions sit at 59-64% of height (Reels-safe, Fish 10-06); titles in the top 14% are pushed to 15%.
(Fish 10-06: "only expensive models for the harder scenes like the product reveal"; "minimax h3 could replace seedance")
Every batch/shot gets a tier in the storyboard; the tier picks the ROLE quality, the registry picks the model.
| Tier | Shots | Model quality |
|---|---|---|
| A, hard | product reveal / band on wrist, hands touching anything, CTA, 3+ characters interacting, the hook's first 3 s | best proven pick for the role (cartoon: H3-max; photoreal: ugc-omni Omni A-roll, product via omni's product gate ≥7/10 or real footage; Seedance 2.5 product shots rejected 10-06); candidates/regens allowed |
| B, standard | one or two characters acting, dialogue staging, room action | H3-max (or H3 / H3-max-turbo once proven equal on a side-by-side) |
| C, easy | establishing shots, empty rooms, clocks, b-roll inserts, reaction holds, paper/2D pans and zooms | cheapest proven simple-motion i2v; a still with a push-in is fine if under ~1.5 s |
- H3 covers A-B for every cartoon format; photoreal A-roll is all ugc-omni (tiers only pick b-roll there).
- Per-batch model choice: BUILT 10-06. scripts/cartoon_h3/tiers.py tags every batch A/B/C in plan.json + <run>/tiers.md (with cost per batch); run.py --tier-models "A=h3-max,B=h3-max,C=h3" routes each batch (default = --model for all, i.e. unchanged); receipts record tier/model/resolution. Proving a cheaper B/C model is still a one-unit side-by-side first.
3. Known lane limits (design around them, don't discover them mid-run)
- No lip-sync in cartoon-h3.
h3.py video_prompt()tells H3 "Audio 1 is the narration… No on-camera dialogue, no lip-sync" in every mode, including dialogue packs. A voiced character never mouths the line on screen. Stage spoken lines as: listener/reaction shot, speaker's back or profile in a wide, over-the-shoulder on the listener, phone/door/through-a-wall, or an insert of what the line is about. On-camera speech does exist on another route:minimax/h3-max/image-to-videowith NO audio input speaks a quoted line natively, lips included (yapper 10-04 ~23:05, Fish: "yea its good";project_dawn_yapper_2026-10-04). Inside cartoon-h3 that would be a new "native speech" batch mode (h3.py + storyboard + assemble keeping the clip's own audio). Cartoon on-camera speech: sync-3 lip-sync PROVEN 10-06 (Fish: "lipsync was fine") — wiring it into cartoon-h3 as a per-beat 'speak' mode is a code build that needs Fish's go; until it ships, cartoon-h3 dialogue = voiced but un-mouthed staging. - Raised wrist = lit smartwatch. H3 lights the band's display on any "look at the wrist" or "press the band" pose and in many night shots. Wrists down or resting flat, band side-on, character not looking at it; "held up" = arm forward at chest height (cartoon-h3 SKILL.md).
- Beats under ~1.2 s never get their own shot. Merge them into a neighbour's intent at pack stage.
- ≤4 panels per H3 batch, ≤15 s per batch, ≤3 panels on product/reveal/CTA batches.
- Lint before approval (BUILT 10-06):
python3 -m scripts.cartoon_h3.check_plan --lint <run_dir>; run.py refuses canvas/gen on blocking issues unless--skip-lint. Opt-in--chain-framesfeeds batch N-1's last frame as a START-frame ref (never identity). - Max 5 references reach a batch when a style anchor is set (run.py slices
refs[:5]): plan ≤3 named people + 1 room per batch or the room sheet is silently dropped (format-mini-movie audit 10-06). - Clay reads as clay only in warm, bright light (Starpop clay articles): wake/night shots keep a warm practical lamp in frame; describe emotion as face mechanics, not mood words.
- Two similar-age characters in one batch contaminate each other's identity. Keep them in separate batches or make them visually distinct (age, hair, colour).
- Narration floor 3.4 wps for EL narrator packs (vo_check gate); songs sing ~2.0-2.5 wps flat (budget lyrics accordingly).
- Photoreal people = ugc-omni gates (they implement the 9-30
feedback_ai_avatar_broll_lanerules): G0 script + hooks +board→ G1 real-creator ref frame + avatar → G2 lock: the same line good 3× (motion proof) → G3 every A-roll line + product check → G4 assembled cut. Every gate is Fish's; eyes over scores. Generated product frames only through omni's product gate (variant/pick --productrefuses <7/10 or the wrong display state,prodcheck; display-off wording; worn or resting across a palm, never pinched upright;product_actionlocked; continuous revealavatar_band→end_frame: holding); real footage is the fallback; Seedance 2.5 product shots stay rejected. No draft step (the 3× lock is the proof). Never work around a provider's likeness/safety filter. - Gemini QA can't see sub-second motion (samples ~1 fps) and grades photoreal against a cartoon rubric. Final gate = eyes.
4. Gates (in order; every one is Fish's unless marked)
- Concept + script approval (Fish): beat sheet filled, every stake/quote sourced, red-team done.
- Hooks: 3 hook variants per concept where the format allows (shared body via pack
body_start; hooks × leads matrix in cartoon-h3 SKILL.md). - VO/dialogue clarity gate (automatic,
vo_check.py), then Fish listens. - Refs/sheets (paid, ~$0.15-0.30 each): one sheet =
python3 -m scripts.cartoon_h3.sheet <out.png> <prompt.txt> [refs...]; the batch wrapperoutput/dawn-iterate-2026-10-04/video/render_sheets.sh <style> [--spend]only knows the 10-04 song cast (dry run first). Render only after Fish's go. Cast checked against the avatar BEFORE rendering. - Storyboard (
storyboard.md) → Fish →touch storyboard.approved. - Motion prompts for review:
run.py --stage gen --prompts-only(feedback_motion_prompt_approval) before paid video. 6b. Draft → final (Jack Vs. AI 10-06, Seedance 2.5 credits video): prototype tier-A/B shots on a cheap DRAFT (low res / draft endpoint), iterate the prompt on drafts, then render only the approved draft at final quality with a motion-preserving step. Routes: Seedance 2.5 Draft → "Generate in 1080p" on Higgsfield (30 vs 120 credits per 10 s); fal Flux 3*/draft→blackforestlabs/flux-3/draft-enhance(same seed, same motion); H3 has no motion-preserving re-render →run.py --draft(BUILT 10-06: 480P into<slug>-draft, copies VO/plan/approval/canvases, no upscale/end card, never touches the shared-body cache, prints the 768P re-render command). Real fal prices read 10-06 (h3.py FAL_PRICE): h3-max 480P $0.05/s, 768P $0.08/s, 1080P $0.16/s + ~$0.10-0.16/batch reference surcharge; e.g. nottardy (24 batches) $24.73 at 768P vs $15.96 as a 480P draft. Proof 10-06 (output/model-proofs-2026-10-06/6-h3-480p-draft): a 480P draft keeps the shots and cuts but is a DIFFERENT take (SSIM 0.34 vs the 768P clip) — use it to check prompt, staging and cut timing only, never to preview the final look. Final = native 768P/1080P re-render of approved batches (a 480→--hdupscale lost to native on Fish's 10-04 eye test). Not adopted from the same video: "stealth prompting" past moderation filters. - Generation:
--spend(Fish's green light),--candidates 1, ≤4 panels/job,--regen Nin its own call, never the Higgsfield UI Rerun (feedback_higgsfield_credit_discipline). - Assemble + QA: freezes 0, dark frames 0, captions from the script, end card only if asked (
run.py --end-card; Fish 10-06: not necessary by default), gemini_qa final, Fish watches the cut. - Placement preview (starpop facebook-instagram-ad-sizes-and-safe-zones, how-to-preview-a-facebook-ad-before-publishing): scrub to the hook frame, reveal frame and end card on a 9:16 overlay. Header, captions, product, price/offer inside Meta Reels' safe box: top 14 %, bottom 35 %, sides 6 % are UI (1080×1920: 269 px / 672 px / 65 px; 720×1280: 179 / 448 / 43 px) → content in y 14-65 %. Feed shows 9:16 at a 4:5 centre crop (~15 % lost top and bottom). The article names the UI regions but no numbers; 14/35/6 is Meta's published Reels spec (cross-checked 10-06). - Caption height verdict (VideoAd.tsx, read 10-06): default mode centres at 50 % + 80 px (≈56 % of 1280) = SAFE.tiktok= +236 px (≈68 % of 1280; ≈62 % if the comp is 1920 high because the offset is fixed px),tiktokboxtop 68 %,dragontop 74 % → all inside the Reels bottom band on Meta (primary text + CTA overlay). Dragon's title header at y 0.08 sits in the top 14 % band AND is cut by the Feed 4:5 crop. reaction-duet composite captions land ~74 % = in the band. Fish's call: a Meta-placement caption height ≤ ~62 % centre (two-line block must end ≤65 %), or accept for TikTok-only cuts. Code change (offset as % of height, per-placement) needs Fish's go. - Primary text: read only what shows before "See more" (symptom must be in it); the headline adds proof, never repeats the hook. Export the preview at the exact frame under review. - Decision-moment check (Starpop teardowns: AI flaws cluster where the ad asks for the click): frame-by-frame review of the last ~15 s (reveal, product, CTA, end card) on every format before Fish's watch. - Before promising any render: check fal, Higgsfield and kie balances (all three went dry 10-03).
5. Pack front matter every format pack carries
format, brand, created, status, parent (the paying ad this iterates, with CPA), iteration (the ONE variable), format_skill (e.g. format-spoken-drama), title, overlay, end_card, style, style_key, style_block, visual_negative, style_anchor, cast, envs, product_truth; run.py reads style/product truth/anchor/refs from the CLI only, so ALWAYS build the args with python3 -m scripts.cartoon_h3.packargs <pack.md> [--storyboard] (refs live in <pack>.args). Only style_<SPEAKER>/tempo_<SPEAKER> keys for speakers in voices: are voice settings (run.py fixed 10-06; before that style_key crashed dialogue VO). Dialogue packs add voices: and pace keys (vo_tempo, gap_same, gap_turn, max_gap, seam_gap, style_<SPK>, tempo_<SPK>); song packs add the suno_* keys. New slug per run (session freshness rule).
Plus the combo_* keys from §0 (combo_avatar … combo_ad_prefix), validated by python3 scripts/combo_loop/tags.py check <pack>.
6. Launch + read (media-buyer rules apply)
- One variable per ad set vs its parent. Each new concept gets its own ad set (Meta starves shared ad sets: the husband/MIL songs were never read).
- Video in SCALE needs a floor; Testing CBO for new formats (project_testing_restructure_2026-10-03).
- Never judge <7 days; never pause, reduce spend; 3x CPA kill rule outside learning.
- Every Meta asset goes through
scripts/meta_preflight.pybefore upload; ad link = Shop Now = text URLs, one page (check_links(),scripts/meta_link_audit.pyon live ads). Runscripts/meta_changes.pybefore any read or change. - Every Meta creative POST goes through
scripts/meta_dof_guard.pypost_creative_strict(Fish 10-06): explicit OPT_OUTdegrees_of_freedom_spec(17 keys, verified by read-back) always; a rejected spec fails the launch loudly — never re-post without the opt-out. Read-only check of live ads:python3 scripts/meta_dof_audit.py(10-06: 21/204 active Dawn ads had an enhancement on, mostlyproduct_browsingon SCALE dupes). - Feedback loop (combo map). Launched Meta ad names START WITH the pack's
combo_ad_prefix(copies/SCALE dupes keep it).python3 scripts/combo_loop/refresh.py --brand dawn(weekly, read-only; launchdcom.fish.combo-loop) reads every ad back by that prefix, codes only new untagged concepts (Sonnet, codebook verbatim), and rewritesoutput/combo-loop/dawn/latest/(MAP.md with DIFF, recipes.json, frequency/decay per family). The next plan starts from it. Corrections to a code go inoutput/combo-loop/dawn/codes.jsonwith"c": "sure". - Read songs/long video on CPA and lifetime frequency, not CTR alone (songs: CPM $49-68, CTR 3.8-4.7%, frequency builds ~half as fast per $ as statics).
- Record the launch in a
project_launch_<date>_<slug>.mdmemory with ad set IDs and the verdict date. - Upload hygiene (are-ai-generated-ads-allowed-on-meta): Advantage+ creative enhancements OFF (Meta's AI has added music, swapped products, garbled text on finished ads; the advertiser owns it). Our launchers copy
degrees_of_freedom_specfrom a model ad and silently re-post WITHOUT it if the first post fails (e.g. scripts/launch_2026_09_30_iterations.py:87-89, launch_2026_10_05_curvelle_statics.py:121-123) → Meta defaults. Proposed: fail loud or set explicit OPT_OUT instead of dropping (code, needs Fish's go). Day 1: open the live preview in Feed + Reels for added music, re-crops, altered frames, wrong destination. - AI label: Meta applies "AI info" to some generated/edited ads (more prominent beside Sponsored when a photoreal person is added via its tools) and is rolling out third-party detection from file signals (C2PA etc.). meta_preflight.py strips metadata, so no label ≠ clearance. In any photoreal vs animated test, record whether each live arm carries a label (hidden second variable). Political/social-issue rules don't apply to Dawn.
- Read = find the loss point (meta-video-ad-metrics-hook-rate-hold-rate-ctr-cpa): hook rate = 3-s views ÷ impressions (compare openings, not a quality score); hold rate = ThruPlays ÷ 3-s views (ThruPlay counts at 15 s, so on cuts >15 s it is first-15-s retention only — use 25/50/75/95 % watched for length); CTR = link clicks ÷ impressions; CVR = purchases ÷ link clicks. Hook up + hold down → body doesn't pay off the hook. Good views + low CTR → product connection/CTA unclear. Good CTR + low CVR → page/offer/message match. Same dates + same attribution window for every arm; verdict stays CPA vs break-even (below) at 7 days.
- Test capacity (how-much-budget-to-test-meta-ad-creatives): decision spend per ad = target CPA × a multiple chosen BEFORE launch (planning threshold, not a kill rule). Supported ads = daily test budget × days ÷ decision spend; over capacity → waves. E.g. Testing $450/day × 7 = $3,150; at $35 CPA × 3 = $105 → ~30 ads max; the 10-03 Testing load should be checked against this. Meta won't split spend evenly — floors exist for that (core §6 already).
- Break-even (how-to-calculate-break-even-roas-for-meta-ads): net revenue = AOV × (1 − refund rate); contribution margin = net revenue − (COGS + shipping + fulfilment + payment fees) = break-even CPA; target CPA = margin − desired profit; break-even ROAS = AOV ÷ break-even CPA; max CPC = target CPA × site CVR. If desired profit ≥ margin, change price/cost/CVR, not the ROAS target. Needs Dawn's real COGS/refund numbers (not in product-truth.md) after the 10-04 $44.99/$69.98/$98.97 price test — media-buyer should own the number.
7. Every format skill has these sections (template: _FORMAT-SKILL-TEMPLATE.md)
Read first · When to use / not · The format · Why it sells (evidence status) · Beat sheet · Pack spec · Visual grammar · Audio · Production commands + cost · Gates & QA checklist · Variants & test plan · Failure modes · Sources (13; scripts/check_format_skills.py enforces the order). Workflow skills: _WORKFLOW-SKILL-TEMPLATE.md (9 sections).