# ═══════════════════════════════════════════════════════════════════════
# PROMPT_BIBLE.yaml — Single source of truth for model-specific prompting
# ═══════════════════════════════════════════════════════════════════════
#
# Every model that the Recoil engine can target has an entry here.
# Confidence levels:
#   CONFIRMED — Validated in production, active code paths exist
#   INFERRED  — Active code paths exist but rules extrapolated from docs/tests
#   UNTESTED  — No active code path; documentation-only placeholder
#
# Source data extracted from:
#   execution/api_client.py   — Client classes, endpoints, parameter names
#   config/model_profiles.json — Model metadata, cost, limits
#   pipeline/_lib/prompt_engine.py — Prompt builders, word counts, styles

schema_version: 1

# ──────────────────────────────────────────────────────────────────────
# GLOBAL DEFAULTS — cross-model prompting toggles
# ──────────────────────────────────────────────────────────────────────
# Per-model rule blocks (below) may override any key here. Builders that
# need a global flag read it via lib.bible_loader.get_global_defaults().
#
# include_focal_length: emit the `{focal}mm` token in the camera line.
#   Default false per pipeline-learnings §28 (Bug T, 2026-05-21): focal
#   length and framing are independent — passing focal-length in the prompt
#   doesn't make models hit it, it just bloats tokens. Flip to true to
#   restore legacy "{framing}, {focal}mm" formatting (and weep).
global_defaults:
  include_focal_length: false
  lens_per_shot_size:
    EWS: "wide lens"
    WS: "wide lens"
    FS: "wide-to-normal lens"
    MLS: "wide-to-normal lens"
    MS: "normal lens"
    MCU: "normal lens"
    CU: "tele lens"
    ECU: "tele lens"
    INSERT: "macro lens"

# ──────────────────────────────────────────────────────────────────────
# 0. SHARED CINEMATIC LEXICON
# ──────────────────────────────────────────────────────────────────────
# Standard cinematic vocabulary used across builders. Per JT 2026-05-07:
# this is JT's working lexicon — use these tokens when composing prompts
# rather than ad-hoc paraphrase. Most modern video models recognize these
# terms (training data is heavily film/photography). Builders should pass
# these tokens through verbatim when present in shot specs.
shared_lexicon:
  no_text_footer: "Do not write text on the image. No subtitles. No captions."
  # Photoreal-only lexicon token; must never be auto-injected.
  anti_ai_look: "grounded, tactile, physically real — no animation style, no painterly rendering, no stylized digital glow"

  shot_size:
    EWS:  "extreme wide shot"     # subject is small dot in environment
    WS:   "wide shot"             # full subject + environment context
    FS:   "full shot"             # subject head-to-toe, minimal environment
    MLS:  "medium long shot"      # subject from knees up
    MS:   "medium shot"           # subject from waist up
    MCU:  "medium close-up"       # subject from chest up
    CU:   "close-up"              # subject head and shoulders
    ECU:  "extreme close-up"      # detail (eyes, hands, prop)
    INSERT: "insert shot"         # cutaway detail, often handheld of object/screen

  camera_movement:
    static:    "static camera"
    pan:       "pan"              # horizontal rotation on fixed axis
    tilt:      "tilt"             # vertical rotation on fixed axis
    dolly_in:  "dolly in"         # camera moves toward subject (Z-axis)
    dolly_out: "dolly out"        # camera moves away from subject
    push_in:   "push-in"          # synonym for dolly in, often slow
    pull_back: "pull back"        # synonym for dolly out, often reveal
    tracking:  "tracking shot"    # camera moves parallel to subject
    crane_up:  "crane up"         # camera rises (Y-axis)
    crane_down: "crane down"      # camera descends
    handheld:  "handheld"         # shoulder-mount, micro-wobble
    gimbal:    "gimbal"           # smoothed motion, no wobble
    steadicam: "steadicam"        # smooth-following operator move
    arc:       "arc"              # camera curves around subject
    rack_focus: "rack focus"      # focus pulls between depth planes
    whip_pan:  "whip pan"         # fast horizontal motion blur

  framing_axis:
    high_angle:    "high angle"   # camera above subject, looking down
    low_angle:     "low angle"    # camera below subject, looking up
    eye_level:     "eye level"
    dutch:         "dutch angle"  # tilted horizon
    overhead:      "overhead"     # birds-eye, straight down
    pov:           "POV"          # subject's point of view
    ots:           "over the shoulder"

  optical:
    shallow_dof:   "shallow depth of field"
    deep_dof:      "deep depth of field"
    motion_blur:   "motion blur"
    lens_flare:    "lens flare"
    bokeh:         "bokeh"
    anamorphic:    "anamorphic"
    film_grain:    "film grain"

  pacing:
    slow:   "slow"      # safe — controlled motion
    smooth: "smooth"
    stable: "stable"
    # NOTE: "fast" without qualifier is a known dangerous keyword — see
    # pipeline-learnings §17b. Combining fast camera + fast subject + busy
    # scene → jitter. Only ONE element should be tagged fast at a time.

  notes:
    - "Builders should map shot-spec abbreviations (CU, MS) to full phrases at compile time."
    - "Models trained heavily on film/photography literature recognize these tokens robustly."
    - "When shot spec uses non-standard language ('really tight on her face'), normalize to closest standard token before prompt assembly."

