agentic-ads-engine 0.2.0 → 0.3.0

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/README.md CHANGED
@@ -116,7 +116,7 @@ node bin/agentic-ads formats show sitcom
116
116
  node bin/agentic-ads plan --format sitcom --site demo
117
117
  ```
118
118
 
119
- Twelve formats cover the eight catalog recipes, lo-fi UGC, sitcom, storytime, and talking objects.
119
+ Fifteen formats cover the eight catalog recipes, lo-fi UGC, sitcom, storytime, talking objects, street interviews, Backrooms dialogue, and horror comedy.
120
120
  MCP adds `list_formats({costClass?, risk?})`, `get_format({id})`, and `plan_format({format, site, goal?, dryRun?})`.
121
121
  These tools return the same JSON as the commands above.
122
122
  The library exports `listFormats`, `getFormat`, and `planFormat` from the package root and `agentic-ads-engine/formats`.
@@ -133,8 +133,30 @@ Lo-fi costs reference reused assets: $0.881 for three videos6 ads and $0.165 for
133
133
  Fresh generation estimates differ. The style catalog did not render its recommended local recipes.
134
134
  No paid calls, current price checks, media QA, or platform approval occur here.
135
135
 
136
- Run `node test/mutate-formats.mjs` to repeat the three deliberate faults and restore the files.
137
- It records failures and the restored pass in `test/artifacts/formats-mutations.json`.
136
+ Run `node test/mutate-formats.mjs` to repeat the twelve deliberate faults and restore the files.
137
+ It records detected faults and the restored run result in `test/artifacts/formats-mutations.json`.
138
+
139
+ ### Audio and caption gates
140
+
141
+ Plans include `gateChecklist` instructions from `src/qa/gates.json`. Each entry starts as `NOT CHECKED`.
142
+ All formats with captions and speech require these four gates:
143
+
144
+ | Gate | Required check |
145
+ | --- | --- |
146
+ | `loudness_master` | Final file: target -14 LUFS, band -16 to -12 LUFS, true peak at or below -1 dBTP. |
147
+ | `caption_ocr` | Final-file OCR character error rate: 0.10 or less for every caption. |
148
+ | `voice_consistency` | OWNER CHECK: pitch spread above 1.25 asks a person to listen. The tool check never fails. |
149
+ | `stem_sfx` | Use separate voice and SFX stems, with laughs left out of SFX. Supply beat times via `--cuts`. Require SFX onsets, with at least 80% within 80 ms of a cut. Cuts without SFX are fine. |
150
+
151
+ Missing stems or voice mappings leave dependent checks `NOT CHECKED`. Voice fit stays an owner check.
152
+ On our own videos, neither pitch spread nor a speaker embedding separated 1 rejected voice from 11 accepted voices.
153
+ This is a small sample; a person must listen with the face.
154
+
155
+ The new estimates describe logged reworks with reused assets. They are upper bounds, not current prices or fresh-production quotes.
156
+ Street interview uses the $0.874 phone-style rework. Backrooms uses the $0.506 VHS rework as its stage plan.
157
+ Its notes also record the $0.128 camcorder and $0.874 street variants. Horror comedy uses the $0.128 scare addition.
158
+ The historical VHS rework has no scare; R25 applies when adding a scare variant.
159
+ Reusing stills, clips and voices costs $0 to $0.13. A new talking clip costs about $0.15; lip sync remains weak.
138
160
 
139
161
  ### Owner feedback checks
140
162
 
@@ -149,10 +171,11 @@ A reviewer must record PASS or FAIL with a measured value. Voice and sound fit n
149
171
  The JavaScript API exports `listRules()`, `getRule(id)`, and `checklistFor(format)`.
150
172
  Pass a format descriptor from `getFormat(id)` to `checklistFor`.
151
173
  Rules use `blocking` severity. `applies` names format kinds; `all` covers every format.
152
- Story formats include stickman, POV, before-after, lo-fi, sitcom, and storytime.
153
- Sitcom has dialogue. Talking-object has visible speech. All current formats have captions.
174
+ Story formats include stickman, POV, before-after, lo-fi, sitcom, storytime, and the three new formats.
175
+ Sitcom, street-interview and backrooms-dialogue have dialogue. Horror-comic has one character with speech. Street interviews use R24; horror-comic uses R25, and a Backrooms scare variant is planned as horror-comic.
176
+ Talking-object has visible speech. All 15 formats have captions and TTS.
154
177
 
155
- Run `node test/mutate-qa.mjs` to verify three deliberate faults and restore the files.
178
+ Run `node test/mutate-qa.mjs` to verify five deliberate faults and restore the files.
156
179
  Results go to `test/artifacts/qa-mutations.json` and `test/artifacts/e2e.json`.
157
180
 
158
181
  ## Hook catalog
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentic-ads-engine",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Offline ad templates, hook selection, policy lint, and planning tools",
5
5
  "type": "module",
6
6
  "engines": { "node": ">=20" },
@@ -1,4 +1,4 @@
1
- import { checklistFor, costRatchet } from '../qa/index.js';
1
+ import { checklistFor, gateChecklistFor, costRatchet } from '../qa/index.js';
2
2
  import { readFileSync } from 'node:fs';
3
3
  const library = JSON.parse(readFileSync(new URL('./library.json', import.meta.url), 'utf8'));
4
4
  const costs = ['free', 'cheap', 'mid', 'expensive'], risks = ['low', 'medium', 'high'];
@@ -29,7 +29,7 @@ export function planFormat(input = {}) {
29
29
  const stages = f.stages.map(id => ({ id, provider: f.providers[id], estimatedCostUsd: f.providers[id].estimatedCostUsd }));
30
30
  const sum = key => Math.round(stages.reduce((n, s) => n + s.estimatedCostUsd[key], 0) * 1000) / 1000;
31
31
  return { format: f.id, site: input.site, goal, dryRun: true, applied: false, spendUsd: 0, stages,
32
- reviewChecklist: checklistFor(f), costRatchet,
32
+ reviewChecklist: checklistFor(f), gateChecklist: gateChecklistFor(f), costRatchet,
33
33
  qaGates: f.qaGates, policyRisk: f.policyRisk, riskNotes: f.riskNotes,
34
34
  total: { low: sum('low'), high: sum('high'), note: f.estimatedCostUsd.note },
35
35
  warnings: ['Plan only; no providers or QA checks run.', 'NOT CHECKED blocks shipping. Record PASS or FAIL with a measured value. Voice fit and sound fit need the owner’s ear.', 'Historical source review dates are not current price verification.'] };
@@ -14,7 +14,7 @@
14
14
  "estimatedCostUsd": {"low": 0, "high": 0, "note": "estimate: sum of stage assumptions; styles catalog recipes; excludes labour, compute, and unbounded retries"},
15
15
  "policyRisk": "low",
16
16
  "riskNotes": ["Use the housing special ad category for housing ads.", "No fake testimonials, eligibility claims, or promised savings.", "No real people or brands."],
17
- "qaGates": ["caption_timing", "dead_air", "tail", "shot_length", "mute_test", "orientation"],
17
+ "qaGates": ["caption_timing", "dead_air", "tail", "shot_length", "mute_test", "orientation", "loudness_master", "caption_ocr", "voice_consistency", "stem_sfx"],
18
18
  "learned": ["Record only generic UI; exclude real addresses and prices.", "Cut on measured voice beats; dead air must stay below 0.4 seconds."],
19
19
  "sources": ["styles/out-styles.md", "storyboards/INDEX.md", "editing/eval3/engine-ideas.md"]
20
20
  },
@@ -33,7 +33,7 @@
33
33
  "estimatedCostUsd": {"low": 0, "high": 0.02, "note": "estimate: sum of stage assumptions; styles catalog recipes; excludes labour, compute, and unbounded retries"},
34
34
  "policyRisk": "low",
35
35
  "riskNotes": ["Use the housing special ad category for housing ads.", "No fake testimonials, eligibility claims, or promised savings.", "No real people or brands."],
36
- "qaGates": ["caption_timing", "dead_air", "tail", "shot_length", "mute_test", "orientation"],
36
+ "qaGates": ["caption_timing", "dead_air", "tail", "shot_length", "mute_test", "orientation", "loudness_master", "caption_ocr", "voice_consistency", "stem_sfx"],
37
37
  "learned": ["Render text as HTML overlays, never generated pixels.", "Use at most eight words per card; hold the end card for 1.5 seconds."],
38
38
  "sources": ["styles/out-styles.md", "storyboards/INDEX.md", "editing/eval3/engine-ideas.md", "videos6/spend.json", "videos4/spend.json"]
39
39
  },
@@ -52,7 +52,7 @@
52
52
  "estimatedCostUsd": {"low": 0, "high": 0, "note": "estimate: sum of stage assumptions; styles catalog recipes; excludes labour, compute, and unbounded retries"},
53
53
  "policyRisk": "low",
54
54
  "riskNotes": ["Use the housing special ad category for housing ads.", "No fake testimonials, eligibility claims, or promised savings.", "No real people or brands."],
55
- "qaGates": ["caption_timing", "dead_air", "tail", "shot_length", "mute_test", "orientation"],
55
+ "qaGates": ["caption_timing", "dead_air", "tail", "shot_length", "mute_test", "orientation", "loudness_master", "caption_ocr", "voice_consistency", "stem_sfx"],
56
56
  "learned": ["Use a faceless renderer; the whiteboard face recipe needs a face plate.", "Trigger strokes within three frames of their words; avoid static boards over two seconds."],
57
57
  "sources": ["styles/out-styles.md", "storyboards/INDEX.md", "editing/eval3/engine-ideas.md"]
58
58
  },
@@ -71,7 +71,7 @@
71
71
  "estimatedCostUsd": {"low": 0, "high": 0, "note": "estimate: sum of stage assumptions; styles catalog recipes; excludes labour, compute, and unbounded retries"},
72
72
  "policyRisk": "low",
73
73
  "riskNotes": ["Use the housing special ad category for housing ads.", "No fake testimonials, eligibility claims, or promised savings.", "No real people or brands."],
74
- "qaGates": ["caption_timing", "dead_air", "tail", "shot_length", "mute_test", "orientation"],
74
+ "qaGates": ["caption_timing", "dead_air", "tail", "shot_length", "mute_test", "orientation", "loudness_master", "caption_ocr", "voice_consistency", "stem_sfx"],
75
75
  "learned": ["Reuse one SVG character asset for consistent proportions.", "Include motion every two seconds; avoid holds over 2.5 seconds."],
76
76
  "sources": ["styles/out-styles.md", "storyboards/INDEX.md", "editing/eval3/engine-ideas.md"]
77
77
  },
@@ -90,7 +90,7 @@
90
90
  "estimatedCostUsd": {"low": 0.256, "high": 0.611, "note": "estimate: sum of stage assumptions; styles catalog recipes; excludes labour, compute, and unbounded retries"},
91
91
  "policyRisk": "medium",
92
92
  "riskNotes": ["Use the housing special ad category for housing ads.", "No fake testimonials, eligibility claims, or promised savings.", "Use a DRAMATIZATION label for fictional people and skits.", "No real people. Synthetic people only; no real or named person; no children; no likeness of a real person."],
93
- "qaGates": ["caption_timing", "dead_air", "tail", "shot_length", "mute_test", "orientation", "continuity", "words_per_second", "black_corners", "eyes"],
93
+ "qaGates": ["caption_timing", "dead_air", "tail", "shot_length", "mute_test", "orientation", "continuity", "words_per_second", "black_corners", "eyes", "loudness_master", "caption_ocr", "voice_consistency", "stem_sfx"],
94
94
  "learned": ["Chain the final frame into the next shot for continuity.", "Cut on voice beats; keep real listing data out of backgrounds."],
95
95
  "sources": ["styles/out-styles.md", "storyboards/INDEX.md", "editing/eval3/engine-ideas.md", "videos6/spend.json", "videos4/spend.json", "sitcom3/spend.json", "videos5/spend.json"]
96
96
  },
@@ -109,7 +109,7 @@
109
109
  "estimatedCostUsd": {"low": 0, "high": 0, "note": "estimate: sum of stage assumptions; styles catalog recipes; excludes labour, compute, and unbounded retries"},
110
110
  "policyRisk": "low",
111
111
  "riskNotes": ["Use the housing special ad category for housing ads.", "No fake testimonials, eligibility claims, or promised savings.", "No real people or brands."],
112
- "qaGates": ["caption_timing", "dead_air", "tail", "shot_length", "mute_test", "orientation"],
112
+ "qaGates": ["caption_timing", "dead_air", "tail", "shot_length", "mute_test", "orientation", "loudness_master", "caption_ocr", "voice_consistency", "stem_sfx"],
113
113
  "learned": ["Keep annotations clear of the element they explain.", "Use deterministic strokes to avoid drift between frames."],
114
114
  "sources": ["styles/out-styles.md", "storyboards/INDEX.md", "editing/eval3/engine-ideas.md"]
115
115
  },
@@ -128,7 +128,7 @@
128
128
  "estimatedCostUsd": {"low": 0.256, "high": 0.611, "note": "estimate: sum of stage assumptions; styles catalog recipes; excludes labour, compute, and unbounded retries"},
129
129
  "policyRisk": "medium",
130
130
  "riskNotes": ["Use the housing special ad category for housing ads.", "No fake testimonials, eligibility claims, or promised savings.", "Use a DRAMATIZATION label for fictional people and skits.", "No real people. Synthetic people only; no real or named person; no children; no likeness of a real person."],
131
- "qaGates": ["caption_timing", "dead_air", "tail", "shot_length", "mute_test", "orientation", "continuity", "words_per_second", "black_corners", "eyes"],
131
+ "qaGates": ["caption_timing", "dead_air", "tail", "shot_length", "mute_test", "orientation", "continuity", "words_per_second", "black_corners", "eyes", "loudness_master", "caption_ocr", "voice_consistency", "stem_sfx"],
132
132
  "learned": ["Align both views before the wipe; match their colour grade.", "Show an achievable checklist result, never promised savings."],
133
133
  "sources": ["styles/out-styles.md", "storyboards/INDEX.md", "editing/eval3/engine-ideas.md", "videos6/spend.json", "videos4/spend.json", "sitcom3/spend.json", "videos5/spend.json"]
134
134
  },
@@ -147,7 +147,7 @@
147
147
  "estimatedCostUsd": {"low": 0, "high": 0, "note": "estimate: sum of stage assumptions; styles catalog recipes; excludes labour, compute, and unbounded retries"},
148
148
  "policyRisk": "medium",
149
149
  "riskNotes": ["Use the housing special ad category for housing ads.", "No fake testimonials, eligibility claims, or promised savings.", "Label parody; do not impersonate a real news outlet.", "No real people. Synthetic people only; no real or named person; no children; no likeness of a real person."],
150
- "qaGates": ["caption_timing", "dead_air", "tail", "shot_length", "mute_test", "orientation"],
150
+ "qaGates": ["caption_timing", "dead_air", "tail", "shot_length", "mute_test", "orientation", "loudness_master", "caption_ocr", "voice_consistency", "stem_sfx"],
151
151
  "learned": ["Trace every fact to a source; never imply a real outlet endorsement.", "Use generic branding and make the parody clear."],
152
152
  "sources": ["styles/out-styles.md", "storyboards/INDEX.md", "editing/eval3/engine-ideas.md"]
153
153
  },
@@ -166,7 +166,7 @@
166
166
  "estimatedCostUsd": {"low": 0.256, "high": 0.636, "note": "estimate: sum of stage assumptions; styles catalog recipes; excludes labour, compute, and unbounded retries; videos6 $0.881 for three reworked ads with reuse; videos5 $0.165 TTS-only for three edits, not fresh generation"},
167
167
  "policyRisk": "medium",
168
168
  "riskNotes": ["Use the housing special ad category for housing ads.", "No fake testimonials, eligibility claims, or promised savings.", "Use a DRAMATIZATION label for fictional people and skits.", "No real people. Synthetic people only; no real or named person; no children; no likeness of a real person."],
169
- "qaGates": ["caption_timing", "dead_air", "tail", "shot_length", "mute_test", "orientation", "continuity", "words_per_second", "black_corners", "eyes"],
169
+ "qaGates": ["caption_timing", "dead_air", "tail", "shot_length", "mute_test", "orientation", "continuity", "words_per_second", "black_corners", "eyes", "loudness_master", "caption_ocr", "voice_consistency", "stem_sfx"],
170
170
  "learned": ["No still frames as shots; animate source stills before the edit.", "Keep voice at its native pace; check words per second below 3.4.", "Exclude knob-to-lever morphs and fused fingers before cutting."],
171
171
  "sources": ["styles/out-styles.md", "storyboards/INDEX.md", "editing/eval3/engine-ideas.md", "videos6/spend.json", "videos6/voices.md", "videos6/qa.json", "videos5/EDIT_NOTES.md", "videos4/spend.json", "sitcom3/spend.json", "videos5/spend.json"]
172
172
  },
@@ -185,7 +185,7 @@
185
185
  "estimatedCostUsd": {"low": 1.756, "high": 2.411, "note": "estimate: sum of stage assumptions; styles catalog recipes; excludes labour, compute, and unbounded retries; sitcom3 actual $2.466 includes trials and one retry, four mouth checks failed"},
186
186
  "policyRisk": "medium",
187
187
  "riskNotes": ["Use the housing special ad category for housing ads.", "No fake testimonials, eligibility claims, or promised savings.", "Use a DRAMATIZATION label for fictional people and skits.", "No real people. Synthetic people only; no real or named person; no children; no likeness of a real person."],
188
- "qaGates": ["caption_timing", "dead_air", "tail", "shot_length", "mute_test", "orientation", "continuity", "words_per_second", "black_corners", "eyes", "mouth_sync"],
188
+ "qaGates": ["caption_timing", "dead_air", "tail", "shot_length", "mute_test", "orientation", "continuity", "words_per_second", "black_corners", "eyes", "mouth_sync", "loudness_master", "caption_ocr", "voice_consistency", "stem_sfx"],
189
189
  "learned": ["Use mediums and close-ups for speech; inspect eyes at full size.", "Cut on action for the door fall: knob, crash, then aftermath.", "No still frames as shots; laughs belong on silent reaction shots.", "Trim talks to line plus 0.1 seconds; time captions from actual words.", "Wan passed only six of ten mouth-sync checks; a retry still failed."],
190
190
  "sources": ["styles/out-styles.md", "storyboards/INDEX.md", "editing/eval3/engine-ideas.md", "sitcom3/NOTES.md", "sitcom3/qa.json", "videos6/spend.json", "videos4/spend.json", "sitcom3/spend.json", "videos5/spend.json"]
191
191
  },
@@ -204,7 +204,7 @@
204
204
  "estimatedCostUsd": {"low": 0.256, "high": 0.611, "note": "estimate: sum of stage assumptions; styles catalog recipes; excludes labour, compute, and unbounded retries"},
205
205
  "policyRisk": "medium",
206
206
  "riskNotes": ["Use the housing special ad category for housing ads.", "No fake testimonials, eligibility claims, or promised savings.", "Use a DRAMATIZATION label for fictional people and skits.", "No real people. Synthetic people only; no real or named person; no children; no likeness of a real person."],
207
- "qaGates": ["caption_timing", "dead_air", "tail", "shot_length", "mute_test", "orientation", "continuity", "words_per_second", "black_corners", "eyes"],
207
+ "qaGates": ["caption_timing", "dead_air", "tail", "shot_length", "mute_test", "orientation", "continuity", "words_per_second", "black_corners", "eyes", "loudness_master", "caption_ocr", "voice_consistency", "stem_sfx"],
208
208
  "learned": ["Keep the character bible identical across shots.", "Use a fictional framing and a checklist payoff, not a testimonial."],
209
209
  "sources": ["styles/out-styles.md", "storyboards/INDEX.md", "editing/eval3/engine-ideas.md", "videos6/spend.json", "videos4/spend.json", "sitcom3/spend.json", "videos5/spend.json"]
210
210
  },
@@ -223,8 +223,65 @@
223
223
  "estimatedCostUsd": {"low": 0, "high": 0, "note": "estimate: sum of stage assumptions; styles catalog recipes; excludes labour, compute, and unbounded retries"},
224
224
  "policyRisk": "low",
225
225
  "riskNotes": ["Use the housing special ad category for housing ads.", "No fake testimonials, eligibility claims, or promised savings.", "No real people. Synthetic people only; no real or named person; no children; no likeness of a real person."],
226
- "qaGates": ["caption_timing", "dead_air", "tail", "shot_length", "mute_test", "orientation", "mouth_sync"],
226
+ "qaGates": ["caption_timing", "dead_air", "tail", "shot_length", "mute_test", "orientation", "mouth_sync", "loudness_master", "caption_ocr", "voice_consistency", "stem_sfx"],
227
227
  "learned": ["Use an illustrated object with simple mouth flaps.", "Keep all readable text in exact overlays."],
228
228
  "sources": ["styles/out-styles.md", "storyboards/INDEX.md", "editing/eval3/engine-ideas.md"]
229
+ },
230
+ {
231
+ "id": "street-interview",
232
+ "kinds": ["captions", "story", "character", "dialogue", "interview", "lo-fi"],
233
+ "feedbackRules": ["R1", "R2", "R3", "R4", "R5", "R6", "R7", "R8", "R9", "R10", "R11", "R12", "R13", "R14", "R15", "R16", "R17", "R18", "R19", "R20", "R21", "R22", "R23", "R24", "R26"],
234
+ "name": "Street interview",
235
+ "description": "A host with a visible handheld mic questions a guest in a 2010s phone look.",
236
+ "aspectRatios": ["9:16"],
237
+ "durationSeconds": {"min": 16, "max": 22},
238
+ "hookPattern": "A startled host holds the mic on the left; the guest stays on the right in every shot.",
239
+ "stages": ["script", "set", "character", "still", "angle_still", "tts", "image_to_video", "talking_head", "sfx", "captions", "edit", "end_card", "mute_test", "qa"],
240
+ "providers": {"script": {"model": "supplied script / human review", "verified": "2026-11-12", "unverified": true, "estimatedCostUsd": {"low": 0, "high": 0, "note": "Reuse existing assets or local processing; backrooms_2010s_v2/FINAL_NOTES.md; COST_TABLE.md rounds 5 to 7. Excludes labour and compute; no fresh-production quote."}}, "set": {"model": "reuse supplied set", "verified": "2026-11-12", "unverified": true, "estimatedCostUsd": {"low": 0, "high": 0, "note": "Reuse existing assets or local processing; backrooms_2010s_v2/FINAL_NOTES.md; COST_TABLE.md rounds 5 to 7. Excludes labour and compute; no fresh-production quote."}}, "character": {"model": "reuse character bible", "verified": "2026-11-12", "unverified": true, "estimatedCostUsd": {"low": 0, "high": 0, "note": "Reuse existing assets or local processing; backrooms_2010s_v2/FINAL_NOTES.md; COST_TABLE.md rounds 5 to 7. Excludes labour and compute; no fresh-production quote."}}, "still": {"model": "bytedance/seedream-v5.5", "verified": "2026-11-12", "unverified": true, "estimatedCostUsd": {"low": 0.124, "high": 0.124, "note": "Logged stage total; still includes all image edits ($0.124 reported). Unit counts imply $0.132; allocation is unreconciled. backrooms_2010s_v2/FINAL_NOTES.md; COST_TABLE.md rounds 5 to 7. Excludes labour and compute; no fresh-production quote."}}, "angle_still": {"model": "bytedance/seedream-v5.5/edit", "verified": "2026-11-12", "unverified": true, "estimatedCostUsd": {"low": 0, "high": 0, "note": "Included in still stage; backrooms_2010s_v2/FINAL_NOTES.md; COST_TABLE.md rounds 5 to 7. Excludes labour and compute; no fresh-production quote."}}, "tts": {"model": "wavespeed-ai/qwen3-tts/text-to-speech", "verified": "2026-11-12", "unverified": true, "estimatedCostUsd": {"low": 0, "high": 0, "note": "Reuse existing assets or local processing; backrooms_2010s_v2/FINAL_NOTES.md; COST_TABLE.md rounds 5 to 7. Excludes labour and compute; no fresh-production quote."}}, "image_to_video": {"model": "wavespeed-ai/wan-2.2/i2v-480p-ultra-fast", "verified": "2026-11-12", "unverified": true, "estimatedCostUsd": {"low": 0, "high": 0, "note": "Reuse existing assets or local processing; backrooms_2010s_v2/FINAL_NOTES.md; COST_TABLE.md rounds 5 to 7. Excludes labour and compute; no fresh-production quote."}}, "talking_head": {"model": "wavespeed-ai/wan-2.2/speech-to-video", "verified": "2026-11-12", "unverified": true, "estimatedCostUsd": {"low": 0.75, "high": 0.75, "note": "Logged rework allocation; backrooms_2010s_v2/FINAL_NOTES.md; COST_TABLE.md rounds 5 to 7. Excludes labour and compute; no fresh-production quote."}}, "sfx": {"model": "ffmpeg lavfi / licensed CC0", "verified": "2026-11-12", "unverified": true, "estimatedCostUsd": {"low": 0, "high": 0, "note": "Reuse existing assets or local processing; backrooms_2010s_v2/FINAL_NOTES.md; COST_TABLE.md rounds 5 to 7. Excludes labour and compute; no fresh-production quote."}}, "captions": {"model": "whisper.cpp + HTML", "verified": "2026-11-12", "unverified": true, "estimatedCostUsd": {"low": 0, "high": 0, "note": "Reuse existing assets or local processing; backrooms_2010s_v2/FINAL_NOTES.md; COST_TABLE.md rounds 5 to 7. Excludes labour and compute; no fresh-production quote."}}, "edit": {"model": "ffmpeg + SVG", "verified": "2026-11-12", "unverified": true, "estimatedCostUsd": {"low": 0, "high": 0, "note": "Reuse existing assets or local processing; backrooms_2010s_v2/FINAL_NOTES.md; COST_TABLE.md rounds 5 to 7. Excludes labour and compute; no fresh-production quote."}}, "end_card": {"model": "HTML overlay", "verified": "2026-11-12", "unverified": true, "estimatedCostUsd": {"low": 0, "high": 0, "note": "Reuse existing assets or local processing; backrooms_2010s_v2/FINAL_NOTES.md; COST_TABLE.md rounds 5 to 7. Excludes labour and compute; no fresh-production quote."}}, "mute_test": {"model": "human review of muted render", "verified": "2026-11-12", "unverified": true, "estimatedCostUsd": {"low": 0, "high": 0, "note": "Reuse existing assets or local processing; backrooms_2010s_v2/FINAL_NOTES.md; COST_TABLE.md rounds 5 to 7. Excludes labour and compute; no fresh-production quote."}}, "qa": {"model": "ffmpeg + human review", "verified": "2026-11-12", "unverified": true, "estimatedCostUsd": {"low": 0, "high": 0, "note": "Reuse existing assets or local processing; backrooms_2010s_v2/FINAL_NOTES.md; COST_TABLE.md rounds 5 to 7. Excludes labour and compute; no fresh-production quote."}}},
241
+ "costClass": "mid",
242
+ "estimatedCostUsd": {"low": 0.874, "high": 0.874, "note": "Logged rework upper bound: backrooms_2010s_v2/FINAL_NOTES.md; COST_TABLE.md rounds 5 to 7. Reuse required; excludes labour, compute and further retries."},
243
+ "policyRisk": "medium",
244
+ "riskNotes": ["Use the housing special ad category for housing ads.", "No fake testimonials, eligibility claims, or promised savings.", "Use a DRAMATIZATION label for fictional people and skits.", "No real people. Synthetic people only; no real or named person; no children; no likeness of a real person.", "No invented numbers. The narrator never says the product worked. Keep the offer on the end card."],
245
+ "qaGates": ["caption_timing", "dead_air", "tail", "shot_length", "mute_test", "orientation", "continuity", "words_per_second", "black_corners", "eyes", "mouth_sync", "loudness_master", "caption_ocr", "voice_consistency", "stem_sfx"],
246
+ "learned": ["Screen direction swap: v1 faces clustered at cx 0.50-0.55. Regenerated v2 shots put host at 0.297-0.360 and guest at 0.672-0.705.", "Missing mic: specify a foam handheld mic in host stills and motion prompts. Check every host shot; visibility remains unverified in the notes.", "2010s v2 cost $0.874 versus $1.082 in v1. Reused all six voices and corridor assets; one failed $0.15 retry left host sync at 0.009.", "Reuse of stills, clips and voices cuts a rework to $0 to $0.13. A new talking clip costs about $0.15.", "Lip sync remains the weak point. Sitcom5b passes 7/10 clips; replacing the doorknob improves continuity but leaves three sync failures.", "The 2010s v2 host voice drifted between lines (353 Hz, then 177 Hz, one line tagged child-like) and was fixed by one fixed voice (spread 1.13). Voice drift across TTS lines: earshot measured sC at 2.2x pitch spread and sitcom wife at 216-441 Hz. Reuse one voice id and review flagged lines by ear; pitch alone cannot prove voice fit.", "Jumbled captions: remove highlight scaling, use a fixed word box, equal stroke and explicit spacing. Final-file OCR still finds CER 0.125 failures in 90s and marble5c.", "Door-break artifacts include duplicate doors and melting. Inspect three event frames; replace failed breaks with cracking walls or falling plaster.", "SFX louder than voice: remix measured sitcom active median +4.5 dB from separate stems. Lower SFX 5 dB; remeasure the final mix and keep owner listening checks.", "COST_TABLE.md rounds 5 to 7 contains logged estimates, not reconciled invoices. Round 6 logs exceed the balance change by about $0.70; treat totals as upper bounds."],
247
+ "sources": ["styles/out-styles.md", "COST_TABLE.md", "backrooms_2010s_v2/FINAL_NOTES.md", "backrooms_2010s_v2/COST_NOTES.md", "sitcom5b/FINAL_NOTES.md", "remix/REPORT.md", "earshot/REPORT.md"]
248
+ },
249
+ {
250
+ "id": "backrooms-dialogue",
251
+ "kinds": ["captions", "story", "character", "dialogue", "lo-fi"],
252
+ "feedbackRules": ["R1", "R2", "R3", "R4", "R5", "R6", "R7", "R8", "R9", "R10", "R11", "R12", "R13", "R14", "R15", "R16", "R17", "R18", "R19", "R20", "R21", "R22", "R23", "R26"],
253
+ "name": "Backrooms dialogue",
254
+ "description": "Two characters argue in endless yellow corridors: 1980s VHS, 1990s camcorder, or 2010s street variants.",
255
+ "aspectRatios": ["9:16"],
256
+ "durationSeconds": {"min": 16, "max": 22},
257
+ "hookPattern": "Open on a reaction face, then reveal the corridor argument. For a scare variant, plan it with horror-comic.",
258
+ "stages": ["script", "set", "character", "still", "angle_still", "tts", "image_to_video", "talking_head", "sfx", "captions", "edit", "end_card", "mute_test", "qa"],
259
+ "providers": {"script": {"model": "supplied script / human review", "verified": "2026-11-12", "unverified": true, "estimatedCostUsd": {"low": 0, "high": 0, "note": "Reuse existing assets or local processing; backrooms_80s_v2/FINAL_NOTES.md; COST_TABLE.md rounds 5 to 7. Excludes labour and compute; no fresh-production quote."}}, "set": {"model": "reuse supplied set", "verified": "2026-11-12", "unverified": true, "estimatedCostUsd": {"low": 0, "high": 0, "note": "Reuse existing assets or local processing; backrooms_80s_v2/FINAL_NOTES.md; COST_TABLE.md rounds 5 to 7. Excludes labour and compute; no fresh-production quote."}}, "character": {"model": "reuse character bible", "verified": "2026-11-12", "unverified": true, "estimatedCostUsd": {"low": 0, "high": 0, "note": "Reuse existing assets or local processing; backrooms_80s_v2/FINAL_NOTES.md; COST_TABLE.md rounds 5 to 7. Excludes labour and compute; no fresh-production quote."}}, "still": {"model": "bytedance/seedream-v5.5", "verified": "2026-11-12", "unverified": true, "estimatedCostUsd": {"low": 0, "high": 0, "note": "Reuse existing assets or local processing; backrooms_80s_v2/FINAL_NOTES.md; COST_TABLE.md rounds 5 to 7. Excludes labour and compute; no fresh-production quote."}}, "angle_still": {"model": "bytedance/seedream-v5.5/edit", "verified": "2026-11-12", "unverified": true, "estimatedCostUsd": {"low": 0.056, "high": 0.056, "note": "Logged rework allocation; backrooms_80s_v2/FINAL_NOTES.md; COST_TABLE.md rounds 5 to 7. Excludes labour and compute; no fresh-production quote."}}, "tts": {"model": "wavespeed-ai/qwen3-tts/text-to-speech", "verified": "2026-11-12", "unverified": true, "estimatedCostUsd": {"low": 0, "high": 0, "note": "Reuse existing assets or local processing; backrooms_80s_v2/FINAL_NOTES.md; COST_TABLE.md rounds 5 to 7. Excludes labour and compute; no fresh-production quote."}}, "image_to_video": {"model": "wavespeed-ai/wan-2.2/i2v-480p-ultra-fast", "verified": "2026-11-12", "unverified": true, "estimatedCostUsd": {"low": 0, "high": 0, "note": "Reuse existing assets or local processing; backrooms_80s_v2/FINAL_NOTES.md; COST_TABLE.md rounds 5 to 7. Excludes labour and compute; no fresh-production quote."}}, "talking_head": {"model": "wavespeed-ai/wan-2.2/speech-to-video", "verified": "2026-11-12", "unverified": true, "estimatedCostUsd": {"low": 0.45, "high": 0.45, "note": "Logged rework allocation; backrooms_80s_v2/FINAL_NOTES.md; COST_TABLE.md rounds 5 to 7. Excludes labour and compute; no fresh-production quote."}}, "sfx": {"model": "ffmpeg lavfi / licensed CC0", "verified": "2026-11-12", "unverified": true, "estimatedCostUsd": {"low": 0, "high": 0, "note": "Reuse existing assets or local processing; backrooms_80s_v2/FINAL_NOTES.md; COST_TABLE.md rounds 5 to 7. Excludes labour and compute; no fresh-production quote."}}, "captions": {"model": "whisper.cpp + HTML", "verified": "2026-11-12", "unverified": true, "estimatedCostUsd": {"low": 0, "high": 0, "note": "Reuse existing assets or local processing; backrooms_80s_v2/FINAL_NOTES.md; COST_TABLE.md rounds 5 to 7. Excludes labour and compute; no fresh-production quote."}}, "edit": {"model": "ffmpeg + SVG", "verified": "2026-11-12", "unverified": true, "estimatedCostUsd": {"low": 0, "high": 0, "note": "Reuse existing assets or local processing; backrooms_80s_v2/FINAL_NOTES.md; COST_TABLE.md rounds 5 to 7. Excludes labour and compute; no fresh-production quote."}}, "end_card": {"model": "HTML overlay", "verified": "2026-11-12", "unverified": true, "estimatedCostUsd": {"low": 0, "high": 0, "note": "Reuse existing assets or local processing; backrooms_80s_v2/FINAL_NOTES.md; COST_TABLE.md rounds 5 to 7. Excludes labour and compute; no fresh-production quote."}}, "mute_test": {"model": "human review of muted render", "verified": "2026-11-12", "unverified": true, "estimatedCostUsd": {"low": 0, "high": 0, "note": "Reuse existing assets or local processing; backrooms_80s_v2/FINAL_NOTES.md; COST_TABLE.md rounds 5 to 7. Excludes labour and compute; no fresh-production quote."}}, "qa": {"model": "ffmpeg + human review", "verified": "2026-11-12", "unverified": true, "estimatedCostUsd": {"low": 0, "high": 0, "note": "Reuse existing assets or local processing; backrooms_80s_v2/FINAL_NOTES.md; COST_TABLE.md rounds 5 to 7. Excludes labour and compute; no fresh-production quote."}}},
260
+ "costClass": "mid",
261
+ "estimatedCostUsd": {"low": 0.506, "high": 0.506, "note": "Logged rework upper bound: backrooms_80s_v2/FINAL_NOTES.md; COST_TABLE.md rounds 5 to 7. Reuse required; excludes labour, compute and further retries."},
262
+ "policyRisk": "medium",
263
+ "riskNotes": ["Use the housing special ad category for housing ads.", "No fake testimonials, eligibility claims, or promised savings.", "Use a DRAMATIZATION label for fictional people and skits.", "No real people. Synthetic people only; no real or named person; no children; no likeness of a real person.", "No invented numbers. The narrator never says the product worked. Keep the offer on the end card."],
264
+ "qaGates": ["caption_timing", "dead_air", "tail", "shot_length", "mute_test", "orientation", "continuity", "words_per_second", "black_corners", "eyes", "mouth_sync", "loudness_master", "caption_ocr", "voice_consistency", "stem_sfx"],
265
+ "learned": ["Era reworks: 1980s VHS $0.506, 1990s camcorder $0.128, 2010s street $0.874. This stage plan uses the 1980s rework.", "Fix eyelines with side-facing stills and small gestures; add the punchline push-in during editing. The 1980s clips still fail lip sync at 0.204, -0.088 and 0.299, and eyeline and gestures were not verified by the workers. The 1980s v2 final measured -16.0 LUFS and -0.2 dBTP before the master step, which fails loudness_master.", "Keep each speaker on one screen side. A scare-beat variant (the 1990s rework) is planned with the horror-comic format, whose R25 checklist applies.", "Reuse of stills, clips and voices cuts a rework to $0 to $0.13. A new talking clip costs about $0.15.", "Lip sync remains the weak point. Sitcom5b passes 7/10 clips; replacing the doorknob improves continuity but leaves three sync failures.", "Voice drift across TTS lines: earshot measured sC at 2.2x pitch spread and sitcom wife at 216-441 Hz. Reuse one voice id and review flagged lines by ear; pitch alone cannot prove voice fit.", "Jumbled captions: remove highlight scaling, use a fixed word box, equal stroke and explicit spacing. Final-file OCR still finds CER 0.125 failures in 90s and marble5c.", "Door-break artifacts include duplicate doors and melting. Inspect three event frames; replace failed breaks with cracking walls or falling plaster.", "SFX louder than voice: remix measured sitcom active median +4.5 dB from separate stems. Lower SFX 5 dB; remeasure the final mix and keep owner listening checks.", "COST_TABLE.md rounds 5 to 7 contains logged estimates, not reconciled invoices. Round 6 logs exceed the balance change by about $0.70; treat totals as upper bounds."],
266
+ "sources": ["styles/out-styles.md", "COST_TABLE.md", "backrooms_80s_v2/FINAL_NOTES.md", "backrooms_90s_v2/FINAL_NOTES.md", "backrooms_2010s_v2/FINAL_NOTES.md", "sitcom5b/FINAL_NOTES.md", "remix/REPORT.md", "earshot/REPORT.md"]
267
+ },
268
+ {
269
+ "id": "horror-comic",
270
+ "kinds": ["captions", "story", "character", "speech", "horror"],
271
+ "feedbackRules": ["R1", "R2", "R3", "R4", "R5", "R6", "R7", "R8", "R9", "R10", "R11", "R12", "R14", "R15", "R17", "R18", "R19", "R20", "R23", "R25", "R26"],
272
+ "name": "Horror comedy",
273
+ "description": "Something moves behind a door, a sting sounds, a character reacts, and the scare resolves as comedy.",
274
+ "aspectRatios": ["9:16"],
275
+ "durationSeconds": {"min": 16, "max": 22},
276
+ "hookPattern": "Open on a startled face. Show movement behind the door, sting within 0.2 s, reaction, then a joke.",
277
+ "stages": ["script", "set", "character", "still", "angle_still", "tts", "image_to_video", "talking_head", "sfx", "captions", "edit", "end_card", "mute_test", "qa"],
278
+ "providers": {"script": {"model": "supplied script / human review", "verified": "2026-11-12", "unverified": true, "estimatedCostUsd": {"low": 0, "high": 0, "note": "Reuse existing assets or local processing; backrooms_90s_v2/FINAL_NOTES.md; COST_TABLE.md rounds 5 to 7. Excludes labour and compute; no fresh-production quote."}}, "set": {"model": "reuse supplied set", "verified": "2026-11-12", "unverified": true, "estimatedCostUsd": {"low": 0, "high": 0, "note": "Reuse existing assets or local processing; backrooms_90s_v2/FINAL_NOTES.md; COST_TABLE.md rounds 5 to 7. Excludes labour and compute; no fresh-production quote."}}, "character": {"model": "reuse character bible", "verified": "2026-11-12", "unverified": true, "estimatedCostUsd": {"low": 0, "high": 0, "note": "Reuse existing assets or local processing; backrooms_90s_v2/FINAL_NOTES.md; COST_TABLE.md rounds 5 to 7. Excludes labour and compute; no fresh-production quote."}}, "still": {"model": "bytedance/seedream-v5.5", "verified": "2026-11-12", "unverified": true, "estimatedCostUsd": {"low": 0, "high": 0, "note": "Reuse existing assets or local processing; backrooms_90s_v2/FINAL_NOTES.md; COST_TABLE.md rounds 5 to 7. Excludes labour and compute; no fresh-production quote."}}, "angle_still": {"model": "bytedance/seedream-v5.5/edit", "verified": "2026-11-12", "unverified": true, "estimatedCostUsd": {"low": 0.028, "high": 0.028, "note": "Logged rework allocation; backrooms_90s_v2/FINAL_NOTES.md; COST_TABLE.md rounds 5 to 7. Excludes labour and compute; no fresh-production quote."}}, "tts": {"model": "wavespeed-ai/qwen3-tts/text-to-speech", "verified": "2026-11-12", "unverified": true, "estimatedCostUsd": {"low": 0, "high": 0, "note": "Reuse existing assets or local processing; backrooms_90s_v2/FINAL_NOTES.md; COST_TABLE.md rounds 5 to 7. Excludes labour and compute; no fresh-production quote."}}, "image_to_video": {"model": "wavespeed-ai/wan-2.2/i2v-480p-ultra-fast", "verified": "2026-11-12", "unverified": true, "estimatedCostUsd": {"low": 0.1, "high": 0.1, "note": "Logged rework allocation; backrooms_90s_v2/FINAL_NOTES.md; COST_TABLE.md rounds 5 to 7. Excludes labour and compute; no fresh-production quote."}}, "talking_head": {"model": "wavespeed-ai/wan-2.2/speech-to-video", "verified": "2026-11-12", "unverified": true, "estimatedCostUsd": {"low": 0, "high": 0, "note": "Reuse existing assets or local processing; backrooms_90s_v2/FINAL_NOTES.md; COST_TABLE.md rounds 5 to 7. Excludes labour and compute; no fresh-production quote."}}, "sfx": {"model": "ffmpeg lavfi / licensed CC0", "verified": "2026-11-12", "unverified": true, "estimatedCostUsd": {"low": 0, "high": 0, "note": "Reuse existing assets or local processing; backrooms_90s_v2/FINAL_NOTES.md; COST_TABLE.md rounds 5 to 7. Excludes labour and compute; no fresh-production quote."}}, "captions": {"model": "whisper.cpp + HTML", "verified": "2026-11-12", "unverified": true, "estimatedCostUsd": {"low": 0, "high": 0, "note": "Reuse existing assets or local processing; backrooms_90s_v2/FINAL_NOTES.md; COST_TABLE.md rounds 5 to 7. Excludes labour and compute; no fresh-production quote."}}, "edit": {"model": "ffmpeg + SVG", "verified": "2026-11-12", "unverified": true, "estimatedCostUsd": {"low": 0, "high": 0, "note": "Reuse existing assets or local processing; backrooms_90s_v2/FINAL_NOTES.md; COST_TABLE.md rounds 5 to 7. Excludes labour and compute; no fresh-production quote."}}, "end_card": {"model": "HTML overlay", "verified": "2026-11-12", "unverified": true, "estimatedCostUsd": {"low": 0, "high": 0, "note": "Reuse existing assets or local processing; backrooms_90s_v2/FINAL_NOTES.md; COST_TABLE.md rounds 5 to 7. Excludes labour and compute; no fresh-production quote."}}, "mute_test": {"model": "human review of muted render", "verified": "2026-11-12", "unverified": true, "estimatedCostUsd": {"low": 0, "high": 0, "note": "Reuse existing assets or local processing; backrooms_90s_v2/FINAL_NOTES.md; COST_TABLE.md rounds 5 to 7. Excludes labour and compute; no fresh-production quote."}}, "qa": {"model": "ffmpeg + human review", "verified": "2026-11-12", "unverified": true, "estimatedCostUsd": {"low": 0, "high": 0, "note": "Reuse existing assets or local processing; backrooms_90s_v2/FINAL_NOTES.md; COST_TABLE.md rounds 5 to 7. Excludes labour and compute; no fresh-production quote."}}},
279
+ "costClass": "cheap",
280
+ "estimatedCostUsd": {"low": 0.128, "high": 0.128, "note": "Logged rework upper bound: backrooms_90s_v2/FINAL_NOTES.md; COST_TABLE.md rounds 5 to 7. Reuse required; excludes labour, compute and further retries."},
281
+ "policyRisk": "medium",
282
+ "riskNotes": ["Use the housing special ad category for housing ads.", "No fake testimonials, eligibility claims, or promised savings.", "Use a DRAMATIZATION label for fictional people and skits.", "No real people. Synthetic people only; no real or named person; no children; no likeness of a real person.", "No invented numbers. The narrator never says the product worked. Keep the offer on the end card."],
283
+ "qaGates": ["caption_timing", "dead_air", "tail", "shot_length", "mute_test", "orientation", "continuity", "words_per_second", "black_corners", "eyes", "mouth_sync", "loudness_master", "caption_ocr", "voice_consistency", "stem_sfx"],
284
+ "learned": ["1990s v2 scare addition costs $0.128: one $0.028 reaction still and two $0.05 clips. Voices and talking clips are reused.", "Check the door silhouette and flinch by eye; normal frame motion does not prove correct content. Keep the sting on the scare beat.", "Marble5c re-edit costs $0 with reused stills. Face detection confirms geometry, not identity or whether the folder reads clearly.", "Reuse of stills, clips and voices cuts a rework to $0 to $0.13. A new talking clip costs about $0.15.", "Lip sync remains the weak point. Sitcom5b passes 7/10 clips; replacing the doorknob improves continuity but leaves three sync failures.", "Voice drift across TTS lines: earshot measured sC at 2.2x pitch spread and sitcom wife at 216-441 Hz. Reuse one voice id and review flagged lines by ear; pitch alone cannot prove voice fit.", "Jumbled captions: remove highlight scaling, use a fixed word box, equal stroke and explicit spacing. Final-file OCR still finds CER 0.125 failures in 90s and marble5c.", "Door-break artifacts include duplicate doors and melting. Inspect three event frames; replace failed breaks with cracking walls or falling plaster.", "SFX louder than voice: remix measured sitcom active median +4.5 dB from separate stems. Lower SFX 5 dB; remeasure the final mix and keep owner listening checks.", "COST_TABLE.md rounds 5 to 7 contains logged estimates, not reconciled invoices. Round 6 logs exceed the balance change by about $0.70; treat totals as upper bounds."],
285
+ "sources": ["styles/out-styles.md", "COST_TABLE.md", "backrooms_90s_v2/FINAL_NOTES.md", "marble5c/FINAL_NOTES.md", "sitcom5b/FINAL_NOTES.md", "remix/REPORT.md", "earshot/REPORT.md"]
229
286
  }
230
287
  ]
@@ -0,0 +1,17 @@
1
+ {
2
+ "continuity": "Compare faces, clothes and props across shots; keep each person on the same screen side.",
3
+ "mouth_sync": "Check mouth onset within 100 to 200 ms of speech. Report measured correlation below 0.4 per failing line.",
4
+ "caption_timing": "Compare captions with word timestamps; start error must stay below 0.15 s.",
5
+ "dead_air": "Reject silent stills over 0.3 s.",
6
+ "tail": "Keep the tail after the last sound under 0.3 s.",
7
+ "shot_length": "Measure shot lengths; no shot exceeds 2.5 s.",
8
+ "words_per_second": "Measure speech pace from word timestamps; target 2 to 3 words per second.",
9
+ "mute_test": "Watch the muted file; the story and offer must remain clear.",
10
+ "orientation": "Verify final dimensions match the selected aspect ratio.",
11
+ "black_corners": "Inspect the final file for black corners caused by crops or motion.",
12
+ "eyes": "Inspect eyelines and eye artifacts in each person shot.",
13
+ "loudness_master": "Measure the final file: integrated target -14 LUFS, inclusive band -16 to -12 LUFS; true peak at or below -1 dBTP.",
14
+ "caption_ocr": "OCR captions from the final file; character error rate must be 0.10 or less for each caption.",
15
+ "voice_consistency": "Measure max/min per-line median pitch per character. Spread above 1.25 asks a person to listen; this tool check never fails. Voice stays an OWNER CHECK. On our own videos (1 rejected voice against 11 accepted voices, a small sample), neither pitch spread nor a speaker embedding separated them. Missing line stems or character mapping means NOT CHECKED. Log the voice id per character and listen with the face.",
16
+ "stem_sfx": "Measure sync and level from separate voice and SFX stems. Use a separate SFX stem with laughs left out. A mixed track cannot measure them separately. Missing stems means NOT CHECKED. Fail when there are no SFX onsets or fewer than 80% land within 80 ms of a picture cut. A cut without SFX is fine; voice onsets do not count. Supply beat times via --cuts. R25 stings start within 0.2 s of the beat. Record level relative to voice and request owner listening review."
17
+ }
package/src/qa/index.js CHANGED
@@ -1,5 +1,12 @@
1
1
  import { readFileSync } from 'node:fs';
2
2
  const rules = JSON.parse(readFileSync(new URL('./rules.json', import.meta.url), 'utf8'));
3
+ const gates = JSON.parse(readFileSync(new URL('./gates.json', import.meta.url), 'utf8'));
4
+ export function gateChecklistFor(format) {
5
+ return format.qaGates.map(id => {
6
+ if (!Object.hasOwn(gates, id)) throw new Error('Unknown QA gate: ' + id);
7
+ return { id, check: gates[id], status: 'NOT CHECKED' };
8
+ });
9
+ }
3
10
  export function listRules() { return structuredClone(rules); }
4
11
  export function getRule(id) {
5
12
  const rule = rules.find(r => r.id === id);
package/src/qa/rules.json CHANGED
@@ -4,15 +4,15 @@
4
4
  {"id": "R3", "title": "Change the camera angle, not only the zoom.", "check": "Look at the frames: the scene-cut filter cannot see a zoom. About one new angle or pose every 2.5 s. Cuts differ in height, pose or distance. A side change stays on one side of the 180-degree line (R21).", "origin": "\"Different left and right views, not slow zooms.\"", "severity": "blocking", "applies": ["character"]},
5
5
  {"id": "R4", "title": "The same person must show different poses and angles.", "check": "Same face, outfit and hair across all shots. At least 3 poses.", "origin": "Marble lady needed jump cuts.", "severity": "blocking", "applies": ["character"]},
6
6
  {"id": "R5", "title": "Show problem, agitation, then solution. Make the agitation hurt and be funny.", "check": "Mark the three beats on the timeline. Agitation has at least 3 quick cuts, one prop each.", "origin": "\"Highlight problem and solution agitation.\"", "severity": "blocking", "applies": ["story"]},
7
- {"id": "R6", "title": "No dead gaps. No silent still over 0.3 s. Tail after the last sound under 0.3 s.", "check": "`freezedetect=n=-60dB:d=0.3` crossed with `silencedetect=n=-35dB:d=0.25`. A range in both is a dead gap.", "origin": "Round 4 had dead gaps.", "severity": "blocking", "applies": ["all"]},
8
- {"id": "R7", "title": "Speed up the picture, not the voice. Normal speech pace.", "check": "Words per second from whisper: 2 to 3. Average shot length: 1.5 to 2.2 s for a lo-fi story (the gate in `qa.md`), 1.2 to 1.5 s only for a fast punch edit. No shot over 2.5 s.", "origin": "\"Sound speed too much, pace better.\"", "severity": "blocking", "applies": ["all"]},
9
- {"id": "R8", "title": "Voice, face and ethnicity must match.", "check": "A person listens to each voice with the face. Log the voice id per character.", "origin": "\"Ethnicity of source does not match.\"", "severity": "blocking", "applies": ["character"]},
7
+ {"id": "R6", "title": "No dead gaps. No silent still over 0.3 s. Tail after the last sound under 0.3 s.", "check": "`ad-audio-qa` R6_dead_audio is a coarse guard: FAIL on any stretch >= 1.5 s below -60 LUFS (see `ad-video-editing/references/audio-qa.md`). `freezedetect=n=-60dB:d=0.3` crossed with `silencedetect=n=-35dB:d=0.25` for the picture side is the gate.", "origin": "Round 4 had dead gaps.", "severity": "blocking", "applies": ["all"]},
8
+ {"id": "R7", "title": "Speed up the picture, not the voice. Normal speech pace.", "check": "`ad-audio-qa` R7_speech_pace: true WPS <= 3.4 from the whisper transcript is the tool alarm; the hand target stays 2 to 3 words per second (see `ad-video-editing/references/audio-qa.md`). Average shot length: 1.5 to 2.2 s for a lo-fi story (the gate in `qa.md`), 1.2 to 1.5 s only for a fast punch edit. No shot over 2.5 s.", "origin": "\"Sound speed too much, pace better.\"", "severity": "blocking", "applies": ["all"]},
9
+ {"id": "R8", "title": "Voice, face and ethnicity must match.", "check": "OWNER CHECK: a person listens to each voice with the face. No automatic check is reliable: on our own videos (1 rejected voice against 11 accepted, a small sample), neither per-line pitch spread nor a speaker embedding separated them. `ad-audio-qa` only prints R8_voice_consistency OWNER CHECK when a character's line pitch spread is above 1.25 (see `ad-video-editing/references/audio-qa.md`); it never fails. Log the voice id per character and have a person listen.", "origin": "\"Ethnicity of source does not match.\"", "severity": "blocking", "applies": ["character"]},
10
10
  {"id": "R9", "title": "A story the viewer can follow. It is personal and coherent, in the first person, as a dramatization. It makes no factual claim.", "check": "Read the captions alone. They tell one story in the first person.", "origin": "Meltdown: \"random words\".", "severity": "blocking", "applies": ["story"]},
11
- {"id": "R10", "title": "Captions have spaces. At most 3 words. Time them to the audio.", "check": "Measure the pixel gap: at least 14 px of clear gap between words at 1080 px. Compare caption start with the whisper word start (under 0.15 s).", "origin": "Caption spacing and sync.", "severity": "blocking", "applies": ["captions"]},
11
+ {"id": "R10", "title": "Captions have spaces. At most 3 words. Time them to the audio.", "check": "Measure the pixel gap: at least 14 px of clear gap between words at 1080 px. `ad-audio-qa` R10_caption_sync: median offset <= 300 ms and >= 70% within 300 ms of speech onsets is the tool alarm; the hand target stays 0.15 s (see `ad-video-editing/references/audio-qa.md`).", "origin": "Caption spacing and sync.", "severity": "blocking", "applies": ["captions"]},
12
12
  {"id": "R11", "title": "Mouth movement matches speech. Mouths close in laugh breaks.", "check": "Manual spot check until measured: mouth movement starts within 100 to 200 ms of voice onset (see `ad-video-editing` qa.md section 6). Where a lip-landmark tool ran, give its correlation (0.4 or more) and the tool name. List each failing line.", "origin": "Sitcom v2 and v3.", "severity": "blocking", "applies": ["dialogue", "speech"]},
13
13
  {"id": "R12", "title": "Never cut to a static image that makes no sense.", "check": "Check each cut. Every shot has motion or a reason to hold.", "origin": "Sitcom v2 feedback.", "severity": "blocking", "applies": ["all"]},
14
14
  {"id": "R13", "title": "Back-and-forth dialogue is wanted. Keep it and add more.", "check": "Count speaker changes. At least 4 in a sitcom.", "origin": "\"Love the dialogue.\"", "severity": "blocking", "applies": ["dialogue"]},
15
- {"id": "R14", "title": "Sound is required. The ad also works muted.", "check": "Play with sound off. The story and the offer still read. Ship both files.", "origin": "Round 3 had no sound.", "severity": "blocking", "applies": ["all"]},
15
+ {"id": "R14", "title": "Sound is required. The ad also works muted.", "check": "`ad-audio-qa` R14_sound_and_cuts and sfx_onset_80ms need `--sfx-stem` (a wav of the sfx alone: leave the laugh track out, it does not land on cuts): FAIL \"Sound is required\" when the stem has no onsets, else FAIL when under 80% of the sfx onsets land within 80 ms of a picture cut (see `ad-video-editing/references/audio-qa.md`). A cut with no sfx is fine. A voice onset does not count as a sound event. Pass beat times that are not picture cuts with `--cuts`. sfx_vs_voice needs `--voice-stem` too. pacing.md: sfx only on events, never one per cut. Play with sound off. The story and the offer still read. Ship both files.", "origin": "Round 3 had no sound.", "severity": "blocking", "applies": ["all"]},
16
16
  {"id": "R15", "title": "Hold the object identity. A phone stays a phone.", "check": "Compare held objects in frames across the clip.", "origin": "Phone turned into a notepad.", "severity": "blocking", "applies": ["character"]},
17
17
  {"id": "R16", "title": "Lo-fi organic look. No logo. No text except captions, the dramatization tag and one end card with the offer.", "check": "Look at the frames. No logo, no studio polish.", "origin": "Polished images were rejected.", "severity": "blocking", "applies": ["lo-fi"]},
18
18
  {"id": "R17", "title": "Housing safety. \"dramatization\" label. No invented numbers. No real people. No children.", "check": "Run the policy lint on all text and captions.", "origin": "Housing special ad category.", "severity": "blocking", "applies": ["all"]},