# H3 Film Studio

> 视频生成 skill（H3/Krea 本地为主）。主题→意图→姿态状态机→分镜→出片。强制 INTENT + shot_table preflight；禁止跳过状态机手搓独立母图。

- Skill: `huangserva/h3-film-studio` (Agent Skill, multi-file: 14 files)
- Install (CLI): `npx skillmds@latest add huangserva/h3-film-studio`
- Raw SKILL.md: https://api.skillmd.com/api/skills/huangserva/h3-film-studio/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: huangserva (https://skillmd.com/u/huangserva)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/huangserva/h3-film-studio

---


# h3-film-studio

> 成人/剧情/电影共用本 skill。旧 `adult-krea2-h3-narrative` 仅作历史参考，**新片只认本目录**。

## 违禁清单（出现即流程失败）

- ❌ 无 `INTENT.md` 就出图/出视频  
- ❌ 无 `shot_table.json` 或 **preflight 未 exit 0** 就调 H3/Krea  
- ❌ 独立母图堆叠 + 纯 i2v 硬拼（坐站不继承）  
- ❌ 坐↔站大变却 `gen_mode=i2v_solo`（必须 **fl2v 首尾** 或 **chain**）  
- ❌ 用剪辑溶解掩盖姿态跳  
- ❌ 直接复制 `/tmp/**/scratchpad` 旁路当主路径且不跑 preflight  
- ❌ **合戏/抽送/骑乘/后入等动作镜不挂 Motion Booster**（见下方运动硬门）  
- ❌ 交付前未做 **motion_mean + 抽帧穿帮验收**  

**为什么会手搓、怎么解：** 见 [`reference/anti-handroll.md`](reference/anti-handroll.md)。

---

## 运动硬门（2026-08-29 用户纠正后强制）

用户原话精神：**动作镜必须挂 Booster**；「为了真实就砍 Booster」= 流程失败。

| 镜类 | LoRA | prompt 触发词 | motion_mean 验收 |
|------|------|---------------|------------------|
| **合戏/动作**（骑乘/后入/抽送/插入进行中） | HMNSFW **0.5** + Motion Booster **0.55–0.7**（+turbo 可叠） | `dynvt1`（垂直/女上）或 `dynfb1`（前后/后入）+ `hmmotion` | **≥2.8**；目标 **3.5–5.5**；&lt;2.8 必须重出 |
| **文戏/铺垫**（走路、接吻、对峙、解衣过渡） | HMNSFW **0.3–0.35**，**禁止 Booster** | 勿写 dynvt1/dynfb1 | **1.2–2.5**；&lt;1.0 像静帧要重出 |

- 「真实/克制」只改 **prompt 措辞与节奏词**（steady/rhythmic），**不许拆掉 Booster**。
- 细节见 [`reference/nsfw-profile.md`](reference/nsfw-profile.md)、[`reference/hmnsfw-register.md`](hmnsfw-register.md)。

---

## 交付前强制验收（不过不交）

每镜出片后、成片交付前：

1. 记 `motion_mean`（`h3_i2v_shot.py` 已打印）——不合上表即重出。  
2. 抽帧 **头/中/尾** 三帧：服装单向（脱了不许穿回）、脸是否漂、结合处是否按 INTENT。  
3. 音轨：合戏 **禁 mute**；成片 loudnorm 统一，禁镜间音量乱跳。  
4. 用户纠正写入 INTENT 原话后再改下一批。

---

## 步骤0（强制）：意图保全 —— INTENT.md

**不许跳过。** 全文：[`reference/intent-protocol.md`](reference/intent-protocol.md)。

1. 项目开始：建 `INTENT.md`（模板 `templates/INTENT.template.md`）。
2. 用户每次纠正 → **立即**写入 INTENT（原话）。
3. 分镜 / prompt / 跑批 / 交付前必须先 Read INTENT。
4. 交付前对照 INTENT 自检门。

---

## 步骤0.5（强制）：衔接硬门 —— shot_table + preflight

**没有这一步，禁止任何生成。** 全文：[`reference/continuity-gate.md`](reference/continuity-gate.md)。

1. 写 **`shot_table.json`**（模板 `templates/shot_table.template.json`）：每镜 `start`/`end` 姿态与道具；`gen_mode` ∈ `i2v|fl2v|chain|i2v_solo`。
2. **相邻镜**：`shots[i].end` 必须等于 `shots[i+1].start`（同名字段）。
3. **镜内坐↔站等大变**：`gen_mode` 必须是 `fl2v`（首尾帧）或 `chain`（上镜真尾帧续），禁止手搓独立 i2v。
4. 跑门禁（必须 exit 0）：

```bash
python3 ~/.claude/skills/h3-film-studio/scripts/preflight_continuity.py \
  --table <项目目录>/shot_table.json \
  --strict-pose-change
```

5. 通过后才：出母图（匹配 start/end）→ H3 → 拼接。

> 剧本层先锁状态机；锁不住的姿态差用 **首尾帧 fl2v / chain** 硬保证——不是事后审计。

---

## 触发条件
用户提到"生成视频"、"广告片"、"短片"、"商业视频"、"产品广告"、"做一个视频"等关键词。

## 工作流程概览

你（宿主 LLM）负责所有思考任务，Python 脚本只负责调用外部 API 生成素材和合成视频。

**视频质量检测标准：** 在检查单个 shot、分析风险片段、执行视觉裁定、以及整片复检时，必须参考 [reference/video_quality_standard.md](~/.claude/skills/h3-film-studio/reference/video_quality_standard.md)。不要只依据数值异常判断，必须按标准文件中的维度逐项检查。

**视频生成参考图策略标准：** 在生成 storyboard 时，必须参考 [reference/video_generation_strategy.md](~/.claude/skills/h3-film-studio/reference/video_generation_strategy.md)。先做语义判断，再把结果优先落成 `video_references`；`shot_type`、`continuity_mode`、`keyframes`、`chain_from_previous` 仍然要写，但它们已经不是视频参考协议的主脑。

**当前 skill 的关键协议已经升级：**

- `storyboard.json` 现在不是旧的松散 shot 列表，而是以 `scenes > shots` 为主结构
- 每个 shot 默认应该显式包含：
  - `shot_type`
  - `subject_constraints`
  - `continuity_mode`
  - `chain_from_previous`
- legacy storyboard 会在执行时被 `run_pipeline.py` 规范化到 `_normalized/storyboard.json`
- 规范化过程会输出 `_normalized/storyboard_migration_report.json`

**当前成片流程已经不是”素材生成完直接合成”那么简单，而是：**

1. 分镜生成
2. 导演写帧级 prompt
3. 角色参考图生成
4. 图片/视频素材生成
5. 导演审图（可选）
6. 视频质量粗筛
7. 母模型视觉裁定
8. pair-level 编辑决策
9. FFmpeg 合成

也就是说：
- 思考在母模型脑子里完成，生图模型只管执行
- 导演审图是主流程的一部分（可选）
- 质量审查是主流程的一部分
- 编辑决策也是主流程的一部分
- 不能再把 compose 理解成”按每个 shot 的 transition_in 直接拼起来”
- 当前视频生成 provider 主路径以 Ark Seedance 2.0 为主；对你来说，storyboard 现在应优先按 `video_references` 的用途协议来约束视频，而不是把“首帧 / 尾帧 / keyframes”当成唯一主结构

```
步骤1: 故事创作              → 你与用户对话讨论方向，输出 story.json
步骤2: 剧本框架 + 角色设计    → 你思考，输出 framework.json（从 narrative 切分 scenes + 设计角色）
步骤3: 角色参考图生成        → python3 scripts/ad_assets.py --mode character_refs
步骤4: 分镜脚本              → 你思考，输出 storyboard.json（scenes > shots，从 narrative 派生）
步骤4.5: 导演写帧级 prompt    → 你思考，输出 director_prompts.json（逐帧画面描述）
步骤5: 素材生成              → python3 scripts/ad_assets.py [--review-mode director_review]
步骤5.5: 导演审图（可选）     → 你查看生成的图片，判断 keep/regenerate
步骤6: 编辑决策 + 视频合成    → python3 scripts/ad_compose.py
```

**审图模式选择建议：**
- `metrics_only`（默认）- 只做自动质量检测，适合快速迭代或对生图质量有信心
- `director_review` - 每张图片生成后暂停，由你审图。适合：
  - 需要严格把控画面质量的商业项目
  - 复杂的角色姿态或情感表达
  - 对连续性要求高的场景（如角色在不同镜头间的一致性）
  - 第一次使用新的角色设定或场景风格