# ──────────────────────────────────────────────────────────────────────
# 1. SEEDREAM v4.5  (Image — ByteDance via fal.ai)
# ──────────────────────────────────────────────────────────────────────
seedream-v4.5:
  meta:
    provider: bytedance
    modality: image
    confidence: CONFIRMED
    codenames: []
    endpoints:
      t2i: "fal-ai/bytedance/seedream/v4.5/text-to-image"
      edit: "fal-ai/bytedance/seedream/v4.5/edit"

  prompt:
    optimal_words:
      default: [40, 80]
    max_chars: null
    style: prose
    negative_prompt: false
    negative_param: null
    best_practices:
      - "Use labeled-roles pattern: 'Figure 1 is a character reference — preserve this person's identity exactly.'"
      - "Natural language emphasis — no tags, no brackets"
      - "When refs present, auto-switches to edit endpoint"
      - "NEVER use 'recreate but replace' phrasing — causes artifacts"
      - "Film stock aesthetics work well — 'Shot on ARRI Alexa, Kodak 5219'"
    anti_patterns:
      - "'Recreate but replace' phrasing — causes visual artifacts"
      - "Negative prompt injection — not supported, gets silently ignored"
      - "Style transfer reroll strategy — forbidden"
    enrichment_profile:
      default: [subject_line, action_line, environment_line, emotion_line,
                character_descs, wardrobe, film_stock, lighting_prose,
                labeled_roles_syntax]

  refs:
    max_count: 10
    format: url
    param_name: "image_urls"
    role_labeling: true
    upload_required: true
    role_map:
      identity: "a character reference — preserve this person's identity exactly"
      scene: "a scene/environment reference — match the setting and lighting mood"
      expression: "an expression reference — match this emotional intensity"
      prop: "a prop reference — include this object"
      keyframe: "a keyframe reference — match this composition"
      pose: "a pose reference — match this body positioning"

  aspect_ratio:
    param_name: "image_size"
    i2v_behavior: null
    supported:
      - "1:1"
      - "9:16"
      - "16:9"
    mapping:
      "9:16": "portrait_16_9"
      "16:9": "landscape_16_9"
      "1:1": "square"

  first_last_frame:
    start_param: null
    end_param: null
    start_format: null
    end_format: null
    upload_required: false
    notes: "Image model — no start/end frame concept"

  duration:
    param_name: null
    format: null
    range: null
    even_only: false

  audio:
    supported: false
    param_name: null
    default_on: false
    notes: ""

  gotchas:
    - "Auto-upgrades to v5.0 Lite when 4+ identity refs detected (multi-character scenes)"
    - "Edit endpoint used when refs present; t2i endpoint when no refs"
    - "Refs uploaded to fal storage and cached per session — stale URL recovery on failure"
    - "Average generation time ~22s"

# ──────────────────────────────────────────────────────────────────────
# 2. SEEDREAM v5 LITE  (Image — ByteDance via fal.ai)
# ──────────────────────────────────────────────────────────────────────
seedream-v5-lite:
  meta:
    provider: bytedance
    modality: image
    confidence: CONFIRMED
    codenames: []
    endpoints:
      t2i: "fal-ai/bytedance/seedream/v5/lite/text-to-image"
      edit: "fal-ai/bytedance/seedream/v5/lite/edit"

  prompt:
    optimal_words:
      default: [40, 80]
    max_chars: null
    style: cot_prose
    negative_prompt: false
    negative_param: null
    best_practices:
      - "Chain of Thought reasoning — model internally reasons about composition"
      - "Auto-selected by SeedreamClient when 4+ identity refs detected"
      - "Same labeled-roles pattern as v4.5 — 'Figure N is {role}.'"
      - "Better multi-subject coherence than v4.5"
      - "Natural language emphasis — no tags, no brackets"
    anti_patterns:
      - "'Recreate but replace' phrasing — causes visual artifacts"
      - "Negative prompt injection — not supported"

  refs:
    max_count: 10
    format: url
    param_name: "image_urls"
    role_labeling: true
    upload_required: true
    role_map:
      identity: "a character reference — preserve this person's identity exactly"
      scene: "a scene/environment reference — match the setting and lighting mood"
      expression: "an expression reference — match this emotional intensity"
      prop: "a prop reference — include this object"
      keyframe: "a keyframe reference — match this composition"
      pose: "a pose reference — match this body positioning"

  aspect_ratio:
    param_name: "image_size"
    i2v_behavior: null
    supported:
      - "1:1"
      - "9:16"
      - "16:9"
    mapping:
      "9:16": "portrait_16_9"
      "16:9": "landscape_16_9"
      "1:1": "square"

  first_last_frame:
    start_param: null
    end_param: null
    start_format: null
    end_format: null
    upload_required: false
    notes: "Image model — no start/end frame concept"

  duration:
    param_name: null
    format: null
    range: null
    even_only: false

  audio:
    supported: false
    param_name: null
    default_on: false
    notes: ""

  gotchas:
    - "Slower than v4.5 (~50s vs ~22s) — only used when multi-character coherence matters"
    - "Auto-upgrade trigger: 4+ identity refs (2+ characters x 2 refs each)"
    - "Same SeedreamClient handles both v4.5 and v5-lite — version selection is automatic"
    - "Shares upload cache with v4.5 within a session"

