Claude Skill

short-drama-assets

从短剧剧本拆出人物/造型、地点/视图、道具/状态和跨场连续性,写成创作者可读的视觉设定。用户说“拆角色/场景/道具”“做资产设定”“判断复用还是新变体”“更新造型/道具状态”,或拿现成剧本直接做视觉资产准备时使用;不写图片提示词,不生成媒体。

LLM Mart · 0 points · 11 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download zenstory-ai-drama-skills-skills_short-drama-assets-9e78a57.zip · 54 KB
Part of zenstory-ai/drama-skills — 11 skills

Install

skills CLI npx skills add https://github.com/zenstory-ai/drama-skills/tree/main/skills/short-drama-assets
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install zenstory-ai-drama-skills@llmmart
Git git clone https://github.com/zenstory-ai/drama-skills.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole zenstory-ai/drama-skills collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

短剧资产拆解

把剧本文字变成可复用、可持续的视觉事实,统一写入 剧集/<EP>/视觉设定.md。不要把每次出场都 变成新资产,也不要抢写剧本语义、镜头走位或生成提示词。

Quick Start

用 $short-drama-assets 从 EP001 剧本拆完人物、造型、地点、道具和本集状态

入口与边界

现成剧本可以直接拆资产。先区分:

  • 身份:换一个就不再是同一个人、地或物;
  • 变体:身份不变,但服装、伤势、时段、天气、开合或持有状态改变;
  • 镜头瞬态:姿势、视线、左右手、站位和相机角度,由分镜拥有;
  • 故事语义:知识、目标、关系和情绪由写作/开发拥有,只引用可见后果。

工作流

  1. 只读当前范围的剧本和已决定的视觉方向。
  2. 逐场找实体、可见描述、状态变化和含混指代,先不创建资产。
  3. 对每项决定新建、复用、增加变体或标记未决;含混指代不猜。
  4. 写最小可识别设定:统一使用 识别锚点、本集状态和变化原因;会持续限制后续动作的身体—物件—空间关系写进有生效范围的状态,不当成一次姿势略过。
  5. 检查造型/伤势、持物/所有权、道具状态、地点时段/天气/光态与跨集 outgoing。
  6. 给跨镜出现且观众看得出不一致的可见事实写一条连续性锁,其余保持识别锚点即可。
  7. 项目提示词语言不是中文时,给每个人物条目写一行 画面代称:<正文里的拼写>;正文从不点名它就写 画面代称:无。地点和道具按需。
  8. 整集请求自动处理完整集,末端一次回报关键身份决定和未决指代。

内容要求

  • 每项先写什么不变使它仍是同一资产,再写本集变化。
  • 识别锚点必须可见、可生成、可比较,不用空泛质量词替代事实。
  • 服装、伤势、湿污、道具开合和光态通常是变体,不是新身份。
  • 不复制剧情摘要,只保留下游生成、分镜或连续性真正需要的事实。
  • 文字、标识和敏感信息遵守项目政策,不擅自补造品牌或私密信息。
  • 多集状态写清生效与持续范围;镜头内瞬态留给分镜。
  • 已确认且持续限制动作空间的关系(身体依托、固定、负重、占位或与功能物件的接触)不是普通镜头姿势:可独立识别的物件仍建资产,关系写进状态并交给分镜逐镜投影;只有剧本明确改变时才结束。
  • 连续性锁只锁不随剧情改变的可见事实;锁面用提示词语言写成最小名词短语,会变的部分留给状态。
  • 「画面代称」把提示词正文里的名字对回本条目:分镜的机械核对靠它判断关键帧里点名的人是谁。条目自身的中文名称 始终算一个代称,所以中文正文通常不必写这一行;提示词正文语言不是中文时,人物条目必须给出结论——写出正文使用的 拼写,或在正文从不点名它时写 画面代称:无。同一个名字全集只用一个拼写,大小写也一致(AST-10 的同一条要求跨语言同样成立); 多个写法用 、 分隔。名字与常用词同形(道具「手机」 撞上「手机店」)时也写 画面代称:无,让核对不按名字找它。

按需知识

默认只读本 SKILL、当前剧本和视觉方向。遇到对应问题时只打开一份:

完成

所有明确出场都已复用、新建、变体化或标为真实未决;需要保持的视觉事实与状态变化已写入唯一 视觉设定文档,即完成。图片提示词、分镜、审查和生产不自动开始。 五份创作文档齐备后,可转 $short-drama 对跨文档结构做一次机械核对;内容质量仍由创作者审查。

安装维护

只有安装、升级或排障时运行 python3 scripts/selftest.py。