- `hybrid_judge` - 图片质量粗筛 + 视频视觉判断，介于两者之间

**输出目录约定：** 所有文件输出到同一个目录，如 `~/video-output/20260319_143022/`。

---

## 步骤1: 故事创作

与用户对话讨论创意方向，然后展开为完整故事。

**流程：**
1. 用户给出主题/想法
2. 如果方向不明确，在对话中提出 2-3 个方向建议（自然对话，不写 JSON）
3. 用户确认方向后，输出 `story.json`

**输出 JSON → `{output_dir}/story.json`：**

```json
{
    "title": "故事标题",
    "synopsis": "一句话梗概（30字以内）",
    "source_interpretation": "对用户输入的理解",
    "narrative": "完整连贯叙事（200-400字，见下方规则）",
    "story_beats": [
        {
            "beat": "开端|发展|高潮|结尾",
            "description": "具体发生什么（50-80字，必须是可视化的场景描述）",
            "emotion": "情感基调",
            "key_visual": "最核心的一个画面（一句话）",
            "narrative_range": "对应 narrative 中的起止句（如：'烟雨西湖…' → '…传来慌乱的脚步声'）"
        }
    ],
    "visual_tone": "整体视觉基调建议（色调、氛围、风格，30-50字）",
    "suggested_duration_per_beat": [8, 10, 12, 10]
}
```

### 1.1 narrative（完整连贯叙事）— 最核心的字段

**narrative 是整个视频的故事主线。** 后续步骤2的场景拆分、步骤4的分镜脚本、步骤5的帧级 prompt / 动作描述提取，都从 narrative 派生。它不是摘要，不是大纲，而是一段**可以直接朗读的、有画面感的连贯叙事**。

**写作规则：**

1. **因果链** — 每个事件必须由前一个事件触发。不是"A发生了，然后B发生了"，而是"因为A发生了，所以B做出了反应，导致C"
   - ❌ "白娘子在桥上。许仙在跑。白娘子递伞。"（三个孤立事件）
   - ✅ "白娘子听到脚步声回头，看到书生在雨中狼狈奔跑，心生怜悯，于是走上前递伞"（因果链）

2. **角色动机** — 每个角色的行为必须有情感/意图驱动
   - ❌ "她递伞给他"（机械动作）
   - ✅ "她看到他无助地望着雨帘，心中一动，将自己的伞递了过去"（有动机）

3. **感官细节** — 写出观众能看到、听到、感受到的东西
   - 声音："慌乱的脚步声"、"雨打伞面"
   - 触觉："指尖在伞柄上相触"
   - 视觉变化："云缝中透出暖光"

4. **环境过渡** — 时间/天气/光线的变化必须是渐变的，不能跳变
   - ❌ "下着大雨。然后阳光灿烂。"（跳变）
   - ✅ "雨渐渐小了，云层裂开一道缝隙，一丝暖光透了出来"（渐变）

5. **连续的动作线** — 角色的位置移动必须有交代
   - ❌ "白娘子在桥上"→ 下一句 "白娘子站在亭子边"（瞬移）
   - ✅ "白娘子将伞交给小青，独自走向亭子"（交代了移动过程）

6. **长度** — 200-400字（中文）。太短没有细节，太长会超出视频表达能力

**示例：**

> 烟雨西湖，白素贞与小青撑伞漫步断桥，享受着雨中的宁静。忽然桥远处传来慌乱的脚步声——一个年轻书生抱着书箱在暴雨中狼狈奔跑，书页从箱中飞散。他踉跄着钻进桥边的石亭，弯腰喘气，脚边散落着被雨水浸湿的书页。白素贞驻足望去，看到他无助地望着亭外的雨帘，心生怜悯。她将伞交给小青，独自走向亭子，站在雨中将伞递给书生。他惊讶抬头，犹豫片刻，伸手接过——两人指尖在伞柄上相触，都微微一怔。雨渐渐停了，云缝中透出暖光。书生感激道别，撑着借来的伞沿桥慢慢走远。走到桥尽头，他忍不住回望——桥的另一端，白素贞仍站在那里，衣袂被晚风轻轻扬起。

### 1.2 story_beats 与 narrative 的关系

**story_beats 是 narrative 的结构化切分**，不是独立创作。每个 beat 的 `narrative_range` 标注它对应 narrative 中的哪段文字。

规则：
- 所有 beats 的 narrative_range 合起来必须覆盖整个 narrative，不能有遗漏
- 每个 beat 的 description 必须与 narrative 对应段落一致，不能添加 narrative 中没有的内容
- beats 之间不能有叙事跳跃——如果 narrative 中有过渡（"雨渐渐停了"），对应的 beat 必须包含这个过渡

**约束：**
- 紧扣原文核心意象，禁止无关发散
- 每个情节点都要能转化为具体画面（有人物、有场景、有动作）
- 必须有明确的 开端 → 发展 → 高潮 → 结尾
- 每个情节点的场景/人物状态/光影必须有明显变化

---

## 步骤2: 剧本框架 + 角色设计

将故事展开为完整剧本框架。**角色设计是本步骤的核心**——角色外貌描述的质量直接决定后续所有视觉一致性。

**输出 JSON → `{output_dir}/framework.json`：**

```json
{
    "title": "剧本标题",
    "synopsis": "故事梗概（50字以内）",
    "narrative": "从 story.json 复制或润色（这是唯一故事主线，必须贯穿始终）",
    "visual_style_anchor": "全局统一视觉风格（色调、质感、光影风格、氛围基调，80-120字）",
    "total_duration": 60,
    "story_time": "day|dusk|night|dawn",
    "suggested_characters": [
        {
            "id": "角色唯一ID（英文，如 blue_mecha, hero_girl）",
            "name": "角色名",
            "role_type": "protagonist|antagonist|supporting|extra",
            "personality": "性格特点（20字以内）",
            "appearance": "详细外貌描述（极其详细！颜色、材质、形状、纹理、特征标记，80-150字。这是角色参考图的生成依据，必须写到看完描述就能画出来的程度）",
            "default_clothing": "默认服装/装甲描述（颜色、款式、配饰、材质细节，50-80字）",
            "key_features": ["识别特征1", "识别特征2", "识别特征3"],
            "ref_description": "传给图片模型的角色说明（可用英文，供图片模型理解角色参考图）"
        }
    ],
    "suggested_locations": [
        {
            "name": "地点名",
            "description": "地点详细视觉描述（100字左右）",
            "environment_type": "indoor|outdoor|natural|urban|fantasy"
        }
    ],
    "scenes": [
        {
            "name": "场景名",
            "location": "地点名",
            "narrative_segment": "对应 narrative 中的原文段落（直接引用，不改写）",
            "summary": "本场剧情概述（30字以内）",
            "visual_description": "场景视觉描述（50-100字）",
            "characters_in_scene": ["角色ID"],
            "emotion_arc": "情感变化",
            "duration": 20
        }
    ]
}
```

### 2.1 scenes 必须从 narrative 切分

**scenes 不是独立创作，而是 narrative 的分段切割。**

每个 scene 的 `narrative_segment` 字段直接引用 narrative 中的一段原文。所有 scenes 的 narrative_segment 合起来必须完整覆盖 narrative，不能遗漏，不能添加 narrative 中没有的内容。

**场景切分原则：**
- 当以下任一条件发生变化时，切为新场景：地点变化、光线质变、天气变化、时间跨度导致视觉环境显著不同
- 切分点应落在 narrative 的自然段落边界上（因果链的节点处）
- 每个 scene 的 narrative_segment 必须是一段**完整的因果片段**，不能在因果链中间切断

**示例 — 白娘子断桥：**

narrative 的自然切分点：
1. "烟雨西湖...书页从箱中飞散" → 场景1：雨中漫步 + 发现书生（同一视觉环境）
2. "他踉跄着...独自走向亭子" → 场景2：书生避雨 + 白素贞决定上前（同一视觉环境）
3. "站在雨中...都微微一怔" → 场景3：递伞 + 指尖相触（动作高潮）
4. "雨渐渐停了...衣袂被晚风轻轻扬起" → 场景4：雨停离别（视觉环境变化——雨停、暖光）

❌ 错误切分："白素贞驻足望去，看到他无助地望着亭外的雨帘，心生怜悯" 和 "她将伞交给小青，独自走向亭子" 切成两个场景 → 把因果链切断了（怜悯→行动 是一个完整的因果）

### 2.2 角色设计关键规则

**角色外貌描述必须极其详细，因为每张图片都是独立生成的。** 描述要达到"只看文字就能画出完全一样的角色"的精度。