# ──────────────────────────────────────────────────────────────────────
# 3. GEMINI 3 PRO IMAGE PREVIEW  (Image — Google)
# ──────────────────────────────────────────────────────────────────────
gemini-3-pro-image-preview:
  meta:
    provider: google
    modality: image
    confidence: CONFIRMED
    codenames:
      - "NBP"
      - "Nanobanana Pro"
    endpoints:
      default: "gemini-3-pro-image-preview"

  prompt:
    optimal_words:
      default: [80, 150]
    max_chars: null
    style: prose
    negative_prompt: false
    negative_param: null
    best_practices:
      - "Natural language emphasis — no weight tags or brackets"
      - "Structured layers: camera line → film stock → subject → environment → action → lighting → quality guard"
      - "Visual Anchors block for grid consistency (3-6 constant traits)"
      - "Wide-shot branching — strip facial demands for WIDE/LS/EWS"
      - "ENV sanitization — strip human-presence language for environment-only shots"
      - "Kinetic descriptors over semantic emphasis"
      - "Positive constraints over negative language"
    anti_patterns:
      - "Negative prompt injection — not supported, forbidden reroll strategy"
      - "Style transfer — forbidden reroll strategy"
      - "Prompt append/rewrite — forbidden reroll strategies"
      - "Section headers in video prompts — model renders text overlays from them"

  refs:
    max_count: 11
    format: part
    param_name: null
    role_labeling: false
    upload_required: false
    role_map: {}

  aspect_ratio:
    param_name: "aspect_ratio"
    i2v_behavior: null
    supported:
      - "1:1"
      - "1:4"
      - "1:8"
      - "2:3"
      - "3:2"
      - "3:4"
      - "4:1"
      - "4:3"
      - "4:5"
      - "5:4"
      - "8:1"
      - "9:16"
      - "16:9"
      - "21:9"
    mapping: {}

  first_last_frame:
    start_param: null
    end_param: null
    start_format: null
    end_format: null
    upload_required: false
    notes: "Image model — refs are inline parts, not start/end frames"

  duration:
    param_name: null
    format: null
    range: null
    even_only: false

  audio:
    supported: false
    param_name: null
    default_on: false
    notes: ""

  gotchas:
    - "Highest cost image model ($0.134/img) — use for final 9:16 production frames"
    - "High content filter sensitivity — requires soften-retry on blocks"
    - "Requires style anchor for long runs to prevent drift"
    - "Concurrent limit: 3 (strict rate limits)"
    - "Max 6 object refs + 5 character refs (11 total)"
    - "Inline refs via genai Parts — not URL-based uploads"

# ──────────────────────────────────────────────────────────────────────
# 4. GEMINI 3.1 FLASH IMAGE PREVIEW  (Image — Google)
# ──────────────────────────────────────────────────────────────────────
gemini-3.1-flash-image-preview:
  meta:
    provider: google
    modality: image
    confidence: CONFIRMED
    codenames:
      - "NB2"
      - "Nanobanana 2"
    endpoints:
      default: "gemini-3.1-flash-image-preview"

  prompt:
    optimal_words:
      default: [60, 120]
    max_chars: null
    style: prose
    negative_prompt: false
    negative_param: null
    best_practices:
      - "Same natural language style as NBP but less detail-sensitive"
      - "Better prompt adherence than 2.5 Flash"
      - "Good for grid exploration and planning — cheaper ($0.039/img)"
      - "Strict QPS rate limits — use exponential backoff"
    anti_patterns:
      - "Overloading with detail — cheaper model, less capable of rendering fine details"

  refs:
    max_count: 11
    format: part
    param_name: null
    role_labeling: false
    upload_required: false
    role_map: {}

  aspect_ratio:
    param_name: "aspect_ratio"
    i2v_behavior: null
    supported:
      - "1:1"
      - "9:16"
      - "16:9"
    mapping: {}

  first_last_frame:
    start_param: null
    end_param: null
    start_format: null
    end_format: null
    upload_required: false
    notes: "Image model — refs are inline parts"

  duration:
    param_name: null
    format: null
    range: null
    even_only: false

  audio:
    supported: false
    param_name: null
    default_on: false
    notes: ""

  gotchas:
    - "Cheaper than NBP ($0.039 vs $0.134) — use for exploration, not final renders"
    - "Strict QPS rate limits — exponential backoff required"
    - "Medium content filter sensitivity — no soften-retry needed"
    - "Fewer supported aspect ratios than NBP (3 vs 14)"

