# Seedance 2.0 Prompt Pipeline Redesign — v2 (Post-Review)

**Date:** 2026-04-13
**Consultation:** `consultations/recoil/seedance-2-pipeline-optimization/` (3-round dual)
**Spec review:** `consultations/recoil/seedance-prompt-pipeline-spec-review/` (1-round dual)
**Status:** Spec v2 — corrected against actual code after review

---

## Problem Statement (Corrected)

The Seedance prompt pipeline has a dedicated R2V builder (`build_seeddance_r2v_prompt()`, line 3256) that already handles @ImageN labels, character descriptions, film stock injection, and "No music." control. It is NOT routing through the Kling builder.

**The actual problems are:**

1. **T2V is aliased to R2V.** Line 3864: `compiled["seeddance_t2v"] = compiled["seeddance_r2v"]`. The R2V builder includes character descriptions, @ImageN reference declarations, and ref_manifest resolution that a pure T2V endpoint doesn't use. T2V needs its own builder with the five-block prose structure at 120-250 words.

2. **No I2V builder exists.** There is no `seeddance_i2v` key in the compiled dict. I2V prompts need motion-only content at 50-75 words with identity erosion protection — fundamentally different from R2V.

3. **Word counts are still too low.** The existing R2V builder calls `_enforce_prompt_length()` but the underlying `optimal_prompt_length` in model_profiles.json is [40, 60] — the community-validated range is 120-250 for T2V, 50-75 for I2V, 120-200 for R2V.

4. **Missing quality suffix.** The existing builder has a "Cinematic, photorealistic" directive but not the full community-validated suffix ("4K, Ultra HD, rich details, sharp clarity, cinematic texture, natural colors, stable picture.").

5. **Missing identity lock phrase for I2V.** No mechanism to tell Seedance to preserve @Image1 facial features during animation.

---

## Architecture Decision

**Approach: Add T2V and I2V builders alongside the existing R2V builder. Enhance the R2V builder in-place.**

- `build_seeddance_t2v_prompt()` — NEW function, five-block prose, no @ImageN refs
- `build_seeddance_i2v_prompt()` — NEW function, motion-only, identity lock
- `build_seeddance_r2v_prompt()` — EXISTING function, enhance with quality suffix + word count update

Naming convention: `seeddance` (double-d) throughout, matching existing codebase.

---

## Phase 1 — Core Builders + Routing (Minimal Viable)

Phase 1 scope is deliberately minimal: two new functions, one routing fix, config updates. No lighting/film stock/verb enforcement — those are Phase 2 enhancements.

### 1.1 `build_seeddance_t2v_prompt(shot, bible, project_config, episode)` — New function

**Five-block prose structure (no section headers):**

```
BLOCK 1 — SUBJECT: Physical description, wardrobe, setting, mood.
  Source: skeleton.subject_line + skeleton.environment_line + skeleton.emotion_line
  Merge into flowing paragraph. No labels.

BLOCK 2 — ACTION: One primary motion.
  Source: skeleton.action_line OR prompt_data.kinetic_action

BLOCK 3 — CAMERA: Framing + movement.
  Source: prompt_data.shot_type + prompt_data.camera_movement

BLOCK 4 — STYLE: Aesthetic anchor from project_config.
  Source: project_config.film_stock + existing lighting data

BLOCK 5 — QUALITY SUFFIX (constant, auto-appended):
  "4K, Ultra HD, rich details, sharp clarity, cinematic texture, natural colors, stable picture."
```

**Audio directive:** Use existing `project_config.allow_music` flag. When false, append "No music, no score." (matches existing R2V behavior, updated wording).

**Word count:** Target 120-250 words. Call `_enforce_prompt_length()` with model_id `"seeddance-t2v"`.

**No @ImageN refs** — T2V endpoint doesn't take reference images, so no ref declarations.

**No single-verb enforcement in Phase 1** — deferred to Phase 2 (item 2.3).
**No ranked lighting keywords in Phase 1** — deferred to Phase 2 (item 2.1).
**No film stock injection in Phase 1** — use whatever project_config provides, deferred enhancement to Phase 2 (item 2.2).

### 1.2 `build_seeddance_i2v_prompt(shot, project_config)` — New function

**Structure:**
```
"@Image1 as the first frame. The subject in @Image1 [ACTION]. [CAMERA MOVEMENT].
[ENVIRONMENT CHANGE IF ANY].
Same person as @Image1. Do not alter facial proportions, eye shape, or hairstyle.
No music, no score.
4K, Ultra HD, rich details, sharp clarity, cinematic texture, natural colors, stable picture."
```

**Hard rules:**
- Opens with "@Image1 as the first frame."
- Uses "The subject in @Image1" as pronoun — NOT the stripped character name
- 50-75 words target
- STRIPS all character visual descriptions (hair, build, wardrobe, identity anchors)
- KEEPS: motion/action, camera movement, environment changes
- Appends identity lock phrase
- Appends audio directive (per project_config.allow_music)
- Appends quality suffix
- Does NOT take bible parameter (bible data is intentionally excluded)