必须包含：
- **形体特征**：身高、体型、比例
- **颜色方案**：主色+辅色+点缀色的精确描述
- **材质纹理**：光滑/粗糙/磨损/反光等质感描述
- **关键识别标记**：至少3个独特视觉锚点（如：胸口徽章、蓝色光眼、肩部排气管）
- **ref_description**：这是给图片模型的角色参考说明，允许使用英文，因为角色参考图阶段仍可能由图片模型受益于英文描述。

### 2.3 语言规则

从现在开始，必须明确区分两类文本：

1. **视频侧 / storyboard 侧字段：默认使用中文**
   - `narrative_segment`
   - `scene_prompt`
   - `end_frame_description`
   - `action_prompt`
   - `lighting`
   - `weather`
   - `environment_description`
   - `props`
   - `motion_control.phase_beats`
   - `subject_constraints.semantic_rules`
   - 其他直接服务于视频生成和视频审查的描述字段

2. **图片侧字段：允许使用英文**
   - `characters.*.ref_description`
   - 如后续存在专门传给图片模型的 image prompt，可按图片模型需要使用英文

原则：
- 字节 Seedance 视频链路支持中文，所以 storyboard 和视频提示词文件不再默认使用英文
- 以后只要是视频生成相关的文本描述，默认都写中文
- 不要再把“英文 prompt 习惯”带回 storyboard 主文件

**示例：**
- ❌ `"appearance": "一个蓝色的机器人"`
- ✅ `"appearance": "40-foot tall humanoid war machine in faded royal blue and weathered flame-red thick riveted steel armor, deep scratches revealing dark gunmetal underneath, oil stains on chest plate, angular helmet with narrow glowing blue optics, battered silver faceplate with micro-scratches, massive forearms with exposed hydraulic cables and rubber hoses, exhaust pipes on shoulders"`

---

## 步骤3: 角色参考图生成

**这一步是视觉一致性的基石。** 在写分镜之前，必须先为每个角色生成参考图。

```bash
cd /path/to/skills/xyz-video-skill/scripts
python3 ad_assets.py \
    --mode character_refs \
    --framework {output_dir}/framework.json \
    --output_dir {output_dir}/character_refs
```

**脚本会自动：**
1. 读取 framework.json 中的 suggested_characters
2. 为每个角色生成一张"角色设计参考图"（白底，正面+3/4侧面，全身，标注细节）
3. 输出到 `{output_dir}/character_refs/ref_{character_id}.png`

**生成后请检查参考图质量**，如果某个角色不满意，可以重新生成单个角色：
```bash
python3 ad_assets.py --mode character_refs --framework framework.json --output_dir character_refs --character_id blue_mecha
```

确认参考图满意后再进入步骤4。

---

## 步骤4: 分镜脚本（最核心步骤）

将剧本框架拆解为逐镜头的分镜脚本。**分镜质量直接决定最终视频效果。**

**⚠️ 核心原则：分镜是 narrative 的视觉化切割，不是独立创作。**

每个 scene 和 shot 都必须标注 `narrative_segment`，直接引用 narrative 中的对应段落。scene_prompt / action_prompt / end_frame_description 三个字段都从 narrative_segment 派生，不凭空创造内容。

**⚠️ 当前必须额外考虑两个层面的结构化约束：**

1. `shot_type`
- `visible_subject`
- `offscreen_reaction`
- `transition_reveal`
- `free_atmosphere`

2. `subject_constraints`
- `required_visible_subjects`
- `optional_visible_subjects`
- `offscreen_subjects`
- `continuity_subjects`
- `forbidden_visible_subjects`
- `semantic_rules`

这两个字段不是注释，它们会直接进入图片 prompt、视频 prompt、质量审查和后续编辑决策。

**⚠️ 强导演模式只用于复杂 shot，普通 shot 保持轻量。**

以下 shot 必须进入**强导演模式**，先写 `director_plan` 再写字段：

1. `shot_type = "transition_reveal"`
2. 强动作 shot
3. 明显情绪转折、身份确认、关系转折 shot
4. `chain_from_previous = true` 的高衔接依赖 shot
5. 中间需要 2 张及以上 `keyframes` 的 shot

其他普通 shot 可以继续轻量写法，只要：
- `scene_prompt` 清楚定义起点
- `action_prompt` 清楚定义过程
- `end_frame_description` 清楚定义落点

**⚠️ 强导演模式下，先做导演设计，再做语义决策，最后才写字段。**

在强导演模式下，母模型必须先完成一层 `director_plan`：

1. 这镜**只完成什么戏剧动作**
2. 这镜**明确不完成什么**
3. 观众**按什么顺序获得信息**
4. 谁是**变化主体**，谁是**稳定主体**
5. 这镜要拆成**几个必要阶段**

只有 `director_plan` 清楚后，才进入参考图策略判断。

在写每个 shot 之前，必须先按 [reference/video_generation_strategy.md](~/.claude/skills/h3-film-studio/reference/video_generation_strategy.md) 判断这 4 个维度：

1. 动作复杂度
2. 状态变化幅度
3. 起止姿态约束强度
4. 与前后镜头的衔接依赖

然后再决定该 shot 需要哪些用途驱动参考素材：

- `first_frame`
- `reference_character`
- `reference_prop`
- `reference_composition`
- `reference_style`
- `reference_stage`
- `reference_target_state`

最后才把这个决策落成字段：

- `video_references`
- `reference_strategy`（可选的分析字段）
- `shot_type`
- `continuity_mode`
- `keyframes`（兼容表达中间阶段）
- `time_beats`
- `chain_from_previous`

不要反过来先凭感觉写字段，再让字段替代判断。

**输出 JSON → `{output_dir}/storyboard.json`：**

```json
{
    "title": "视频标题",
    "total_duration": 70,
    "character_ref_dir": "{output_dir}/character_refs",
    "bgm_style": "BGM 风格描述",
    "narrative": "从 framework.json 复制的完整连贯叙事（唯一故事主线）",
    "style_anchor": "从 framework.json 的 visual_style_anchor 复制（全局渲染风格：色调、质感、渲染方式）",
    "characters": {
        "character_id": {
            "ref_image": "ref_{character_id}.png",
            "ref_description": "从 framework.json 复制的 ref_description",
            "appearance": "从 framework.json 复制的 appearance",
            "key_features": ["特征1", "特征2", "特征3"],
            "weapon": "武器描述（如有）"
        }
    },
    "scenes": [
        {
            "id": "scene_1",
            "name": "场景名称（如：断桥雨中）",
            "location": "地点（如：杭州西湖断桥）",
            "narrative_segment": "对应 narrative 中的原文段落（从 framework.json 的 scene.narrative_segment 复制）",
            "lighting": "光线参数（色温、方向、质感，中文）",
            "weather": "天气/粒子效果（中文）",
            "props": ["道具1", "道具2"],
            "environment_description": "环境视觉描述（中文，80-150字。同场景所有镜头共享的视觉基底）",
            "scene_continuity": {
                "stable_facts": {
                    "spatial_layout": ["稳定空间关系事实1", "稳定空间关系事实2"],
                    "prop_states": ["稳定道具状态事实1"],
                    "environment_states": ["稳定环境状态事实1"],
                    "character_states": ["稳定角色状态事实1"]
                },
                "entity_registry": {
                    "prop_id": {
                        "count": 1,
                        "holder": "character_id_1.right_hand",
                        "persistent_state": "持续状态描述"
                    }
                },
                "carry_forward_subjects": ["character_id_1", "character_id_2", "prop_id"]
            },
            "shots": [
                {
                    "id": 1,
                    "characters_in_shot": ["character_id_1", "character_id_2"],
                    "narrative_segment": "本镜头对应的 narrative 片段（从 scene 的 narrative_segment 中进一步切分）",
                    "director_plan": {
                        "dramatic_core": "这镜只完成什么戏剧动作",
                        "not_this_shot": "这镜明确不完成什么",
                        "viewer_information_flow": [
                            "观众先知道什么",
                            "观众再知道什么",
                            "观众最后情绪落到哪里"
                        ],
                        "camera_intent": {
                            "shot_purpose": "观察|揭示|压迫|跟随|等待",
                            "framing_base": "基础景别和构图",
                            "camera_contract": "机位、轴线、构图变化允许范围"
                        },
                        "stable_subjects": ["这一镜里基本不变的人或物"],
                        "changing_subjects": ["这一镜里主要发生变化的人或物"],
                        "invariants": ["整镜不变项1", "整镜不变项2"],
                        "allowed_progressions": ["整镜允许推进的变化1"],
                        "nodes": [
                            {
                                "id": "n1",
                                "story_function": "第一个阶段负责什么",
                                "visual_focus": "观众先看哪里",
                                "must_show": ["该阶段必须出现的内容"],
                                "must_not_show": ["该阶段不能提前完成的内容"],
                                "delta_from_previous": "相对上一阶段的主变化"
                            }
                        ]
                    },
                    "scene_prompt": "故事起点 + 起始画面状态（中文，从 narrative_segment 派生。见 5.5 规则）",
                    "end_frame_description": "故事终点 + 结束画面状态（必填！从 narrative_segment 派生。见 5.6 规则）",
                    "action_prompt": "动作描述（中文，从 narrative_segment 派生，只写运动过程）",
                    "camera_movement": "运镜方式",
                    "camera_technical": "焦距+光圈",
                    "speed_baseline": "1.0x",
                    "narration": "",
                    "tts_text": "",
                    "subtitle": "",
                    "estimated_duration": 8,
                    "shot_type": "visible_subject",
                    "chain_from_previous": false,
                    "continuity_mode": "scene_end",
                    "time_beats": [
                        "0-2s：主体保持当前状态，镜头先建立画面",
                        "2-4s：出现第一段明确的动作或状态推进",
                        "4-6s：镜头落到最终结果状态"
                    ],
                    "subject_constraints": {
                        "required_visible_subjects": ["character_id_1"],
                        "optional_visible_subjects": ["character_id_2"],
                        "offscreen_subjects": [],
                        "continuity_subjects": ["character_id_1"],
                        "forbidden_visible_subjects": [],
                        "semantic_rules": ["保持本镜头内的动作与主体关系连续。"],
                        "pose_contract": ["如果该角色在多个参考阶段之间必须保持同一承重姿态，就在这里写固定身体支撑关系。"],
                        "gaze_contract": {
                            "character_id_1": {
                                "primary_target": "character_id_2",
                                "target_zone": "画面右侧近处"
                            }
                        }
                    },
                    "shot_delta": [
                        "本镜头只允许发生的变化1",
                        "其他空间关系、姿态、道具状态保持不变"
                    ],
                    "motion_control": {
                        "subject_facing": "away_from_camera",
                        "camera_relation": "rear_three_quarter",
                        "movement_direction": "upstairs",
                        "screen_trajectory": "lower_right_to_upper_left",
                        "target": "cave_entrance",
                        "distance_to_target": "getting_closer",
                        "phase_beats": ["at foot of stairs", "ascending halfway", "approaching cave entrance"]
                    },
                    "transition_in": {"type": "cross-dissolve", "duration": 0.5},
                    "consistency_anchors": {
                        "characters": [
                            {"id": "character_id", "must_show": ["特征1", "特征2", "特征3"], "expression": "情绪"}
                        ],
                        "environment": ["环境元素1", "环境元素2"]
                    }
                }
            ]
        }
    ]
}
```