# ──────────────────────────────────────────────────────────────────────
# 5. SEEDDANCE 2.0  (Video — ByteDance via fal.ai)
# ──────────────────────────────────────────────────────────────────────
seeddance-2.0:
  meta:
    provider: bytedance
    modality: video
    confidence: CONFIRMED
    codenames: []
    endpoints:
      i2v: "bytedance/seedance-2.0/image-to-video"
      t2v: "bytedance/seedance-2.0/text-to-video"
      r2v: "bytedance/seedance-2.0/reference-to-video"

  prompt:
    optimal_words:
      default: [120, 250]
      t2v: [120, 250]
      i2v: [50, 75]
      r2v: [120, 200]
      multi_shot: [200, 400]
    max_chars: null
    style: prose
    negative_prompt: false
    negative_param: null
    quality_suffix: "4K, Ultra HD, rich details, sharp clarity, cinematic texture, natural colors, stable picture."
    identity_lock_phrase: "Same person as @Image1. Do not alter facial proportions, eye shape, or hairstyle."
    lighting_vocabulary:
      # Ranked Seedance-preferred terms — higher rank = stronger cinematic response.
      # Source: community production guide + dual consultation (Opus won 89-72).
      ranked_terms:
        - "motivated lighting"
        - "practical light sources visible in frame"
        - "warm tungsten bounce"
        - "volumetric dust particles"
        - "negative fill"
      # Mapping from lighting analysis signals to Seedance-preferred anchor phrases.
      # Keys are signal categories detected from structured lighting source data.
      signal_map:
        hard_directional: "motivated lighting"
        practical_source: "practical light sources visible in frame"
        warm_color_temp: "warm tungsten bounce"
        atmospheric: "volumetric dust particles"
        shadow_shaping: "negative fill"
    film_stock_default: "Kodak Vision3 500T"
    film_stock_anchors:
      warm: "Kodak Vision3 500T"
      digital: "ARRI Alexa color science"
      indie: "35mm film grain"
    action_enforcement:
      mode: soft
      compound_markers:
        - ", and "
        - ", then "
        - ", while "
        - ", as "
        - "; "
    enrichment_profile:
      t2v: [subject_line, action_line, environment_line, emotion_line,
            kinetic_action, character_descs, wardrobe, character_anchor,
            scene_visual_locks, film_stock, lighting_prose_with_anchors,
            quality_suffix, single_verb_enforce, arc_preamble_conditional,
            director_notes, audio_directive]
      i2v: [action_line, kinetic_action, camera_movement,
            environment_line_motion_only, identity_lock, audio_directive]
      r2v: [subject_line, action_line, environment_line, emotion_line,
            character_descs, wardrobe, scene_visual_locks, film_stock,
            lighting_prose_with_anchors, quality_suffix, arc_preamble,
            ref_declarations, audio_directive]
      multi_shot: [subject_line, action_line, environment_line, emotion_line,
                   character_descs, wardrobe, scene_visual_locks, film_stock,
                   lighting_prose_with_anchors, quality_suffix, arc_preamble,
                   ref_declarations, format_mode, audio_directive]
    best_practices:
      - "T2V: Use five-block prose structure — Subject, Action, Camera, Style, Quality Suffix. No section headers."
      - "I2V: Motion-only at 50-75 words. Strip ALL character visual descriptions — the start frame IS the identity reference."
      - "I2V: Open with '@Image1 as the first frame.' Use 'The subject in @Image1' as pronoun, never the character name."
      - "I2V: Append identity lock phrase to prevent facial feature erosion during animation."
      - "R2V: Reference inputs using @Image1, @Image2 ... @Image9 tags. Describe what each reference IS."
      - "@Image1 gets 40-50% of model attention — always place primary character identity ref at index 0."
      - "Use 'Shot 1:', 'Shot 2:', 'Shot 3:' labels for explicit cut points between shots"
      - "Without Shot N labels, model produces continuous takes (no cuts)"
      - "Put spoken dialogue in double quotes for lip-synced audio generation"
      - "Use cinematography terms: dolly zoom, rack focus, tracking shot, handheld, POV"
      - "One primary action per shot, one camera movement per shot"
      - "Camera control embedded in natural language ('slow pull back', '85mm lens')"
      - "Short negative directives IN the positive prompt work ('No music, no score', 'no jitter, no warping'). Keep to 2-3 terms max, specific to actual artifacts."
      - "Append quality suffix to every prompt: '4K, Ultra HD, rich details, sharp clarity, cinematic texture, natural colors, stable picture.'"
      - "Start with shorter durations (5s) to refine, then extend"
      - "Lighting: use Seedance-preferred terms — 'motivated lighting', 'practical light sources visible in frame', 'warm tungsten bounce' ranked by impact"
      - "Film stock: default to 'Kodak Vision3 500T' when no project-level override — anchors color science"
      - "Action: one primary verb per shot — compound actions ('runs and jumps') confuse motion synthesis"
    anti_patterns:
      - "Section headers (CAMERA:, SUBJECT:) — rendered as text overlays"
      - "Negative prompt API PARAMETER — not supported, silently ignored. Use short negative directives in positive prompt text instead."
      - "I2V: Including character visual descriptions (hair color, build, wardrobe) — causes identity erosion when model tries to reconcile text with start frame pixels."
      - "I2V: Using character name instead of 'The subject in @Image1' — model may hallucinate a different face."
      - "Flowing prose without Shot N labels — produces one continuous take, no editorial cuts"
      - "Referencing images without @ImageN tags — model ignores unlabeled refs"
      - "Multiple actions per shot — confuses the model, pick one"
      - "Placing secondary refs before primary character in @Image1 slot — wastes the 40-50% attention bonus"
      - "Compound actions in T2V ('She turns and walks and opens the door') — model picks one randomly. Use single dominant verb."

  refs:
    max_count: 9
    format: url
    param_name: "image_urls"
    role_labeling: true
    upload_required: true
    label_syntax: "@ImageN"
    label_notes: "Use @Image1 through @Image9 in prompt text to reference uploaded images by position"
    role_map:
      identity: "@Image1 is [character name] — [visual description]"
      scene: "@ImageN is the setting — [environment description]"
      style: "@ImageN is the visual style — [style description]"
      prop: "@ImageN is [prop name] — [prop description]"
    video_refs:
      max_count: 3
      label_syntax: "@VideoN"
      notes: "Up to 3 reference videos (480p-720p, combined max 15s, max 50MB). Use @Video1 etc. in prompt for motion/action reference."
    audio_refs:
      max_count: 3
      label_syntax: "@AudioN"
      notes: "Up to 3 audio clips (MP3/WAV, each max 15MB, combined max 15s). Use @Audio1 etc. for voice/sound reference."
    total_file_limit: 12

  aspect_ratio:
    param_name: "aspect_ratio"
    i2v_behavior: respects_param
    supported:
      - "auto"
      - "21:9"
      - "16:9"
      - "4:3"
      - "1:1"
      - "3:4"
      - "9:16"
    mapping: {}

  first_last_frame:
    start_param: "image_url"
    end_param: "end_image_url"
    start_format: url
    end_format: url
    upload_required: true
    notes: "I2V endpoint only. Start frame via payload['image'] -> image_url. End frame via payload['image_tail'] -> end_image_url. I2V does NOT support @ImageN ref syntax — the start frame IS the reference."

  duration:
    param_name: "duration"
    format: string
    range: [4, 15]
    auto_supported: true
    even_only: false
    notes: "'auto' lets model pick optimal length. Range 4-15 seconds."

  audio:
    supported: true
    param_name: "generate_audio"
    default_on: true
    notes: "Native audio generation + lip sync. Put dialogue in double quotes in prompt for lip-synced speech."

  gotchas:
    - "Primary multi-shot + dialogue engine"
    - "Duration sent as string — '5' not 5, or 'auto' for model-selected"
    - "r2v ref weights are NOT per-image floats — model uses prompt context + upload order"
    - "Upload order matters: first image (@Image1) gets most attention"
    - "Endpoint routing: image_url present -> i2v, image_urls -> r2v, otherwise -> t2v"
    - "I2V endpoint: start frame + optional end frame only, no @ImageN refs"
    - "R2V endpoint: up to 9 images + 3 videos + 3 audio, all @-referenceable in prompt"
    - "T2V endpoint: text only, but can also accept reference_images for r2v-like behavior"
    - "Enrichment model: claude-sonnet-4-6 with cinematic_dp style"
    - "2K output resolution (720p default, 480p available)"
    - "Multi-shot: use 'Shot 1:', 'Shot 2:' labels — without them, no cuts"
    - "Cost: fal.ai Standard $0.3034/sec, Fast $0.2419/sec. 5s clip = $1.52 standard. Verified 2026-04-13."