**Pre-flight check:** If the I2V builder is called but no start frame is provided in the shot data, raise a hard error. I2V without a start frame is meaningless.

### 1.3 Enhance existing `build_seeddance_r2v_prompt()`

Minimal changes to the existing 3-zone builder:
- Append quality suffix constant to Zone C (alongside existing "Cinematic, photorealistic" directive)
- Update word count enforcement to target 120-200 words
- Keep existing @ImageN, character description, and "No music" behavior

Do NOT restructure the existing R2V builder from 3-zone to five-block in Phase 1. That's a Phase 2 consideration after we validate the T2V/I2V builders work.

### 1.4 Fix `compile_all_prompts()` routing

**Before (line 3864):**
```python
compiled["seeddance_t2v"] = compiled["seeddance_r2v"]  # alias for backward compat
```

**After:**
```python
compiled["seeddance_t2v"] = build_seeddance_t2v_prompt(
    shot=shot, bible=bible, project_config=project_config, episode=episode,
)
compiled["seeddance_i2v"] = build_seeddance_i2v_prompt(
    shot=shot, project_config=project_config,
)
```

**Rollback:** Keep the old alias available behind a config flag `use_legacy_seeddance_routing: true` in project_config. If the new builders produce worse results, flip the flag and we're back to the old behavior instantly.

### 1.5 Specify StepRunner prompt key selection

The spec must define how the pipeline maps `(model=seeddance-2.0, sub_pipeline)` to the right compiled key:

| Sub-Pipeline | Compiled Key |
|-------------|-------------|
| `t2v` | `compiled["seeddance_t2v"]` |
| `i2v` | `compiled["seeddance_i2v"]` |
| `r2v` / `multi_shot` | `compiled["seeddance_r2v"]` |

This mapping lives in the assembler or StepRunner's payload builder. Check how Kling routing currently works (`kling_i2v` vs `kling_t2v`) and follow the same pattern.

### 1.6 Update PROMPT_BIBLE.yaml — seeddance-2.0 section

**Word counts (keep backward-compatible format):**
```yaml
optimal_words:
  default: [120, 250]
  t2v: [120, 250]
  i2v: [50, 75]
  r2v: [120, 200]
  multi_shot: [150, 250]
```

**New fields:**
```yaml
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."
```

**Updated best_practices and anti_patterns** per consultation findings (five-block structure, identity erosion rule, @Image1 attention weight, positive-negative directives).

### 1.7 Update model_profiles.json — safe format

**Do NOT change `optimal_prompt_length` to a dict** — this breaks `jit_prompt.py` and `VideoEnhancementCritic` which expect an array.

Instead, follow the Kling precedent — add per-endpoint keys:
```json
{
  "optimal_prompt_length": [120, 250],
  "optimal_prompt_length_t2v": [120, 250],
  "optimal_prompt_length_i2v": [50, 75],
  "optimal_prompt_length_r2v": [120, 200]
}
```

Existing callers continue reading `optimal_prompt_length` (array) as before. New Seedance-specific code reads the endpoint-specific keys.

### 1.8 Update VideoEnhancementCritic — minimal change

Pass endpoint context through existing mechanism. The critic constructor already takes `target_model`. Add an optional `endpoint` kwarg:

```python
def __init__(self, target_model: str, endpoint: str = "default", ...):
```

Word count check reads `optimal_prompt_length_{endpoint}` if it exists, falls back to `optimal_prompt_length`.

**Safety filter: NO changes in Phase 1.** Keep the full safety_filter_words list and _SAFETY_REPLACEMENTS as-is. Add logging to record every safety gate fire (word matched, replacement applied, full prompt context). This gives us baseline data before reducing the list in Phase 2.

### 1.9 @Image1 ordering enforcement

Moved from Phase 2 — the R2V builder depends on @Image1 being the primary character. In `allocate_references()` (assembler.py), when target model is `seeddance-2.0`, ensure primary character identity ref is at index 0. Log warning when reordering.

### 1.10 Dry-run test script

New `tools/test_seeddance_builders.py`:
- Takes a mock shot dict (or reads a real plan file)
- Runs all three builders (t2v, i2v, r2v)
- Prints resulting prompts to console with word counts
- Validates: no section headers present, quality suffix present, identity lock phrase in I2V, word count in range
- Zero API cost — pure prompt validation

### 1.11 Generation metadata sidecar

Moved from Phase 2 — needed as foundation for Phase 2 A/B tests. After each video generation, StepRunner writes a YAML sidecar alongside the output video:

```yaml
generation:
  id: gen_20260413_001
  timestamp: 2026-04-13T03:22:00Z
  model: seeddance-2.0
  endpoint: i2v
  prompt_builder: build_seeddance_i2v_prompt
  prompt_text: "..."
  prompt_word_count: 63
  inputs:
    character_refs: [kai_frontal.jpg]
    start_frame: kai_neutral_doorway.jpg
  parameters:
    duration: 5
    aspect_ratio: "9:16"
    generate_audio: true
  cost_usd: 1.52
  latency_seconds: 45
  lineage:
    parent_gen: null
    re_run_reason: null
```