### 4.x 当前分镜额外约束

- `shot_type` 必须当成生成策略字段，而不是普通标签
- `offscreen_reaction` shot 必须明确写清楚谁不能露出
- `transition_reveal` shot 必须明确写清楚 reveal 前后谁要保持连续
- 对多人强交互镜头，`action_prompt` 和 `motion_control.phase_beats` 必须能表达一条连续动作链
- 如果这是同一时刻的连续切镜，要用 `chain_from_previous=true`
- 如果只是同场景连续动作但不是同一视觉时刻，不要滥用 `chain_from_previous`
- 如果下一镜 `chain_from_previous=true`，当前镜必须产出可复用的结束状态，通常应强制尾帧
- `keyframes` 是否需要，不由“有没有这个字段”决定，而由该 shot 的语义复杂度决定；字段只是承载决策结果

### 4.4 director_plan（强导演模式启用）

`director_plan` 是复杂 shot 的导演设计层。它首先服务于母模型，不直接给最终用户看；但它决定后续 `scene_prompt / action_prompt / end_frame_description / keyframes` 应该怎么写。

不要对所有 shot 一刀切。普通镜头可以轻量写法；只有高风险、高阶段依赖镜头才强制进入这层。

核心规则：

1. 一个 shot 只完成**一个戏剧动作**
2. `director_plan.nodes` 是这个 shot 的**阶段节点**
3. 第一个节点 = 首帧锚点
4. 最后一个节点 = 尾帧锚点
5. 中间节点 = `keyframes`

也就是说：
- 不先想“要几张 keyframe”
- 先想“这个 shot 必须经过几个阶段”
- 如果阶段太多、跳变太大，不是继续加 keyframe，而是这个 shot 应该拆开

最小判断顺序：

1. `dramatic_core`：这镜只完成什么
2. `not_this_shot`：这镜明确不完成什么
3. `viewer_information_flow`：观众先知道什么、再知道什么、最后落在哪里
4. `nodes`：把这个过程拆成 2-4 个必要阶段
5. 再把这些阶段投影成现有字段

投影规则：
- `scene_prompt` = `nodes[0]` 的故事起点和起始画面状态
- `end_frame_description` = `nodes[-1]` 的明确结果落点
- `keyframes` = `nodes[1:-1]`
- `action_prompt` = 各节点之间的过渡过程
- `continuity_mode` = 最终落点约束强度
- 建议每个 shot 显式产出 `video_references`，把“语义决策层”真正落到用途协议
- 如果镜头内部存在明确阶段推进，建议显式产出 `time_beats`
- 连续场景应优先声明 `scene_continuity`
- 人物支撑姿态不能漂移的镜头应写 `subject_constraints.pose_contract`
- “看向谁”本身是叙事推进关键时，应写 `subject_constraints.gaze_contract`
- 每个 shot 最好显式写出 `shot_delta`，明确“这一镜只改变什么”

### 4.x 当前视频 prompt 的额外 guardrails

当前视频 prompt 应区分两层：

- 审阅层：可保留分镜主旨、策略判断、设计理由，供人审阅
- 模型层：只保留给 Seedance 的可执行视觉描述

所以你在写 `action_prompt`、`time_beats`、`motion_control` 时，不要混入“让观众感受到”“为后面做准备”“执拗感更强”这类抽象导演评论。模型层只写谁在动、动哪里、画面关系如何变化。

### 4.1 场景（Scene）层级 — 视觉基底共享

**同一个场景下的所有镜头共享完全相同的视觉基底**：光线、天气、道具、环境描述。这是风格一致性的核心机制。

**Scene 字段说明：**

| 字段 | 说明 | 示例 |
|------|------|------|
| `id` | 场景唯一ID | `"scene_1"` |
| `name` | 场景名称 | `"断桥雨中"` |
| `location` | 地点 | `"杭州西湖断桥"` |
| `narrative_segment` | 对应 narrative 中的原文段落 | 从 framework scenes 复制 |
| `lighting` | 光线参数（色温、方向、质感，中文） | `"阴天漫射冷光，偏冷蓝灰，约6500K"` |
| `weather` | 天气/粒子效果（中文） | `"细密冷雨，远处湖面薄雾"` |
| `props` | 场景核心道具列表 | `["oil-paper umbrellas", "stone bridge"]` |
| `environment_description` | 环境视觉描述（中文，80-150字） | 地面材质、远景、建筑、水面等完整描述 |

**场景拆分规则：** 场景从 framework.json 的 scenes 继承（已经按 narrative 切分好了）。当以下任一条件变化时，必须拆成新场景：
- 光线发生质变（如从阴雨变为雨霁暖光）
- 天气变化（如雨停）
- 地点变化
- 时间跨度导致视觉环境显著不同

**Shot 的 narrative_segment 切分：** 每个 shot 从所属 scene 的 narrative_segment 中进一步切分出自己负责讲述的那段叙事。所有 shot 的 narrative_segment 合起来必须完整覆盖 scene 的 narrative_segment。

**Shot 在 scene 中只写自己特有的内容：** 动作、姿态、构图、镜头参数。不需要重复环境/光线/天气描述。

### 4.1.1 motion_control（结构控制层，人物运动镜头必填）

`scene_prompt / action_prompt / end_frame_description` 负责讲清楚故事，但它们仍然是自然语言。为了避免“角色朝向错了”“明明是上楼却像下楼”“目标关系不明确”这类系统性问题，人物运动镜头必须额外填写 `motion_control`。

**作用：** 把最容易被模型脑补错的空间关系、运动方向、时间阶段拆成结构化字段，作为 prompt builder 的硬约束输入。

**格式：**