# ──────────────────────────────────────────────────────────────────────
# 6. KLING V3  (Video — Kling via fal.ai)
# ──────────────────────────────────────────────────────────────────────
kling-v3:
  meta:
    provider: kuaishou
    modality: video
    confidence: CONFIRMED
    codenames: []
    endpoints:
      i2v_standard: "fal-ai/kling-video/v3/standard/image-to-video"
      i2v_pro: "fal-ai/kling-video/v3/pro/image-to-video"
      t2v_standard: "fal-ai/kling-video/v3/standard/text-to-video"
      o3_i2v_standard: "fal-ai/kling-video/o3/standard/image-to-video"
      o3_ref_standard: "fal-ai/kling-video/o3/standard/reference-to-video"
      o3_ref_pro: "fal-ai/kling-video/o3/pro/reference-to-video"

  prompt:
    optimal_words:
      i2v: [15, 40]
      t2v: [50, 100]
      default: [50, 100]
    max_chars: 2500
    style: prose
    negative_prompt: true
    negative_param: "negative_prompt"
    best_practices:
      - "I2V: Motion + camera + director notes ONLY (18-25 words target, 30 hard max)"
      - "I2V: Do NOT include environment or subject — keyframe carries all visual context"
      - "I2V: Text encoder fights image encoder on overlap, causing background melting"
      - "T2V: Direct labeling works well ('Subject:', 'Action:', 'Setting:', 'Mood:', 'Lighting:')"
      - "T2V: Active verbs, 75-100 words"
      - "Multi-prompt: array of {prompt, duration} entries, shot_type='customize'"
      - "Supports inline Elements (frontal_image_url + reference_image_urls)"
      - "Audio cues appended after || delimiter, outside word cap"
    anti_patterns:
      - "I2V: Including environment_line or subject_line — causes background melting"
      - "I2V: Prompts over 30 words — encoder fighting degrades quality"
      - "Prompts over 2500 chars — API hard limit"
    enrichment_profile:
      t2v: [subject_line, action_line, kinetic_action, environment_line,
            emotion_line, character_descs, wardrobe, film_stock,
            scene_visual_locks_compressed, lighting_prose,
            arc_preamble_conditional, director_notes, verb_calibration]
      i2v: [action_line, camera_movement, director_notes, verb_calibration,
            audio_cues_after_delimiter]

  refs:
    max_count: 4
    format: url
    param_name: "elements"
    role_labeling: false
    upload_required: false
    role_map: {}

  aspect_ratio:
    param_name: "aspect_ratio"
    i2v_behavior: matches_start_frame
    supported:
      - "1:1"
      - "9:16"
      - "16:9"
    mapping: {}

  first_last_frame:
    start_param: "image_url"
    end_param: "image_tail"
    start_format: url
    end_format: base64
    upload_required: false
    notes: "V3 I2V uses image_url (accepts data URIs). O3 ref endpoints use start_image_url. Direct API uses image (base64) and image_tail (base64). kling-o3 is the same model at a different quality tier — same endpoints, same params."

  duration:
    param_name: "duration"
    format: string
    range: [5, 15]
    even_only: false

  audio:
    supported: true
    param_name: "generate_audio"
    default_on: true
    notes: "Native audio generation enabled by default for multi-shot"

  gotchas:
    - "kling-o3 is the same model at a different quality tier — shares all endpoints and params"
    - "fal.ai proxy — Standard pricing same as direct API, Pro tier 2x markup"
    - "Multi-prompt max 6 shots, max 15s total, min 3s per shot"
    - "Duration sent as string (not int)"
    - "I2V endpoint auto-selected when image_url present in payload"
    - "Direct API (KlingClient) rounds duration to 5 or 10 seconds only"
    - "Enrichment model: claude-sonnet-4-6 with cinematic_dp style"
    - "Default negative prompt: 'morphing, blurry, disfigured hands, extra fingers, bad anatomy, text, watermark'"
    - "Safety filter words list for content moderation"
    - "Verb calibration applied to I2V prompts (narrative verbs -> physics-grounded)"