Cost calculated via `duration × model_profiles.cost_per_second`. Latency via `time.time()` wrapper in StepRunner.

---

## Phase 2 — Quality Enhancements + Experiments

### 2.1 Lighting keyword integration
Update builders to prefer ranked Seedance terms when available: "motivated lighting" > "practical light sources" > "warm tungsten bounce" > "volumetric dust particles" > "negative fill".

### 2.2 Film stock anchor injection
When target model is seeddance-2.0, inject film stock anchor into style block. Source: project_config.film_stock, default "Kodak Vision3 500T".

### 2.3 Single-verb action enforcement
In T2V builder: detect compound actions (heuristic: count "and"/"while"/"as" between verb phrases), pick dominant. Log warning. Soft enforcement — simplify, don't block.

### 2.4 Safety filter reduction (data-driven)
After Phase 1 logging provides 50+ generations of baseline data, analyze safety gate fires. If keyword replacements are causing more flags (evasive language) than preventing them, reduce the Seedance safety list to hard triggers only (age terms, real identity). Add PRODUCTION_CONTEXT soft dimension (warn if <2 production terms in user-authored content, before suffix is appended).

### 2.5 Camera reference self-generation utility
`tools/generate_camera_refs.py` — generates 15 camera movement clips via Seedance T2V. Empty room prompts. ~$5-8 one-time cost. Store with manifest.yaml.

### 2.6 Chinese translation A/B test
Gemini Flash translation, 20 shots, blind review. Focus on fabric/spatial/architectural shots. Toggle via project_config.zh_translate. Requires metadata sidecar (1.11) for tracking.

### 2.7 JSON prompt structure test
Manual test first (2-3 prompts via fal.ai playground). If JSON keys don't render as text overlays, build `build_seeddance_json_prompt()` and A/B test 10 multi-shot sequences against prose. If keys DO render as overlays, abandon immediately.

### 2.8 R2V builder restructure (optional)
If the five-block structure proves superior in T2V testing, consider restructuring the existing R2V builder from 3-zone to five-block. Only if Phase 1 results warrant it.

---

## Phase 3 — Experimental & Triage (Deferred)

### 3.1 Two-pass hybrid workflow (R2V → extract frame → Seedream → I2V)
### 3.2 Storyboard → photorealistic style transfer test
### 3.3 Morning triage pipeline (deferred until full pipeline run)
### 3.4 Expression reference investigation (fal.ai expression_refs)

---

## Files Modified

| File | Phase | Change |
|------|-------|--------|
| `pipeline/lib/prompt_engine.py` | 1 | Add `build_seeddance_t2v_prompt()`, `build_seeddance_i2v_prompt()`. Enhance existing R2V builder. Fix routing in `compile_all_prompts()`. |
| `config/PROMPT_BIBLE.yaml` | 1 | Update seeddance-2.0 section: word counts, quality_suffix, identity_lock_phrase, best_practices, anti_patterns. |
| `config/model_profiles.json` | 1 | Add per-endpoint `optimal_prompt_length_*` keys (keep array format). |
| `pipeline/lib/critics/video_enhancement_critic.py` | 1 | Add `endpoint` kwarg, per-endpoint word count lookup, safety gate logging. |
| `execution/assembler.py` | 1 | @Image1 ordering for Seedance. |
| `execution/step_runner.py` | 1 | Metadata sidecar writing. Prompt key selection for Seedance endpoints. |
| `tools/test_seeddance_builders.py` | 1 | New dry-run test script. |

---

## Validation Strategy

**Phase 1 validation (before declaring done):**
1. Run `test_seeddance_builders.py` on 5 real plan shots — verify word counts, no section headers, quality suffix present, identity lock in I2V
2. Generate 3 shots via each endpoint (T2V, I2V, R2V) at Standard pricing = ~$14 total
3. Compare T2V output quality: new five-block vs old R2V-alias (use rollback flag)
4. Compare I2V output quality: new stripped prompt vs old (no comparison possible — key didn't exist)
5. Verify metadata sidecar writes correctly
6. Verify @Image1 ordering in assembler output

**Rollback:** `project_config.use_legacy_seeddance_routing: true` restores old T2V=R2V alias behavior.

---

## Model Behavior Assumptions

| Decision | Depends On | Risk If Wrong | Rollback |
|----------|-----------|---------------|----------|
| 120-250 word T2V | Community data | Prompts too long for some shots | Reduce range, critic warns |
| I2V identity erosion | Community consensus | Stripping descriptions reduces consistency | Rollback flag → old behavior |
| @Image1 40-50% attention bonus | Community report | Ordering enforcement is harmless | No rollback needed |
| Quality suffix | Universal practice | Worst case neutral | Remove suffix constant |
| "No music, no score" | Empirical (2026-04-13) | Audio bleeds. Does NOT affect lip-sync (dialogue in quotes still works) | project_config.allow_music toggle |
| Positive-negative directives work | Empirical + Morphic guide | "No X" may cause model to attend to X | Rephrase positively ("diegetic ambient only") |