```json
"motion_control": {
  "subject_facing": "away_from_camera",
  "camera_relation": "rear_three_quarter",
  "movement_direction": "upstairs",
  "screen_trajectory": "lower_right_to_upper_left",
  "target": "cave_entrance",
  "distance_to_target": "getting_closer",
  "phase_beats": [
    "at foot of stairs",
    "ascending halfway",
    "approaching cave entrance"
  ]
}
```

**字段说明：**

| 字段 | 作用 | 示例 |
|------|------|------|
| `subject_facing` | 主体身体朝向 | `away_from_camera`, `toward_camera`, `left_profile` |
| `camera_relation` | 镜头相对主体位置 | `rear_three_quarter`, `front_of_subject`, `side_follow_left` |
| `movement_direction` | 主体真实运动方向 | `upstairs`, `downstairs`, `toward_target`, `away_from_target`, `static` |
| `screen_trajectory` | 主体在画面中的轨迹 | `lower_right_to_upper_left`, `left_to_right`, `foreground_to_background` |
| `target` | 当前动作目标点 | `cave_entrance`, `master`, `cliff_edge` |
| `distance_to_target` | 与目标点距离变化 | `getting_closer`, `getting_farther`, `holding_position` |
| `phase_beats` | 时间过程分段 | `["takes first step", "reaches halfway point"]` |

**什么时候必须写：**
- 角色在镜头内发生明确位移
- 上下楼、前进后退、转身、接近目标、远离目标
- 多角色对位、视线交汇、空间调度

**什么时候可以省略：**
- 纯环境空镜
- 几乎静止的情绪特写
- 纯氛围粒子 / 光影镜头

**核心原则：**
- prose 负责“可读性”，`motion_control` 负责“不可误解性”
- 如果 prose 与 `motion_control` 冲突，以 `motion_control` 为准
- `phase_beats` 应该对应动作过程的关键阶段，而不是重复完整句子

### 4.2 分镜拆分原则 — 3x3 法则

先按场景分组（1-3个场景），再在每个场景内按动作拆分镜头。每阶段2-3个镜头：

| 阶段 | 节奏 | 内容 | 镜头数 |
|------|------|------|--------|
| 建立（Phase 1） | 慢 0.7x | 环境建立、角色出场 | 2-3 |
| 冲突（Phase 2） | 快 1.0-1.2x | 核心动作、冲突爆发 | 2-3 |
| 结局（Phase 3） | 慢 0.7x | 情感释放、余韵收尾 | 1-2 |

**硬性约束：**
- **每个镜头只做一个动作** — 一个镜头 = 一个清晰的视觉事件。如果你想写"A攻击B，B反击，然后A找到破绽刺穿B"，这必须拆成3个镜头。
- **每个镜头 5-12 秒** — 但 `estimated_duration` 不应主要按动作复杂度拍脑袋决定，而应按**导演节奏**决定。Seedance API 支持 [5, 12] 区间任意整数。
- **动作路径必须物理合理** — 不能出现角色瞬移、位置互换、违反惯性的运动
- 总镜头数 ≈ total_duration ÷ 平均镜头时长（通常 7-10 秒/镜头）

**导演版时长判断顺序：**
1. 先看这镜有几个必要阶段
2. 再看观众需要多久看清关键信息
3. 再看最终落点是否需要停留半拍到一拍
4. 最后再看它在整段剪辑里是快切镜还是收束镜

**不要再这样想：**
- 轻动作 = 5-6 秒
- 中动作 = 7-9 秒
- 重动作 = 10-12 秒

**要改成这样想：**
- **2 个阶段 + 无明显停留** → 可短
- **3 个阶段 + 需要完成确认 / reveal** → 中等
- **3 个阶段 + 情绪落点需要停留** → 中偏长
- **4 个阶段以上** → 先考虑拆 shot，不先靠拉长时长解决

一句话：
- **estimated_duration = 阶段推进时间 + 落点停留时间**
- 不是 **estimated_duration = 动作复杂度区间**

### 4.3 characters_in_shot（必填）

**每个 shot 必须标注该镜头出现的角色列表。** 这决定了生成图片时传哪些角色参考图给图片模型。

规则：
- 只列出画面中**实际可见**的角色
- 如果镜头只有环境/道具没有角色，留空 `[]`
- 如果角色还是非人形态（如：卡车还没变形成机器人），不要列角色ID

### 4.4 单一真相源原则（Single Source of Truth）

**角色外貌只在一个地方定义：`storyboard.characters`（从 framework.json 复制）。分镜中禁止重复描述角色外貌。**

这是一致性的核心原则。代码会自动从 `storyboard.characters` 读取角色外貌，注入到图片和视频 prompt 中。如果你在 `scene_prompt` 里写了外貌，代码的 ContentFilter 会主动过滤掉。

**黄金规则：**
1. **只能引用，不能创造** — `scene_prompt` 只写动作、姿态、场景环境，不写角色外貌/服装
2. **分镜只写动作，不写外观** — ✅ "The robot stands at the intersection" ❌ "The blue-red robot with riveted armor stands..."
3. **角色外观以 `storyboard.characters` 为准** — 代码自动注入，不需要你重复

| 允许写在 scene_prompt | 禁止写在 scene_prompt |
|---------------------|---------------------|
| 角色动作/姿态 | 角色外貌/身高/体型 |
| 场景环境 | 服装/装甲描述 |
| 镜头参数/构图 | 颜色方案/材质纹理 |
| 光影/氛围 | 配饰/武器外观 |

### 4.5 scene_prompt 规则（这个镜头要讲什么 + 开始时的画面状态）

**scene_prompt = 第一个阶段的故事起点 + 视觉起始状态。** 环境/光线/天气不需要写——由 scene 层自动提供。默认用中文撰写。

**⚠️ 核心原则：scene_prompt 不是孤立的画面描述，它是故事线上的一个锚点。**

代码会用 scene_prompt + action_prompt + end_frame_description 三个字段的完整故事上下文，通过 LLM 提取出精确的首帧生图 prompt。所以 scene_prompt 的职责是**讲清楚这个镜头第一个阶段的故事起点**：

1. **第一阶段的任务** — 这个阶段只负责建立什么
2. **视觉起始状态** — 角色的精确位置、姿态、朝向、手持物品、空间关系
3. **构图** — 景别、角度

不要把整个 shot 的发展过程塞进 `scene_prompt`。如果写成“这镜发生什么”，首帧就会偷跑到中段甚至终点。

**三段式故事线：**
| 字段 | 故事作用 | 描述什么 |
|------|----------|----------|
| `scene_prompt` | 第一个阶段 | 动作即将开始时的画面状态 |
| `action_prompt` | 阶段过渡 | 阶段1如何到阶段2，阶段2如何到阶段3 |
| `end_frame_description` | 最后一个阶段 | 动作完成后的明确落点 |

三个字段构成一条**因果链**。代码会将完整因果链交给 LLM 提取起始画面 / 目标状态 / 动作描述，使参考素材和视频 prompt 都天然带有故事方向。

**示例 — 递伞场景：**
- ❌ "女人把伞递向书生"（纯动作，无故事上下文，无起始状态）
- ✅ "她看见书生浑身湿透地蜷在亭边石阶上，终于压不住心里的怜悯，已经从桥上走到亭边。她手里稳稳举着油纸伞，低头看向仍在喘息的书生，脚边散着被雨打湿的纸页。她正准备再向前一步，把伞递过去。平视中景双人镜头，两人相距约两米。"（有意图、有状态、有空间关系）

**示例 — 战斗场景：**
- ❌ "机器人挥刀砍向敌人"（动作正在发生，无故事起点）
- ✅ "它已经看准敌方防线的破口，身体在路口中央稳稳锁定目标，刀臂高高抬起，蓄势待发，正处于致命一击落下前的那一瞬。低机位英雄式构图。"（有意图、有蓄力姿态、有叙事张力）

**禁止：**
- 写角色外貌/服装（单一真相源原则，代码自动注入）
- 用列表/分点格式（必须自然语言流）
- 只写静态画面没有故事上下文（"A woman stands on a bridge" → 缺少为什么站在这里、要做什么）

### 4.5.1 scene_prompt 完整性检查清单

写完每个 scene_prompt 后，心中过一遍以下清单。不需要所有项都写进 prompt，但如果某项在逻辑上应该存在却没有提及，**必须补上**：

| 维度 | 检查项 | 示例 |
|------|--------|------|
| 角色状态 | 姿态/朝向/手持物品/身体状态 | "holding oil-paper umbrella", "wet hair clinging to forehead" |
| 环境交互 | 天气/场景对角色和物体的影响 | 雨→湿发/水面反光/撑伞, 战斗→地面碎裂/烟尘 |
| 光影逻辑 | 光源方向与阴影是否匹配 | 夕阳从右侧→左侧阴影, 火焰→暖色环境光 |
| 道具/载具 | 场景中应该存在的物品 | 油纸伞、书箱、翻倒的椅子、破碎的窗户 |
| 天气/粒子 | 大气效果 | 雨丝、雪花、灰尘、火星、雾气、水面涟漪 |