# ──────────────────────────────────────────────────────────────────────
# 7. WAN 2.7 I2V  (Video — Alibaba via fal.ai)
# ──────────────────────────────────────────────────────────────────────
wan-2.7-i2v:
  meta:
    provider: alibaba
    modality: video
    confidence: INFERRED
    codenames: []
    endpoints:
      default: "fal-ai/wan/v2.7/image-to-video"

  prompt:
    optimal_words:
      default: [150, 300]
    max_chars: 5000
    style: prose
    negative_prompt: true
    negative_param: "negative_prompt"
    best_practices:
      - "Dense prose 150-300 words — uses DiT with T5-XXL, craves dense text"
      - "Include environment and lighting as reinforcement to prevent background drift"
      - "Three prompt styles: directed (250-300 words), balanced (150-200, default), open (50-80)"
      - "In Between mode (has_end_frame=True): describe transition path, not destination"
      - "Disable enable_prompt_expansion in production"
      - "Verb calibration applied (narrative verbs -> physics-grounded)"
      - "Photorealistic, cinematic quality footer"
    anti_patterns:
      - "Short prompts (<50 words) — model needs dense text for quality output"
      - "enable_prompt_expansion in production — causes unpredictable prompt changes"
    enrichment_profile:
      default: [subject_line, action_line, kinetic_action, environment_line,
                emotion_line, character_descs_reinforcement, wardrobe,
                scene_visual_locks, film_stock, lighting_prose,
                arc_preamble_conditional, director_notes, verb_calibration]

  refs:
    max_count: 0
    format: null
    param_name: null
    role_labeling: false
    upload_required: false
    role_map: {}

  aspect_ratio:
    param_name: null
    i2v_behavior: matches_start_frame
    supported:
      - "16:9"
      - "9:16"
      - "1:1"
    mapping: {}

  first_last_frame:
    start_param: "image_url"
    end_param: "end_image_url"
    start_format: url
    end_format: url
    upload_required: true
    notes: "Start+end frame interpolation specialist. Supports audio_url and video_url params. Duration is integer (NOT string like Kling)."

  duration:
    param_name: "duration"
    format: integer
    range: [2, 15]
    even_only: false

  audio:
    supported: false
    param_name: null
    default_on: false
    notes: "Supports audio_url input param but not native audio generation"

  gotchas:
    - "Duration is integer, NOT string (unlike Kling/SeedDance)"
    - "Negative prompt max 500 chars"
    - "Default negative prompt: 'morphing, melting, teleporting, fast cuts, text, watermark'"
    - "Supported resolutions: 720p, 1080p"
    - "Character cap per prompt style: balanced ~1800 chars, directed ~1800 chars"
    - "In Between mode: describes transition path between two known visual states"
    - "fal_client.subscribe() used (blocking) — SDK handles queue polling internally"

# ──────────────────────────────────────────────────────────────────────
# 8. WAN 2.7 R2V  (Video — Alibaba via fal.ai)
# ──────────────────────────────────────────────────────────────────────
wan-2.7-r2v:
  meta:
    provider: alibaba
    modality: video
    confidence: INFERRED
    codenames: []
    endpoints:
      default: "fal-ai/wan/v2.7/reference-to-video"

  prompt:
    optimal_words:
      default: [250, 400]
    max_chars: 5000
    style: prose
    negative_prompt: false
    negative_param: null
    best_practices:
      - "Extra dense 250-400 words — no start frame, prompt carries ALL visual context"
      - "Must include: environment, lighting, action, wardrobe, character descriptions from bible"
      - "Multi-shot mode: beat structure with 'Beat N (camera):' prefixes"
      - "Camera flow summary: 'Camera progresses through: close-up, then medium shot, then wide shot.'"
      - "Director notes aggregated and deduplicated"
      - "Film stock and quality footer"
      - "JIT hydration of skeleton tokens from live bible"
    anti_patterns:
      - "Short prompts — identity comes from refs only, prompt must carry everything else"
      - "Negative prompt — NOT supported, quality constraints go in positive prompt"
      - "Omitting character descriptions — R2V has no frame to carry identity context"
    enrichment_profile:
      default: [subject_line, action_line, kinetic_action, environment_line,
                emotion_line, character_descs, wardrobe, scene_visual_locks,
                film_stock, lighting_prose, arc_preamble, director_notes,
                spatial_continuity, verb_calibration]

  refs:
    max_count: 10
    format: url
    param_name: "reference_image_urls"
    role_labeling: false
    upload_required: true
    role_map: {}

  aspect_ratio:
    param_name: "aspect_ratio"
    i2v_behavior: null
    supported:
      - "16:9"
      - "9:16"
      - "1:1"
      - "4:3"
      - "3:4"
    mapping: {}

  first_last_frame:
    start_param: null
    end_param: null
    start_format: null
    end_format: null
    upload_required: false
    notes: "R2V has NO start frame — identity from reference images only"

  duration:
    param_name: "duration"
    format: integer
    range: [2, 10]
    even_only: false

  audio:
    supported: false
    param_name: null
    default_on: false
    notes: ""

  gotchas:
    - "Wider aspect ratio support than I2V (includes 4:3 and 3:4)"
    - "Also supports reference_video_urls (up to 5 video refs)"
    - "multi_shots=True lets model decide camera changes internally"
    - "Duration is integer (NOT string)"
    - "Character cap: 2400 chars, word cap: 400 words"
    - "No negative_prompt support — all quality constraints in positive prompt"