Files (drama-skills)
  • agents
    • openai.yaml 282 B
      interface:
        display_name: "短剧资产拆解"
        short_description: "从剧本拆解角色场景道具、身份变体、状态变化与跨场连续性"
        default_prompt: "使用 $short-drama-assets 从已接受剧本拆解并核对角色、场景、道具、变体和连续性。"
      
  • assets
    • character-look.example.jsonl 2.8 KB · in bundle
    • continuity.example.jsonl 3.4 KB · in bundle
    • decisions.example.jsonl 4 KB · in bundle
    • location-view.example.jsonl 1.9 KB · in bundle
    • occurrences.example.jsonl 4.4 KB · in bundle
    • prop-state.example.jsonl 1.8 KB · in bundle
  • examples
    • minimal
      • characters.jsonl 652 B · in bundle
      • looks.jsonl 742 B · in bundle
  • references
    • asset-review-checklist.md 4.6 KB
      # 资产拆解审查清单
      
      ## Binding gate (`AST-05`)
      
      资产设定完成前,每个下游引用都要解析到明确的身份条目,且变体必须在引用的剧情范围内有效。
      未解决的出现项继续保持未决;不得为了让提示词或分镜继续工作,猜一个看似合理的 ID。
      
      按“机械事实 → 语义判断 → 创作者接受”顺序检查。Owner 可按 finding 修订;修订动作本身
      不等于审查 verdict。需要 verdict 时另起一次审查动作,自检也可以如实记录有证据支持的结论。
      
      ## A. 机械检查(`structural_invariant`)
      
      - [ ] 每个制作相关的剧本段都有资产处理结果,或明确说明本段没有资产变化。
      - [ ] 每个结果都指向可见场景 ID,并能从当前 `剧本.md` 找到依据。
      - [ ] 来源位置、剧本明示事实、资产判断与连续性变化没有混写。
      - [ ] 每个出现项恰为复用、新变体、新身份或未决之一。
      - [ ] 新变体写清基底、变化、原因、生效位置与结束位置;没有明确终点时标为持续。
      - [ ] ID 唯一;所有已接受 binding 精确解析到 Character+Look、Location+View 或
        Prop+State。
      - [ ] 未决项没有伪造成已确认身份,不能流入图片提示词或分镜。
      - [ ] 每条变化有前态、后态、原因/来源、生效范围和受影响的可见 ID。
      - [ ] linked outgoing/incoming 状态一致,或有明确待处理 reconciliation。
      - [ ] 可读文字政策完整;`exact_readable` 不与 `no_readable_text` 冲突。
      
      机械失败指出具体文件、标题 ID、owner 和修复方向,不回显整段创作文本。
      
      ## B. 语义审查(`reviewed_invariant`)
      
      逐条引用剧本/设定集 证据回答:
      
      - [ ] 含混代词、匿名人、同款多件物没有被猜成某个已有 ID。
      - [ ] Character 身份锚点与服装、伤势、湿污、姿势等临时状态分离。
      - [ ] Location 地理/固定锚点没有因角度、时段、天气、灯光被复制成新地点。
      - [ ] Prop 形制/功能身份没有因 owner、开合、破损、内容或文字状态被复制。
      - [ ] 每个新 asset 有不能复用的持久证据;每个 variant 说明 base 什么保持不变。
      - [ ] 提取没有凭空补脸、品牌、门窗、内容物、伤势、灯光或可读文字。
      - [ ] transfer、伤势、换装、停电、天气/光态和知识变化都有足以支持的剧情原因。
      - [ ] occurrence 的 production disposition 合理:提及不被误当出镜,关键操作物不被
        降成普通布景。
      
      Reviewer finding 应含文件与标题 ID、证据、影响、修订结果、owner、严重程度和状态;不能只说
      “资产不够细”或“看起来 AI”。
      
      ## C. Craft 默认(可覆盖,不单独阻断)
      
      - [ ] 身份未变时优先复用或 variant,没有追求“一场一资产”。
      - [ ] 瞬时 pose/camera/framing 留给 storyboard,没有制造 Look/View/State 爆炸。
      - [ ] 设定集 只保留识别、复用、prompt、镜头和连续性所需事实。
      - [ ] Location 先有地理/入口/固定锚点,再有氛围;Prop 有尺度/形制/材料/功能;
        Character 有可观察的区分锚点。
      - [ ] outgoing 精简但足以让下一场/集无脑内补全地继续。
      
      Creator 可用制作原因覆盖,例如需要逐件审批背景陈设;记录 override,而非改成硬规则。
      
      ## D. Taste 选项(只确认选择)
      
      - [ ] 群演按 group 还是个体管理已选择。
      - [ ] 同建筑空间拆分颗粒度能支持当前制作。
      - [ ] 蒙太奇/梦境/主观断裂已声明边界。
      - [ ] 临时可见状态用 delta 还是升为 variant 符合本项目复用策略。
      
      不得仅因 reviewer 偏好另一个选项而判失败。
      
      ## E. 创作者确认
      
      给创作者看的摘要必须列出:
      
      1. 复用项及匹配证据;
      2. 新 variant 及 base/difference/cause/validity;
      3. 新 asset 及持久区分证据;
      4. unresolved 的原文证据、候选和不同选择的下游影响;
      5. continuity deltas 与跨集 outgoing;
      6. 确认后需要同步更新的下游文件。
      
      把创作者确认的结果直接写进 `视觉设定.md`,未确认选择保留为“未决”并说明影响。创作者确认不等于
      结构检查通过、审查通过或可以投产;这些动作各自独立发生。
      
      ## 当前范围完成判据
      
      本次范围内结构检查通过、每个出现项已决定、没有未决引用、连续性可交接,且创作者已确认需要选择的
      新增/复用/变体/变化,即完成当前资产批次。审查与交付是后续独立动作,
      不作为图片提示词或 storyboard 这两个并列分支的隐式前置门槛。
      
    • character-and-look.md 11.5 KB
      # Character / Look 设计
      
      ## 目录
      
      1. 分工
      2. Character:最小稳定识别集
      3. Look:一次相容的外观组合
      4. 可选声音方向:身份锚点不等于本场 delivery
      5. 制作形态与身份锚点的可表达性
      6. 合成例:顾禾
      7. 角色 设定集 的实用检查
      
      ## 分工
      
      - **Character** 回答“这个人持续是谁、怎样被再次认出”。
      - **Look** 回答“这个人在一个明确时段以怎样的完整外观出现”。
      
      把两者拆开,是为了换装、受伤、湿污和年龄变化时仍能复用人物身份,同时让图片提示
      词与分镜精确选择一个相容造型。
      
      
      ## 画面代称要覆盖每一种正文语言
      
      `画面代称` 是下游覆盖检查唯一用来匹配的字符串:冻结关键帧、图片提示词、视频提示词里出现了这个词,
      才算「这一镜点了这个资产的名」。匹配是逐字的,不做翻译。
      
      问题在于三份正文的语言不一定相同:图片提示词与冻结关键帧跟 `format/prompt_language`,
      视频提示词优先跟已接受的 `video_prompt_language`。而校验器只按后者的解析结果决定要不要强制
      「代称不能等于名字」——于是一个 zh + en 混配的项目很容易写成**只有英文代称**,
      然后中文正文里这个资产永远匹配不上。
      
      **这种失败是静默的**:匹配不上不会报错,只会让覆盖检查对这条资产整个不生效,
      看起来一路绿灯。所以:
      
      - 一个资产的 `画面代称` 要**把它在各份正文里实际会出现的写法都列上**(中文名与英文代称并列),
        不是二选一;
      - 正文里确实不该靠名字识别时才写 `画面代称:无`,那是明确的弃权,不是省事。
      
      ## Character:最小稳定识别集
      
      从剧本与 creator reference 中选择足以区别此人的持久锚点:
      
      - 叙事身份与关系(用于消歧,不是外观替代品);
      - 稳定的面部/头部比例、体态轮廓、永久特征;
      - 基础发质/发际等稳定部分;具体梳法可属于 Look;
      - 永久性饰物仅在确实不随 Look 变化时作为 anchor;
      - 表演/声音的持续事实可写入 设定集,供后续提示词或 casting 参考,但不伪装成可见外观。
      
      锚点要**有区分度且能被观察**。“漂亮年轻女性”“霸总气质”“电影感”都不足以认人。
      也不要过度指定剧本没有要求的肤色、年龄、五官或身体特征;以
      `creator_decision_needed` 暴露空缺。
      
      锚点集合里至少有一条在**侧面**成立、一条在**背面**成立。很多镜头只给到侧身、背影或
      远景,只在正脸可读的特征(眉眼间距、瞳色、唇形)不能是唯一识别通道;轮廓与重心、
      发型剪影与发际形状、肩背线、固定配饰的外形都可以承担这两个方向。写法示例:
      `["方额窄下颌", "后颈发际收成尖角", "右肩比左肩低约两指、起步时先出右肩"]`——第一条正面
      成立,第二条背面成立,第三条侧面与背影都成立。
      
      同时写出全组**可比较的尺度次序**:谁高于谁、差多少量级,并给一个绝对参照点。示例:
      「甲最高,乙比甲矮约一个头,丙比乙再矮半头;甲站直时头顶与门框上沿齐平」。这条次序由
      Character 拥有,换鞋、加厚外套、盘发都不改写它(那些属于 Look)。剧本没有给出身高
      关系时,以 `creator_decision_needed` 暴露,由创作者定,不由角色表模板的默认体型补齐。
      
      人物关系帮助理解谁控制空间、谁观察谁、谁会保护或攻击谁,但**不自动决定美丑、肤色、
      体型或面部善恶感**。主角不因“主角”字段自动变漂亮,反派也不因“反派”字段自动变丑。
      需要视觉层级时,从创作者已接受的社会位置、职业使用痕迹、轮廓区别、服装职责、姿态和
      调度中取证;审美理想化、夸张或反类型都是创作者选择,不能由关系标签偷偷补完。
      
      **`reviewed_invariant` AST-04**: 临时淤青、制服、雨水、当天妆容不得成为 Character
      持久锚点,否则同一人恢复后会被误判成另一身份。
      
      ## Look:一次相容的外观组合
      
      一个 Look 只容纳能同时存在的组合:
      
      - 服装层次与鞋履;
      - 发型整理、妆容、伪装;
      - 伤势、包扎、污渍、湿度、天气化;
      - 与身体绑定的可见配件;手持道具仍绑定 Prop,不吞进 Look;
      - cause、valid_from、valid_until 和 source refs。
      
      写 Look 时先引用 Character identity,再只写相对 base/前一 Look 的差异。不要复制整段
      脸部描述形成第二份权威。
      
      ## 可选声音方向:身份锚点不等于本场 delivery
      
      **`AST-07 · reviewed_invariant`**:项目需要跨集保持声音时,可在 Character 记录 `voice_direction`:语言/方言范围、
      **创作者授权的参考音频绑定**、用于选型与验收的判据、已接受的专名发音。紧张、淋雨、受伤、
      耳语、某句重音和临时音量属于场景/台词 delivery,不要固化成声音身份;变声、年龄跨度或
      伪装若跨段持续,则记录有来源和有效范围的变体。
      
      音色的载体是参考音频,不是一段文字描述。没有参考时保持 `reference: null` 并标「待选型」,
      不要用形容词冒充音色身份。参考绑定要写清这次借用它的什么、哪些内容不能跟着来
      (那条参考里的情绪、录音空间、背景内容永远不进身份),以及证据到了哪一步——
      详见 [声音方向](voice-direction.md)。声音方向直接写在对应人物的 `视觉设定.md` 条目中,不另建
      配音表或第二份权威。
      
      文本引用只能保证角色、台词、声音方向与引用关系没有被改写,不能保证任何真实合成结果、
      口型或音色复现。引用使用 `视觉设定.md` 内的可见 ID,不把网址、外部任务字段或服务参数写进设定;
      参考音频本身留在 `输入/`,不复制进 `视觉设定.md`,也不进公开交付。
      
      **资产阶段不生成音频,也不调用任何语音、合成或克隆服务**。 记录先落进文件、由创作者
      确认;实际 TTS 交给 `$short-drama-produce`,并须对它展示的本次任务预览明确确认。
      
      ### 什么时候值得新 Look
      
      **`craft_default` AST-03**: 满足任一条件时提议:跨多个 block/scene 持续;会重复使用;
      需要独立角色参考图;改变观众识别/剧情信息;下游必须精确绑定。瞬时表情、姿势、
      视线和手位由 shot/continuity 处理,通常不新建 Look。
      
      ### 造型不能互相污染
      
      - “工作服”和“庆典礼服”如果时空不重叠,就不能在同一 Look 中同时出现;
      - 包扎在 source block 后才生效,前面镜头不能提前绑定;
      - 外套脱下后仍是同一 Character,但应切换 Look 或记录可追踪的 layer delta;
      - 若雨水只造成短暂局部湿痕,可用 delta;若跨集保持并需视觉复现,再升为 Look。
      
      ## 制作形态与身份锚点的可表达性
      
      写最小稳定识别集之前,先读 `视觉设定.md` 的“项目视觉方向”和已明确的制作形态;尚未决定且会
      实质影响识别通道时,向创作者给出选择,不从对话记忆补造。
      
      原因很直接:**形态决定观众靠什么通道认人**,因而决定哪些锚点在这个项目里根本可被表达。
      
      | 主要识别通道 | 值得写的锚点 | 写了也看不见的锚点 |
      |---|---|---|
      | 面部结构 | 面部比例、眉眼鼻唇相对关系、下颌与颧骨体积 | 只有大远景才成立的体量差 |
      | 剪影与线条 | 轮廓与肩背重心、发型剪影与发际形状、固定配饰外形、服装廓形、步态负重 | 细微的五官不对称、浅色小痣、依赖真实皮肤纹理的痕迹 |
      | 材质与表面 | 皮肤/毛发/布料的可比较表面状态、长期使用痕迹 | 需要柔和过渡才能读出的体积差 |
      
      ### 写实细节与身份连续性
      
      **`craft_default`**:写实项目可以在稳定识别特征之外,补充皮肤纹理、自然不对称或使用痕迹。
      远景中不易读出的细节,不宜单独承担跨镜识别。
      
      痣、疤和牙齿形态等如果是人物既定特征,仍须保持;不能因它们难生成就允许随镜变化。
      毛孔、细纹等微观纹理不必逐像素一致。是否需要这些细节由画面尺度与已接受风格决定。
      
      **`taste_option`**:偶像化、漫画或其他风格可采用更平滑的表面,不以写实程度判错。
      
      ### 边界:形态选通道,身份定事实
      
      - **`reviewed_invariant`**:形态可以改变锚点的**表达通道与颗粒度**,不得改写身份事实本身。
        不能因为“不好画”把五十岁写成二十五岁、把厚重体格写成修长,也不能为了轮廓好看新增
        疤痕、异色瞳或不存在的配饰。
      - 已确认身份在当前形态下找不到可靠表达通道时,标为“需创作者决定”:由创作者决定
        换形态还是改身份,资产环节不静默调整。
      - Look 层同理:形态决定服装层次、材质响应能表达到多细,不决定 Look 由哪些单品组成,也不
        决定 cause、valid_from、valid_until。
      - 形态同样不改写地理、持物归属、可读文字政策与故事状态;这四项分别属于 Location/View、
        Prop/State、text policy 与 write/develop owner。
      
      ### 模板槽位不得覆盖身份锚点
      
      **`reviewed_invariant`**:模板槽位不得覆盖已确认的身份锚点;当模板默认值与当前身份冲突时,
      **身份优先,模板让位**。
      
      合成例:角色甲的锚点是五十岁上下、体格厚重、肩背略沉。若角色表模板对每一条记录
      机械填入“九头身黄金比例、极度修长的双腿”,角色甲的身份就在没有任何创作者决定的情况下被
      改写;而且因为每个角色都带同一句,下游会把它读成风格统一,不会当成冲突。角色乙即使确实
      修长,也应由自己的当前视觉设定写出,不由模板默认值代写。
      
      排查方式:把同一批角色记录并排看,找出**逐字重复且与该角色无关**的描述句;不能追溯到某条
      当前视觉设定的,删除或改写成该角色的真实锚点,并在预览里说明改动。
      
      ## 合成例:顾禾
      
      Character `CHAR-GUHE` 的当前锚点:窄长脸、左眉尾短缺口、自然卷黑发、肩背略前倾。
      这些锚点不会因本集换装改变。
      
      - `LOOK-GUHE-WORK`:灰绿工作夹克、深色长裤、头发低束;渡站值班期有效。
      - `LOOK-GUHE-RAIN`:在工作装外加橙色旧雨衣,兜帽放下;从 SC004 出门前穿上,
        到 SC006 脱下。
      - SC005 右肩湿痕:若只维持几镜,作为 delta 指向 `LOOK-GUHE-RAIN`,不是
        `CHAR-GUHE-WET`。
      
      反例:“橙色雨衣女人,湿头发,脸上有伤”被直接写成 Character identity。这样雨衣
      脱下、头发变干、伤口愈合时,人物就失去所有所谓“身份锚点”。
      
      ## 角色 设定集 的实用检查
      
      - 去掉本集服装和伤污后,这个人还能与其他角色区分吗?
      - 每个 Look 是否只引用一个 Character,并且内部状态能同时成立?
      - 造型变化是否有 source/cause/validity,而不是凭审美换装?
      - 相同显示名是否误合并,不同称谓是否误拆分?
      - 手持物是否仍由 Prop/State 拥有,未被埋进角色描述?
      - creator 是否接受了新增身份锚点,而不是系统替其补完?
      
      群演可以按制作需要使用 group Character;一旦某个个体有台词、关键交接或跨镜连续,
      应提议独立身份。此颗粒度是 **`taste_option`**,但选择要能满足故事追踪。
      
    • continuity-delta.md 7.2 KB
      # 资产连续性与变化记录
      
      ## 目录
      
      - [边界匹配](#边界匹配-con-01)
      - [状态表与变化记录](#状态表与变化记录的区别)
      - [各环节负责什么](#各环节负责什么)
      - [需要追踪的状态](#需要追踪的五组状态)
      - [记录步骤](#写变化记录的步骤)
      - [状态链示例](#合成状态链)
      - [非线性时间](#非线性时间与有意断裂)
      - [修订影响](#修订影响)
      - [交接检查](#交接检查)
      
      ## 边界匹配 (`CON-01`)
      
      对相连镜头,逐字段比较前一镜已接受的结束状态与下一镜已接受的开始状态。
      只有上游负责人已经记录变化,或负责人明确提出的修订仍在处理中,差异才有依据;
      不得靠运动提示词悄悄“补过去”。
      
      ## 状态表与变化记录的区别
      
      - **状态表**回答某个边界“现在是什么状态”。
      - **变化记录**回答“什么发生了变化、变化前后分别是什么、原因是什么、何时生效、影响谁”。
      
      不要在每场复制完整设定表;只记录下游会依赖的状态和变化。这样既能发现无来源的跳变,
      也能在剧本修改后准确标记 `stale`,不必重做整个项目。
      
      ## 各环节负责什么
      
      资产技能负责身份、资产版本和场景级资产状态变化,例如造型何时切换、道具何时转手、
      场景光线何时由正常变为停电。故事开发技能负责计划中的故事状态;剧本技能负责已经
      写出的知情情况、信念、目标、关系与权力、情绪变化及其故事意义。资产状态表只引用
      这些来源,不得在 `after` 中另写一套故事事实。
      
      分镜技能负责每个镜头的开始和结束边界,包括姿态、位置、目光、双手、持物和可见状态。
      资产可以引用这些边界,但不能另写一套相冲突的手位和走位。
      
      **`structural_invariant` CON-04**: 每条变化都要有前态、后态、原因或来源、有效范围,以及受影响的
      现有 `IMG-...`、`SHOT-...` 或 `MOTION-...`。没有变化时不能只写“更新了”。
      
      **`structural_invariant` CON-06**:受影响列表要覆盖所有已经存在的使用方;尚未建立的工作只说明
      负责 owner 和需要同步的事实,不预建占位记录。
      
      ## 需要追踪的五组状态
      
      1. **人物的故事状态**:知情情况、信念、目标、关系与权力、情绪。只在它们会影响后续
         行动或审稿时追踪,并指向故事开发或剧本中的准确来源;它们不属于资产技能自行创造的变化。
      2. **人物的可见状态**:准确造型、伤势、污湿和场景级携带物。单镜内的姿势由分镜负责。
      3. **地点状态**:地点、观察视角、可用入口、时段、天气、光线和关键陈设。
      4. **道具状态**:准确版本、负责人、持有人、位置、状况、内容物和可读文字。
      5. **场次或集间交接**:上一场或上一集的结束状态、下一场或下一集的进入状态,以及
         依赖这些状态的提示词和镜头。
      
      **`craft_default` CON-03**: 只跟踪后续可能引用的事实。顾禾鞋带的颜色若不影响识别或
      动作,不必在每场重复;她带走的唯一号牌必须进入结束状态。
      
      ## 写变化记录的步骤
      
      1. 在资产出现记录中发现明确变化或边界不一致。
      2. 读取 `视觉设定.md` 中上一处明确状态,绝不从下游提示词反推。
      3. 写清单一的 `before` 和 `after`;一个复合事件可以拆成多条同因变化。
      4. 写明导致变化的场景 ID、动作/对白或视觉设定条目,让原因能在当前五文档中直接核对。
      5. 声明从哪个剧本段、场次或集开始生效,到何时结束;没有明确终点时写 `open_ended`。
      6. 已经存在的使用方列出受影响的 `IMG-...`、`SHOT-...` 或 `MOTION-...`;未来工作只说明需要
         同步,不预建占位引用。
      7. 与下一处相连的开始状态比较:一致就通过,不一致就明确提出修订。
      
      ### “未知”不等于“恢复默认”
      
      上一集结尾人物仍带伤,本集没有提伤势,不能自动恢复为无伤。保留最后一次确认的状态,
      并建立 `unresolved` 的连续性问题:仍然带伤、发生了有意省略的治疗,还是剧本遗漏?
      
      ### 知道事实不等于知道对方也知道
      
      身份或秘密场景至少区分:甲是否知道事实、乙是否知道事实、甲是否知道乙已经知道。
      如果本集进入状态已经声明“双方互知身份”,就不能把“他确认对方认出自己”当成本集
      新的揭示。要么修正进入状态,要么重新说明本场真正改变的是哪种信念或策略。
      
      ### 有来源不等于变化合理
      
      引用某个剧本段只能证明变化有文字来源,不能自动证明因果成立。例如“灯闪了一下”
      不足以解释整栋渡站永久断电。**`reviewed_invariant` CON-02**: 审查者要结合剧本
      证据,判断伤势、知情情况、道具归属、天气和光线变化是否可信;字段非空不能代替判断。
      
      ## 合成状态链
      
      SC004 开始:顾禾穿 `LOOK-GUHE-RAIN`;铁皮匣闭合,号牌在里面;值班室灯亮。
      
      1. 她开匣取牌:匣从“闭合、装有号牌”变为“打开、空”;号牌从匣内转到顾禾右手。
      2. 她把号牌交给魏叔:号牌从顾禾右手转到魏叔右手,原因指向交付动作。
      3. 配电箱跳闸:场景由正常夜间照明变为停电,直到维修;若集末仍未维修就写 `open_ended`。
      4. 暴雨从破窗打湿雨衣右肩:人物可见状态变湿;若跨集复用或需要参考图,再建立新造型版本。
      
      结束状态至少声明:魏叔持号牌、铁皮匣空且打开、值班室断电、顾禾雨衣右肩湿。
      下一集若直接写顾禾手持号牌,必须有归还记录或由创作者接受的修订,不能静默“对齐”。
      
      ## 非线性时间与有意断裂
      
      蒙太奇、省略、梦境和主观画面可以不遵守普通的前后相接,但要明确写出断裂类型、
      进入和退出边界,以及哪些状态仍可信。这属于 **`taste_option` CON-05**,不是逃避记录的
      理由。闪回不会重写当前时间线的状态表;它在自己的时间分支中使用相应的历史版本。
      
      ## 修订影响
      
      - 修改伤势开始的剧本段:列出直接读取该段的造型、提示词、镜头或运动提示词;需要刷新时由各 owner 更新。
      - 只修改道具显示名,ID 和事实不变:通常只更新显示文本,不重做无关分镜。
      - 修改镜头构图:不自动修改资产文件。
      - 修改截图说明或提示词文案:不得反向改写当前视觉状态。
      
      ## 交接检查
      
      - 每个相连镜头的结束状态与下一镜开始状态一致,或已有负责人明确提出修订。
      - 转手同时说明从谁手中到谁手中,物件不会在两人手里复制。
      - 伤势、造型、天气和光线不会无原因复位。
      - 道具内容状态与人物知情情况分开记录。
      - 变化的有效范围覆盖所有依赖镜头,而且不会提前生效。
      - 受影响列表覆盖所有已经存在的提示词、镜头和运动提示词使用方;修订时重新
        计算实际使用方,不只沿用旧清单。
      - 结束状态足以让下一集继续,不依赖创作者脑内记忆。
      
    • continuity-lock.md 7.1 KB
      # 跨镜一致性锁
      
      ## 目录
      
      - [为什么识别锚点还不够](#为什么识别锚点还不够)
      - [什么值得上锁](#什么值得上一把锁)
      - [锁面怎么写](#锁面怎么写)
      - [语法](#语法)
      - [谁负责把锁面带走](#谁负责把锁面带走)
      - [反例](#反例)
      - [校验](#校验)
      
      ## 为什么识别锚点还不够
      
      识别锚点是写给创作者读的事实;执行端读不到它,只读得到每条提示词自己的正文。同一件毛衣第一镜
      织的是蓝色、第二镜变成红色,通常不是资产写错了,而是**没有任何一条提示词把颜色带进去**:视觉
      设定写了,关键帧写了一半,运动提示词以为参考帧已经交代过就把重复描述删掉了。
      
      参考图也补不上这一层。一张角色板只能约束它自己画到的部分;正在编织、只成形一半的毛衣,被手挡住
      的道具,每镜重新构图的服装细节,都落在身份参考“默认不能控制”的一侧。
      
      一致性锁补的就是这一段:把跨镜不变的那几个字固定下来,并要求它原样出现在每条受影响的提示词里。
      
      ## 什么值得上一把锁
      
      三条同时成立才值得上锁:
      
      1. 这个可见事实在本集出现于两个以上镜头,或一个镜头加一张资产板;
      2. 观众能直接看出前后不一致;
      3. 它不随剧情改变——会变的是状态,**状态不上锁**。
      
      典型:服装主色与形制、道具主色与材质、发型长度、伤口位置与形状、招牌或界面的形制配色。
      不典型:情绪、姿势、左右手、镜头角度、时段光线、破损进度、持有人——这些是状态或镜头瞬态,
      分别归 `本集状态` 与分镜。
      
      **`craft_default`**: 一集里的锁通常是个位数。把每条识别锚点都升级成锁,每条提示词都会被同一串
      名词短语撑满,反而挤掉本镜真正要执行的动作。
      
      ## 锁面怎么写
      
      `锁面` 是**要被逐字带进提示词正文的那一小段字**,不是描述,也不是给人读的说明:
      
      - 锁面必须**逐字出现在它管到的每一份可复制正文里**。校验器会在三处查同一个字符串:
        图片提示词正文、冻结关键帧提示词、视频提示词正文。
      - 图片与冻结关键帧使用 `format/prompt_language`,视频可能使用另选的 `video_prompt_language`。
        语言不同时,优先使用确实适合各份正文的共同色值、型号或专名。
        当前 `镜头:` 同时约束本镜首帧与视频正文,不能靠拆成两把锁分别约束两种语言。
        没有合适共同词组时,在视觉设定中保留事实并报告此限制,暂不声明这把逐字锁;
        不缩小事实实际出现的镜头范围来绕过检查,也不自行改变用户选择的正文语言。
      
      - 只保留最小可辨识名词短语,通常是「颜色 + 材质/形制 + 物体」,例如
        `pale blue chunky knit wool sweater`;
      - 不含句号、动作、状态、数量、镜头信息和剧情;
      - 不写会随镜头变化的词(`in her hands`、`half-finished`、`on the sofa`);
      - 一个实体一把锁;同一实体的两种造型是两把锁,各写各的生效镜头。
      
      比较时忽略大小写,换行按空格处理(可复制正文本来就渲染成一段,硬折行不影响匹配),其余字符
      逐字比较。提示词可以在锁面前后接语法(`she keeps knitting the pale blue chunky knit wool
      sweater`),但不能改写锁面内部——少一个 `stand-collar`、多一个逗号都算漂移。
      
      还有两处刻意不算数,因为它们看起来命中、实际并没有把事实写进画面:
      
      - **粘在词上的匹配不算**。锁面首尾是字母数字时,紧挨着的字母数字或连字符会让它落空:
        `chipped white enamel mug` 不被 `unchipped white enamel mug` 满足,
        `pale blue knit` 不被 `pale blue knitwear-print fleece` 满足。锁面要能作为一个完整的词组读出来。
      - **负面提示词里的匹配不算**。写在 `no`/`not`/`without`/`avoid` 或 `不要`/`不能`/`没有`/`避免`
        之后的锁面说的是「不要出现它」,正好与锁的目的相反;`..., no pale blue chunky knit wool
        sweater` 不能作为锁面在场的证据。锁面必须出现在描述画面内容的正文里。
        「粘在词上」只对 ASCII 词生效——中文本来不分词,`织着浅蓝色粗棒针毛线` 正常命中。
      
      ## 语法
      
      写在 `视觉设定.md` 对应资产条目下,与识别锚点并列:
      
      ```markdown
      ## 道具 · 织了一半的毛衣
      
      - 识别锚点:浅蓝色粗棒针手织毛线,竹制棒针两根,衣身只织到胸口。
      - 本集状态:SC002 起衣身加长约一掌;颜色与针法不变。
      - 连续性锁:LOCK-KNIT《织了一半的毛衣》(镜头:全集;图片提示词项:IMG-PROP-KNIT)· 锁面:pale blue chunky knit wool sweater
      ```
      
      - 列表记号写 `-`、`*` 还是 `+`,缩进几格,都不影响识别;一条看起来像锁却写不完整的行会被
        报成语法错误,不会被悄悄跳过——锁不能变成空转;
      - `LOCK-...`:本集唯一稳定 ID,与 `IMG-...`、`REF-...`、`SHOT-...` 互不混用;
      - `《中文名》`:给创作者读的名称,必须含中文;
      - `镜头:`:`全集`,或 `、` 分隔的 `SHOT-...` 列表;两者不能混写;
      - `图片提示词项:`:可选,`、` 分隔的 `IMG-...` 列表,整段省略即表示不约束图片提示词;
      - `锁面:`:被逐字带走的那段字,写在行尾。
      
      ## 谁负责把锁面带走
      
      | 文档 | 义务 |
      |---|---|
      | `图片提示词.md` | 被点名的 `IMG-...` 可复制正文含锁面,让参考图本身就是对的 |
      | `分镜.md` | 生效镜头的冻结关键帧提示词含锁面 |
      | `视频提示词.md` | 对应 `MOTION-...` 可复制正文含锁面 |
      
      **`structural_invariant` CON-07**: 锁面在生效范围内不得以“参考帧已经交代过”为由删除。图生视频
      删掉重复的静态描述是常规做法,但锁面是这条常规做法写明的例外——它正是为了挡住跨镜漂移才存在。
      
      状态变化不进锁面:毛衣越织越长写在 `本集状态`,颜色和针法写在锁面。两者同时存在时,提示词自己
      写长度,锁面负责颜色和针法。
      
      ## 反例
      
      - `锁面:蓝色毛衣`——提示词正文是英文时永远匹配不上,且“蓝色”分不出浅蓝和藏青;
      - `锁面:a pale blue sweater that she has been knitting since the first scene`——含剧情和时态,
        下一镜必须改写它,锁当场失效;
      - `锁面:high quality, detailed`——不是可辨识事实,锁住了也证明不了一致;
      - 只把锁的 `镜头` 范围写到「文字已经对的那几镜」——范围要按这个事实**实际出现在哪几镜**来写,
        按已经写对的地方来写,等于把校验器指向没有风险的位置;
      - 把 `镜头:全集` 用在只出现两镜的道具上——其余镜头会被迫写入与本镜无关的名词。
      
      ## 校验
      
      ```bash
      python3 <core 技能目录>/scripts/creator_markdown_check.py 剧集/<EP> --project-root .
      ```
      
      它只机械核对语法、范围可解析,以及锁面是否真的出现在被点名的正文里。该不该上这把锁、锁面写得
      准不准,仍由创作者和审查判断。
      
    • identity-vs-variant.md 7.7 KB
      # 身份、版本与复用判断
      
      ## 目录
      
      - [三个问题](#三个问题)
      - [身份与状态](#持续身份与可变状态)
      - [判断步骤](#判断步骤)
      - [容易误判的情况](#容易误判的情况)
      - [合成示例](#合成判断例)
      - [决定记录](#决定记录要说明理由)
      - [制作案例](#制作案例说明)
      
      ## 三个问题
      
      不要先问“要不要新建文件”,按顺序问:
      
      1. **持续身份是否相同**? 去掉临时状态后,人物、空间或物体仍是不是同一个?
      2. **下游是否需要区分一个可复用状态**? 变化会持续、再次出现,或影响参考图、
         分镜绑定和连续性吗?
      3. **若不建新版本,只写变化记录是否够用**? 一瞬的动作、姿势、表情或摄影机角度,
         通常交给出现记录、连续性或镜头,不要为它们建立大量资产组合。
      
      结果只能是 `reuse`、`new_variant`、`new_asset` 或 `unresolved`。
      **`structural_invariant`**: 不能用“已处理”代替这四类,也不能让未决项带着假 ID 流入下游。
      
      ## 持续身份与可变状态
      
      | 类别 | 持续身份通常由什么区分 | 通常属于版本或状态 | 通常不建新版本 |
      |---|---|---|---|
      | 人物 | 同一人物、稳定面部与体态、永久特征、叙事身份 | 服装、发型整理、妆容、伤势、污湿、年龄阶段、伪装 | 瞬时表情、姿势、视线、景别 |
      | 地点 | 同一空间地理、分区关系、入口、固定锚点、主要材料 | 可复用观察视角、陈设阶段、时段、天气、光线状态 | 单一镜头角度、焦段、演员站位 |
      | 道具 | 同一物件或同一可复用型号的形制、尺度、材料、功能、唯一标记 | 持有人、持有手、位置、开闭、破损、内容物、文字、启停 | 被抬起的一瞬、画面左右位置 |
      
      这张表属于 **`craft_default`**,不是见词建档的公式。变化是否值得成为新版本,取决于
      持续时间、重复使用、故事辨识度和下游绑定需要。
      
      ## 判断步骤
      
      ### A. 先排除“只属于镜头的事实”
      
      “侧脸”“从背后看”“俯拍柜台”“杯子在画面右侧”都不改变资产。若事实只在一个镜头的
      一瞬成立,就交给分镜边界或关键帧。
      
      ### B. 寻找同一身份的证据
      
      用已有 ID 的稳定识别点、剧情连续、来源与去向和唯一标记来对齐,不要只按显示名。
      两个都叫“值班员”的人不一定是同一个;同一人换了称谓也不会因此变成另一个人。
      
      ### C. 判断变化是否值得建立新版本
      
      同时问:
      
      - 有明确原因和来源吗?例如换装、受伤、停电、拆封、转交。
      - 有清楚的有效范围吗?从哪个剧本段、场次或集生效,到何时失效或仍未知。
      - 下游需要准确选择吗?角色带伤跨三场时,造型版本值得建立;手指刚沾一滴水又擦掉,
        一条变化记录可能就够了。
      - 它能否只写差异,而不篡改基础身份?新版本只写变化,不重写成另一个身份。
      
      在 `视觉设定.md` 中把基础身份与当前变体放在同一项目下。变体只写相对基础身份发生的变化;
      跨文档需要引用视觉事实时直接使用该项目标题;只有引用《图片提示词.md》的可见提示词条目时才使用
      稳定 `IMG-...` ID。`IMG-...` 不表示真实图片,不建立隐藏的来源声明。
      
      ### D. 只有持续身份不同才建新资产
      
      - 剧本确认是另一个人,即使穿着相同。
      - 空间拥有不同地理、入口或固定锚点,即使同属一个建筑。
      - 两件需要独立追踪的同款物件,或形制、功能、材料身份真正不同。
      
      证据不足时选择 `unresolved`,不要靠常识补齐。
      
      ## 容易误判的情况
      
      ### 年龄、替身与伪装
      
      - 同一人物的少年和成年阶段通常是不同造型版本。即使制作上由不同演员扮演,仍可以
        保持一个人物身份,并在造型版本中写明表演或演员限制。
      - 剧情中的双胞胎、复制体和冒名者是不同人物,即使视觉识别点近似。
      - 易容或制服伪装是造型版本,不要把“伪装身份”写成真实人物身份。观众暂时不知道时,
        可以限制显示名,但不能破坏内部事实。
      
      ### 同址空间
      
      “渡站大厅”和“渡站值班室”若有各自可制作的地理和入口,适合建立两个地点,再用关系
      引用连接;不要把整栋建筑所有房间塞进一组观察视角。相反,同一值班室停电前后仍是
      同一地点,不因光线变化新建地点。
      
      ### 同款多件道具
      
      两枚外观相同、分别由不同人物保管并且会被交换的号牌,需要两个道具身份;十只没有
      连续性要求的白杯可以作为一类普通布景。是否逐件建立身份由故事追踪需要决定,不由
      外观相似决定。
      
      ### 损坏与改造
      
      裂纹、血迹、装入文件通常属于道具状态。若物体被不可逆地改造成另一种功能,例如门牌
      被熔成钥匙,而且之后按新物件使用,可以提出新资产,并保留由原物改造而来的关系。
      这个决定需要创作者接受。
      
      ## 合成判断例
      
      已有 `CHAR-GUHE` 与 `LOOK-GUHE-WORK`。本集顾禾套上橙色雨衣进入渡站,第三场雨衣
      右肩被淋湿。
      
      - 橙色雨衣会跨两场并用于参考图:选择 `new_variant`,基础身份仍是顾禾,不是新角色。
      - 右肩湿痕只持续半场:先写连续性变化,不必自动生成第三个造型版本。
      - 若下一集仍需准确复现湿痕并绑定参考图,再由创作者接受一个湿雨衣造型版本。
      
      若文本只写“穿橙雨衣的人”,且无法证明就是顾禾,决定必须保持 `unresolved`。
      
      ## 决定记录要说明理由
      
      每条决定至少展示:来源场景、选择、稳定证据、差异、原因、有效范围和下游影响。
      
      - `new_asset` 要说明为什么不能复用现有资产。
      - `new_variant` 要说明基础身份中哪些内容不变。
      - `reuse` 要说明现有版本怎样覆盖本次事实。
      
      能从剧本与既有视觉事实直接判断的内容直接写入 `视觉设定.md`;真正存在多个成立选择时,把它标为
      “未决”并列出会改变的下游影响,等创作者选择后再改正文。不要伪造决定 ID、接受状态或额外决策文件。
      
      **`reviewed_invariant` AST-04**: 审查者检查身份识别点与临时状态是否混写,不以
      “资产越少越好”作为评价标准。
      
      ## 制作案例说明
      
      对完整制作链的定性案例阅读表明,下面这些命名和拆分做法可以减少换装、伤势和观察
      视角变化带来的重复资产。它们属于可调整的常用做法,不是固定格式:
      
      - **版本后缀命名**:同一人物的状态或造型放在身份名之后,例如“苏某—火灾后无伤”
        或“祁某—红色礼服”。下游一眼就能看出身份和当前版本。
      - **空间按观察方向区分**:同一地点可以按方向或分区命名,例如“医院走廊(北)”和
        “办公室(东)”,每个观察视角可以单独绑定参考图。
      - **稳定识别字段**:性别、年龄段和人物类别可以用于制作追踪。少年到成年这样的大跨度,
        通常用不同造型版本处理。
      - **参考图绑定**:需要稳定识别的人物和空间逐一绑定参考图;没有个体连续性的路人按
        普通布景处理,不必建立身份。
      - **命名分层**:主角可用实名;功能人物可用姓氏加职务;只作背景的人物可用类型加序号。
        资产名与描述中的名字必须一致,不能写甲的名字却描述乙。
      - **持续多集的大状态**:被俘或重伤若会持续多集,可以直接建立新的造型参考。若状态只
        在一镜内发生,就先留在镜头边界或变化记录中,不要提前扩张资产表。
      
    • location-and-view.md 3.9 KB
      # Location / View 设计
      
      ## 分工
      
      - **Location** 保存空间身份:可行走的地理、分区关系、入口、固定锚点和主要材料。
      - **View** 保存可复用的观看方向/生产状态:面向哪个区域、哪些锚点同时可见,以及
        陈设阶段、时段、天气、光态。
      
      Location 不是场景标题字符串,View 也不是每个相机角度。目标是让参考图、分镜和相邻
      镜头共享同一空间逻辑,而不是为每镜创建“新场景”。
      
      ## 先画脑内平面,再写氛围
      
      读取剧本时先整理:
      
      1. 内/外与可通行边界;
      2. 区域之间的相对关系;
      3. 人和道具会使用的入口/出口;
      4. 不能移动的识别锚点;
      5. 主要材料、尺度和功能;
      6. 剧本明确的时间、天气与有因果的光源。
      
      “潮湿、压迫、怀旧”可以辅助气氛,却不能替代“北门通走廊、窗口面向河面、配电箱
      在柜台后”这样的地理事实。也不能为了画面好看新增一扇后来影响追逐路线的门。
      
      ## Location 边界
      
      ### 新 Location
      
      当空间具有不同、可独立生产的地理/入口/固定锚点时新建。例如同一渡站建筑里的候船
      厅和值班室各自有完整动作和明确连接,适合两个 Location,并记录 `connected_to`。
      
      ### 同一 Location
      
      停电前后、白天夜晚、晴雨、临时布置变化都不改变空间身份。若只因镜头转向就新建
      地点,会让门窗方向和演员动线互相矛盾。
      
      **`reviewed_invariant` AST-04**: 固定地理与临时时段/天气/灯光不得混成两个
      Location identity。
      
      ## View 边界
      
      View 值得存在,是因为下游要反复引用一种稳定、能说明空间的方向或状态:
      
      - 从柜台侧朝北门,能同时看到门、河窗和配电箱;
      - 停电后的同方向,主灯灭、只剩河窗冷光;
      - 暴雨期外景,站牌、台阶和河岸关系保持,天气和地面状态变化。
      
      单纯“低机位”“特写门把手”“35mm”属于 shot/keyframe,不建 View。演员站在左边也
      不是地点事实。**`craft_default` AST-03**: 一组镜头能共用方向/状态并需要参考 plate
      时才建 View。
      
      ## 光、天气和陈设必须有来源
      
      - 剧本写停电,则 View 可从 `normal_night` 变为 `blackout`,cause 指向断电 block;
      - 创作者选择蓝调夜景是 visual direction,可记录 creator source;
      - 不能因 prompt 惯性擅加霓虹、雾、逆光或移动窗户;
      - 临时海报、散落文件、积水若会影响故事/连续性,进入 View state 或 delta;普通可替换
        装饰可留作 set dressing。
      
      ## 合成例:旧渡站值班室
      
      `LOC-FERRY-OFFICE`:狭长室内;北门通候船厅;东侧河窗;西侧整面旧柜台;配电箱
      固定在柜台后墙。Location 不写“夜”“暴雨”“顾禾站在门口”。
      
      - `VIEW-FERRY-OFFICE-NORTH-NIGHT`:从柜台内侧朝北门,河窗位于画面右后方;夜间
        顶灯正常,柜台上只有铁皮匣。
      - `VIEW-FERRY-OFFICE-NORTH-BLACKOUT`:同一地理和方向;断电后顶灯灭,河窗冷光
        保留,铁皮匣位置不变。它是 View/state 变体,不是“黑暗值班室”新 Location。
      
      如果剧本只需要一次门把手特写,无需 `VIEW-FERRY-OFFICE-DOOR-CLOSEUP`;镜头可以
      引用 Location/既有 View 后自行拥有 framing。
      
      ## 检查问题
      
      - 不看氛围词,仍能判断入口、固定锚点和区域关系吗?
      - 同一空间各 View 的门窗/光向是否保持可调和?
      - View 是否错误拥有相机焦段、演员站位或 shot boundary?
      - 时段、天气、光态与陈设变化有 source/cause/validity 吗?
      - 相邻空间该拆 Location 还是做 View,是否依据地理和制作复用,而非名称习惯?
      - creator 是否明确选择空景/含角色政策?该政策由后续 image prompt 具体实现。
      
      空间拆分颗粒度可以是 **`taste_option`**;但无论选粗或细,入口关系和连续性必须可
      解释,不能用风格选择掩盖矛盾。
      
    • occurrence-extraction.md 5.9 KB
      # 从剧本提取资产出现记录
      
      ## 目录
      
      - [目的](#目的保存证据不抢先下结论)
      - [逐段检查](#逐段检查而不是只找名词)
      - [事实分层](#事实分层)
      - [指代](#称谓代词和匿名对象)
      - [资产判断](#出现不等于视觉资产)
      - [状态](#状态摘取要点)
      - [完整性](#完整性检查)
      
      ## 目的:保存证据,不抢先下结论
      
      出现记录说明“某段剧本在某处要求了什么”,它不是资产身份本身。先记录出现,再判断
      它对应哪个资产,可以避免把同一人物的几套衣服拆成几个人,也能避免把两个同称谓的人
      误并成一人。
      
      **`structural_invariant` AST-01**: 每个出现判断都要指向 `剧本.md` 中准确的场景 ID,并用动作、
      对白或必要短引文说明依据。行号只能帮助阅读,不能代替稳定场景来源。
      
      ## 逐段检查,而不是只找名词
      
      逐个检查与制作有关的剧本段,回答六个问题:
      
      1. **谁或什么需要被看见、听见**? 人物、群体、动物、地点或可操作道具。
      2. **以什么方式出现**? `on_screen`(出镜)、`voice_only`(只有声音)、
         `represented`(照片、屏幕或画像中出现)、`mentioned_only`(只被提到)。
      3. **此刻能直接看到什么**? 只摘录剧本已经给出的外观、状态、位置和动作后果。
      4. **制作时必须实现什么**? 服装、伤污、可读文字、内容物、功能状态、关键入口等。
      5. **它为什么重要**? 识别、行动、信息揭示、关系与权力、交接、气氛或普通布景。
      6. **它从哪里来、要去哪里**? 进入状态、同场变化和结束状态;若有变化,只预留指向
         连续性变化记录的位置,不在出现记录里另造第二份事实。
      
      一个动作句常会带出多种资产。例如合成剧本段:
      
      > 顾禾推开旧渡站值班室的北门,把带裂口的白瓷号牌放进铁皮匣。匣盖内侧写着
      > “潮位 7”。
      
      至少记录:顾禾出镜;旧渡站值班室出现,北门可能是固定空间锚点;白瓷号牌有裂口;
      铁皮匣被打开,匣盖内出现必须保留的文字。不要把“北门”“裂口”“潮位”各自建成资产。
      
      ## 事实分层
      
      出现记录中的内容按来源分开:
      
      | 内容 | 字段用途 | 写法 |
      |---|---|---|
      | 剧本段和场次位置 | 来源 | 场景 ID 加动作/对白标签或必要短引文;不复制整段正文 |
      | 剧本明示的可见事实 | 可见事实 | 简短摘义,并指回同一场景 |
      | 拟绑定的资产 | 资产判断 | 只保留复用、新变体、新身份或未决结论 |
      | 连续性变化 | 状态变化 | 指向 `视觉设定.md` 中对应身份与前后状态 |
      
      不要把推测伪装成可见事实。可以直接推出的结果也要说明依据,例如“匣盖被推开”可以
      记为 `open`;“顾禾很内疚”不能只因她低头就升级成资产事实。
      
      决定尚未形成时就在对应条目标“未决”,列候选、缺失证据和影响。未来才会发生的连续性变化只写
      “需要同步”的说明,不为尚不存在的文件建立占位记录。
      
      ## 称谓、代词和匿名对象
      
      **`reviewed_invariant` AST-02**: 只要两个候选都合理,就先不绑定。
      
      - 原文写“她”,但本段内没有唯一先行人物:保留 `surface_form: 她`,把决定标为
        `unresolved`,列出候选和需要确认的内容。
      - “穿雨衣的人”后来被叫出姓名:可以提出合并,但必须展示前后剧本证据;不能只因服装相同就自动认定是同一人。
      - “另一把钥匙”说明它不是上一把,但还不知道是否对应已有道具 ID:先建立独立出现记录,不能复用上一把。
      - 同一人被职务、关系和名字交替称呼:别名只有创作者接受后才能进入人物资产。
      - 群体称谓先按制作用途处理。没有个体连续性的“候车乘客”可以作为群组或普通布景;
        后来接过关键道具的那个人应单独建立人物记录。
      
      不要为了“让流程继续”做低把握的猜测。保留未决项,可以防止错误身份被下游放大成
      参考图、分镜和视频提示词。
      
      ## 出现不等于视觉资产
      
      - `voice_only` 需要人物或声音事实,但本场不一定需要造型绑定。
      - `represented` 可能需要同一人物的历史造型,也可能只是不可辨认的照片;按剧情要求处理,不默认露脸。
      - `mentioned_only` 通常只保留故事追踪,不触发图片资产。若台词要求观众必须读到某份文件,还要建立道具出现记录。
      - 环境中的普通桌椅可以标为 `set_dressing`;只有它们承担动作、识别或连续性时才升级为道具。
      
      **`craft_default` AST-06**: 只有会改变识别、复用、提示词、镜头或连续性的事实才进入
      资产记录。若项目有特别制作原因,例如品牌布景需要逐件批准,就写明原因后调整。
      
      ## 状态摘取要点
      
      ### 人物
      
      记录本次造型线索、伤污与湿度、伪装、携带物和故事作用。不在这里发明脸型、年龄或
      颜色搭配;设定表缺少的识别点交给创作者决定。
      
      ### 地点
      
      记录地点称谓、内外、可见分区、入口、固定锚点,以及剧本明示的时段、天气和光源。
      “低机位看门”是镜头意图,不是新地点事实。
      
      ### 道具
      
      记录形制、材料、尺度线索、唯一标记、持有人与位置、持有手、开闭与启停、破损、
      内容物和可读文字。文字保持原样并注明候选文字政策,不能私自润色剧情证据。
      
      ## 完整性检查
      
      - 对索引中的每个制作相关剧本段,都记录已提取内容,或写明 `no_asset_change`。
      - 同一剧本段中的动作若改变状态,至少能找到一条变化候选。
      - 同一对象使用不同称谓时,没有被静默重复或合并。
      - 只被提到的名词不会无缘无故扩张视觉资产表。
      - 每个拟议绑定在创作者接受决定前都保持提案状态。
      
    • prop-and-state.md 6.8 KB
      # 道具与状态设计
      
      ## 目录
      
      - [道具与状态](#道具不是名词表)
      - [个体与集合](#个体同款与集合)
      - [文字政策](#可读文字政策)
      - [内容物与人物认知](#内容物与知识是两条连续性)
      - [关键道具的完整过程](#有明确用途的关键道具)
      - [示例与检查](#合成例铁皮匣)
      
      ## 道具不是名词表
      
      只有会被识别、操作、传递、读取、揭示或保持连续性的物体,才值得成为受管道具。
      普通背景物可保留为 `set_dressing` occurrence;一旦角色拿起它打开秘密、它跨场转手,
      就要能以稳定身份追踪。
      
      ## 道具:持续身份
      
      Prop 保存不随本次动作轻易改变的识别信息:
      
      - 大小/尺度参照、基本形制和部件关系;
      - 主要材料、表面工艺、持久磨损;
      - 原本功能和操作方式;
      - 序列号、缺口、纹章等永久唯一标记;
      - 同款多件时的个体区分,或按类别和数量管理的方法。
      
      “重要文件”“神秘盒子”“高级质感”只是剧情/氛围标签,不能保证再次生成时认出同一
      件物体。反过来,也不要补写剧本没给的品牌和装饰。
      
      ## 状态:此刻怎样
      
      State 记录可变而需跟踪的事实:
      
      - 负责人、持有人、左手或右手,以及具体位置;
      - 完好/裂损、干湿/污渍、开/闭/锁、开机/关机;
      - 内容物及其是否已被看见;
      - 可读文字/图形的当前版本;
      - 组装、封条、消耗量或功能状态;
      - 变化原因与有效范围。
      
      物件若持续承担人物的支撑、固定、负重或行动条件,状态同时记录**人物—物件关系**与有效范围,
      再由分镜投影到起终边界;不能只写物件存在或只在角色板中画出。短暂坐下、靠墙或伸手接触仍是
      镜头瞬态,不为此制造长期状态。
      
      **`craft_default` AST-03**: 同一物体的转手、开合、破损、内容与文字变化优先做
      记录状态变化,不重复创建道具。
      
      ## 个体、同款与集合
      
      - 两枚外观相同的白瓷号牌分别被交换、藏匿:创建两个道具,因为它们的流转记录必须独立;
      - 柜台上的一叠普通空白便签仅作背景:可以按类别和数量记录,不必逐张建立 ID;
      - 一把钥匙从顾禾手中转到值班员手中:仍是同一道具,只修改持有人和持有手;
      - “另一把钥匙”明确不是上一把,但尚不知对应哪个已有 ID:新建一次出现记录并标为 `unresolved`,
        不能偷懒复用。
      
      个体化程度是 **`taste_option`**,选择依据故事追踪与制作需求,而不是总数最少。
      
      ## 可读文字政策
      
      每个出现文字的 Prop 明确选一种:
      
      1. `exact_readable`:剧情要求观众读到;保存准确字符、语言、大小写/标点及来源;
      2. `graphic_only`:需要标签/印记形状,但不要求可辨文字;
      3. `no_readable_text`:画面不应出现可读字;
      4. `pending_creator_text`:剧本要求文字但内容未定,阻断依赖它的提示词。
      
      文字是剧本事实时,资产环节只保存政策与来源位置,不自行润色。后续提示词不能
      同时要求 `exact_readable` 和全局 `no_text`。
      
      若剧情出现网址、号码、合同内容等敏感屏显,必须写明来源、准确屏显文字和创作者确认的处理方式;
      不能把本地引用路径或内部地址带入交付。
      
      **`craft_default`**: 可读文字或标识指向**现实中存在的品牌、商标、机构或人物**时,四个选项里
      选哪一个不是资产环节的默认动作,而是创作者的决定。改编常把这类专名从原著原样带下来,此时
      保留原名、只留形制而不留标识、换一个虚构名都可能是对的,取决于创作者对权利、题材真实感和
      交付渠道的判断。
      
      这里未定的是**政策本身**,不是文字内容,所以不要挪用 `pending_creator_text`(那一条针对
      "剧本要求文字但内容未定")。按已有的未决决定写:`decisions.jsonl` 里一条
      `decision_kind: unresolved`,把三种处理列进 `candidate_bindings` 并各写 support/conflict,
      `creator_question` 问创作者选哪一种,`downstream_impact` 列出会被挡住的下游,
      `creator_acceptance.status` 保持 `pending_choice`。`text_policy.mode` 在创作者选定前不改写,
      原文来源位置照常保留;不替创作者定案,也不擅自改名——擅自改名同样是改写上游事实。
      
      本套件不判断合规、不给禁令、不内置品牌清单;它只保证这个选择有人做、被记下来,而不是在
      生成被拒之后由执行端顺手改掉。
      
      ## 内容物与知识是两条连续性
      
      盒中始终有号牌,是道具内容状态;顾禾在打开盒子后才知道号牌存在,是人物获得了新信息。
      不要因道具早已装有物品,就让人物提前知道。相反,镜头没展示
      内容物不代表它凭空消失。
      
      ## 有明确用途的关键道具
      
      若道具能保护人物、打开路线、储存能力或证明身份,不要只写外观。道具状态还要记录:
      它从哪里取得、现在由谁拿着、是否启用、由什么触发、启用时观众能看到什么、还剩多少
      可用次数或进度、本次使用造成什么后果,以及何时能再次使用。没有来源的“关键时刻突然
      生效”应退回故事或资产负责人;只在提示词里加发光、震动等效果,不能补上缺失的过程。
      
      ## 合成例:铁皮匣
      
      `PROP-TIN-CASE`:手掌至小臂长度的扁长黑铁匣;铰链盖;右前角永久凹陷;用途为保存
      渡站号牌。`“潮位 7”` 写在盖内,政策 `exact_readable`。
      
      - `PSTATE-TIN-CLOSED-BADGE-IN`:匣闭合,白瓷号牌在内,位于柜台下层;
      - SC004 顾禾取出:状态从 `closed` 变为 `open`,内容从 `badge` 变为 `empty`;号牌另记
        自己的持有人状态;
      - 匣盖内文在开启后才可见,但文字不是这时才“产生”。观众/角色知道它则另写
        揭示记录和人物知情变化。
      
      反例:开匣后创建 `PROP-OPEN-CASE`,拿出号牌后再创建 `PROP-EMPTY-CASE`。这样无法
      表达同一匣子的状态演进,也容易让不同镜头各拿一个假副本。
      
      ## 检查问题
      
      - 去掉持有人、开合和内容物后,道具是否仍有可识别的形制、尺度和材料?
      - 同一物体的状态能否沿剧本来源段落逐步变化,而不是瞬移?
      - 同款多件是否因故事追踪需要而正确区分?
      - 可读文字是否精确、来源明确、与 no-text 政策不冲突?
      - 角色手里拿的物体是否既出现在道具状态中,也在后续分镜边界中被引用?
      - 不可逆功能改造若成为新道具,是否保留来源关系与创作者决定?
      
      **`reviewed_invariant` AST-04**: 审查者应指出身份与状态混写、无证据的内容或文字、
      或无法解释的转手;简单关键词匹配不能替代这项判断。
      
    • stage-contract.md 4 KB
      # 视觉设定阶段契约
      
      本阶段只拥有 `剧集/<EP>/视觉设定.md` 中的人物/造型、地点/视图、道具/状态、声音身份与跨场连续性。
      它继承剧本事实,不决定镜头瞬态、构图、动作终点或生成提示词。
      
      每个判断在同一文档中写成复用、新身份、新变体或真实未决;来源用场景 ID,跨文档消费用可见标题和
      稳定 ID。不要为身份决定建立 occurrence、decision、ledger 或接受记录。
      
      ## 本阶段规则
      
      ### `AST`
      
      | ID | Class | Knowledge |
      |---|---|---|
      | AST-01 | structural_invariant | Before creating or reusing an asset, point to the scene ID and visible evidence that establishes it. |
      | AST-02 | reviewed_invariant | Reconcile each occurrence as reuse, new identity, new variant, or unresolved—never guess an ambiguous name/pronoun. |
      | AST-03 | craft_default | Separate Character/Look, Location/View, and Prop/State. |
      | AST-04 | reviewed_invariant | Persistent identifying anchors and mutable state are not mixed. |
      | AST-05 | structural_invariant | Every downstream reference resolves to a visible identity item and the stated current variant in `视觉设定.md`. |
      | AST-06 | craft_default | Track only asset facts needed for recognition, reuse, prompt writing, or continuity. |
      | AST-07 | reviewed_invariant | Persistent voice identity and pronunciation refs stay separate from scene-level breath, emotion, volume, and delivery state. |
      | AST-08 | reviewed_invariant | A voice reference binding states what it controls and what it must not; the take's emotion, its recording space, and its background never enter identity. |
      | AST-09 | reviewed_invariant | A claim about what is audible in a reference requires a creator or rights-holder description, or an authorized listening observation bound to the inspected bytes; otherwise admission stays unverified. |
      | AST-10 | structural_invariant | One accepted pronunciation of a proper noun uses one spelling throughout `视觉设定.md`. |
      | AST-11 | reviewed_invariant | Characters designed together are not bound to confusable references; each names the audible trait telling it apart from its nearest neighbour, and names that character. |
      | AST-12 | craft_default | Selection criteria are few, audible and counter-exampled; they judge a candidate reference or a clone result, they do not stand in for one. |
      | AST-13 | structural_invariant | When the project's prompt language is not the language of the entry headings, every 人物 entry a shot's 视觉依据 references reaches a stated conclusion: `画面代称` carrying the exact spelling prompt bodies use, or `画面代称:无` when no body names it. One accepted name uses one spelling, case included. 地点/道具 declare one only when a body names them. |
      
      ### `CON`
      
      | ID | Class | Knowledge |
      |---|---|---|
      | CON-01 | structural_invariant | Linked end and next start states match or have an explicit owner revision. |
      | CON-02 | reviewed_invariant | Knowledge, injury, ownership, weather, light, or physical state does not teleport/regress without story cause. |
      | CON-03 | craft_default | Track downstream-relevant deltas, not the whole 设定集 in every shot. |
      | CON-04 | structural_invariant | A continuity change states before, after, cause/source scene, effective range, and affected visible IDs. |
      | CON-05 | taste_option | Declared montage, ellipsis, dream, or subjective imagery may intentionally break ordinary continuity. |
      | CON-06 | structural_invariant | A continuity change names every existing downstream document it affects; future work is described, not pre-created. |
      | CON-07 | structural_invariant | A continuity lock declared in `视觉设定.md` fixes one verbatim surface plus its shot and image scope; every in-scope frozen keyframe, motion prompt, and named image prompt carries that surface unchanged. |
      
      规则分级由高到低:`structural_invariant`(结构缺陷,阻断)、
      `reviewed_invariant`(需证据判断)、`craft_default`(常用做法,可覆盖)、
      `taste_option`(创作者选择,不作缺陷)。创作者已接受的事实优先于本表。
      
    • voice-direction.md 9.6 KB
      # 声音方向
      
      ## 目录
      
      - [参考音频是载体](#参考音频是载体)
      - [参考可以是合成出来的](#参考可以是合成出来的)
      - [身份与表演的界线](#身份与表演的界线)
      - [一段参考音频可以决定什么](#一段参考音频可以决定什么)
      - [绑定不等于听过](#绑定不等于听过)
      - [选型判据](#选型判据)
      - [写出区分度](#写出区分度)
      - [专名发音](#专名发音)
      - [变体:伪装、年龄与跨段变化](#变体伪装年龄与跨段变化)
      - [voice-casting.md 的渲染规则](#voice-castingmd-的渲染规则)
      
      `voice_direction` 是 Character 记录的一部分(`AST-07`),跨集稳定。本场怎么说属于表演,
      由 `$short-drama-write` 的配音本逐句决定。
      
      ## 参考音频是载体
      
      短剧的音色由**一段参考音频**承担:配音演员的试音、声音库里的一条、或创作者授权的克隆源。
      文字不承担音色——它承担两件参考音频承担不了的事:
      
      1. **选型与验收**:用什么判据挑一条参考、判一次克隆结果算不算这个角色;
      2. **专名发音**:参考音频里没有的那些名字该怎么念。
      
      所以 `voice_direction` 以 `reference` 为主字段。没有参考音频时,记录保持
      `reference: null` 并写明「待选型」——**不要用一段文字描述冒充音色身份**。
      下游拿着一段形容词去找声音,找回来的每次都不一样,而记录看起来是完整的。
      
      ### 参考可以是合成出来的
      
      使用声音库时,确认所选音色及其使用范围符合供应商提供的许可。
      项目没有配音演员、也不打算克隆谁的时候,这条路是让 `reference` 从 `待选型` 变成实际路径的
      办法——**挑一个预置音色,合成一条,绑上去**。合成由 `$short-drama-produce` 的 `tts` 模态执行。
      
      合成出来的参考和录音来的参考在本文档里受同样的约束:它一样要写用途边界,一样要有
      `admission_status`,一样不能用形容词代替。差别只有一处——**它的选型判据是拿来挑预置音色的,
      不是拿来判克隆像不像的**,所以「与谁最易混」这条要在同一批预置音色里回答,而不是在角色之间
      凭空回答。预置库里两个音色听起来就是同一个人时,换一个,不要靠判据文字假装区分。
      
      音色 ID 本身写进 `reference` 那一行,和路径、用途边界一起。它是创作决定:同一个角色在两集之间
      换 ID 就是换人说话,这件事必须在文档里看得见、diff 里查得出。
      
      ## 身份与表演的界线
      
      一句话判定:**这个特征在角色安静地坐着说一句无关紧要的话时还在吗**?
      
      在,就是身份。不在,就是表演,留给场次与台词。
      
      | 属于身份 | 属于表演 |
      |---|---|
      | 音区、音色质感、年龄印象 | 本场因奔跑造成的气息不稳 |
      | 语速与停顿习惯、句尾处理 | 某一句的重音位置 |
      | 口音与方言范围 | 这一场压低声音说话 |
      | 已接受的专名发音 | 情绪激动时的音量 |
      
      界线画错的代价不对称:把表演写进身份,角色每一集都会被要求带着上一集的伤势说话;
      把身份漏进表演,配音者每一场都要重新猜这个人是谁。
      
      `not_voice_identity` 与身份字段必须同时写。只写身份,下游无从判断某个特征是被遗漏
      还是被有意排除。
      
      ## 一段参考音频可以决定什么
      
      参考绑定必须自带用途边界:写清这次参考它的**什么**,以及哪些内容**不能**跟着来
      (`AST-08`)。同一段录音承担多个用途时拆成多条绑定分别审查。
      
      | `role` | 可以借用 | 默认不可借用 |
      |---|---|---|
      | `timbre` | 音色质感、共鸣、年龄与体量印象 | 这条里的情绪、语速、录音空间 |
      | `accent` | 口音与语域范围 | 音色、音区、说话人身份 |
      | `pace` | 语速基线与停顿习惯 | 音色、口音、这条的情绪 |
      | `age_impression` | 听感年龄 | 具体音色与口音 |
      
      三类东西**永远写进 `must_not_control`**,因为它们在任何一条参考里都必然存在,
      而且必然不属于角色身份:
      
      - **这条参考里的情绪**:试音稿在哭,不代表这个角色总是在哭;
      - **录音空间**:混响、房间声、话筒距离属于那次录音,不属于这个人;
      - **背景内容**:底噪、伴奏、另一个说话人。
      
      一条参考同时要定音色和口音时,写两条绑定分别审查,不要把 `may_control` 加长。
      
      ## 绑定不等于听过
      
      文件名或可见引用只说明绑定了哪段素材,**不能证明有人真的听过这段音频**(`AST-09`)。
      每条绑定写清证据到了哪一步:
      
      | `admission_status` | 什么情况 | 可以下什么结论 |
      |---|---|---|
      | `creator_described` | 创作者或参考权利人给了可核对的听感说明 | 按说明内容判断 |
      | `audibly_inspected` | 运行环境获授权检查该音频并完成检查 | 按检查记录判断 |
      | `unverified` | 只有文件名或文字描述,音频不可听或没有授权 | **不得**声称音色、口音、底噪或说话人数已经确认 |
      
      没有证据就老实保持 `unverified` 并列出风险。负面描述擦不掉参考里已有的内容——
      参考里有底噪就是有底噪,写「不要底噪」不会让它消失;要么重录、清理、换一条,
      要么退回参考权利人决策。
      
      参考音频与授权说明属于创作者输入,放在 `输入/` 下;`视觉设定.md` 只写项目相对路径、用途、
      允许控制/不得控制的内容与观察状态。**媒体本身不复制进视觉设定,也不进公开交付**。
      
      ## 选型判据
      
      `selection_criteria` 是三到五条可听的判据,用来**挑**一条参考、或**判**一次克隆结果
      算不算这个角色(`AST-12`)。它不生成声音,也不替代参考。
      
      从六类里挑,不要写满:音区(相对位置而非 Hz——「比同剧其他成年男性低半档」)、
      质感、年龄与体量印象、节奏、口音与语域、可重复的标记性习惯(至多一条)。
      
      每条带一个 `counter_example`,写清什么算过头。判据没有上界时,试音会一轮比一轮夸张,
      克隆结果也没法判到底算不算过。
      
      情绪词不是判据。锚点里出现「愤怒」「悲伤」「紧张」,说明写的是表演不是身份;
      换成产生那个印象的可听特征。
      
      ## 写出区分度
      
      同一批角色对比着选(`AST-11`)。对每个角色回答:同剧里跟他最容易混的是谁,
      用哪一条判据区分。
      
      ```
      CHAR-A 与 CHAR-B 最易混:两条参考同为中低音区、语速偏慢。
      区分判据:A 句尾收住不拖长;B 句尾常带一段下滑的余音。
      ```
      
      这条写不出来,说明两条参考实际上是同一个声音——观众会分不清谁在说话,
      而这个问题在文本审查里看不出来,只在成片里被听见。回到创作者那里换一条参考,
      不要靠堆形容词假装区分。
      
      角色多时按易混组分批选型,而不是按出场顺序。
      
      ### 对比选择预置音色
      
      用同一组有代表性的台词试听候选,比较角色间是否易混,以及同一角色在不同情绪下是否稳定。
      音色名称仅供检索,选择依据是实际声音。
      
      需要测量时,F0 和谱特征可辅助定位近似音色;同一候选应有多句样本,才能估计自身变化。
      不把音高间距或「组间距离大于组内极差」设为通用门槛。最终按角色需求与回听结果选择,
      不为追求数字分离而改变已接受的声音方向。
      
      ## 专名发音
      
      已接受的专名发音只能有一种拼法(`AST-10`)。
      
      这一栏是纯文字,而且参考音频通常帮不上忙:试音稿里多半没有这些名字。真人配音会按
      自己的习惯念,语音合成会按模型的习惯念,两边都不会报错。同一个名字出现两种拼法,
      配音就会在两集之间改口。
      
      新增发音走正常的接受流程,不在渲染时就地决定。
      
      ## 变体:伪装、年龄与跨段变化
      
      角色装成另一个人、跨越十年、或中毒后长期失声,都**不改写基础身份**,而是按
      [身份与变体](identity-vs-variant.md) 记一条变体:
      
      - `cause`:由哪条已接受的剧本事实引起;
      - `validity`:从哪里生效到哪里失效;
      - `delta`:换了哪条参考、或相对基础身份改了哪几条判据,其余继承。
      
      短暂的一场变化不建变体,交给表演。判据和 Look 一样:跨多个场次持续、会重复使用、
      需要独立参考、或下游必须精确绑定时才升为变体。
      
      ## 写入 `视觉设定.md`
      
      需要跨集保持声音身份时,在对应人物条目下增加“声音方向”,只保留:
      
      1. **参考**:项目相对路径、用途、允许借用与不得借用;没有参考时写“待选型”;
      2. **选型判据**:三到五条可听判据,每条带反例;
      3. **对比角色**:最容易混淆的是谁,以及靠什么可听差异区分;
      4. **专名发音**:唯一确认读法;
      5. **变体**:跨多场持续的伪装、年龄跨度或音色变化,写原因与有效范围。
      
      临时情绪、呼吸、重音、音量和空间混响仍由剧本/镜头/视频提示词拥有。声音方向是生产输入,不是
      合成结果保证;实际 TTS 仍由 `$short-drama-produce` 在精确预览并显式确认后执行。
      
      参考音频落地之后,它在下游有两个去处:作为视频模型的音色参考绑定(见目标模型的方言文档),
      以及作为配音本的实际人声。配音使用同一已选音色逐句合成;参考音频用于选型或模型绑定,不能直接充当不同台词的成品音轨。
      是否保持角色声音,仍需在生成后回听确认。
      
  • scripts
    • asset_check.py 10 KB
      #!/usr/bin/env python3
      """Validate the structural core of standalone character and look records."""
      
      from __future__ import annotations
      
      import argparse
      import json
      import re
      import sys
      from pathlib import Path
      from typing import Any, NamedTuple
      
      MINIMUM_PYTHON = (3, 9)
      if sys.version_info < MINIMUM_PYTHON:
          raise SystemExit("asset_check.py requires Python 3.9 or newer")
      
      SKILL_ROOT = Path(__file__).resolve().parents[1]
      HASH_RE = re.compile(r"[0-9a-f]{64}")
      ACCEPTANCE_STATUSES = {"accepted", "proposed", "pending_choice"}
      
      
      # ---------------------------------------------------------------------------
      # REFERENCE RESOLVER -- reference implementation.
      #
      # Each skill checker carries its own copy of this block. The suite has no shared
      # library on purpose: a skill must stay runnable after copying only its own
      # directory, so duplicating these few lines across skills is the correct shape.
      # Copy the block verbatim; do not import it.
      # ---------------------------------------------------------------------------
      
      SOURCES_RECORD_TYPE = "sources"
      SOURCES_SCHEMA_VERSION = "1.0.0"
      
      
      class ResolvedRef(NamedTuple):
          """An upstream reference with its snapshot resolved, whichever form it used."""
      
          owner: str
          artifact: str
          record_id: str | None
          field: str | None
          authority: str | None
      
      
      class RefFinding(NamedTuple):
          """A structural defect in a reference object."""
      
          code: str
          location: str
          detail: str
      
      
      def load_sources(document: Any) -> dict[str, dict[str, Any]]:
          """Return the ``sources`` declaration of a parsed file, or ``{}`` if absent.
      
          Accepts a parsed ``.json`` document (a dict) or the parsed record list of a
          ``.jsonl`` file, whose declaration lives on the first record.
          """
          if isinstance(document, list):
              document = document[0] if document else None
          if not isinstance(document, dict):
              return {}
          declared = document.get("sources")
          if not isinstance(declared, dict):
              return {}
          return {key: value for key, value in declared.items() if isinstance(value, dict)}
      
      
      def resolve_ref(
          ref: Any, sources: dict[str, dict[str, Any]], location: str
      ) -> tuple[ResolvedRef | None, RefFinding | None]:
          """Resolve a reference object written in either the compact or expanded form."""
          if not isinstance(ref, dict):
              return None, RefFinding("REF_IS_NOT_AN_OBJECT", location, f"got {type(ref).__name__}")
          src = ref.get("src")
          if isinstance(src, str):
              entry = sources.get(src)
              if entry is None:
                  return None, RefFinding(
                      "REF_SRC_IS_NOT_DECLARED", location, f"src {src!r} has no sources entry"
                  )
              owner, artifact = entry.get("owner"), entry.get("artifact")
              if not (isinstance(owner, str) and isinstance(artifact, str)):
                  return None, RefFinding(
                      "SOURCE_ENTRY_IS_INCOMPLETE", location, f"sources[{src!r}] needs owner/artifact"
                  )
          elif all(isinstance(ref.get(key), str) for key in ("owner", "artifact")):
              owner, artifact = ref["owner"], ref["artifact"]
          else:
              return None, RefFinding(
                  "REF_HAS_NO_UPSTREAM_BINDING", location, "needs src, or owner+artifact"
              )
          optional = {
              key: ref[key] for key in ("record_id", "field", "authority") if isinstance(ref.get(key), str)
          }
          return (
              ResolvedRef(
              owner,
              artifact,
                  optional.get("record_id"),
                  optional.get("field"),
                  optional.get("authority"),
              ),
              None,
          )
      
      
      # ---------------------------------------------------------------------------
      # END REFERENCE RESOLVER
      # ---------------------------------------------------------------------------
      
      
      class ValidationError(ValueError):
          pass
      
      
      class RecordFile(NamedTuple):
          """One JSONL file: its upstream snapshot declaration and its records."""
      
          sources: dict[str, dict[str, Any]]
          records: list[dict[str, Any]]
      
      
      def resolve_input(value: str | Path) -> Path:
          path = Path(value).expanduser()
          if path.exists() or path.is_absolute():
              return path
          return SKILL_ROOT / path
      
      
      def load_jsonl(value: str | Path) -> RecordFile:
          path = resolve_input(value)
          parsed: list[dict[str, Any]] = []
          for number, line in enumerate(path.read_text(encoding="utf-8").splitlines(), 1):
              if not line.strip():
                  continue
              try:
                  record = json.loads(line)
              except json.JSONDecodeError as exc:
                  raise ValidationError(f"{path}:{number}: invalid JSON") from exc
              if not isinstance(record, dict):
                  raise ValidationError(f"{path}:{number}: each JSONL record must be an object")
              parsed.append(record)
          sources = load_sources(parsed)
          records = [
              record for record in parsed if record.get("record_type") != SOURCES_RECORD_TYPE
          ]
          if not records:
              raise ValidationError(f"{path}: no records")
          return RecordFile(sources, records)
      
      
      def require_text(record: dict[str, Any], key: str, label: str) -> str:
          value = record.get(key)
          if not isinstance(value, str) or not value.strip():
              raise ValidationError(f"{label}: {key} must be non-empty text")
          return value
      
      
      def require_list(record: dict[str, Any], key: str, label: str) -> list[Any]:
          value = record.get(key)
          if not isinstance(value, list) or not value:
              raise ValidationError(f"{label}: {key} must be a non-empty list")
          return value
      
      
      def validate_ref(
          value: Any, sources: dict[str, dict[str, Any]], label: str
      ) -> ResolvedRef:
          resolved, finding = resolve_ref(value, sources, label)
          if finding is not None:
              raise ValidationError(f"{label}: {finding.code}: {finding.detail}")
          assert resolved is not None
          if not resolved.record_id or not resolved.record_id.strip():
              raise ValidationError(f"{label}: record_id must be non-empty text")
          return resolved
      
      
      def validate_acceptance(
          value: Any, sources: dict[str, dict[str, Any]], label: str
      ) -> None:
          if not isinstance(value, dict) or value.get("status") not in ACCEPTANCE_STATUSES:
              raise ValidationError(f"{label}: invalid creator_acceptance status")
          decision_ref = value.get("decision_ref")
          if value["status"] == "accepted" or decision_ref is not None:
              resolved = validate_ref(decision_ref, sources, f"{label}.decision_ref")
              # A decision_ref pointing at a block or an asset record binds acceptance to
              # something that never recorded a creator decision.
              if not str(resolved.record_id).startswith("CD-"):
                  raise ValidationError(
                      f"{label}.decision_ref: record_id must be a creator decision starting with CD-"
                  )
      
      
      def validate_records(characters: RecordFile, looks: RecordFile) -> dict[str, Any]:
          character_ids: set[str] = set()
          for index, record in enumerate(characters.records, 1):
              label = f"character[{index}]"
              character_id = require_text(record, "character_id", label)
              if not character_id.startswith("CHAR-"):
                  raise ValidationError(f"{label}: character_id must start with CHAR-")
              if character_id in character_ids:
                  raise ValidationError(f"{label}: duplicate character_id {character_id}")
              character_ids.add(character_id)
              require_text(record, "display_name", label)
              anchors = require_list(record, "identity_anchors", label)
              if any(not isinstance(item, str) or not item.strip() for item in anchors):
                  raise ValidationError(f"{label}: identity_anchors must contain text")
              for ref_index, ref in enumerate(require_list(record, "source_refs", label), 1):
                  validate_ref(ref, characters.sources, f"{label}.source_refs[{ref_index}]")
              validate_acceptance(record.get("creator_acceptance"), characters.sources, label)
      
          look_ids: set[str] = set()
          for index, record in enumerate(looks.records, 1):
              label = f"look[{index}]"
              look_id = require_text(record, "look_id", label)
              if not look_id.startswith("LOOK-"):
                  raise ValidationError(f"{label}: look_id must start with LOOK-")
              if look_id in look_ids:
                  raise ValidationError(f"{label}: duplicate look_id {look_id}")
              look_ids.add(look_id)
              character_ref = validate_ref(
                  record.get("character_ref"), looks.sources, f"{label}.character_ref"
              )
              if character_ref.record_id not in character_ids:
                  raise ValidationError(
                      f"{label}: character_ref does not resolve: {character_ref.record_id}"
                  )
              differences = record.get("differences")
              if not isinstance(differences, dict) or not any(differences.values()):
                  raise ValidationError(f"{label}: differences must describe an observable change")
              validity = record.get("validity")
              if not isinstance(validity, dict) or not validity.get("from"):
                  raise ValidationError(f"{label}: validity.from is required")
              validate_acceptance(record.get("creator_acceptance"), looks.sources, label)
      
          return {
              "status": "valid",
              "characters": len(characters.records),
              "looks": len(looks.records),
              # `source_declaration` is this file's own `sources` header resolving;
              # `look_binding` is a real cross-record lookup, and it is one only
              # because both files are handed to this checker.
              "checks": ["unique_ids", "acceptance_shape", "source_declaration", "look_binding"],
          }
      
      
      def validate_files(characters: str | Path, looks: str | Path) -> dict[str, Any]:
          return validate_records(load_jsonl(characters), load_jsonl(looks))
      
      
      def main() -> int:
          parser = argparse.ArgumentParser(description=__doc__)
          parser.add_argument("--characters", required=True)
          parser.add_argument("--looks", required=True)
          args = parser.parse_args()
          try:
              result = validate_files(args.characters, args.looks)
          except (OSError, ValidationError) as exc:
              print(f"asset check failed: {exc}", file=sys.stderr)
              return 2
          print(json.dumps(result, ensure_ascii=True, indent=2))
          return 0
      
      
      if __name__ == "__main__":
          raise SystemExit(main())
      
    • selftest.py 3.1 KB
      #!/usr/bin/env python3
      """Offline self-test for the standalone asset checker."""
      
      from __future__ import annotations
      
      import copy
      import sys
      from pathlib import Path
      
      from asset_check import SKILL_ROOT, RecordFile, ValidationError, load_jsonl, validate_records
      
      MINIMUM_PYTHON = (3, 9)
      if sys.version_info < MINIMUM_PYTHON:
          raise SystemExit("selftest.py requires Python 3.9 or newer")
      
      
      def require(condition: bool, message: str) -> None:
          if not condition:
              raise AssertionError(message)
      
      
      def expect_failure(characters: RecordFile, looks: RecordFile, marker: str) -> None:
          try:
              validate_records(characters, looks)
          except ValidationError as exc:
              if marker not in str(exc):
                  raise AssertionError(f"expected {marker!r}, got {exc!s}") from exc
          else:
              raise AssertionError(f"expected validation failure containing {marker!r}")
      
      
      def edited(source: RecordFile, index: int) -> tuple[RecordFile, dict]:
          records = copy.deepcopy(source.records)
          return RecordFile(copy.deepcopy(source.sources), records), records[index]
      
      
      def main() -> int:
          example = Path(SKILL_ROOT, "examples/minimal")
          characters = load_jsonl(example / "characters.jsonl")
          looks = load_jsonl(example / "looks.jsonl")
          result = validate_records(characters, looks)
          require(result["characters"] == 1 and result["looks"] == 1, "valid fixture count")
      
          duplicate = copy.deepcopy(characters.records[0])
          expect_failure(
              RecordFile(characters.sources, [*characters.records, duplicate]),
              looks,
              "duplicate character_id",
          )
      
          broken_look, record = edited(looks, 0)
          record["character_ref"]["record_id"] = "CHAR-MISSING"
          expect_failure(characters, broken_look, "does not resolve")
      
          undeclared, record = edited(looks, 0)
          record["character_ref"]["src"] = "not-declared"
          expect_failure(characters, undeclared, "REF_SRC_IS_NOT_DECLARED")
      
          unbound, record = edited(looks, 0)
          record["character_ref"] = {"record_id": "CHAR-LIN"}
          expect_failure(characters, unbound, "REF_HAS_NO_UPSTREAM_BINDING")
      
          # A released project may still carry the snapshot inline on the reference.
          inline, record = edited(looks, 0)
          record["character_ref"] = {
              **inline.sources["characters"],
              "record_id": "CHAR-LIN",
          }
          inline.sources.pop("characters")
          require(validate_records(characters, inline)["status"] == "valid", "inline snapshot")
      
          candidate, record = edited(characters, 0)
          record["creator_acceptance"] = {"status": "proposed", "decision_ref": None}
          require(validate_records(candidate, looks)["status"] == "valid", "candidate status")
      
          invalid, record = edited(characters, 0)
          record["creator_acceptance"] = {"status": "approved", "decision_ref": None}
          expect_failure(invalid, looks, "invalid creator_acceptance status")
      
          misbound, record = edited(characters, 0)
          record["creator_acceptance"]["decision_ref"]["record_id"] = "SC001-A01"
          expect_failure(misbound, looks, "must be a creator decision starting with CD-")
      
          print("9 self-tests passed")
          return 0
      
      
      if __name__ == "__main__":
          raise SystemExit(main())
      
  • SKILL.md 5.2 KB
    ---
    name: short-drama-assets
    description: 从短剧剧本拆出人物/造型、地点/视图、道具/状态和跨场连续性,写成创作者可读的视觉设定。用户说“拆角色/场景/道具”“做资产设定”“判断复用还是新变体”“更新造型/道具状态”,或拿现成剧本直接做视觉资产准备时使用;不写图片提示词,不生成媒体。
    license: MIT
    ---
    
    # 短剧资产拆解
    
    把剧本文字变成可复用、可持续的视觉事实,统一写入 `剧集/<EP>/视觉设定.md`。不要把每次出场都
    变成新资产,也不要抢写剧本语义、镜头走位或生成提示词。
    
    ## Quick Start
    
    ```text
    用 $short-drama-assets 从 EP001 剧本拆完人物、造型、地点、道具和本集状态
    ```
    
    ## 入口与边界
    
    现成剧本可以直接拆资产。先区分:
    
    - **身份**:换一个就不再是同一个人、地或物;
    - **变体**:身份不变,但服装、伤势、时段、天气、开合或持有状态改变;
    - **镜头瞬态**:姿势、视线、左右手、站位和相机角度,由分镜拥有;
    - **故事语义**:知识、目标、关系和情绪由写作/开发拥有,只引用可见后果。
    
    ## 工作流
    
    1. 只读当前范围的剧本和已决定的视觉方向。
    2. 逐场找实体、可见描述、状态变化和含混指代,先不创建资产。
    3. 对每项决定新建、复用、增加变体或标记未决;含混指代不猜。
    4. 写最小可识别设定:统一使用 `识别锚点`、本集状态和变化原因;会持续限制后续动作的身体—物件—空间关系写进有生效范围的状态,不当成一次姿势略过。
    5. 检查造型/伤势、持物/所有权、道具状态、地点时段/天气/光态与跨集 outgoing。
    6. 给跨镜出现且观众看得出不一致的可见事实写一条连续性锁,其余保持识别锚点即可。
    7. 项目提示词语言不是中文时,给每个人物条目写一行 `画面代称:<正文里的拼写>`;正文从不点名它就写 `画面代称:无`。地点和道具按需。
    8. 整集请求自动处理完整集,末端一次回报关键身份决定和未决指代。
    
    ## 内容要求
    
    - 每项先写什么不变使它仍是同一资产,再写本集变化。
    - 识别锚点必须可见、可生成、可比较,不用空泛质量词替代事实。
    - 服装、伤势、湿污、道具开合和光态通常是变体,不是新身份。
    - 不复制剧情摘要,只保留下游生成、分镜或连续性真正需要的事实。
    - 文字、标识和敏感信息遵守项目政策,不擅自补造品牌或私密信息。
    - 多集状态写清生效与持续范围;镜头内瞬态留给分镜。
    - 已确认且持续限制动作空间的关系(身体依托、固定、负重、占位或与功能物件的接触)不是普通镜头姿势:可独立识别的物件仍建资产,关系写进状态并交给分镜逐镜投影;只有剧本明确改变时才结束。
    - 连续性锁只锁不随剧情改变的可见事实;锁面用提示词语言写成最小名词短语,会变的部分留给状态。
    - 「画面代称」把提示词正文里的名字对回本条目:分镜的机械核对靠它判断关键帧里点名的人是谁。条目自身的中文名称
      始终算一个代称,所以中文正文通常不必写这一行;提示词正文语言不是中文时,人物条目必须给出结论——写出正文使用的
      拼写,或在正文从不点名它时写 `画面代称:无`。同一个名字全集只用一个拼写,大小写也一致(`AST-10` 的同一条要求跨语言同样成立);
      多个写法用 `、` 分隔。名字与常用词同形(`道具「手机」` 撞上「手机店」)时也写 `画面代称:无`,让核对不按名字找它。
    
    ## 按需知识
    
    默认只读本 SKILL、当前剧本和视觉方向。遇到对应问题时只打开一份:
    
    - 阶段边界与规则分级:[阶段契约](references/stage-contract.md)
    - 从剧本找真正的资产出现证据:[出现提取](references/occurrence-extraction.md)
    - 新身份、复用、变体或未决:[身份与变体](references/identity-vs-variant.md)
    - 人物识别与造型层次:[人物与造型](references/character-and-look.md)
    - 地点地理、视图和固定锚点:[地点与视图](references/location-and-view.md)
    - 功能道具、所有权、状态变化,或画面上的可读文字与标识:[道具与状态](references/prop-and-state.md)
    - 声音参考的身份与授权边界:[声音方向](references/voice-direction.md)
    - 跨场、跨镜、跨集状态:[连续性变化](references/continuity-delta.md)
    - 跨镜服装/道具颜色、材质漂移:[跨镜一致性锁](references/continuity-lock.md)
    - 完成前做一次资产漏项检查:[资产审查清单](references/asset-review-checklist.md)
    
    ## 完成
    
    所有明确出场都已复用、新建、变体化或标为真实未决;需要保持的视觉事实与状态变化已写入唯一
    视觉设定文档,即完成。图片提示词、分镜、审查和生产不自动开始。
    五份创作文档齐备后,可转 `$short-drama` 对跨文档结构做一次机械核对;内容质量仍由创作者审查。
    
    ## 安装维护
    
    只有安装、升级或排障时运行 `python3 scripts/selftest.py`。
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related