**核心原则：推导隐含的视觉细节，明确写出来。** 图片模型不会自动推理"下雨所以应该撑伞"——你必须写出来。同理，end_frame_description 也需要过这个检查清单。

### 4.6 end_frame_description 规则（故事发展到哪了 + 结束时的画面状态）

**每个 shot 都必须有 end_frame_description，包括最后一个。** 没有尾帧约束，Seedance 就不知道动作终点在哪里，会产生随机漫游的画面。

**⚠️ end_frame_description 不是孤立的画面描述，它是故事线上的终点锚点。**

代码会用完整故事上下文（scene_prompt + action_prompt + end_frame_description）通过 LLM 提取出精确的尾帧生图 prompt。所以 end_frame_description 的职责是**讲清楚这个镜头最后一个阶段的明确落点**：

1. **结果状态** — 动作完成后，故事推进到了什么状态
2. **视觉结束状态** — 角色的精确位置、姿态、表情、手持物品
3. **下镜接口**（如需要）— 为下一镜头的视觉衔接留下稳定落点

不要把“也许会发生什么”写进 `end_frame_description`。它负责的是最后一个阶段已经到达的状态，不是泛泛的结尾气氛。

**示例 — 递伞场景：**
- ❌ "Close-up of two hands on the umbrella handle."（纯画面描述，无故事进展）
- ✅ "The scholar has gratefully accepted the umbrella — close-up of their hands meeting on the bamboo handle, her delicate fingers and his ink-stained hand. Both frozen in the unexpected intimacy of the moment. The rain has softened slightly, and through a thin gap in the clouds a faint warm light begins to appear on the umbrella canopy."（有故事进展、有情感、有过渡暗示）

**规则：**
- 描述本镜头动作完成后的**故事状态和画面状态**
- **同样遵守单一真相源**：只写姿态/场景，不写角色外貌（代码自动注入）
- 最后一个 shot 的 end_frame_description 描述最终定格画面
- **同一个 shot 内的动作推进必须单向、物理合理**：不能方向矛盾、不能景别跳变、位移量必须在 estimated_duration 内物理可完成

### 4.6.1 单向运动法则（用途驱动协议下的物理约束）

**这是最容易犯错的规则。** 现在的执行层虽然不再把视频主脑理解成“旧的时间点硬插值协议”，但模型仍然会同时参考：

- 起始画面
- 中间阶段参考（如果存在）
- 目标状态参考（如果存在）
- 动作描述与时间节拍

如果这些输入在动作方向上互相矛盾，模型一样会被迫在有限时长内强行对齐，产生不自然的一刷、瞬移或突兀转向。

**核心法则：一个 shot 内只能有一个主运动方向。**

| 维度 | 允许 | 禁止 |
|------|------|------|
| 朝向 | 正面保持正面 | 正面 → 背面 → 又正面 |
| 朝向 | 正面 → 背面（一次明确转向） | 背面 → 正面 → 又背面 |
| 位移 | 从 A 走到 B（单向） | 从 A 走到 B 再走回 A |
| 景别 | 中景推到近景（单向） | 近景→远景→近景 |
| 姿态 | 站立→坐下（单向） | 站→坐→站 |

**常见错误场景：**

❌ 错误："角色走向亭子（背影），到达后转身面对镜头微笑"
- 动作本体里包含了背向离开和重新正面面对镜头两种相反方向
- 即使只给起始画面和目标状态，或者再加中间参考，模型都会被迫在一个 shot 里完成折返

✅ 正确拆法：
- Shot A：角色转身走向亭子（起始画面为正面，结果状态为到达亭子时的背影）
- Shot B：角色在亭子里转身面对镜头（起始画面承接背影，结果状态才是正面微笑）

**自检方法：** 写完每个 shot 后，不要只问“有没有首尾画面”，而要问：

- `scene_prompt`
- `time_beats`
- `keyframes`（如果有）
- `end_frame_description`
- `video_references`

它们共同描述的动作路径是否需要“折返”。如果需要折返，就必须拆成两个 shot。

**特别注意目标状态参考 shot：** 当 `continuity_mode` 为 `"strict"` 或该 shot 是 scene 末尾时，执行层通常会为这个镜头准备 `reference_target_state`。如果动作本身存在方向矛盾，模型会被强行拉向错误落点，问题会更严重。

### 4.6.2 chain_from_previous（链式衔接，默认 false）

**默认每个 shot 独立生成首帧。** 只有当你确定相邻两个 shot 满足以下**全部条件**时，才在后一个 shot 设置 `"chain_from_previous": true`：

1. **角色完全相同** — 前后 shot 的 `characters_in_shot` 一致
2. **景别相近** — 不能从特写跳到全景，或反过来
3. **场景/环境连续** — 同一地点、同一光线条件、动作连贯
4. **前一 shot 的 end_frame_description 在视觉上适合作为后一 shot 的开始画面**

当 `chain_from_previous: true` 时，代码会从前一 shot 的实际视频中提取最后一帧作为本 shot 的首帧，不再独立生图。这能保证两个 shot 之间视觉连续。

**链式与转场的配合：**
- `chain_from_previous: true` + `transition_in: null` → 纯链式（连续动作，无转场）
- `chain_from_previous: true` + `transition_in: {"type": "cross-dissolve", "duration": 0.3}` → 链式+溶解兜底（景别微调时）
- `chain_from_previous: false` + `transition_in: {...}` → 独立生成首帧 + 剪辑转场掩盖视觉跳变

**链式生成规则：** 同场景内**默认独立生成首帧**（`chain_from_previous: false`）。只有当相邻 shot 满足上述全部条件时，才显式设置 `chain_from_previous: true`。以下情况**必须断链**：
1. **跨场景** — 地点/环境变化
2. **闪回进入/退出** — 即使叙事上是同一角色的记忆
3. **时间跳跃导致光线质变** — 如雨天→雨停暖光
4. **反打/视角大跳** — 角度差异太大无法链式

**目标状态参考策略（由 `continuity_mode` 控制）：**

每个 shot 必须标注 `continuity_mode`，但这个字段应当是语义决策的结果，不是随手填写。代码根据它决定是否生成尾帧图。

| 模式 | 含义 | 代码行为 |
|------|------|---------|
| `"strict"` | 关键镜头，必须明确结果落点 | 生成目标状态参考图，强约束收束方向 |
| `"scene_end"` | 普通镜头（默认） | 仅当该 shot 是 scene 最后一个时生成目标状态参考图 |
| `"free"` | 氛围/空镜/过场 | 不生成目标状态参考图，Seedance 自由运动 |

**你应该标 `"strict"` 的情况（必须有目标状态参考图锚定落点）：**
1. **情绪转折点** — 角色表情/情感状态发生关键变化的镜头（如从平静到震惊、从犹豫到坚定）
2. **角色状态大变化** — 姿态/位置/持有物品发生重要改变（如拿起关键道具、倒下、起身）
3. **关键动作落点** — 叙事上必须精确到达某个视觉状态的镜头（如递伞完成、门被推开、角色相遇）
4. **scene 最后一个 shot** — 段落收口，确保命中目标画面（这种情况 `"scene_end"` 也会生成，但如果你认为这个收口特别重要，用 `"strict"` 更明确）
5. **下一个 shot 是 `chain_from_previous: true`** — 如果下一个 shot 要从本 shot 视频提取尾帧作为首帧，本 shot 的结果状态就必须准确

**你应该标 `"scene_end"`（默认）的情况：**
- 普通叙事推进，动作方向明确，不需要精确锚定终点
- scene 内中间镜头，动作自然过渡即可

**你应该标 `"free"` 的情况：**
- 纯环境空镜（云、水、风景）
- 氛围渲染段落（慢动作粒子、光影变化）
- 不包含角色的过场镜头

**典型 scene 内标注示例（3 个 shot）：**
- Shot 1（角色抬头看向门口）：`"continuity_mode": "scene_end"` — 普通过渡
- Shot 2（角色快步走向门口，到达门前）：`"continuity_mode": "strict"` — 关键位移落点，下一 shot chain 依赖本帧
- Shot 3（角色推门出去，光线涌入）：`"continuity_mode": "scene_end"` — scene 末尾，默认也会生成目标状态参考