# ──────────────────────────────────────────────────────────────────────
# 9. VEO 3.1  (Video — Google)
# ──────────────────────────────────────────────────────────────────────
veo-3.1:
  meta:
    provider: google
    modality: video
    confidence: CONFIRMED
    codenames: []
    endpoints:
      default: "veo-3.1-generate-preview"

  prompt:
    optimal_words:
      default: [80, 150]
    max_chars: 1500
    style: prose
    negative_prompt: true
    negative_param: "negative_prompt"
    best_practices:
      - "Dense cinematic prose — NO section headers (Veo renders text overlays from them)"
      - "Camera control embedded in natural language ('slow pull back', '85mm lens')"
      - "Negative prompt uses bare nouns only — no 'don't' or 'no' phrasing"
      - "Positive constraints over negative language"
      - "Include 'photorealistic, high production value' footer"
      - "Film stock reference works well ('shot on {film_stock}')"
    anti_patterns:
      - "Section headers (CAMERA:, SUBJECT:) — rendered as text overlays"
      - "'Don't' or 'no' phrasing in negative prompt — use bare nouns"
      - "Combining start_frame with reference_images — mutually exclusive"
      - "Prompts over 1500 chars — hard cap enforced"
    enrichment_profile:
      t2v: [subject_line, action_line, kinetic_action, environment_line,
            emotion_line, character_descs, wardrobe, scene_visual_locks,
            film_stock, lighting_prose_comma_separated, arc_preamble,
            director_notes, audio_direction, veo_quality_footer]

  refs:
    max_count: 3
    format: part
    param_name: "reference_images"
    role_labeling: false
    upload_required: false
    role_map: {}

  aspect_ratio:
    param_name: "aspect_ratio"
    i2v_behavior: respects_param
    supported:
      - "9:16"
      - "16:9"
      - "1:1"
    mapping: {}

  first_last_frame:
    start_param: "image"
    end_param: "last_frame"
    start_format: part
    end_format: part
    upload_required: false
    notes: "Start frame (image) and reference_images are MUTUALLY EXCLUSIVE. When both provided, prefer reference_images (identity anchoring) over start_frame (first-frame control). End frame via config.last_frame."

  duration:
    param_name: "duration_seconds"
    format: integer
    range: [4, 8]
    even_only: true

  audio:
    supported: true
    param_name: null
    default_on: true
    notes: "Native audio generation — always on, no explicit param to control"

  gotchas:
    - "Start frame and reference_images are MUTUALLY EXCLUSIVE"
    - "Even durations only: 4, 6, or 8 seconds (nearest valid duration auto-selected)"
    - "3-ref limit — character consistency risk for multi-character scenes"
    - "Long shots/ENV only recommended — identity drift on close-ups"
    - "High content filter sensitivity on API model (veo-3.1-generate-preview)"
    - "Default negative prompt: 'morphing, face distortion, warping, duplicate limbs, text overlay, watermark'"
    - "Supports scene extension"
    - "person_generation param: 'allow_adult' (default) or 'dont_allow'"
    - "Enrichment model: gemini-3.1-flash-preview with dense_prose style"

# ──────────────────────────────────────────────────────────────────────
# 10. WAN 2.2  (Video — Open Source / Legacy)
# ──────────────────────────────────────────────────────────────────────
wan-2.2:
  meta:
    provider: open-source
    modality: video
    confidence: UNTESTED
    codenames: []
    endpoints:
      default: null

  prompt:
    optimal_words:
      default: [50, 100]
    max_chars: null
    style: prose
    negative_prompt: true
    negative_param: "negative_prompt"
    best_practices:
      - "Brief prose prompts — 50-100 words"
      - "Simple scene descriptions work best"
      - "Active verbs for motion"
    anti_patterns:
      - "Prompts over 100 words — older model struggles with long text"
      - "Complex multi-character scenes — limited coherence"

  refs:
    max_count: 1
    format: url
    param_name: "image_url"
    role_labeling: false
    upload_required: true
    role_map: {}

  aspect_ratio:
    param_name: "aspect_ratio"
    i2v_behavior: matches_start_frame
    supported:
      - "16:9"
      - "9:16"
      - "1:1"
    mapping: {}

  first_last_frame:
    start_param: "image_url"
    end_param: null
    start_format: url
    end_format: null
    upload_required: true
    notes: "Legacy model — start frame only, no end frame support"

  duration:
    param_name: "duration"
    format: integer
    range: [2, 5]
    even_only: false

  audio:
    supported: false
    param_name: null
    default_on: false
    notes: ""

  gotchas:
    - "Temporal degradation after 3 seconds — quality drops significantly"
    - "Legacy/open-source model — no active code path in engine"
    - "Superseded by wan-2.7-i2v for all production use"

# ──────────────────────────────────────────────────────────────────────
# 11. Z-IMAGE TURBO  (Image — Open Source)
# ──────────────────────────────────────────────────────────────────────
z-image-turbo:
  meta:
    provider: open-source
    modality: image
    confidence: UNTESTED
    codenames: []
    endpoints:
      default: null

  prompt:
    optimal_words:
      default: [10, 50]
    max_chars: null
    style: keywords
    negative_prompt: true
    negative_param: "negative_prompt"
    best_practices:
      - "Keywords and short phrases — under 50 words"
      - "Comma-separated tag style"
      - "Sub-second generation — designed for rapid iteration"
    anti_patterns:
      - "Dense prose — model responds better to keywords"
      - "Long prompts — diminishing returns past 50 words"

  refs:
    max_count: 0
    format: null
    param_name: null
    role_labeling: false
    upload_required: false
    role_map: {}

  aspect_ratio:
    param_name: "aspect_ratio"
    i2v_behavior: null
    supported:
      - "1:1"
      - "9:16"
      - "16:9"
    mapping: {}

  first_last_frame:
    start_param: null
    end_param: null
    start_format: null
    end_format: null
    upload_required: false
    notes: "Image model — no frame concept"

  duration:
    param_name: null
    format: null
    range: null
    even_only: false

  audio:
    supported: false
    param_name: null
    default_on: false
    notes: ""

  gotchas:
    - "Sub-second generation time — useful for rapid prototyping only"
    - "No active code path in engine — documentation-only"
    - "Low quality compared to Seedream/Gemini — not for production"

