Prompt-quality preflight
Seven mechanical anti-pattern checks driven by the Seedance 2.0 Handbook. They run inside director-preflight against every storyboard scene's text. They are warnings by default and promote to blocking errors when DIRECTOR_STRICT_PROMPT_QUALITY=1 is set in the environment.
The module (src/video/prompt-quality.ts) is pure: no disk I/O, no global state beyond reading the severity env var at runtime. The checks surface through the existing director-preflight JSON output — there is no new CLI command and no new smoke.
Issue codes
| Code | What it catches | Threshold |
|---|---|---|
prompt-quality-adjective-soup | A single clause piling up comma-separated modifiers | > ADJECTIVE_SOUP_THRESHOLD (default 4) |
prompt-quality-multiple-actions | Serial third-person-present actions in one clause | > 1 |
prompt-quality-multiple-camera-moves | More than one camera-move family per prompt | > 1 movement family |
prompt-quality-style-word-overload | Stacked "cinematic/epic/atmospheric" style words | > STYLE_WORDS_THRESHOLD (default 3) |
prompt-quality-literary-emotion | Inner-state language (feels, profound sadness) instead of visible behavior | any match |
prompt-quality-overlong | Prompt word count above the single-shot budget | > OVERLONG_WORDS_THRESHOLD (default 120) |
negative-direction | A negated motion/tempo clause (no/don't/avoid/without/never + slow-motion/blur/motion/movement/zoom/camera shake) — these models ignore negated direction | any match outside the sanctioned-negation allowlist (on-screen text, specular kill, anti-plastic) |
Vocabularies live in CAMERA_MOVE_VOCABULARY, SHOT_TYPE_VOCABULARY, and STYLE_VOCABULARY in src/video/prompt-quality.ts and are deliberately short and conservative.
Camera movement is checked separately from shot size. Movement families include push-in, pull-out, tracking, orbit, static / locked-off, crane reveal, pan, tilt, zoom, handheld, and steadicam. Shot-size terms such as wide shot, medium shot, close-up, and establishing shot do not count as movement conflicts, so wide shot, slow push-in is valid while push-in and orbit still raises prompt-quality-multiple-camera-moves.
For Seedance image-to-video work, keep imported still-image guidance separate from motion prompting. The still image should carry composition, identity, wardrobe, props, lighting, and framing. The Seedance prompt should preserve the source image and add one visible action plus one camera move for a short clip.
Dialogue duration fit
director-preflight also runs a dialogue timing check from dialogue-fit.ts. It is separate from the seven prompt-quality anti-pattern checks because it uses a duration budget rather than prompt wording alone.
Default behavior:
- scenes without
durationSecondsuse a 15-second target - spoken dialogue is estimated at about 2.5 words per second
DIALOGUE_DURATION_OVERFLOWis emitted when the estimated spoken duration exceeds the scene target- the issue is a warning by default
DIRECTOR_STRICT_DIALOGUE_FIT=1promotes it to a blocking error
This check catches scripts that look fine as text but cannot be delivered naturally inside the clip duration.
Sample output
A scene with adjective soup and a second camera move produces director-preflight JSON like:
{
"pass": true,
"warnings": [
{
"severity": "warn",
"code": "prompt-quality-adjective-soup",
"scope": "scene:0",
"message": "Scene 1: clause has 6 adjectives (threshold: 4): ...",
"suggestion": "Tighten scene wording before approval; see docs/PROMPT_QUALITY.md."
}
],
"errors": []
}Under DIRECTOR_STRICT_PROMPT_QUALITY=1 the same issues appear on errors, pass flips to false, and execute/produce is blocked (director-mode preflight failures gate provider submission).
Promoting to blocking
DIRECTOR_STRICT_PROMPT_QUALITY=1 vclaw video director-preflight --project <slug>Team workflows that want prompt-quality to be a release gate can export the variable in CI so every run treats handbook violations the same as content hazards. Teams still iterating can leave it unset and treat the warnings as review cues.
Integration points
- Emitted by
checkPromptQualityinsrc/video/director-preflight.ts. - Surfaces in the existing
vclaw video director-preflightJSON payload. - Flows into
director-preflight'sresult.warnings/result.errorsbuckets, so all downstream consumers (tests, CI checks, storyboard.md approval review) see them automatically.
Multi-shot cinematic prompt validation
runMultiShotChecks(prompt, preset) enforces the structural rules for multi-shot cinematic prompts. It lives in the same module as runPromptQualityChecks (src/video/prompt-quality.ts) and is invoked via:
vclaw video multi-shot --validateUnlike the seven anti-pattern checks above, all issues from runMultiShotChecks are always error severity — they are structural requirements of the multi-shot format, not stylistic guidelines, so they cannot be downgraded to warnings by omitting DIRECTOR_STRICT_PROMPT_QUALITY.
Presets
Six presets ship today, each declaring its own duration, shot-count window, per-shot duration bounds, and character budget. The default remains cinematic-15s; the others target a specific provider's clip duration:
| Preset | total | shot range | shot count | maxChars |
|---|---|---|---|---|
cinematic-15s (default) | 15 s | 2–5 s | 3–7 | 1500 |
seedance-10s | 10 s | 2–5 s | 2–5 | 1500 |
veo-8s | 8 s | 2–4 s | 2–4 | 1500 |
runway-10s | 10 s | 2–5 s | 2–5 | 1000 |
music-video-15s | 15 s | 1–4 s | 4–12 | 1500 |
social-hook-8s | 8 s | 1–3 s | 3–8 | 1000 |
Issue codes
| Code | What it catches |
|---|---|
multi-shot-timecode-parse | A timecode in the prompt cannot be parsed |
multi-shot-timecode-start | First timecode is not 00:00 |
multi-shot-timecode-gap | Consecutive timecodes overlap or leave a gap |
multi-shot-timecode-total | Timecodes do not sum to the preset's totalSeconds |
multi-shot-shot-duration | A shot's duration falls outside the preset's [minShotSeconds, maxShotSeconds] |
multi-shot-shot-count-out-of-range | Parsed shot count is outside the preset's [minShots, maxShots] window. Message branches on under vs over |
multi-shot-overlong | Total prompt character count exceeds the preset's maxChars |
multi-shot-repeated-parameter | A consecutive pair of shots shares the same shot size, lens, angle, or camera movement |
multi-shot-missing-metadata | The required Location / Style / Audio metadata block is absent |
Matching for consecutive-shot-repeat checks is hyphen/space-insensitive, so push in and push-in are treated as the same value.
Canonical vocabularies
The shot-parameter vocabularies used for consecutive-repeat detection are defined in src/video/prompt-quality.ts and are now canonical for the entire module:
SHOT_SIZE_VOCABULARY—wide,medium,medium close-up,close-up,macroLENS_VOCABULARY—24mm,35mm,50mm,85mmANGLE_VOCABULARY—low angle,high angle,eye-level,over-the-shoulder,Dutch angleCAMERA_MOVE_VOCABULARY— movement families shared withrunPromptQualityChecks
Smoke
npm run smoke:multi-shotBuilds the project, runs vclaw video multi-shot --plan, then validates a fixture at references/video/.fixtures/multi-shot-valid.txt — a no-network plan → validate round-trip.
filmmaking-prompts artifact lint (prompt-lint)
Distinct from the storyboard-scene anti-pattern checks above, lintFilmmakingPrompts (src/video/prompt-lint.ts) is a separate, pure validator that runs over a filmmaking-prompts.json artifact (not storyboard scenes). It is exposed as its own command:
vclaw video prompt-lint (--project <slug> | --file <path>) [--register prose|numeric] [--cast <Name:descriptor> ...] [--brand <token> ...]Per Seedance packet it checks the 13-block order (text-driven packets only), word count (warn outside the 280–600 words/packet window), the required video blocks SUBJECT LOCK / CAPTURE REALISM / CAMERA CAPTURE (text-driven only), the single-full-frame grid guard whenever a storyboard-grid reference is attached (else error — the grid leaks as a moving split-screen), no Kelvin/degree numeric tokens in a prose-register packet, and optional proper-name / brand scrubs when --cast / --brand are passed. It also runs an anti-slop advisory over every packet — empty hype words the model cannot act on (empty evaluators like cinematic/epic, borrowed image-model tokens like 8K/masterpiece, stacked superlatives, feel-suffix vibe words, and quality-insurance negation like no blur) are flagged with the observable production language to use instead. It is advisory (warning severity, so it never flips ok); the toolchain's own emitted guard blocks are ignored and constraint-slot negation (no on-screen text) is allowlisted, so the linter never flags its own wording. The lexicon is adapted from the MIT Emily2040/seedance-2.0 repo. Output is { packets: [{ sceneIndex, issues[] }], ok }, where ok is true iff no error-severity issue is raised on any packet.
Issue codes
| Code | What it catches |
|---|---|
seedance-block-order | 13-block order violated (text-driven packets only) |
shot-timing-sum | per-shot timecodes do not tile the runtime — a gap is where the model invents a shot, an overlap is where it drops one |
over-max-duration | packet runs past the 15s single-prompt ceiling (error) — split by camera vantage or by beat rather than compressing |
directive-block-cap | more than ~6 directive blocks on top of the structural skeleton; past that they compete and all lose weight |
duplicated-fact | one fact (haze, cadence, text suppression) stated in two blocks — the repeat adds length without specificity |
lipsync-closure-map-missing | a sung line with no scored lip-seal map; the mouth will flap plausibly and sync to nothing |
strobe-eats-closures | a scored closure map behind a full-strength strobe — flash-to-black hides ~half the seals |
word-count | Packet word count outside the 280–600 window (warning) |
missing-required-block | A required video block (SUBJECT LOCK / CAPTURE REALISM / CAMERA CAPTURE) is absent |
grid-guard-missing | A storyboard-grid reference is attached but the single-full-frame guard is missing (error) |
numeric-in-prose | Kelvin/degree numeric tokens in a prose-register packet |
proper-name-leak | A proper name survives the cast scrub (only when --cast is passed) |
brand-leak | A brand token survives the scrub (only when --brand is passed) |
slop | Empty hype language flagged (warning) with its observable-language replacement — one aggregated issue per packet; tool-emitted blocks ignored, constraint-slot negation allowlisted |
reference-transfer | A packet's references span ≥2 bleed-relevant domains (identity/continuity/environment/appearance) without a transfer/ignore clause (warning); references bleed, so state what each controls and what must not transfer |
allocation | One packet over-allocates the fidelity budget — ≥3 of identity-detail / bold-motion / scene-density / readable-text collide in a single shot (warning); name one primary spend and split the rest into separate shots. Conservative strict cues; standing blocks never self-trigger |
Follow-on roadmap
- Per-project threshold overrides (today: hardcoded constants).
- LLM-backed checks for subtler anti-patterns ("show don't tell", beat structure).
- Auto-fix suggestions, analogous to
DIRECTOR_AUTO_FIX_CONTENT=1for content hazards. - Tie into the Tier-2 prompt-structure schema (item 9 in
MASTER_PLAN_ALIGNMENT.md) so each check points at the matching schema section.