### 4.6.3 keyframes（中间关键帧，可选）

先说新的主原则：

- Seedance 2.0 的多参考图主脑不是“旧的时间点插值协议”
- 执行层现在会把所有视频参考素材统一归一成 `video_references`
- 每张参考图都必须先定义**用途**，再在视频 prompt 里用 `@图片N` 显式调用
- `keyframes` 仍保留，但它现在是“中间阶段参考”的兼容写法，不再代表模型必须命中的时间点

`keyframes` 是否需要，先看导演阶段，不先看字段。

核心规则：
- `keyframes` 不是“中间插几张图”
- `keyframes` 是这个 shot **不能省略的中间阶段**
- 第一个阶段属于 `scene_prompt`
- 最后一个阶段属于 `end_frame_description`
- 只有中间阶段才写成 `keyframes`

也就是说：
- `director_plan.nodes = [起点, 中段1, 中段2, 终点]`
- 那么 `keyframes = [中段1, 中段2]`

只有当以下至少一类需求成立时，才应该写 `keyframes`：

- 动作复杂度中到高
- 状态变化幅度中到高
- 中间过程比终点本身更重要
- 首尾两点不足以稳定约束镜头过程

`continuity_mode: "strict"` 往往会和 `keyframes` 同时出现，但不是唯一前提。

如果一个镜头内有多个不能省略的中间阶段，不是简单的首帧 A → 尾帧 B 单向运动，可以标注中间关键帧：

```json
"keyframes": [
  {"timestamp": 3.0, "description": "角色转身面对镜头，表情从平静变为惊讶"},
  {"timestamp": 6.0, "description": "角色举起手中的伞，伞面完全展开"}
]
```

**规则：**
- `timestamp` 必须在 `0` 到 `estimated_duration` 之间，且按时间递增
- `description` 遵守单一真相源：写姿态、位置、动作、场景，不写外貌
- `keyframes` 是 storyboard 的兼容字段，用来表达“镜头中间关键状态”
- 当前执行层会把它们映射成 `video_references[].usage = "reference_stage"`
- Seedance 2.0 看到的是“用途驱动参考素材 + @引用调用”，不是旧式时间点插值协议
- 其他只支持有限参考图的视频模型会按用途优先级裁剪参考素材

### 4.6.3.0 video_references（视频参考素材协议，推荐）

从现在开始，写 storyboard 时推荐把视频参考素材显式写成 `video_references`。

推荐用途：
- `first_frame`
- `reference_character`
- `reference_prop`
- `reference_composition`
- `reference_style`
- `reference_color`
- `reference_stage`
- `reference_target_state`

执行层会优先读取 `video_references`；如果没写，才会从首帧 / 角色参考图 / 场景图 / `keyframes` / 尾帧自动合成一份用途驱动参考清单。

示例：

```json
"video_references": [
  {"source_type": "frame", "source_id": "first_frame", "usage": "first_frame"},
  {"source_type": "scene", "usage": "reference_composition"},
  {"source_type": "character", "source_id": "medical_girl", "usage": "reference_character", "subject": "medical_girl"},
  {"source_type": "prop", "source_id": "oil_paper_umbrella", "usage": "reference_prop", "subject": "oil_paper_umbrella"},
  {"source_type": "stage", "source_id": "1", "usage": "reference_stage", "stage": "confirm_source"},
  {"source_type": "frame", "source_id": "target_state", "usage": "reference_target_state"}
]
```

语义原则：
- 先定义“这张图拿来干什么”
- 再让执行层在视频 prompt 中写成 `@图片1 作为首帧`、`@图片2 参考角色` 这类显式调用
- 不再把所有图片都当成同权重的 `reference_image`

**何时标注 `keyframes`：**
- 镜头内有明确的阶段性姿态变化（如：蹲下 → 起身 → 转身）
- 需要精确控制中间某个时刻的画面状态
- 动作路径复杂，起始画面与目标状态不足以约束
- 情绪或关系变化需要经过明确的多个状态节点

**何时应该拆 shot，而不是继续加 `keyframes`：**
- 中间阶段已经超过 4 个
- 相邻阶段之间不止一个主变化
- 人物状态、镜头尺度、空间关系同时大跳
- 某个中间阶段已经像“另一镜”而不是“同一镜中的一步”

**何时不要标注：**
- 简单的 A → B 运动（走、转头、伸手）
- 连续流畅的动作（跑步、挥手）
- 大部分普通推进镜头
- `offscreen_reaction` / 纯氛围镜头 / 不应出现中间实体 reveal 的镜头

### 4.6.3.1 time_beats（时间节拍，可选但重要）

`time_beats` 用来表达镜头内部的时间推进，主要服务于最终给 Seedance 的视频 prompt。

推荐格式：

```json
"time_beats": [
  "0-2s：固定近景，主体保持当前状态",
  "2-4s：出现第一段明确动作或状态变化",
  "4-6s：镜头落到最终结果状态"
]
```

规则：
- `time_beats` 是给模型看的时间脚本，必须写成可执行的视觉描述
- 不要写“让观众感到”“情绪更强”“为后面做准备”这类抽象导演语言
- 要写清楚谁在动、动哪里、画面位置是否变化、物件是否变化
- `time_beats` 可以存在，但不一定生成 `keyframes`
- 如果某一段视觉差异不足，就只保留为时间节拍，不要硬升成关键帧

#### 4.6.3.2 参考图分配规则（最多 9 张）

当当前视频模型支持多参考图时，代码会先组装 `video_references`，再按用途优先级裁剪。

**默认自动组装来源：**
1. **首帧图** → `first_frame`
2. **scene 图** → `reference_composition`
3. **角色参考图** → `reference_character`
4. **道具参考图** → `reference_prop`
5. **中间阶段图**（通常来自 `keyframes`）→ `reference_stage`
6. **目标状态图**（根据 `continuity_mode` 决定是否存在）→ `reference_target_state`

**代码行为：**
- 代码会根据当前视频模型的 `max_reference_images` 自动计算可用槽位
- 如果参考素材超限，不再按“首帧 / keyframes / 尾帧”裁，而是按用途优先级裁
- 当前优先级大致是：
  1. `first_frame`
  2. `reference_character`
  3. `reference_prop`
  4. `reference_composition`
  5. `reference_style`
  6. `reference_color`
  7. `reference_target_state`
  8. `reference_stage`
- 因此，真正容易被裁掉的通常是中间阶段参考，而不是首帧、角色或关键道具

**写 storyboard 时的指导：**
- 真正关键的参考素材，优先显式写进 `video_references`
- `keyframes` 只用来表达“中间阶段确实重要”，不要再把它当唯一的中段控制入口
- 如果某个中间状态没有独立用途，就不要为了“多给一张图”而写进去

**母模型在写 storyboard 时的实际判断顺序应该是：**

1. 先看这个 shot 的 narrative 任务是什么
2. 再按 4 个语义维度判断参考图策略
3. 再决定是否需要 `chain_from_previous`
4. 产出 `video_references`
5. 如有需要，再补 `reference_strategy`
6. 判断是否需要 `time_beats`
7. 最后输出 `shot_type`、`continuity_mode`、`keyframes`

### 4.6.3.3 `reference_strategy`（兼容审计字段，不推荐作为主产物）

如果你确实需要把“语义判断过程”额外暴露出来，可以保留一个 `reference_strategy` 字段做复盘或审计。

但从现在开始，它**不是推荐默认产出的主字段**，更不是视频参考协议入口。

执行层真正优先读取的是：
- `video_references`
- `shot_type`
- `continuity_mode`
- `keyframes`
- `chain_from_previous`

因此从现在开始，写 storyboard 时应优先先产出 `video_references`，只有在你明确需要保留“策略判断痕迹”时，才额外显式写 `reference_strategy`。

**示例 — 适合链式：**
- Shot 3: 两人对话中景 → Shot 4: 同一场景两人继续对话，镜头略推近

**示例 — 不适合链式（必须 false）：**
- Shot 1（白素贞+小青）→ Shot 2（许仙独自）：角色不同
- Shot 3（递伞手部特写）→ Shot 4（断桥全景）：景别跳转
- Shot 2（雨天亭下）→ Shot 3（雨中桥上）：场景变化

### 4.6.4 强动作镜头规则（必须当成导演调度稿来写）

**这是当前视频生成里最容易写差、也最影响模型表现的一类 shot。**  
如果镜头包含扑击、打斗、摔倒、脱手、追逐、爆发式转身、强烈肢体冲突，不能只写一句“事件摘要”，必须把它写成一个有节拍的镜头内动作设计。