# ──────────────────────────────────────────────────────────────────────
# 12. FLUX 2  (Image — BFL)
# ──────────────────────────────────────────────────────────────────────
flux-2:
  meta:
    provider: bfl
    modality: image
    confidence: UNTESTED
    codenames: []
    endpoints:
      default: null

  prompt:
    optimal_words:
      default: [80, 200]
    max_chars: null
    style: prose
    negative_prompt: false
    negative_param: null
    best_practices:
      - "T5-driven — craves dense descriptive prose (like Wan 2.7)"
      - "Hybrid JSON+prose format for structured prompts"
      - "Strong natural language understanding from T5 encoder"
      - "Detailed scene descriptions with lighting and composition"
    anti_patterns:
      - "Keyword-only prompts — T5 encoder benefits from full sentences"
      - "Negative prompts — not supported by architecture"

  refs:
    max_count: 0
    format: null
    param_name: null
    role_labeling: false
    upload_required: false
    role_map: {}

  aspect_ratio:
    param_name: "aspect_ratio"
    i2v_behavior: null
    supported:
      - "1:1"
      - "9:16"
      - "16:9"
      - "4:3"
      - "3:4"
      - "3:2"
      - "2:3"
    mapping: {}

  first_last_frame:
    start_param: null
    end_param: null
    start_format: null
    end_format: null
    upload_required: false
    notes: "Image model — no frame concept"

  duration:
    param_name: null
    format: null
    range: null
    even_only: false

  audio:
    supported: false
    param_name: null
    default_on: false
    notes: ""

  gotchas:
    - "No active code path in engine — documentation-only"
    - "T5-XXL text encoder — same family as Wan 2.7, benefits from same dense prose approach"
    - "Hybrid JSON+prose prompting can structure complex scene descriptions"

# ──────────────────────────────────────────────────────────────────────
# 13. GPT IMAGE 2  (Image — OpenAI via fal.ai)
# ──────────────────────────────────────────────────────────────────────
gpt-image-2:
  meta:
    provider: openai
    modality: image
    confidence: UNTESTED
    codenames: []
    endpoints:
      t2i:  "fal-ai/gpt-image-2"
      edit: "fal-ai/gpt-image-2/image-to-image"

  prompt:
    optimal_words:
      default: [40, 100]
    max_chars: null
    style: prose
    negative_prompt: false
    negative_param: null
    best_practices:
      - "Natural language prose — no tags, no brackets."
      - "Text-rendering specialist: spell out exact text in single/double quotes."
      - "Single ref only — pass via `image_urls: [url]`, never a list of length >1."
      - "Quality tier selected per shot via `quality` field (low/medium/high)."
    anti_patterns:
      - "Multi-ref payloads — endpoint is single-ref; constraint validator fails fast."
      - "Transparent background requests — not supported; constraint validator fails fast."
      - "Omitting `quality` — adapter injects `medium` to avoid silent high-quality overspend."

  refs:
    max_count: 1
    format: url
    param_name: "image_urls"
    role_labeling: false
    upload_required: true
    role_map: {}

  aspect_ratio:
    param_name: "size"
    i2v_behavior: null
    supported:
      - "1:1"
      - "9:16"
      - "16:9"
    mapping:
      "1:1":  "1024x1024"
      "9:16": "1024x1536"
      "16:9": "1536x1024"

  first_last_frame:
    start_param: null
    end_param: null
    start_format: null
    end_format: null
    upload_required: false
    notes: "Image model — no start/end frame concept."

  duration:
    param_name: null
    format: null
    range: null
    even_only: false

  audio:
    supported: false
    param_name: null
    default_on: false
    notes: ""

  gotchas:
    - "Token-based pricing; cost is stamped onto the receipt from fal's billing field."
    - "Quality defaults to 'medium' if absent in payload — adapter-side injection."
    - "Endpoint discriminated by payload shape: image_urls present → edit, else → t2i."
    - "fal markup vs OpenAI direct unverified at spec time; verify in Phase 5."

# ──────────────────────────────────────────────────────────────────────
# CONSTRAINT DICTIONARY — structural prompting vocabulary (Phase 2a)
# ──────────────────────────────────────────────────────────────────────
# Slugs referenced by CINEMA_MODES.yaml mode.default_constraints and
# per-shot overrides. Values are ban-phrase strings that will be emitted by
# render_constraint_block() in cinema_loader.py.
#
# Emit policy: keyed off model_profiles.json::supports_negative_prompt.
#   - False/missing → bans go to positive-suffix closing block.
#   - True → bans go to the API negative_prompt param.
constraint_dictionary:
  no_modern_color_grade: "modern color grade, teal and orange, HDR look"
  no_digital_sharpness: "digital sharpness, clinical rendering, oversharpened"
  no_text_overlays: "text overlays, watermarks, UI elements, subtitles"
  no_smooth_video_look: "smooth video look, 60fps, interpolation, soap opera effect"
  no_camera_shake: "camera shake, jitter, unstable framing"
  no_extra_characters: "extra characters, background people, crowded"