**先判断这是不是强动作 shot：**
- 镜头内存在明显的爆发动作，而不是平缓位移
- 至少两个主体之间存在高强度交互或对抗
- 结果姿态与起始姿态差异很大
- 如果只给起始画面和目标状态，模型大概率会用平滑补间糊过去

**一旦属于强动作 shot，必须同时满足以下规则：**
1. `continuity_mode` 不能写 `"free"`，默认写 `"strict"`
2. `action_prompt` 不能只写一句结果摘要，必须体现镜头内的节奏变化
3. `motion_control.phase_beats` 不能少于 3 段
4. `keyframes` 不应只有 0 张；原则上至少 2 段关键状态
5. `scene_prompt` / `action_prompt` / `end_frame_description` 必须能对应到同一条动作链，不能只写开头和结尾

**强动作 shot 的最小结构：**
- 起势：谁先动，如何蓄力，危险从哪里来
- 爆发：碰撞、闪躲、扑击、失衡、脱手、翻滚等关键事件
- 落点：这一个 shot 结束时，角色和道具落到什么状态

**强动作 shot 不要这样写：**
- “老虎扑向武松，武松闪开，木棒脱手。”
- 这是剧情摘要，不是镜头调度。模型只会把它理解成一条模糊的 A→B 变化。

**强动作 shot 应该这样写：**
- `scene_prompt` 交代爆发前 0.5-1 秒的紧绷状态
- `action_prompt` 写出动作的节奏曲线：突然爆发、瞬间失衡、短促碰撞、结果落点
- `end_frame_description` 只负责定义这一 shot 必须精确到达的最终状态
- `motion_control.phase_beats` 至少拆成：
  - 起势
  - 爆发/碰撞
  - 结果姿态

**`keyframes` 规则要升级：**
- 普通叙事镜头：`keyframes` 可以没有或只有 1 张
- 强动作镜头：默认至少 2 张，分别锚定“爆发中段”和“结果前一拍”
- 如果镜头里还有明显的武器脱手、主体位置互换、压制关系反转，应该继续增加关键帧，而不是让模型自行脑补

**写 `action_prompt` 时必须补足这些维度：**
- 爆发方式：sudden / explosive / violent / abrupt，而不是 only “moves”
- 力度变化：猛扑、急闪、撞击、失衡、翻滚、压制
- 节奏变化：先静后爆、短促碰撞、落地后的余势
- 镜头关系：镜头是稳跟、被动作带动，还是保持观察位

**判断写得够不够的自检标准：**
- 如果把 `action_prompt` 拿掉，只剩起始画面和目标状态，模型会不会变成慢吞吞的补间？
- 如果答案是“会”，那这个强动作 shot 写得还不够。

**示例 — 武松打虎 shot 3：**
- 不够好的写法：
  - “The tiger lunges in a single explosive pounce while Wu Song dodges, swings the staff, and loses his weapon in the collision.”
- 更符合强动作规则的写法：
  - 起势：虎伏身蓄力，武松刚意识到扑击方向
  - 爆发：老虎突然前扑，武松侧闪半步并本能横棒格挡，冲击把木棒震飞
  - 落点：虎身擦落前景，木棒旋出画面，武松重心下坠进入失衡后的防守姿态

**一句话原则：**
- 普通 shot 可以写“发生了什么”
- 强动作 shot 必须写“这个镜头内部是怎么打起来的”

### 4.6.5 transition_in（剪辑转场）

**每个 shot 必须标注 `transition_in`**，描述从前一个 shot 过渡到本 shot 时使用的剪辑转场效果。第一个 shot 的 transition_in 为 `null`。

**格式：** `{"type": "类型", "duration": 秒数}` 或 `null`

| 转场类型 | 适用场景 | 时长 | FFmpeg 实现 |
|---------|---------|------|------------|
| `"straight-cut"` | 叙事"突然"感（听到脚步声）、反打对望 | 0s | 硬切拼接 |
| `"cross-dissolve"` | 同场景视角切换、景别跳变、空镜→人物 | 0.3-1.0s | xfade fade |
| `"flash-white"` | 闪回进入/退出——模拟记忆闪现 | 0.3s | xfade fadewhite |

**选择逻辑：**
- 景别/视角大跳 + 同场景 → `cross-dissolve`
- 景别/视角大跳 + 跨场景 → `flash-white` 或 `straight-cut`（看叙事意图）
- 连续动作 + 同角色 + `chain_from_previous: true` → `null`（链式无需转场）
- 连续动作但景别微调 → `cross-dissolve` 0.3s（兜底）
- 反打对望 → `straight-cut`（保留张力）
- 从特写到大全景 → `cross-dissolve` 1.0s（呼应节奏放慢）
- 闪回进入/退出 → `flash-white` 0.3s

**示例（白娘子断桥）：**
```
Shot 1→2: flash-white 0.3s（进入闪回）
Shot 2→3: flash-white 0.3s（退出闪回）
Shot 3→4: straight-cut（"忽然"脚步声）
Shot 4→5: cross-dissolve 0.5s（同场景视角切换）
Shot 5→6: cross-dissolve 0.3s（链式+溶解兜底）
Shot 6→7: null（链式连续递伞动作）
Shot 7→8: cross-dissolve 1.0s（特写→大全景）
Shot 8→9: cross-dissolve 0.5s（空镜→人物）
Shot 9→10: straight-cut（反打对望）
```

### 4.7 action_prompt 规则（只写视觉动作！）

**首帧图已经确定了画面，action_prompt 只描述阶段之间的运动/动作变化。** 角色外貌由代码通过结构化 prompt 自动注入到视频生成请求中，此字段**禁止任何外貌描述**。

关键原则 — **"首帧决定视觉"：**
- 首帧图锚定了角色外貌、场景环境、光影氛围
- action_prompt 只需要告诉 Seedance "各阶段之间发生了什么过渡"
- 不要再把 `action_prompt` 写成剧情摘要
- 推荐按顺序写：阶段1如何到阶段2，阶段2如何到阶段3
- 描述的动作必须在该镜头的 estimated_duration 内可完成
- 代码会自动构建结构化视频 prompt：`【角色外观设定】+ 【场景动作】+ 【一致性要素】`

**示例：**
- ❌ "The 40-foot tall blue-red robot warrior with riveted armor in the destroyed city at sunset slowly pulls its blade..." （重复了外貌和场景）
- ✅ "The robot slowly pulls its energy blade out of the enemy's chest with a grinding metal sound, the enemy's optics flash once then go permanently dark, the enemy's body tips backward and crashes onto the asphalt sending dust upward"

**强动作镜头额外要求：**
- 不能只写“谁打了谁”，必须写出动作的节拍和冲击链
- 优先使用能表达节奏的动词：`surges`, `snaps`, `slams`, `jerks`, `whips`, `bursts`, `crashes`, `stumbles`, `locks`
- 避免只用平缓词：`moves`, `goes`, `turns`, `changes position`
- 如果镜头内有明显的先静后爆、碰撞后失衡、武器脱手、角色压制关系变化，必须写进 `action_prompt`

**错误示例（强动作 shot）：**
- “He fights the tiger and gains the upper hand.”
- “The tiger attacks and Wu Song responds.”

**正确方向（强动作 shot）：**
- “The tiger explodes forward from a low crouch; Wu Song snaps sideways into a hurried dodge, catches the pounce on the staff for a fraction of a beat, and the impact jerks the weapon out of his hands.”
- “Wu Song surges chest-first into the tiger, collides hard at shoulder level, both bodies tumble through leaves and rock dust, and he fights to climb into partial top control.”

### 4.8 PONYO 6D 物理系统

光线和天气/物理效果已移至 scene 层级（`lighting` 和 `weather` 字段），同场景所有镜头共享。Shot 层保留镜头相关的参数：

| 字段 | 层级 | 说明 | 必填 |
|------|------|------|------|
| `lighting` | **Scene** | 光源方向+色温+光比+布光风格 | 必填 |
| `weather` | **Scene** | 天气/粒子/物理细节（至少3个要素） | 必填 |
| `camera_movement` | Shot | 运镜方式（tracking/dolly/crane/static/handheld） | 必填 |
| `camera_technical` | Shot | 焦距+光圈（如 "50mm, f/2.8"） | 必填 |
| `speed_baseline` | Shot | 动作速度（慢镜头0.7x / 正常1.0x / 快节奏1.3x） | 必填 |

**weather 三要素（写在 scene 层）：**
1. **粒子材质** — 雨丝、雪花、火花、碎片、灰尘、液体
2. **物理交互** — 碰撞、重力、水面涟漪、

…(truncated)
