eternityspring/shuohao-skills · 第 2 次运行

写了一份全剧骨架,2,077 字(去掉空白计)。

skill
eternityspring/shuohao-skills@5612d2a0
装进工作目录的 skill
character-refs、novel-art、novel-characters、novel-outline、novel-script、novel-staging、novel-storyboard
时间
2026-10-31 08:20–08:25(北京时间),5 分钟
轮数
3 轮,工具调用 11 次,按 API 价格折算 $3.01
匿名编号
735(评审只看到这个编号)
盲比
和另外 20 个参赛者的第 2 次运行各比一场:对照组(不装 skill) · zenstory-ai/drama-skills · lixiaoxiao9888-create/manju-laoli-skill · jtydhr88/screenwriting-skills · Shanyin-ai/shanyin-screenwriting-master · lixiaoxiao9888-create/short-drama-factory · adoria111/emma-screenwriting-skills · zhu5859243-free/hongguo-shortdrama-screenwriting · catyiqian/qianqianwriting · Hao0321/ai-short-drama · zenstory-ai/oh-story-claudecode · lingfengQAQ/webnovel-writer · 0xsline/short-drama · Tomsawyerhu/Chinese-WebNovel-Skill · xindoo/sumeru · LanyXiaosheng/bookflow-skill · dama-cyber/Distilled-Novel-Toolbox · tance-mang/chinese-webnovel-skills · xiaofeng-928/chinese-longnovel-skill · qingyabailu-git/webfiction-write
原始文件
产出和问答记录 · 原始日志

写出的文件

标了标签的是认定为全剧骨架、拿去匿名打包的文件(deliverables.toml)。

验尸官的婚礼-outline.json 10,851 字
{
  "source": "验尸官的婚礼",
  "lang": "zh",
  "stage": "skeleton",
  "params": {
    "episodes": 60,
    "minutesPerEpisode": 1.5,
    "genre": "女频·大女主复仇悬疑·豪门",
    "adaptMode": "忠实",
    "preferences": [
      "每集至少拿出一样看得见的证据(疤、切片、录音……),沈家每次都毁证或抹黑",
      "林晚每赢一次,既离三年前的真凶更近,也离她自己签过的假报告更近",
      "结局按选题卡锁死:宋知远认罪、宋婉仪交出沈氏、程野带妹妹自首、林晚当庭改签他杀、墓前放表",
      "爽点落在沈家一家和周启年脸上"
    ]
  },
  "adaptation": {
    "core": "被退婚的女法医在前未婚夫的婚礼上认出新郎是替身,一件件拿证据剥开沈家,最后连自己签过的假报告一起推翻。",
    "keep": [
      {
        "what": "婚礼上一眼认出替身的开场",
        "why": "全剧卖点,第 1 集必须兑现",
        "evidence": "被退婚的女法医去参加前未婚夫的婚礼,一眼认出新郎是替身:真新郎三年前就躺在她的解剖台上。"
      },
      {
        "what": "一集一样证据的推进引擎",
        "why": "管住 60 集的节奏:证据出现 → 沈家毁证抹黑 → 林晚反杀,循环不断",
        "evidence": "每一集她都要拿出一样看得见的证据:一道疤、一张切片、一段录音。"
      },
      {
        "what": "林晚自己签过的假报告",
        "why": "大女主的内伤与终局:赢到最后要先审判自己,这是区别于普通复仇剧的落点",
        "evidence": "被周启年逼着改成“意外”,签了字,这是她心里的一根刺。"
      },
      {
        "what": "左手虎口的疤",
        "why": "第一件证据,也是替身的破绽:沈峥有疤,程野没有",
        "evidence": "婚礼上一直戴着白手套,左手虎口光洁,没有疤。"
      },
      {
        "what": "苏曼丽的求救与反水",
        "why": "前期的帮手,中段的刀子,豪门联姻的利益逻辑",
        "evidence": "答谢宴上偷偷把求救的房卡塞给她;为了家里的生意,后面还会反水一次。"
      },
      {
        "what": "沈砚那晚在游艇上",
        "why": "埋给宋知远嫁祸的雷,也是沈砚自己的心结",
        "evidence": "那晚游艇上他也在,喝醉了,什么都记不清,这件事他谁都没说。"
      },
      {
        "what": "遗嘱股份是宋婉仪找替身的动机",
        "why": "让主母可恨但可回头,最后交出沈氏才成立",
        "evidence": "长孙沈峥三十岁接手沈氏一半的股份,人没了就归二房。"
      }
    ],
    "cut": [
      {
        "what": "林晚与沈峥四年前的恋爱往事",
        "why": "只留闪回碎片(旧表、虎口疤的来历),不单开感情线;一集 1–2 分钟装不下回忆戏"
      },
      {
        "what": "二房争产线",
        "why": "二房只作为遗嘱条款里的压力存在,不出场,免得多出一组脸和一条豪门内斗副线"
      },
      {
        "what": "林晚与沈砚的明确恋爱线",
        "why": "两人是盟友,只留克制的张力;大女主的落点是自我翻案,不是嫁入沈家"
      },
      {
        "what": "宋知远海外洗钱的跨国过程",
        "why": "海外场景全部场外处理,洗钱只落在码头仓库和一张货运单上"
      }
    ],
    "merge": [
      {
        "what": "办案刑警、纪委、押送人员并为一名刑侦支队队长",
        "why": "官方力量只需要一张脸,负责立案、取证、抓捕"
      },
      {
        "what": "营销号、狗仔、发布会记者并为一名跟拍记者",
        "why": "沈家抹黑林晚的嘴只需要一张脸"
      },
      {
        "what": "法医中心同事并为一名实习法医",
        "why": "林晚停职后在中心里需要一双手:调档、送检、通风报信"
      }
    ],
    "risks": [
      {
        "what": "程野与闪回中的沈峥是同一张脸,AI 生成容易串",
        "plan": "共用一套面部资产;区分靠可见标记:沈峥左手虎口有疤、表戴在自己腕上;程野常年白手套"
      },
      {
        "what": "手部特写多(虎口疤、白手套、袖扣、表),AI 手部易崩",
        "plan": "手部特写一律静态插入镜头,单手入画,不做手指复杂动作"
      },
      {
        "what": "解剖、尸体画面过审风险",
        "plan": "只拍白布、托盘、显微镜视野和报告纸,不露伤口"
      },
      {
        "what": "水戏:游艇抛尸闪回、程野坠海",
        "plan": "落水只拍入水前和出水后,水下用暗场处理"
      },
      {
        "what": "60 集证据循环容易重复",
        "plan": "证据介质轮换:身体(疤)→ 检材(切片)→ 声音(录音)→ 物证(袖扣)→ 文书(报告),每幕换一种"
      }
    ],
    "cutNote": "这意味着:全剧只有一条主线——查清沈峥之死,副线是林晚对自己那份签字的审判;终点在法庭和墓前,不在豪门争产。",
    "mergeNote": "主角组定林晚、沈砚、程野、宋知远:林晚有完整的自我翻案弧,沈砚从不信到内应,程野从替身到自首,宋知远是最终对手;周启年、宋婉仪、苏曼丽是爽点的落点,放重要配角。"
  },
  "characters": [
    {
      "id": "C01",
      "name": "林晚",
      "role": "女主 · 法医",
      "tier": "lead",
      "arc": "从心里扎着一根刺、只敢私下查的停职法医,到当庭推翻自己签过的报告,重新签下沈峥他杀",
      "from": ["选题卡林晚"]
    },
    {
      "id": "C02",
      "name": "沈砚",
      "role": "沈家二少 · 沈峥的弟弟 · 林晚的内应",
      "tier": "lead",
      "arc": "从不信法医的浪荡二少,到唯一的内应;被舅舅嫁祸成杀兄凶手,靠林晚洗清,也找回那晚的记忆",
      "from": ["选题卡沈砚"]
    },
    {
      "id": "C03",
      "name": "程野",
      "role": "新郎 · 顶着沈峥的脸的替身",
      "tier": "lead",
      "arc": "从怕死的赌债替身,到被灭口时被林晚救下、反手做污点证人,最后带妹妹自首",
      "from": ["选题卡程野"]
    },
    {
      "id": "C04",
      "name": "宋知远",
      "role": "沈家舅舅 · 真凶 · 最终对手",
      "tier": "lead",
      "arc": "前半程是最可靠的舅舅,后半程撕下脸来灭口、嫁祸外甥、挟持人质,最后在码头被截认罪",
      "from": ["选题卡宋知远"]
    },
    {
      "id": "C05",
      "name": "周启年",
      "role": "法医中心主任 · 三年前逼她签字的人",
      "tier": "support",
      "arc": "用职权停职、栽赃林晚,被她录下受贿自白倒台,为减刑交出藏了三年的物证",
      "from": ["选题卡周启年"]
    },
    {
      "id": "C06",
      "name": "宋婉仪",
      "role": "沈家主母",
      "tier": "support",
      "arc": "为保股份找替身、一直以为儿子是意外,护着弟弟打压林晚;得知凶手是弟弟后交出沈氏",
      "from": ["选题卡宋婉仪"]
    },
    {
      "id": "C07",
      "name": "苏曼丽",
      "role": "新娘 · 苏家千金",
      "tier": "support",
      "arc": "带头踩林晚,暗里塞房卡求救;为港口生意反水一次,最后交出码头货运单赎回自己",
      "from": ["选题卡苏曼丽"]
    },
    {
      "id": "C08",
      "name": "程野的妹妹",
      "role": "宋知远拿来拴住程野的人质",
      "tier": "functional",
      "from": ["原创:从结局“程野带着妹妹去自首”补出,给程野一个不能逃的理由"]
    },
    {
      "id": "C09",
      "name": "实习法医",
      "role": "林晚在法医中心的一双手",
      "tier": "functional",
      "from": ["原创", "合并:法医中心同事"]
    },
    {
      "id": "C10",
      "name": "刑侦支队队长",
      "role": "立案、抓捕、受理自首的官方力量",
      "tier": "functional",
      "from": ["原创", "合并:办案刑警、纪委、押送人员"]
    },
    {
      "id": "C11",
      "name": "跟拍记者",
      "role": "沈家抹黑林晚的嘴",
      "tier": "functional",
      "from": ["原创", "合并:营销号、狗仔、发布会记者"]
    },
    {
      "id": "C12",
      "name": "审判长",
      "role": "终局庭审的主持者",
      "tier": "functional",
      "from": ["原创:结局当庭推翻报告需要"]
    }
  ],
  "scenes": [
    { "id": "S01", "name": "酒店宴会厅(婚礼 / 答谢宴 / 沈家寿宴)", "primary": true },
    { "id": "S02", "name": "酒店套房", "primary": true },
    { "id": "S03", "name": "法医中心解剖室", "primary": true },
    { "id": "S04", "name": "法医中心档案室", "primary": true },
    { "id": "S05", "name": "沈家老宅客厅", "primary": true },
    { "id": "S06", "name": "林晚公寓", "primary": true },
    { "id": "S07", "name": "沈家游艇(三年前闪回 / 重返现场)", "primary": true },
    { "id": "S08", "name": "沈氏集团会议室", "primary": true },
    { "id": "S09", "name": "港口码头仓库", "primary": true },
    { "id": "S10", "name": "法庭", "primary": true },
    {
      "id": "S11",
      "name": "公安局询问室",
      "primary": false,
      "reusePlan": "复用法医中心档案室的冷色灯光与金属桌,换单面镜背板"
    },
    {
      "id": "S12",
      "name": "沈峥墓前",
      "primary": false,
      "reusePlan": "复用码头仓库外的海天背景,换清晨薄雾和山坡草地前景"
    }
  ],
  "props": [
    {
      "id": "P01",
      "name": "男式旧机械表",
      "function": "沈峥当年送她的订婚表。第 3 集拿它试新郎,替身答不出来历;最后一集放回墓前——情感的锚和全剧的收束",
      "beatIds": ["B02", "B26"]
    },
    {
      "id": "P02",
      "name": "婚礼白手套",
      "function": "替身藏破绽的东西:手套一摘,虎口光洁,对不上沈峥的疤",
      "beatIds": ["B01", "B05"]
    },
    {
      "id": "P03",
      "name": "虎口疤痕照片",
      "function": "林晚私留的三年前尸检工作照,第一件实证;把无名男尸和沈峥连起来",
      "beatIds": ["B04", "B05"]
    },
    {
      "id": "P04",
      "name": "病理切片蜡块",
      "function": "尸体三天就被火化,蜡块是唯一的身体检材:既能做亲缘比对,也能复检溺死征象,证明死后入水",
      "beatIds": ["B06", "B09", "B16"]
    },
    {
      "id": "P05",
      "name": "求救房卡",
      "function": "苏曼丽的求救,也是她后来反水的把柄",
      "beatIds": ["B03", "B11"]
    },
    {
      "id": "P06",
      "name": "录音笔",
      "function": "林晚录下周启年承认受贿改结论的话,扳倒他;终局又录下宋知远的认罪",
      "beatIds": ["B17", "B24"]
    },
    {
      "id": "P07",
      "name": "船锚银袖扣",
      "function": "死者手里攥着的那一枚,被周启年私藏三年;对上宋知远袖口那对新配的,钉死真凶",
      "beatIds": ["B18", "B19", "B21"]
    },
    {
      "id": "P08",
      "name": "三年前的尸检报告",
      "function": "林晚签了名的“意外”结论:沈家拿它堵她的嘴,周启年拿它要挟她,终局她当庭改签“他杀”",
      "beatIds": ["B08", "B25"]
    }
  ],
  "beats": [
    { "id": "B01", "type": "悬念钩", "weight": "minor", "episode": 1, "setup": "新郎全程戴白手套,林晚敬酒时他的手套被酒打湿褪下", "payoff": "左手虎口光洁——她解剖过的那具无名男尸,虎口有一道一模一样位置的疤" },
    { "id": "B02", "type": "打脸", "weight": "minor", "episode": 3, "setup": "苏曼丽带头当众说她摸死人晦气、来闹婚礼", "payoff": "林晚亮出腕上的旧表,问新郎这块表是谁送的,新郎答不上,满场宾客看向他" },
    { "id": "B03", "type": "反转", "weight": "minor", "episode": 5, "setup": "苏曼丽前两集最凶", "payoff": "答谢宴上她把一张房卡塞进林晚掌心——新娘在求救" },
    { "id": "B04", "type": "揭破", "weight": "minor", "episode": 8, "setup": "周启年说三年前的档案早已封存,照片已经损毁", "payoff": "林晚拿出自己私留的工作照,虎口疤清清楚楚" },
    { "id": "B05", "type": "打脸", "weight": "major", "episode": 10, "setup": "沈家家宴,宋婉仪说儿子的疤早就做了激光祛除", "payoff": "林晚当众把照片拍在桌上:激光祛疤必然留下色差和皮纹断裂,新郎虎口皮纹完整,从来没有过疤。新郎不敢伸手,宋婉仪当场散席" },
    { "id": "B06", "type": "反击", "weight": "minor", "episode": 13, "setup": "周启年停她的职,把一批旧检材列进待销毁清单", "payoff": "焚化炉点火前,林晚和实习法医抢出写着那具无名男尸编号的蜡块" },
    { "id": "B07", "type": "收服", "weight": "minor", "episode": 16, "setup": "沈砚一直当她是来讹钱的前未婚妻", "payoff": "林晚说出尸体锁骨上一处陈旧骨折,那是沈砚小时候把哥哥推下树留下的,只有兄弟俩知道——沈砚倒向她" },
    { "id": "B08", "type": "打脸", "weight": "minor", "episode": 18, "setup": "周启年召开听证会要吊销她的执业资格,跟拍记者在门口直播", "payoff": "林晚当场指出档案签收时间是在火化之后补签的,周启年改口,听证会延期" },
    { "id": "B09", "type": "揭破", "weight": "major", "episode": 20, "setup": "沈砚偷偷给了她自己的血样", "payoff": "亲缘比对结果:无名男尸与沈砚是同胞兄弟。沈峥三年前就死了——林晚把报告摆在宋婉仪面前,却发现她一点都不惊讶" },
    { "id": "B10", "type": "反转", "weight": "minor", "episode": 23, "setup": "宋婉仪一直是体面的受害母亲", "payoff": "她承认替身是她找的:儿子落水是意外,股份不能归二房。她只知道意外,不知道谋杀" },
    { "id": "B11", "type": "反转", "weight": "minor", "episode": 26, "setup": "苏家港口合同被宋知远卡住", "payoff": "苏曼丽反水,拿出房卡作证林晚私闯套房、偷检材,林晚被带进询问室" },
    { "id": "B12", "type": "打脸", "weight": "minor", "episode": 28, "setup": "苏曼丽在询问室外作证", "payoff": "林晚用房卡的开门记录反证:那天是苏曼丽自己开的门。证词当场作废,林晚走出询问室" },
    { "id": "B13", "type": "反转", "weight": "major", "episode": 30, "setup": "程野察觉自己快没用了,宋知远约他夜里去码头", "payoff": "程野连人带车坠海,林晚把他拖上岸。他终于开口:自己是替身,整容、学签名全是宋家安排" },
    { "id": "B14", "type": "揭破", "weight": "minor", "episode": 33, "setup": "程野拿出替身三年的账本", "payoff": "整容款与封口费都走沈氏海外账户——海外业务是舅舅宋知远在管" },
    { "id": "B15", "type": "揭破", "weight": "minor", "episode": 36, "setup": "林晚和沈砚重返封存的游艇", "payoff": "机舱工具箱少了一把扳手,尺寸正好对上颅骨凹陷骨折" },
    { "id": "B16", "type": "揭破", "weight": "minor", "episode": 38, "setup": "沈家坚持儿子是醉酒落水", "payoff": "蜡块复检:肺组织里没有溺死征象,死者是死后入水。医学上锁死他杀" },
    { "id": "B17", "type": "打脸", "weight": "major", "episode": 40, "setup": "周启年拿她签过的报告要挟:翻案,你就是同谋", "payoff": "林晚当众放出录音:周启年亲口承认收了宋知远的钱改结论。刑侦支队队长把他带走" },
    { "id": "B18", "type": "揭破", "weight": "minor", "episode": 43, "setup": "周启年为求减刑", "payoff": "交出私藏三年的物证袋:死者手里攥着一枚船锚银袖扣" },
    { "id": "B19", "type": "揭破", "weight": "minor", "episode": 45, "setup": "沈家寿宴上宋知远照常一身和气", "payoff": "林晚注意到他袖口那对船锚袖扣,一枚旧一枚新——另一枚是后配的" },
    { "id": "B20", "type": "反转", "weight": "minor", "episode": 48, "setup": "宋知远说沈家男人人手一对袖扣", "payoff": "他拿出沈砚当晚醉在游艇上、袖扣也丢了一枚的证据,嫁祸沈砚杀兄,宋婉仪站到弟弟那边" },
    { "id": "B21", "type": "打脸", "weight": "major", "episode": 50, "setup": "沈砚被带走,宋知远在会议室接手沈氏", "payoff": "林晚用颅骨伤口角度推出凶手身高在一米八五以上、从上往下砸,沈砚不够高,而宋知远够。沈砚当场洗清" },
    { "id": "B22", "type": "反转", "weight": "major", "episode": 52, "setup": "宋婉仪一直以为儿子是意外", "payoff": "林晚把复检报告交到她手里:杀她儿子的是她的亲弟弟。宋婉仪当着股东会交出沈氏" },
    { "id": "B23", "type": "反击", "weight": "minor", "episode": 55, "setup": "宋知远挟持程野的妹妹,要从码头连夜出境", "payoff": "程野回到他身边当诱饵,苏曼丽交出码头货运单,妹妹被救出" },
    { "id": "B24", "type": "收网", "weight": "major", "episode": 57, "setup": "宋知远在码头最后一搏,扳手又举了起来", "payoff": "录音笔里是他亲口说出的游艇那一夜,刑侦支队队长收网,宋知远认罪" },
    { "id": "B25", "type": "自我翻案", "weight": "major", "episode": 59, "setup": "庭上对方律师拿出她三年前的签名,说她的证词不可信", "payoff": "林晚当庭承认那份报告是她签的、是假的,重新写下死者沈峥,他杀,签上自己的名字" },
    { "id": "B26", "type": "收束", "weight": "minor", "episode": 60, "setup": "程野带着妹妹去自首", "payoff": "林晚把那块旧表放在沈峥墓前" }
  ]
}
验尸官的婚礼-全剧骨架.md 全剧骨架 2,515 字
# 《验尸官的婚礼》全剧骨架

60 集 × 1–2 分钟 · 竖屏 · AI 制作 · 女频大女主复仇悬疑豪门

> 已定稿:砍线、合人、大爆点集数,以及第二节补出的底牌设定,都按本稿执行。

## 一、核心

被退婚的女法医在前未婚夫的婚礼上认出新郎是替身,一件件拿证据剥开沈家,最后连自己签过的假报告一起推翻。

**主线**:查清沈峥之死(替身 → 认尸 → 他杀 → 真凶)。
**副线**:林晚审判自己。她每赢一步,三年前那个签名就离她更近一步。

## 二、真相底牌(只给编剧,按第五节的节奏放)

- 四年前:林晚与沈峥订婚,沈峥送她一块男式机械表。
- 三年前:沈峥发现舅舅宋知远借海外港口洗钱。游艇上,宋知远用扳手砸他后脑,推下海。沈砚醉倒在船上。死者手里攥下宋知远一枚船锚袖扣。
- 无名男尸送到林晚的解剖台,她初判他杀。周启年收了宋知远的钱,私藏袖扣,逼她改签“意外”,三天火化,只剩病理蜡块。
- 宋婉仪以为儿子是意外落水。为保住遗嘱股份,她让宋知远找来程野整容顶替。替身以“国外养病”为名回国。
- 一年前:替身怕被林晚看穿,当众退婚。
- 现在:沈峥“三十岁”生日临近,股份要交接,所以必须和苏家联姻坐实身份。这是全剧的倒计时。

## 三、人物(12 人)

**主角组**
- **林晚**:一根刺 → 当庭改签“他杀”。说话不抬声调,认证据不认人。
- **沈砚**:不信她 → 内应 → 被嫁祸杀兄 → 洗清,找回那晚的记忆。
- **程野**:怕死的替身 → 被灭口时被林晚救下 → 污点证人 → 带妹妹自首。
- **宋知远**:前半程是最可靠的舅舅,第 45 集起撕下脸来,第 57 集认罪。

**重要配角(爽点落点)**
- **周启年**:停职、栽赃林晚 → 第 40 集倒台 → 交出袖扣。
- **宋婉仪**:护替身、护弟弟 → 第 52 集交出沈氏。
- **苏曼丽**:踩人 → 求救 → 反水(第 26 集)→ 交出货运单赎罪。

**功能角色**:程野的妹妹(人质)、实习法医、刑侦支队队长、跟拍记者、审判长。

## 四、五幕结构

| 幕 | 集数 | 证据介质 | 走向 |
|---|---|---|---|
| 一 · 婚礼认尸 | 1–10 | 疤 | 认出替身,旧表试探,房卡求救,家宴当众比对虎口疤 |
| 二 · 切片认亲 | 11–22 | 切片 | 停职,抢下蜡块,收服沈砚,DNA 认尸,宋婉仪早就知情 |
| 三 · 替身倒戈 | 23–34 | 人证 | 主母承认找替身,苏曼丽反水,程野坠海获救招供,钱指向海外 |
| 四 · 游艇他杀 | 35–46 | 录音 + 物证 | 重返游艇,复检死后入水,录音扳倒周启年,袖扣浮出水面 |
| 五 · 真凶与翻案 | 47–60 | 文书 | 宋知远嫁祸沈砚,伤口角度洗清,主母倒戈,码头收网,当庭改签,墓前放表 |

## 五、爽点表(26 个,间隔 ≤ 3 集,★ 为大爆点)

| 集 | 类型 | 落点 |
|---|---|---|
| 1 | 钩 | 手套褪下,虎口光洁——真沈峥有疤 |
| 3 | 打脸 | 旧表是谁送的?新郎答不上 |
| 5 | 反转 | 苏曼丽塞房卡求救 |
| 8 | 揭破 | 档案说照片已损毁,她拿出私留的疤痕照 |
| ★10 | 打脸 | 家宴上宋婉仪说激光祛过疤,林晚指出祛疤必留痕,替身不敢伸手 |
| 13 | 反击 | 焚化炉前抢下蜡块 |
| 16 | 收服 | 锁骨旧伤只有兄弟俩知道,沈砚倒向她 |
| 18 | 打脸 | 听证会上拆穿档案补签,周启年改口 |
| ★20 | 揭破 | DNA 证实无名男尸就是沈峥,宋婉仪不惊讶 |
| 23 | 反转 | 主母承认替身是她找的 |
| 26 | 反转 | 苏曼丽反水,林晚被带走 |
| 28 | 打脸 | 房卡开门记录反证,苏曼丽证词作废 |
| ★30 | 反转 | 程野连人带车坠海,林晚救起,他招供 |
| 33 | 揭破 | 替身款走海外账户,指向舅舅 |
| 36 | 揭破 | 游艇少一把扳手,对上颅骨骨折 |
| 38 | 揭破 | 复检证实死后入水 |
| ★40 | 打脸 | 录音曝光,周启年被带走 |
| 43 | 揭破 | 周启年交出死者攥着的袖扣 |
| 45 | 揭破 | 宋知远的袖扣一旧一新 |
| 48 | 反转 | 宋知远嫁祸沈砚杀兄 |
| ★50 | 打脸 | 伤口角度推出凶手身高 ≥185,洗清沈砚 |
| ★52 | 反转 | 宋婉仪得知凶手是弟弟,交出沈氏 |
| 55 | 反击 | 码头救出程野妹妹 |
| ★57 | 收网 | 宋知远认罪 |
| ★59 | 自我翻案 | 当庭推翻自己的报告,重签“死者沈峥,他杀” |
| 60 | 收束 | 墓前放表 |

## 六、叙事道具(8 件)

旧机械表、婚礼白手套、虎口疤痕照片、病理切片蜡块、求救房卡、录音笔、船锚银袖扣、三年前的尸检报告。

## 七、场景(10 主 + 2 复用)

**主场景**:宴会厅、酒店套房、解剖室、档案室、沈家客厅、林晚公寓、游艇、沈氏会议室、码头仓库、法庭。
**复用场景**:询问室(借档案室改)、墓前(借码头海天背景改)。

## 八、铁律(管住分集和剧本)

1. 每集至少亮一样看得见的证据。沈家每次都要毁证或抹黑,林晚当集或下集反杀。
2. 林晚不哭,不吼,不求人。打脸靠专业判断,不靠运气和外援。
3. “自己签过字”每幕至少被人拿出来刺她一次,第 59 集才拔刺。
4. 观众比林晚早知道的只有一件事:程野在怕。真凶身份第 33 集起才露线头,第 45 集才落到宋知远身上。
5. 不加感情线。林晚和沈砚是盟友,最多留克制的张力。
6. 制作约束:
   - 解剖戏只拍白布、托盘、显微镜视野。
   - 手部特写单手静态入画。
   - 沈峥闪回和程野共用一张脸,靠疤和手套区分。
   - 落水只拍入水前和出水后。

过程

按时间顺序。点开工具调用能看到完整的输入和返回;思考的内容 Claude Code 不对外输出,日志里只有「在这里思考过多久」。

第 1 轮

输入:
/novel-outline 我要给一部短剧写全剧骨架,要求在 premise.md 里,写好存成文件放在当前目录。

思考

Bash List directory and read premise file

输入

ls -la && cat premise.md && ls .claude/skills/novel-outline .claude/skills/novel-outline/references

返回

total 16
drwxr-xr-x  4 user  wheel   128 Oct 30 17:20 .
drwxr-xr-x  4 user  wheel   128 Oct 30 17:20 ..
drwxr-xr-x  4 user  wheel   128 Oct 30 17:20 .claude
-rw-r--r--  1 user  wheel  4233 Oct 30 17:20 premise.md
这是一张选题卡。它要拍成 60 集竖屏短剧,每集 1–2 分钟,全部由 AI 拍。
请交一份全剧骨架:整部剧怎么走,用来管住后面的分集大纲和剧本。
只交骨架,不写分集大纲和剧本正文,3000 字以内。

《验尸官的婚礼》

标签:女频、大女主、复仇、悬疑、豪门

一句话卖点:被退婚的女法医去参加前未婚夫的婚礼,一眼认出新郎是替身:真新郎三年前就躺在她的解剖台上。

观众代入谁:林晚,被当众退婚的女法医,冷静、专业,靠本事翻盘

爽点落在谁脸上:沈家一家,和三年前逼她签字的周启年

故事靠什么往前推:每一集她都要拿出一样看得见的证据:一道疤、一张切片、一段录音。沈家每次都想办法毁掉证据、抹黑她;她每赢一次,就离“三年前是谁杀了沈峥”更近一步,也离她自己签过的那份假报告更近一步。

人物:
- **林晚**(女主 · 法医)
  - 样子:28 岁,齐耳短发,素颜,常穿一件深灰长风衣;左手腕戴一块男式旧机械表,表带磨白了。说话不抬声调。
  - 小传:市法医中心的法医,干了七年。四年前和沈家长子沈峥订婚,一年前被“沈峥”当众退婚,说她天天摸死人、晦气。三年前她解剖过一具无名男尸,最初写的是“他杀”,被周启年逼着改成“意外”,签了字,这是她心里的一根刺。冷静,嘴不饶人,认证据不认人。
- **程野**(新郎 · 顶着沈峥的脸的替身)
  - 样子:30 岁,和沈峥一样的脸、一样的金丝眼镜;婚礼上一直戴着白手套,左手虎口光洁,没有疤。
  - 小传:欠了一身赌债的模仿演员。三年前被宋家找去整容,学沈峥说话、走路、签名,替他活着。他不知道沈峥是怎么死的,只知道自己哪天没用了,也会“意外”。
- **沈砚**(沈家二少 · 沈峥的弟弟)
  - 样子:26 岁,寸头,西装从来不扣扣子,右耳一枚黑耳钉,笑起来像什么都不在乎。
  - 小传:从小跟在哥哥后面。三年前哥哥从国外“养病”回来,他第一个觉得不对,可没人信他。那晚游艇上他也在,喝醉了,什么都记不清,这件事他谁都没说。一开始不信林晚这个法医,后来成了她在沈家唯一的内应。
- **苏曼丽**(新娘 · 苏家千金)
  - 样子:26 岁,长卷发,永远带着全妆,酒红色指甲。
  - 小传:苏家和沈氏一起做港口生意,她嫁进沈家是两家的买卖。新婚夜她就觉得丈夫不对,却不敢说。前期带头踩林晚,答谢宴上偷偷把求救的房卡塞给她;为了家里的生意,后面还会反水一次。
- **周启年**(法医中心主任 · 三年前逼她签字的人)
  - 样子:55 岁,花白背头,老花镜挂在胸前,腕上一块和工资不相称的金表。
  - 小传:林晚的老领导。三年前收了宋知远的钱,把那具无名男尸的结论从“他杀”改成“意外”,逼林晚签字,三天内火化。
- **宋婉仪**(沈家主母 · 沈峥、沈砚的母亲)
  - 样子:52 岁,盘发,素色旗袍,珍珠耳环,说话永远轻声细语。
  - 小传:丈夫早逝,靠弟弟宋知远撑起沈氏。按老爷子的遗嘱,长孙沈峥三十岁接手沈氏一半的股份,人没了就归二房。三年前儿子“落水”,她为了保住股份找人顶替了他,一直以为儿子是意外死的。
- **宋知远**(宋婉仪的弟弟 · 沈氏海外业务负责人)
  - 样子:48 岁,高个子,笑容和气,袖口总是一对船锚形状的银袖扣。
  - 小传:沈家人眼里最可靠的舅舅。借沈氏的海外港口洗钱,三年前被沈峥发现,在游艇上用扳手砸了他的后脑,推下海;之后买通周启年改了结论,又让姐姐以为是意外。全剧最后的对手。

结局:宋知远认罪伏法,宋婉仪交出沈氏,程野带着妹妹去自首。林晚当庭推翻自己三年前签的报告,重新写下“死者沈峥,他杀”,签上自己的名字。最后一场,她把那块表放在沈峥的墓前。
.claude/skills/novel-outline:
README.en.md
README.md
SKILL.md
assets
examples
references
scripts

.claude/skills/novel-outline/references:
episode-pass.md
outline-pass.md
report-style.md
schema.md
volume-pass.md

思考 2 秒

Bash Read outline-pass and schema references

输入

cd /scratch/003/ws/003-skeleton/eternityspring__shuohao-skills/run-2/.claude/skills/novel-outline && cat references/outline-pass.md references/schema.md

返回

# 骨架 · 砍线 / 合人 / 排爽点

拿全部卷摘要(或短篇原文)+ 用户参数,产出 outline.json 的**骨架四块**:`adaptation` / `characters` / `scenes` / `beats`。分集梗概**这一步不写**——骨架没拍板就写分集,方向错了全废。

## 顺序就是方法

1. **砍线**。短剧只装得下一条主线 + 至多一条副线。砍掉的写进 `adaptation.cut`,每条都要有 why。`adaptMode` 不是忠实时一条不砍会被 validate 拦。
2. **合人**。功能重复的角色合成一张脸(两个报信的 → 一个),合并进 `characters[].from` 的 ← 改动记录。然后**分档**(`tier`):主角组 `lead` 1–5 人、有名字的重要配角 `support` ≤ 10、功能性角色 `functional` ≤ 10。功能性角色占脸不占名——`name` 用称呼标签(「急诊医生」),也不用写人物弧。无名背景人不进表。
3. **收场景**。主场景上限随集数走(4 + ⌈集数/10⌉,夹在 5–15:6 集给 5 个,60 集给 10 个),以 `validate` 报的数为准。这是 AI 短剧的宽松版——场景是生成的,多开几个景换观赏性是划算的,别把戏都憋在一个屋里。原著里只出现一次的场景,要么砍掉、要么写 `reusePlan`(复用哪个现有环境资产、换什么时段天气改出来)。
4. **排爽点**。先定 major(大爆点)落在哪几集,再拿 minor 填缝。硬规则:相邻间隔 ≤ 3 集、开头结尾无真空、最早的 major 不能在最后一集。`type` 跟着题材走——女频打脸和男频战神的爽点不是一种东西,看 `params.genre`。
5. **收道具**。**排在爽点之后不是随便排的**——叙事道具的判据是「它托起哪个爽点」,爽点没定就分不清哪件物件是道具、哪件只是陈设。上限 8 件,跟主角数量一个量级。每件写 `function`(它在戏里承载什么)与 `beatIds`(托起哪几个爽点),分集用 `propIds` 引用。

   **一件东西该不该进这张表,问一句就够了:它坏了、丢了、被换掉,剧情会不会塌。** 会塌就是叙事道具(皮箱、砚台、那封信);不会塌就是场景陈设(船舱的挂灯、桌上的茶碗),归 `novel-art` 的场景锚点管,不进这张表。填不出 `function` 的一律不收——**填不出来的不是道具,是背景**。

每一步都基于卷摘要里的原料。**关键取舍在 `adaptation.keep` 里附 `evidence`(原文逐字片段)**——大纲要能指回原文,不是凭印象改编。

砍完、合完之后各补一句结论:`adaptation.cutNote` 写砍线的含义(「这意味着:全剧终点是……」),`adaptation.mergeNote` 写主角组入选理由(谁有完整转变弧)。这两句会出现在报告最前面的「关键决策」区块,也是拍板时给用户看的核心内容。

## 两轮规则 ⛔

骨架必须跑两轮:

- **第一轮快版**:四块填齐、能过 `validate --stage beats` 就停,**交给用户拍板**。砍了哪条线、合了哪些人、大爆点在第几集——这三件事用户不点头,后面全是白干。
- **第二轮细版**:吸收用户意见后细化,再过一次 `validate --stage beats`。

快版的意义是便宜——错了只损失一轮骨架,不是 60 集梗概。

## 自检

写完跑:

```bash
node scripts/novel-outline.mjs validate <outline.json> --stage beats
```

有违规逐条修,改完重跑,直到通过才允许进分集。
# outline.json 结构

五件套的载体。**模型只管填这份 JSON**,Markdown 和 report.html 由 `render` 渲染出来,资产清单由脚本汇总——五件套是四件模型写 + 一件算出来的。

```json
{
  "source": "书名",
  "lang": "zh",
  "params": { "episodes": 60, "minutesPerEpisode": 2, "genre": "女频逆袭", "adaptMode": "抽核", "preferences": [] },
  "adaptation": { "core": "…", "keep": [], "cut": [], "merge": [], "risks": [] },
  "characters": [ { "id": "C01", "name": "…", "role": "…", "arc": "…", "from": ["原著…", "合并:…"] } ],
  "scenes": [ { "id": "S01", "name": "…", "primary": true, "reusePlan": "…" } ],
  "beats": [ { "id": "B01", "type": "打脸", "weight": "major", "episode": 3, "setup": "…", "payoff": "…" } ],
  "episodes": [ { "ep": 1, "synopsis": "…", "hook": "…", "suspense": "…", "sceneIds": ["S01"], "characterIds": ["C01"], "propIds": ["P01"], "crowdPlan": "…", "warnings": [] } ]
}
```

## params

| 字段 | 必填 | 说明 |
| --- | --- | --- |
| `episodes` | 是 | 总集数,正整数。分集数量必须与它一致 |
| `minutesPerEpisode` | 是 | 单集时长(分钟) |
| `genre` | 是 | 题材,**决定爽点类型**,不许缺。投放平台的差异不单设字段,直接体现在 `thresholds` 上 |
| `adaptMode` | 是 | `忠实` / `抽核` / `借壳`,只能这三个 |
| `preferences` | 否 | 用户点名要保的角色、戏 |
| `thresholds` | 否 | 逐项覆盖质量门阈值:`maxLeads`(5) / `maxSupport`(10) / `maxFunctional`(10) / `maxBeatGap`(3) / `maxPrimaryScenes`(缺省不是常数,**随集数动态**:4 + ⌈集数/10⌉ 夹在 5–15,60 集 → 10。按 AI 短剧定的——场景是生成的,上限守的是一致性资产不是搭景钱)。短篇建议收紧角色档 |

## adaptation 改编说明

`core` 一句话核心,必填。`keep` / `cut` / `merge` 每条 `{what, why}`,`keep` 至少一条;**`adaptMode` 不是忠实时 `cut` 不能为空**。`keep` 可带 `evidence`——**原文逐字片段**,禁止凭书名脑补的对策就在这:关键取舍要能指回原文。`risks` 每条 `{what, plan}`。

两个可选的**决策补注**,报告的「关键决策」区块会展示:

- `cutNote` — 砍线的结论句(「这意味着:全剧终点是……原著后 30 章基本不用」这种),说清砍完之后故事的终点变成了什么
- `mergeNote` — 合人的补注,通常写主角组入选理由(谁有完整转变弧)

给了就不能是空字符串,`validate` 会拦。大爆点落点列表**不用写**——报告从 `beats` 里自动算。

## characters 人物表

每个字段都以校验器为准,一个不多一个不少:

| 字段 | 必填 | 说明 |
| --- | --- | --- |
| `id` | 是 | `C01` 格式,全局唯一,分集靠它引用 |
| `name` | 是 | 姓名;功能性角色用称呼标签(「急诊医生」) |
| `role` | 是 | 定位一句话:女主 / 搅局配角 / 渡口的报时人…… |
| `tier` | 是 | 三档之一,见下表 |
| `arc` | lead / support 必填 | 人物弧;functional 可省——医生就是来缝针的 |
| `from` | 是 | **← 改动记录**,非空数组:原著对应谁、合并了谁、纯原创写 `["原创"]` |

- `tier` 只能三档——一刀切的角色上限混淆了「观众要记住谁」和「制作要维护多少张脸」,分档把它拆开:

  | tier | 是谁 | 上限 | 规则 |
  | --- | --- | --- | --- |
  | `lead` | 主角组(男女主 + 主反派) | 1–5 人 | `arc` 必填 |
  | `support` | 有名字的重要配角(亲属、闺蜜、副反派) | ≤ 10 | `arc` 必填 |
  | `functional` | 功能性角色(医生、秘书、店员) | ≤ 10 | **占脸不占名**:`name` 用称呼标签(「急诊医生」);`arc` 可省——医生就是来缝针的 |

  无名背景人不进表、不追踪、不限量。

  **这份表是下游 novel-characters 的角色清单**:谁进谁不进、谁是主角组在这里定死,角色设定照着做,不用再判断一遍轻重(`tier` 对应过去就是 `importance`:`lead` → protagonist、`support` → supporting、`functional` → minor)。反过来,手上已经有 cast.json 的话也能映射进来:protagonist/major → `lead`,supporting → `support`,minor → `functional`

## scenes

- `id` 格式 `S01`,全局唯一;`name` 必填;`primary` 必填布尔(主场景上限随集数动态,见 `thresholds`)
- **在全剧只出现一次的场景必须带 `reusePlan`**(规避方案:复用哪个现有环境资产、换什么时段天气改出来)

## props 叙事道具

- **可选字段**。没写照常通过全部质量门(`prop-cap` 与 `refs` 两道会明说跳过);写了就按下面查
- `id` 格式 `P01`,全局唯一;`name` 必填
- **`function` 必填——这一层唯一要拍板的东西:这件物件在戏里承载什么。** 填不出来说明它不是叙事道具,是场景陈设,那归 `novel-art` 的场景锚点管,不进这张表
- `beatIds` 可选:它托起哪几个爽点。写了就必须指向 `beats` 里真实存在的 id
- 上限 `maxProps` 默认 8 件(进 `params.thresholds` 可覆盖)。跟主角数量一个量级——**只收有特写、跨集出现、承载剧情的**
- 分集用 `episodes[].propIds` 引用。**没有任何一集引用的道具会被 `refs` 门点名**——跟失业角色、空转场景同一个判据

边界:尺度、锚点、状态变体、白底提示词都是 `novel-art` 的活,不在这里定。这张表回答「哪几件物件承载剧情、各自承载什么」,美术层回答「它长什么样、怎么保证六集都长一样」。

## beats 爽点表

- `id` 格式 `B01`;`type` 自由文本(打脸/揭破/反转…);`weight` 只能 `major` / `minor`(缺省算 minor)
- `episode` 是**唯一数据源**——分集不再列 beatIds,节奏条和资产清单都从这里算
- 硬规则:相邻爽点间隔 ≤ `maxBeatGap`,开头结尾无真空;**至少一个 major,且最早的 major 不能落在最后一集**

## episodes 分集梗概

- `ep` 从 1 连续编号,总数等于 `params.episodes`
- `synopsis` / `hook` / `suspense` 三栏**都必填**——【钩子】【悬念】空了视为未完成
- **叙述体**:三栏里出现 `「」『』“”` 引号对白就是在写剧本,越界,validate 会拦
- `sceneIds` / `characterIds` 必填且必须指向已登记的 id;每个角色至少出现一集、每个场景至少用一次
- `propIds` 可选(写了 `props` 才有意义),必须指向已登记的道具 id;每件道具至少出现一集
- `characterIds` ≥ 3 的集必须写 `crowdPlan`(同框拆解方案)。**校验按人数判,是代理指标**——如果这一集三人实际不同框(分处不同场次),把这个事实写成方案即可:「三人分处两场,无同框,分场拍」,照样通过
- `warnings`:梗概里扫到生成难点关键词(雨戏/肢体接触/人群/手部特写)就必须列进来,宁可多报

## 校验

```bash
node scripts/novel-outline.mjs validate outline.json --stage skeleton|beats|full
node scripts/novel-outline.mjs checkup outline.json   # 只打印 14 道质量门 ✓/✗
```

stage 就是流程门:骨架拍板前过 `skeleton`,**写分集之前必须过 `beats`**(爽点间隔和 major 时机错了,分集写完全废),交付前过 `full`。

思考

Bash Inspect example outline and script stage handling

输入

head -c 5000 examples/渡口-outline.json; echo; grep -n "stage\|episodes" scripts/novel-outline.mjs | head -60

返回

{
  "source": "渡口",
  "lang": "zh",
  "params": {
    "episodes": 6,
    "minutesPerEpisode": 2,
    "genre": "民国情感悬疑",
    "adaptMode": "抽核",
    "preferences": [
      "保留浓雾开船的意象",
      "老周不能改成反派"
    ]
  },
  "adaptation": {
    "core": "浓雾渡口的一船陌生人,各自揣着说不出口的来路,雾散之前每个人都得亮一次底。",
    "keep": [
      {
        "what": "浓雾渡口的封闭空间",
        "why": "天然的单场景悬疑舞台,省景又聚戏",
        "evidence": "雾一厚,连自己的手都看不清。"
      },
      {
        "what": "沈知微怀里的旧皮箱",
        "why": "全剧最强的悬念道具,每集都能给一眼",
        "evidence": "她把一只旧皮箱抱在怀里,指节因为用力而发白。"
      }
    ],
    "cut": [
      {
        "what": "县城当铺的支线",
        "why": "空间跳出渡口,两分钟一集装不下第二个舞台"
      }
    ],
    "merge": [
      {
        "what": "岸边问路的路人甲乙并入胡二爷",
        "why": "同一个功能(打探消息)不需要三张脸"
      }
    ],
    "risks": [
      {
        "what": "全剧困在船上,节奏容易闷",
        "plan": "每集固定一次外部威胁打进来:汽笛、巡查、涨水"
      }
    ],
    "cutNote": "这意味着:全剧困在渡口一夜之内,当铺、县城全部场外处理,终点是开箱翻案,不是进城打官司。",
    "mergeNote": "主角组定的是沈知微与陆行远——全篇只有这两个人有完整的转变弧。"
  },
  "characters": [
    {
      "id": "C01",
      "name": "沈知微",
      "role": "女主",
      "arc": "从抱箱防备到当着全船开箱,把最怕被看见的东西变成翻盘的底牌",
      "from": [
        "原著沈知微"
      ],
      "tier": "lead"
    },
    {
      "id": "C02",
      "name": "陆行远",
      "role": "男主",
      "arc": "大衣口袋里的硬东西从威胁翻成守护,最后一集摊开手心",
      "from": [
        "原著陆行远"
      ],
      "tier": "lead"
    },
    {
      "id": "C03",
      "name": "老周",
      "role": "关键配角",
      "arc": "静止的锚,四十年不问客人来路,最后用一句话掀翻全船的谎",
      "from": [
        "原著老周",
        "合并:岸边艄公的台词功能"
      ],
      "tier": "support"
    },
    {
      "id": "C04",
      "name": "胡二爷",
      "role": "搅局配角",
      "arc": "自来熟的货郎,每集戳一个人的痛处,自己反而是藏得最深的",
      "from": [
        "原著胡二爷",
        "合并:岸边问路的路人甲乙"
      ],
      "tier": "support"
    },
    {
      "id": "C05",
      "name": "岸上挑灯的更夫",
      "tier": "functional",
      "role": "渡口的报时人,雾里只闻梆子声",
      "from": [
        "原创:把原著里「岸上远远的灯火」拟人化成可拍的角色"
      ]
    }
  ],
  "scenes": [
    {
      "id": "S01",
      "name": "渡船船舱",
      "primary": true
    },
    {
      "id": "S02",
      "name": "渡口栈桥",
      "primary": true
    },
    {
      "id": "S03",
      "name": "对岸芦苇滩",
      "primary": false,
      "reusePlan": "栈桥同一机位换背板,芦苇前景遮挡"
    }
  ],
  "props": [
    {
      "id": "P01",
      "name": "旧皮箱",
      "function": "沈知微的底牌与软肋:里面是一摞状纸和一块灵位。从「谁都不许碰」到当众打开,开箱即翻案——全剧最重的一件物证",
      "beatIds": [
        "B01",
        "B04"
      ]
    },
    {
      "id": "P02",
      "name": "县衙旧砚",
      "function": "陆行远的身份物证:砚底刻着县衙的印。掏出即揭破他不是揣枪的人,压到状纸上即与皮箱合成同一桩冤案",
      "beatIds": [
        "B02",
        "B04"
      ]
    }
  ],
  "beats": [
    {
      "id": "B01",
      "type": "悬念钩",
      "weight": "minor",
      "episode": 1,
      "setup": "皮箱谁都不许碰",
      "payoff": "胡二爷伸手要帮忙拎箱,被沈知微一把按住,全船瞬间安静"
    },
    {
      "id": "B02",
      "type": "身份揭破",
      "weight": "major",
      "episode": 3,
      "setup": "陆行远的右手始终揣在大衣口袋里",
      "payoff": "胡二爷一句玩笑逼得他当众掏出口袋——是一方旧砚,不是枪,可砚底刻着县衙的印"
    },
    {
      "id": "B03",
      "type": "反转",
      "weight": "major",
      "episode": 5,
      "setup": "老周四十年不问客人来路",
      "payoff": "老周头一次开口,点破沈知微要去的县城根本没有接应她的人"
    },
    {
      "id": "B04",
      "type": "收束",
      "weight": "minor",
      "episode": 6,
      "setup": "雾开始散",
      "payoff": "皮箱当众打开,船上每个人的谎都落了地,船也到了岸"
    }
61:export function primarySceneCap(episodes) {
62:  if (!Number.isInteger(episodes) || episodes < 1) return 8; // 没有集数信息给个居中值
63:  return Math.max(5, Math.min(15, 4 + Math.ceil(episodes / 10)));
171:  if (explicit.maxPrimaryScenes === undefined) th.maxPrimaryScenes = primarySceneCap(outline?.params?.episodes);
189:  const eps = Array.isArray(outline?.episodes) ? outline.episodes : [];
190:  const total = outline?.params?.episodes ?? eps.length;
354: * 三档 stage 就是流程门:
363:export function validateOutline(outline, stage = 'full') {
374:    if (!Number.isInteger(params.episodes) || params.episodes < 1) p('params.episodes 必须是正整数');
471:  if (stage === 'skeleton') return problems;
497:  if (stage === 'beats') return problems;
499:  // --- episodes 分集梗概 ---
500:  const eps = outline.episodes;
502:    p('episodes 为空');
504:    if (params?.episodes && eps.length !== params.episodes) {
505:      p(`分集写了 ${eps.length} 集,params.episodes 说好 ${params.episodes} 集`);
532:  const eps = Array.isArray(outline?.episodes) ? outline.episodes : [];
535:    const episodes = eps.filter((e) => (e?.sceneIds ?? []).includes(s.id)).map((e) => e.ep);
536:    return { id: s.id, name: s.name, primary: !!s.primary, uses: episodes.length, episodes, reusePlan: s.reusePlan ?? null };
540:    const episodes = eps.filter((e) => (e?.characterIds ?? []).includes(c.id)).map((e) => e.ep);
541:    return { id: c.id, name: c.name, role: c.role, tier: c.tier, uses: episodes.length, episodes };
547:    const episodes = eps.filter((e) => (e?.propIds ?? []).includes(pr.id)).map((e) => e.ep);
550:      uses: episodes.length, episodes, beatIds: pr.beatIds ?? [],
593:  'beat-gap': 'Beat gap ≤ {0} episodes, no dead zone',
628:      `${p.episodes} 集 × ${p.minutesPerEpisode} 分钟 · ${p.genre} · ${p.adaptMode}改编`,
635:      decisions: '关键决策', rhythm: '爽点节奏', episodes: '分集梗概',
636:      episodesOverview: '分集概览', matrix: '每集调度矩阵',
649:      episodes: '核心交付 · 每集三栏齐全',
656:      episodes: '总集数', perEp: (m) => `× ${m} 分钟`, runtime: (m) => `正片约 ${m} 分钟`,
725:      `${p.episodes} eps × ${p.minutesPerEpisode} min · ${p.genre} · ${p.adaptMode} adaptation`,
732:      decisions: 'Key decisions', rhythm: 'Beat rhythm', episodes: 'Per-episode synopses',
733:      episodesOverview: 'Episode overview', matrix: 'Dispatch matrix',
746:      episodes: 'Core deliverable · three fields per episode',
748:      sceneOverview: 'Top right = episodes present',
753:      episodes: 'Episodes', perEp: (m) => `× ${m} min`, runtime: (m) => `about ${m} min of footage`,
763:    showAllEps: (n) => `Show all ${n} episodes`,
781:    planPropSpec: 'One white-plate sheet plus state variants each; must stay identical across episodes',
846:  const { source, params, adaptation: ad, characters, beats, episodes } = outline;
883:  out.push(`## ${t.mdSec(4, t.sections.episodes)}`, '');
884:  for (const e of episodes) {
897:    out.push(mdRow([s.id, s.name, s.primary ? t.yes : t.no, s.episodes.join(t.sep), s.uses, s.reusePlan ?? '—']));
903:      out.push(mdRow([pr.id, pr.name, pr.function, pr.episodes.join(t.sep), pr.uses, pr.beatIds.join(t.sep) || '—']));
907:  for (const c of assets.characters) out.push(mdRow([c.id, c.name, c.role, c.episodes.join(t.sep), c.uses]));
976:  const total = outline.params.episodes;
1055:  const { source, params, adaptation: ad, characters, beats, episodes } = outline;
1060:  const total = params.episodes;
1077:  <div class="kpi accent"><div class="l">${esc(t.kpi.episodes)}</div><div class="v">${total} <small>${esc(t.kpi.perEp(params.minutesPerEpisode))}</small></div><div class="d">${esc(t.kpi.runtime(total * params.minutesPerEpisode))}</div></div>
1086:  const epCards = episodes
1109:    ...byTier(assets.characters).map((c) => mxRow(c.name, t.tier[c.tier] ?? c.tier, c.episodes, '', String(c.uses))),
1112:      mxRow(s.name, s.primary ? t.primaryScene : t.onceScene, s.episodes, ' sc', s.uses === 1 ? `${s.uses} ⚠` : String(s.uses)),
1119:          mxRow(pr.name, pr.beatIds.join(t.sep) || t.none, pr.episodes, ' pp', String(pr.uses)),
1140:      const set = new Set(s.episodes);
1149:      const castIn = [...new Set(episodes.filter((e) => (e.sceneIds ?? []).includes(s.id)).flatMap((e) => e.characterIds ?? []))];
1151:  <span class="snum">${esc(fmtEps(s.episodes, t))}</span>
1464:<section id="sec-episodes">
1465:  ${secHead('02', t.sections.episodesOverview, t.secNotes.episodes)}
1466:  <div class="epswrap${episodes.length > 3 ? ' clip' : ''}">
1470:    ${episodes.length > 3 ? `<button class="epsmore">▾ ${esc(t.showAllEps(episodes.length))}</button>` : ''}
1562:  validate <outline.json> [--stage s] 校验;有违规逐条打印并 exit 1
1563:                                      stage: skeleton / beats / full(默认 full)

思考 114 秒

Bash Read validator logic for skeleton and beats stages

输入

sed -n 363,500p scripts/novel-outline.mjs

返回

export function validateOutline(outline, stage = 'full') {
  const problems = [];
  const p = (msg) => problems.push(msg);
  if (!outline || typeof outline !== 'object') return ['outline 不是对象'];
  const th = thresholdsOf(outline);

  // --- params ---
  const params = outline.params;
  if (!params || typeof params !== 'object') {
    p('缺少 params(总集数/单集时长/题材/改编幅度)');
  } else {
    if (!Number.isInteger(params.episodes) || params.episodes < 1) p('params.episodes 必须是正整数');
    if (!(params.minutesPerEpisode > 0)) p('params.minutesPerEpisode 必须大于 0');
    if (!thText(params.genre)) p('params.genre 缺失——题材决定爽点类型,不能缺');
    if (!ADAPT_MODES.includes(params.adaptMode)) {
      p(`params.adaptMode 必须是 ${ADAPT_MODES.join('/')},实际是 ${JSON.stringify(params.adaptMode)}`);
    }
  }

  // --- adaptation 改编说明 ---
  const ad = outline.adaptation;
  if (!ad || typeof ad !== 'object') {
    p('缺少 adaptation(改编说明)');
  } else {
    if (!thText(ad.core)) p('adaptation.core 缺失——一句话核心是整份大纲的锚');
    for (const key of ['keep', 'cut', 'merge', 'risks']) {
      if (!Array.isArray(ad[key])) p(`adaptation.${key} 必须是数组`);
    }
    if (Array.isArray(ad.keep) && ad.keep.length === 0) p('adaptation.keep 至少要有一条——什么都不保还改编什么');
    if (params?.adaptMode && params.adaptMode !== '忠实' && Array.isArray(ad.cut) && ad.cut.length === 0) {
      p(`adaptMode=${params.adaptMode} 却一条线都没砍,说不过去`);
    }
    for (const [key, fields] of [['keep', ['what', 'why']], ['cut', ['what', 'why']], ['merge', ['what', 'why']], ['risks', ['what', 'plan']]]) {
      for (const item of ad[key] ?? []) {
        for (const f of fields) if (!thText(item?.[f])) p(`adaptation.${key} 里有条目缺 ${f}`);
      }
    }
    // 决策补注(可选):给了就不能是空壳
    for (const f of ['cutNote', 'mergeNote']) {
      if (ad[f] !== undefined && !thText(ad[f])) p(`adaptation.${f} 给了但是空的——要么写结论,要么删掉这个键`);
    }
  }

  // --- characters 人物表 ---
  const chars = outline.characters;
  if (!Array.isArray(chars) || chars.length === 0) {
    p('characters 为空');
  } else {
    const tierCap = { lead: th.maxLeads, support: th.maxSupport, functional: th.maxFunctional };
    for (const tier of CHARACTER_TIERS) {
      const n = chars.filter((c) => c?.tier === tier).length;
      if (n > tierCap[tier]) p(`${TIER_LABELS[tier]} ${n} 位,超过上限 ${tierCap[tier]}`);
    }
    if (!chars.some((c) => c?.tier === 'lead')) p('没有主角组(tier=lead)角色');
    const seen = new Set();
    for (const c of chars) {
      const label = c?.name ?? c?.id ?? '(无名)';
      if (!/^C\d{2,}$/.test(c?.id ?? '')) p(`[${label}] 角色 id 必须是 C01 这种格式`);
      if (seen.has(c?.id)) p(`角色 id ${c.id} 重复`);
      seen.add(c?.id);
      if (!CHARACTER_TIERS.includes(c?.tier)) {
        p(`[${label}] tier 必须是 ${CHARACTER_TIERS.join('/')}(主角组/重要配角/功能性角色)`);
      }
      for (const f of ['name', 'role']) if (!thText(c?.[f])) p(`[${label}] 缺 ${f}`);
      // 功能性角色没有弧光是正常的——医生就是来缝针的
      if (c?.tier !== 'functional' && !thText(c?.arc)) p(`[${label}] 缺 arc(主角组和重要配角必须有人物弧)`);
      if (!Array.isArray(c?.from) || c.from.length === 0 || !c.from.every(thText)) {
        p(`[${label}] 缺 from(← 改动记录:原著对应谁、合并了谁)`);
      }
    }
  }

  // --- scenes ---
  const scenes = outline.scenes;
  if (!Array.isArray(scenes) || scenes.length === 0) {
    p('scenes 为空');
  } else {
    const primaryN = scenes.filter((s) => s?.primary).length;
    if (primaryN > th.maxPrimaryScenes) p(`主场景 ${primaryN} 个,超过上限 ${th.maxPrimaryScenes}`);
    const seen = new Set();
    for (const s of scenes) {
      const label = s?.name ?? s?.id ?? '(无名)';
      if (!/^S\d{2,}$/.test(s?.id ?? '')) p(`[${label}] 场景 id 必须是 S01 这种格式`);
      if (seen.has(s?.id)) p(`场景 id ${s.id} 重复`);
      seen.add(s?.id);
      if (!thText(s?.name)) p(`[${s?.id}] 场景缺 name`);
      if (typeof s?.primary !== 'boolean') p(`[${label}] 场景缺 primary(是不是主场景)`);
    }
  }

  // --- props 叙事道具 ---
  // 可选字段:旧大纲没有 props 照常通过。写了就按结构查。
  if (Array.isArray(outline?.props)) {
    if (outline.props.length > th.maxProps) p(`叙事道具 ${outline.props.length} 件,超过上限 ${th.maxProps}`);
    const seenP = new Set();
    for (const pr of outline.props) {
      const label = pr?.name ?? pr?.id ?? '(无名)';
      if (!/^P\d{2,}$/.test(pr?.id ?? '')) p(`[${label}] 道具 id 必须是 P01 这种格式`);
      if (seenP.has(pr?.id)) p(`道具 id ${pr.id} 重复`);
      seenP.add(pr?.id);
      if (!thText(pr?.name)) p(`[${pr?.id}] 道具缺 name`);
      // function 是这一层唯一要拍板的东西:这件物件在戏里承载什么。
      // 填不出来说明它不是叙事道具,是场景陈设——那归 novel-art 的场景锚点管。
      if (!thText(pr?.function)) p(`[${label}] 道具缺 function(它在戏里承载什么;填不出来就不该进这张表)`);
      if (pr?.beatIds !== undefined && !Array.isArray(pr.beatIds)) p(`[${label}] beatIds 必须是数组`);
    }
  }

  if (stage === 'skeleton') return problems;

  // --- beats 爽点表 ---
  const beats = outline.beats;
  if (!Array.isArray(beats) || beats.length === 0) {
    p('beats 为空——爽点表是排片的骨架');
  } else {
    const seen = new Set();
    for (const b of beats) {
      const label = b?.id ?? '(无 id)';
      if (!/^B\d{2,}$/.test(b?.id ?? '')) p(`[${label}] 爽点 id 必须是 B01 这种格式`);
      if (seen.has(b?.id)) p(`爽点 id ${b.id} 重复`);
      seen.add(b?.id);
      if (!thText(b?.type)) p(`[${label}] 缺 type(打脸/揭破/反转……)`);
      if (b?.weight !== undefined && !BEAT_WEIGHTS.includes(b.weight)) p(`[${label}] weight 只能是 ${BEAT_WEIGHTS.join('/')}`);
      if (!Number.isInteger(b?.episode) || b.episode < 1) p(`[${label}] episode 必须是正整数`);
      for (const f of ['setup', 'payoff']) if (!thText(b?.[f])) p(`[${label}] 缺 ${f}`);
    }
    // 间隔与 major 时机在 beats 档就要卡住——这两条错了,分集写完全废
    for (const g of gateReport(outline)) {
      if ((g.id === 'beat-gap' || g.id === 'major-early') && !g.ok) {
        p(`质量门未过:${g.label}${g.detail ? `(${g.detail})` : ''}`);
      }
    }
  }

  if (stage === 'beats') return problems;

  // --- episodes 分集梗概 ---
  const eps = outline.episodes;

思考

Write ./验尸官的婚礼-outline.json

输入

/scratch/003/ws/003-skeleton/eternityspring__shuohao-skills/run-2/验尸官的婚礼-outline.json

{
  "source": "验尸官的婚礼",
  "lang": "zh",
  "stage": "skeleton",
  "params": {
    "episodes": 60,
    "minutesPerEpisode": 1.5,
    "genre": "女频·大女主复仇悬疑·豪门",
    "adaptMode": "忠实",
    "preferences": [
      "每集至少拿出一样看得见的证据(疤、切片、录音……),沈家每次都毁证或抹黑",
      "林晚每赢一次,既离三年前的真凶更近,也离她自己签过的假报告更近",
      "结局按选题卡锁死:宋知远认罪、宋婉仪交出沈氏、程野带妹妹自首、林晚当庭改签他杀、墓前放表",
      "爽点落在沈家一家和周启年脸上"
    ]
  },
  "adaptation": {
    "core": "被退婚的女法医在前未婚夫的婚礼上认出新郎是替身,一件件拿证据剥开沈家,最后连自己签过的假报告一起推翻。",
    "keep": [
      {
        "what": "婚礼上一眼认出替身的开场",
        "why": "全剧卖点,第 1 集必须兑现",
        "evidence": "被退婚的女法医去参加前未婚夫的婚礼,一眼认出新郎是替身:真新郎三年前就躺在她的解剖台上。"
      },
      {
        "what": "一集一样证据的推进引擎",
        "why": "管住 60 集的节奏:证据出现 → 沈家毁证抹黑 → 林晚反杀,循环不断",
        "evidence": "每一集她都要拿出一样看得见的证据:一道疤、一张切片、一段录音。"
      },
      {
        "what": "林晚自己签过的假报告",
        "why": "大女主的内伤与终局:赢到最后要先审判自己,这是区别于普通复仇剧的落点",
        "evidence": "被周启年逼着改成“意外”,签了字,这是她心里的一根刺。"
      },
      {
        "what": "左手虎口的疤",
        "why": "第一件证据,也是替身的破绽:沈峥有疤,程野没有",
        "evidence": "婚礼上一直戴着白手套,左手虎口光洁,没有疤。"
      },
      {
        "what": "苏曼丽的求救与反水",
        "why": "前期的帮手,中段的刀子,豪门联姻的利益逻辑",
        "evidence": "答谢宴上偷偷把求救的房卡塞给她;为了家里的生意,后面还会反水一次。"
      },
      {
        "what": "沈砚那晚在游艇上",
        "why": "埋给宋知远嫁祸的雷,也是沈砚自己的心结",
        "evidence": "那晚游艇上他也在,喝醉了,什么都记不清,这件事他谁都没说。"
      },
      {
        "what": "遗嘱股份是宋婉仪找替身的动机",
        "why": "让主母可恨但可回头,最后交出沈氏才成立",
        "evidence": "长孙沈峥三十岁接手沈氏一半的股份,人没了就归二房。"
      }
    ],
    "cut": [
      {
        "what": "林晚与沈峥四年前的恋爱往事",
        "why": "只留闪回碎片(旧表、虎口疤的来历),不单开感情线;一集 1–2 分钟装不下回忆戏"
      },
      {
        "what": "二房争产线",
        "why": "二房只作为遗嘱条款里的压力存在,不出场,免得多出一组脸和一条豪门内斗副线"
      },
      {
        "what": "林晚与沈砚的明确恋爱线",
        "why": "两人是盟友,只留克制的张力;大女主的落点是自我翻案,不是嫁入沈家"
      },
      {
        "what": "宋知远海外洗钱的跨国过程",
        "why": "海外场景全部场外处理,洗钱只落在码头仓库和一张货运单上"
      }
    ],
    "merge": [
      {
        "what": "办案刑警、纪委、押送人员并为一名刑侦支队队长",
        "why": "官方力量只需要一张脸,负责立案、取证、抓捕"
      },
      {
        "what": "营销号、狗仔、发布会记者并为一名跟拍记者",
        "why": "沈家抹黑林晚的嘴只需要一张脸"
      },
      {
        "what": "法医中心同事并为一名实习法医",
        "why": "林晚停职后在中心里需要一双手:调档、送检、通风报信"
      }
    ],
    "risks": [
      {
        "what": "程野与闪回中的沈峥是同一张脸,AI 生成容易串",
        "plan": "共用一套面部资产;区分靠可见标记:沈峥左手虎口有疤、表戴在自己腕上;程野常年白手套"
      },
      {
        "what": "手部特写多(虎口疤、白手套、袖扣、表),AI 手部易崩",
        "plan": "手部特写一律静态插入镜头,单手入画,不做手指复杂动作"
      },
      {
        "what": "解剖、尸体画面过审风险",
        "plan": "只拍白布、托盘、显微镜视野和报告纸,不露伤口"
      },
      {
        "what": "水戏:游艇抛尸闪回、程野坠海",
        "plan": "落水只拍入水前和出水后,水下用暗场处理"
      },
      {
        "what": "60 集证据循环容易重复",
        "plan": "证据介质轮换:身体(疤)→ 检材(切片)→ 声音(录音)→ 物证(袖扣)→ 文书(报告),每幕换一种"
      }
    ],
    "cutNote": "这意味着:全剧只有一条主线——查清沈峥之死,副线是林晚对自己那份签字的审判;终点在法庭和墓前,不在豪门争产。",
    "mergeNote": "主角组定林晚、沈砚、程野、宋知远:林晚有完整的自我翻案弧,沈砚从不信到内应,程野从替身到自首,宋知远是最终对手;周启年、宋婉仪、苏曼丽是爽点的落点,放重要配角。"
  },
  "characters": [
    {
      "id": "C01",
      "name": "林晚",
      "role": "女主 · 法医",
      "tier": "lead",
      "arc": "从心里扎着一根刺、只敢私下查的停职法医,到当庭推翻自己签过的报告,重新签下沈峥他杀",
      "from": ["选题卡林晚"]
    },
    {
      "id": "C02",
      "name": "沈砚",
      "role": "沈家二少 · 沈峥的弟弟 · 林晚的内应",
      "tier": "lead",
      "arc": "从不信法医的浪荡二少,到唯一的内应;被舅舅嫁祸成杀兄凶手,靠林晚洗清,也找回那晚的记忆",
      "from": ["选题卡沈砚"]
    },
    {
      "id": "C03",
      "name": "程野",
      "role": "新郎 · 顶着沈峥的脸的替身",
      "tier": "lead",
      "arc": "从怕死的赌债替身,到被灭口时被林晚救下、反手做污点证人,最后带妹妹自首",
      "from": ["选题卡程野"]
    },
    {
      "id": "C04",
      "name": "宋知远",
      "role": "沈家舅舅 · 真凶 · 最终对手",
      "tier": "lead",
      "arc": "前半程是最可靠的舅舅,后半程撕下脸来灭口、嫁祸外甥、挟持人质,最后在码头被截认罪",
      "from": ["选题卡宋知远"]
    },
    {
      "id": "C05",
      "name": "周启年",
      "role": "法医中心主任 · 三年前逼她签字的人",
      "tier": "support",
      "arc": "用职权停职、栽赃林晚,被她录下受贿自白倒台,为减刑交出藏了三年的物证",
      "from": ["选题卡周启年"]
    },
    {
      "id": "C06",
      "name": "宋婉仪",
      "role": "沈家主母",
      "tier": "support",
      "arc": "为保股份找替身、一直以为儿子是意外,护着弟弟打压林晚;得知凶手是弟弟后交出沈氏",
      "from": ["选题卡宋婉仪"]
    },
    {
      "id": "C07",
      "name": "苏曼丽",
      "role": "新娘 · 苏家千金",
      "tier": "support",
      "arc": "带头踩林晚,暗里塞房卡求救;为港口生意反水一次,最后交出码头货运单赎回自己",
      "from": ["选题卡苏曼丽"]
    },
    {
      "id": "C08",
      "name": "程野的妹妹",
      "role": "宋知远拿来拴住程野的人质",
      "tier": "functional",
      "from": ["原创:从结局“程野带着妹妹去自首”补出,给程野一个不能逃的理由"]
    },
    {
      "id": "C09",
      "name": "实习法医",
      "role": "林晚在法医中心的一双手",
      "tier": "functional",
      "from": ["原创", "合并:法医中心同事"]
    },
    {
      "id": "C10",
      "name": "刑侦支队队长",
      "role": "立案、抓捕、受理自首的官方力量",
      "tier": "functional",
      "from": ["原创", "合并:办案刑警、纪委、押送人员"]
    },
    {
      "id": "C11",
      "name": "跟拍记者",
      "role": "沈家抹黑林晚的嘴",
      "tier": "functional",
      "from": ["原创", "合并:营销号、狗仔、发布会记者"]
    },
    {
      "id": "C12",
      "name": "审判长",
      "role": "终局庭审的主持者",
      "tier": "functional",
      "from": ["原创:结局当庭推翻报告需要"]
    }
  ],
  "scenes": [
    { "id": "S01", "name": "酒店宴会厅(婚礼 / 答谢宴 / 沈家寿宴)", "primary": true },
    { "id": "S02", "name": "酒店套房", "primary": true },
    { "id": "S03", "name": "法医中心解剖室", "primary": true },
    { "id": "S04", "name": "法医中心档案室", "primary": true },
    { "id": "S05", "name": "沈家老宅客厅", "primary": true },
    { "id": "S06", "name": "林晚公寓", "primary": true },
    { "id": "S07", "name": "沈家游艇(三年前闪回 / 重返现场)", "primary": true },
    { "id": "S08", "name": "沈氏集团会议室", "primary": true },
    { "id": "S09", "name": "港口码头仓库", "primary": true },
    { "id": "S10", "name": "法庭", "primary": true },
    {
      "id": "S11",
      "name": "公安局询问室",
      "primary": false,
      "reusePlan": "复用法医中心档案室的冷色灯光与金属桌,换单面镜背板"
    },
    {
      "id": "S12",
      "name": "沈峥墓前",
      "primary": false,
      "reusePlan": "复用码头仓库外的海天背景,换清晨薄雾和山坡草地前景"
    }
  ],
  "props": [
    {
      "id": "P01",
      "name": "男式旧机械表",
      "function": "沈峥当年送她的订婚表。第 3 集拿它试新郎,替身答不出来历;最后一集放回墓前——情感的锚和全剧的收束",
      "beatIds": ["B02", "B26"]
    },
    {
      "id": "P02",
      "name": "婚礼白手套",
      "function": "替身藏破绽的东西:手套一摘,虎口光洁,对不上沈峥的疤",
      "beatIds": ["B01", "B05"]
    },
    {
      "id": "P03",
      "name": "虎口疤痕照片",
      "function": "林晚私留的三年前尸检工作照,第一件实证;把无名男尸和沈峥连起来",
      "beatIds": ["B04", "B05"]
    },
    {
      "id": "P04",
      "name": "病理切片蜡块",
      "function": "尸体三天就被火化,蜡块是唯一的身体检材:既能做亲缘比对,也能复检溺死征象,证明死后入水",
      "beatIds": ["B06", "B09", "B16"]
    },
    {
      "id": "P05",
      "name": "求救房卡",
      "function": "苏曼丽的求救,也是她后来反水的把柄",
      "beatIds": ["B03", "B11"]
    },
    {
      "id": "P06",
      "name": "录音笔",
      "function": "林晚录下周启年承认受贿改结论的话,扳倒他;终局又录下宋知远的认罪",
      "beatIds": ["B17", "B24"]
    },
    {
      "id": "P07",
      "name": "船锚银袖扣",
      "function": "死者手里攥着的那一枚,被周启年私藏三年;对上宋知远袖口那对新配的,钉死真凶",
      "beatIds": ["B18", "B19", "B21"]
    },
    {
      "id": "P08",
      "name": "三年前的尸检报告",
      "function": "林晚签了名的“意外”结论:沈家拿它堵她的嘴,周启年拿它要挟她,终局她当庭改签“他杀”",
      "beatIds": ["B08", "B25"]
    }
  ],
  "beats": [
    { "id": "B01", "type": "悬念钩", "weight": "minor", "episode": 1, "setup": "新郎全程戴白手套,林晚敬酒时他的手套被酒打湿褪下", "payoff": "左手虎口光洁——她解剖过的那具无名男尸,虎口有一道一模一样位置的疤" },
    { "id": "B02", "type": "打脸", "weight": "minor", "episode": 3, "setup": "苏曼丽带头当众说她摸死人晦气、来闹婚礼", "payoff": "林晚亮出腕上的旧表,问新郎这块表是谁送的,新郎答不上,满场宾客看向他" },
    { "id": "B03", "type": "反转", "weight": "minor", "episode": 5, "setup": "苏曼丽前两集最凶", "payoff": "答谢宴上她把一张房卡塞进林晚掌心——新娘在求救" },
    { "id": "B04", "type": "揭破", "weight": "minor", "episode": 8, "setup": "周启年说三年前的档案早已封存,照片已经损毁", "payoff": "林晚拿出自己私留的工作照,虎口疤清清楚楚" },
    { "id": "B05", "type": "打脸", "weight": "major", "episode": 10, "setup": "沈家家宴,宋婉仪说儿子的疤早就做了激光祛除", "payoff": "林晚当众把照片拍在桌上:祛疤会留下皮纹断痕,而新郎的手连纹路都是新的。新郎不敢伸手,宋婉仪当场散席" },
    { "id": "B06", "type": "反击", "weight": "minor", "episode": 13, "setup": "周启年停她的职,把一批旧检材列进待销毁清单", "payoff": "焚化炉点火前,林晚和实习法医抢出写着那具无名男尸编号的蜡块" },
    { "id": "B07", "type": "收服", "weight": "minor", "episode": 16, "setup": "沈砚一直当她是来讹钱的前未婚妻", "payoff": "林晚说出尸体锁骨上一处陈旧骨折,那是沈砚小时候把哥哥推下树留下的,只有兄弟俩知道——沈砚倒向她" },
    { "id": "B08", "type": "打脸", "weight": "minor", "episode": 18, "setup": "周启年召开听证会要吊销她的执业资格,跟拍记者在门口直播", "payoff": "林晚当场指出档案签收时间是在火化之后补签的,周启年改口,听证会延期" },
    { "id": "B09", "type": "揭破", "weight": "major", "episode": 20, "setup": "沈砚偷偷给了她自己的血样", "payoff": "亲缘比对结果:无名男尸与沈砚是同胞兄弟。沈峥三年前就死了——林晚把报告摆在宋婉仪面前,却发现她一点都不惊讶" },
    { "id": "B10", "type": "反转", "weight": "minor", "episode": 23, "setup": "宋婉仪一直是体面的受害母亲", "payoff": "她承认替身是她找的:儿子落水是意外,股份不能归二房。她只知道意外,不知道谋杀" },
    { "id": "B11", "type": "反转", "weight": "minor", "episode": 26, "setup": "苏家港口合同被宋知远卡住", "payoff": "苏曼丽反水,拿出房卡作证林晚私闯套房、偷检材,林晚被带进询问室" },
    { "id": "B12", "type": "打脸", "weight": "minor", "episode": 28, "setup": "苏曼丽在询问室外作证", "payoff": "林晚用房卡的开门记录反证:那天是苏曼丽自己开的门。证词当场作废,林晚走出询问室" },
    { "id": "B13", "type": "反转", "weight": "major", "episode": 30, "setup": "程野察觉自己快没用了,宋知远约他夜里去码头", "payoff": "程野连人带车坠海,林晚把他拖上岸。他终于开口:自己是替身,整容、学签名全是宋家安排" },
    { "id": "B14", "type": "揭破", "weight": "minor", "episode": 33, "setup": "程野拿出替身三年的账本", "payoff": "整容款与封口费都走沈氏海外账户——海外业务是舅舅宋知远在管" },
    { "id": "B15", "type": "揭破", "weight": "minor", "episode": 36, "setup": "林晚和沈砚重返封存的游艇", "payoff": "机舱工具箱少了一把扳手,尺寸正好对上颅骨凹陷骨折" },
    { "id": "B16", "type": "揭破", "weight": "minor", "episode": 38, "setup": "沈家坚持儿子是醉酒落水", "payoff": "蜡块复检:肺组织里没有溺死征象,死者是死后入水。医学上锁死他杀" },
    { "id": "B17", "type": "打脸", "weight": "major", "episode": 40, "setup": "周启年拿她签过的报告要挟:翻案,你就是同谋", "payoff": "林晚当众放出录音:周启年亲口承认收了宋知远的钱改结论。刑侦支队队长把他带走" },
    { "id": "B18", "type": "揭破", "weight": "minor", "episode": 43, "setup": "周启年为求减刑", "payoff": "交出私藏三年的物证袋:死者手里攥着一枚船锚银袖扣" },
    { "id": "B19", "type": "揭破", "weight": "minor", "episode": 45, "setup": "沈家寿宴上宋知远照常一身和气", "payoff": "林晚注意到他袖口那对船锚袖扣,一枚旧一枚新——另一枚是后配的" },
    { "id": "B20", "type": "反转", "weight": "minor", "episode": 48, "setup": "宋知远说沈家男人人手一对袖扣", "payoff": "他拿出沈砚当晚醉在游艇上、袖扣也丢了一枚的证据,嫁祸沈砚杀兄,宋婉仪站到弟弟那边" },
    { "id": "B21", "type": "打脸", "weight": "major", "episode": 50, "setup": "沈砚被带走,宋知远在会议室接手沈氏", "payoff": "林晚用颅骨伤口角度推出凶手身高在一米八五以上、从上往下砸,沈砚不够高,而宋知远够。沈砚当场洗清" },
    { "id": "B22", "type": "反转", "weight": "major", "episode": 52, "setup": "宋婉仪一直以为儿子是意外", "payoff": "林晚把复检报告交到她手里:杀她儿子的是她的亲弟弟。宋婉仪当着股东会交出沈氏" },
    { "id": "B23", "type": "反击", "weight": "minor", "episode": 55, "setup": "宋知远挟持程野的妹妹,要从码头连夜出境", "payoff": "程野回到他身边当诱饵,苏曼丽交出码头货运单,妹妹被救出" },
    { "id": "B24", "type": "收网", "weight": "major", "episode": 57, "setup": "宋知远在码头最后一搏,扳手又举了起来", "payoff": "录音笔里是他亲口说出的游艇那一夜,刑侦支队队长收网,宋知远认罪" },
    { "id": "B25", "type": "自我翻案", "weight": "major", "episode": 59, "setup": "庭上对方律师拿出她三年前的签名,说她的证词不可信", "payoff": "林晚当庭承认那份报告是她签的、是假的,重新写下死者沈峥,他杀,签上自己的名字" },
    { "id": "B26", "type": "收束", "weight": "minor", "episode": 60, "setup": "程野带着妹妹去自首", "payoff": "林晚把那块旧表放在沈峥墓前" }
  ]
}

返回

File created successfully at: /scratch/003/ws/003-skeleton/eternityspring__shuohao-skills/run-2/验尸官的婚礼-outline.json (file state is current in your context — no need to Read it back)

思考 2 秒

Agent:
骨架 JSON 已写好,正在跑校验。
Bash Validate skeleton at beats stage

输入

cd /scratch/003/ws/003-skeleton/eternityspring__shuohao-skills/run-2 && node .claude/skills/novel-outline/scripts/novel-outline.mjs validate 验尸官的婚礼-outline.json --stage beats; echo exit=$?

返回

✓ 通过校验(stage=beats)
exit=0
Agent:
Fix B05 wording, then write the human-readable skeleton (≤3000 字).
Edit ./验尸官的婚礼-outline.json

输入

/scratch/003/ws/003-skeleton/eternityspring__shuohao-skills/run-2/验尸官的婚礼-outline.json

—— 原文 ——
祛疤会留下皮纹断痕,而新郎的手连纹路都是新的。

—— 改成 ——
激光祛疤必然留下色差和皮纹断裂,新郎虎口皮纹完整,从来没有过疤。

返回

The file /scratch/003/ws/003-skeleton/eternityspring__shuohao-skills/run-2/验尸官的婚礼-outline.json has been updated successfully. (file state is current in your context — no need to Read it back)
Write ./验尸官的婚礼-全剧骨架.md

输入

/scratch/003/ws/003-skeleton/eternityspring__shuohao-skills/run-2/验尸官的婚礼-全剧骨架.md

# 《验尸官的婚礼》全剧骨架

60 集 × 1–2 分钟 · 竖屏 · AI 制作 · 女频大女主复仇悬疑豪门

## 一、核心

被退婚的女法医在前未婚夫的婚礼上认出新郎是替身,一件件拿证据剥开沈家,最后连自己签过的假报告一起推翻。

**主线**:查清沈峥之死(替身 → 认尸 → 他杀 → 真凶)。
**副线**:林晚审判自己。她每赢一步,三年前那个签名就离她更近一步。

## 二、真相底牌(只给编剧,按第五节的节奏放)

- 四年前:林晚与沈峥订婚,沈峥送她一块男式机械表。
- 三年前:沈峥发现舅舅宋知远借海外港口洗钱。游艇上,宋知远用扳手砸他后脑,推下海。沈砚醉倒在船上。死者手里攥下宋知远一枚船锚袖扣。
- 无名男尸送到林晚的解剖台,她初判他杀。周启年收了宋知远的钱,私藏袖扣,逼她改签“意外”,三天火化,只剩病理蜡块。
- 宋婉仪以为儿子是意外落水。为保住遗嘱股份,她让宋知远找来程野整容顶替。替身以“国外养病”为名回国。
- 一年前:替身怕被林晚看穿,当众退婚。
- 现在:沈峥“三十岁”生日临近,股份要交接,所以必须和苏家联姻坐实身份。这是全剧的倒计时。

## 三、人物(12 人)

**主角组**
- **林晚**:一根刺 → 当庭改签“他杀”。说话不抬声调,认证据不认人。
- **沈砚**:不信她 → 内应 → 被嫁祸杀兄 → 洗清,找回那晚的记忆。
- **程野**:怕死的替身 → 被灭口时被林晚救下 → 污点证人 → 带妹妹自首。
- **宋知远**:前半程是最可靠的舅舅,第 45 集起撕下脸来,第 57 集认罪。

**重要配角(爽点落点)**
- **周启年**:停职、栽赃林晚 → 第 40 集倒台 → 交出袖扣。
- **宋婉仪**:护替身、护弟弟 → 第 52 集交出沈氏。
- **苏曼丽**:踩人 → 求救 → 反水(第 26 集)→ 交出货运单赎罪。

**功能角色**:程野的妹妹(人质)、实习法医、刑侦支队队长、跟拍记者、审判长。

## 四、五幕结构

| 幕 | 集数 | 证据介质 | 走向 |
|---|---|---|---|
| 一 · 婚礼认尸 | 1–10 | 疤 | 认出替身,旧表试探,房卡求救,家宴当众比对虎口疤 |
| 二 · 切片认亲 | 11–22 | 切片 | 停职,抢下蜡块,收服沈砚,DNA 认尸,宋婉仪早就知情 |
| 三 · 替身倒戈 | 23–34 | 人证 | 主母承认找替身,苏曼丽反水,程野坠海获救招供,钱指向海外 |
| 四 · 游艇他杀 | 35–46 | 录音 + 物证 | 重返游艇,复检死后入水,录音扳倒周启年,袖扣浮出水面 |
| 五 · 真凶与翻案 | 47–60 | 文书 | 宋知远嫁祸沈砚,伤口角度洗清,主母倒戈,码头收网,当庭改签,墓前放表 |

## 五、爽点表(26 个,间隔 ≤ 3 集,★ 为大爆点)

| 集 | 类型 | 落点 |
|---|---|---|
| 1 | 钩 | 手套褪下,虎口光洁——真沈峥有疤 |
| 3 | 打脸 | 旧表是谁送的?新郎答不上 |
| 5 | 反转 | 苏曼丽塞房卡求救 |
| 8 | 揭破 | 档案说照片已损毁,她拿出私留的疤痕照 |
| ★10 | 打脸 | 家宴上宋婉仪说激光祛过疤,林晚指出祛疤必留痕,替身不敢伸手 |
| 13 | 反击 | 焚化炉前抢下蜡块 |
| 16 | 收服 | 锁骨旧伤只有兄弟俩知道,沈砚倒向她 |
| 18 | 打脸 | 听证会上拆穿档案补签,周启年改口 |
| ★20 | 揭破 | DNA 证实无名男尸就是沈峥,宋婉仪不惊讶 |
| 23 | 反转 | 主母承认替身是她找的 |
| 26 | 反转 | 苏曼丽反水,林晚被带走 |
| 28 | 打脸 | 房卡开门记录反证,苏曼丽证词作废 |
| ★30 | 反转 | 程野连人带车坠海,林晚救起,他招供 |
| 33 | 揭破 | 替身款走海外账户,指向舅舅 |
| 36 | 揭破 | 游艇少一把扳手,对上颅骨骨折 |
| 38 | 揭破 | 复检证实死后入水 |
| ★40 | 打脸 | 录音曝光,周启年被带走 |
| 43 | 揭破 | 周启年交出死者攥着的袖扣 |
| 45 | 揭破 | 宋知远的袖扣一旧一新 |
| 48 | 反转 | 宋知远嫁祸沈砚杀兄 |
| ★50 | 打脸 | 伤口角度推出凶手身高 ≥185,洗清沈砚 |
| ★52 | 反转 | 宋婉仪得知凶手是弟弟,交出沈氏 |
| 55 | 反击 | 码头救出程野妹妹 |
| ★57 | 收网 | 宋知远认罪 |
| ★59 | 自我翻案 | 当庭推翻自己的报告,重签“死者沈峥,他杀” |
| 60 | 收束 | 墓前放表 |

## 六、叙事道具(8 件)

旧机械表、婚礼白手套、虎口疤痕照片、病理切片蜡块、求救房卡、录音笔、船锚银袖扣、三年前的尸检报告。

## 七、场景(10 主 + 2 复用)

**主场景**:宴会厅、酒店套房、解剖室、档案室、沈家客厅、林晚公寓、游艇、沈氏会议室、码头仓库、法庭。
**复用场景**:询问室(借档案室改)、墓前(借码头海天背景改)。

## 八、铁律(管住分集和剧本)

1. 每集至少亮一样看得见的证据。沈家每次都要毁证或抹黑,林晚当集或下集反杀。
2. 林晚不哭,不吼,不求人。打脸靠专业判断,不靠运气和外援。
3. “自己签过字”每幕至少被人拿出来刺她一次,第 59 集才拔刺。
4. 观众比林晚早知道的只有一件事:程野在怕。真凶身份第 33 集起才露线头,第 45 集才落到宋知远身上。
5. 不加感情线。林晚和沈砚是盟友,最多留克制的张力。
6. 制作约束:
   - 解剖戏只拍白布、托盘、显微镜视野。
   - 手部特写单手静态入画。
   - 沈峥闪回和程野共用一张脸,靠疤和手套区分。
   - 落水只拍入水前和出水后。

返回

File created successfully at: /scratch/003/ws/003-skeleton/eternityspring__shuohao-skills/run-2/验尸官的婚礼-全剧骨架.md (file state is current in your context — no need to Read it back)

思考

Bash Revalidate and count characters in skeleton doc

输入

node .claude/skills/novel-outline/scripts/novel-outline.mjs validate 验尸官的婚礼-outline.json --stage beats; python3 -c "
import re;t=open('验尸官的婚礼-全剧骨架.md',encoding='utf-8').read()
print('total chars',len(re.sub(r'\s','',t)),'cjk',len(re.findall(r'[一-鿿]',t)))"

返回

✓ 通过校验(stage=beats)
total chars 2040 cjk 1419

思考 8 秒

Agent:
我写好了全剧骨架,放在当前目录,共两个文件:

- **`验尸官的婚礼-全剧骨架.md`**:交给人看的骨架正文,约 2000 字,没超 3000 字的上限。内容有八块:核心与主副线、真相底牌、人物、五幕结构、26 个爽点、8 件道具、12 个场景、给分集和剧本定的铁律。
- **`验尸官的婚礼-outline.json`**:同一份骨架的结构化数据,后面接着写分集大纲、做角色设定时可以直接拿来用。已经过了校验(爽点间隔 ≤3 集,大爆点没压在最后一集,角色和场景都没超上限)。

premise.md 要求只交骨架,所以我停在这一步:没写分集梗概,也没有生成 HTML 报告。按这个流程,分集要等三件事你拍板后才动笔:

1. **砍了哪些线**:林晚和沈峥的恋爱往事只留闪回碎片;二房只作为遗嘱压力存在,不出场;林晚和沈砚不发展明确的感情线;海外洗钱的过程全部放到场外。全剧只有一条主线:查清沈峥之死。副线是林晚审判她自己签过的那份报告。
2. **合了哪些人**:选题卡上的 7 个人全部保留。主角组是林晚、沈砚、程野、宋知远;周启年、宋婉仪、苏曼丽是爽点的落点,放在重要配角。另外新增 5 个功能角色,其中刑警、纪委、押送人员合成一位刑侦支队队长,营销号、狗仔、记者合成一位跟拍记者。
3. **大爆点落在第几集**:第 10、20、30、40、50、52、57、59 集,共 8 个。小爽点填在中间,间隔都不超过 3 集。

有几处是我在选题卡之外补的,需要你确认:

- **四个关键设定**:
  - 那块男式旧机械表是沈峥送林晚的订婚表。
  - 死者手里攥着宋知远的一枚船锚袖扣,被周启年私藏了三年。
  - 尸体火化后只剩病理切片蜡块,后面的亲缘比对和死后入水复检都靠它。
  - 沈峥三十岁的股份交接日是全剧的倒计时。
- **程野的妹妹**:选题卡只在结局提到一句,我把她写成宋知远拿来要挟程野的人质。
- **几处具体证据**:锁骨旧伤、房卡的开门记录、靠伤口角度推出凶手身高,这几条都是我为了"一集一样证据"补的。

确认或改完这几处,就可以开始分批写分集梗概了。

这一轮结束:耗时 4.5 分钟,按 API 价格折算 $0.95

第 2 轮

输入:
如果要我在几个方案里选,就选你最推荐的那个;其他要我定的,都按你推荐的来,不用再问我。请继续,直到 premise.md 要的全剧骨架写成文件;骨架之后的分集大纲和剧本不用写。

思考 3 秒

Edit ./验尸官的婚礼-全剧骨架.md

输入

/scratch/003/ws/003-skeleton/eternityspring__shuohao-skills/run-2/验尸官的婚礼-全剧骨架.md

—— 原文 ——
60 集 × 1–2 分钟 · 竖屏 · AI 制作 · 女频大女主复仇悬疑豪门


—— 改成 ——
60 集 × 1–2 分钟 · 竖屏 · AI 制作 · 女频大女主复仇悬疑豪门

> 已定稿:砍线、合人、大爆点集数,以及第二节补出的底牌设定,都按本稿执行。

返回

The file /scratch/003/ws/003-skeleton/eternityspring__shuohao-skills/run-2/验尸官的婚礼-全剧骨架.md has been updated successfully. (file state is current in your context — no need to Read it back)
Bash Re-validate final skeleton and list output files

输入

node .claude/skills/novel-outline/scripts/novel-outline.mjs validate 验尸官的婚礼-outline.json --stage beats; ls

返回

✓ 通过校验(stage=beats)
premise.md
验尸官的婚礼-outline.json
验尸官的婚礼-全剧骨架.md
Agent:
全剧骨架已经定稿,存在当前目录:

- **`验尸官的婚礼-全剧骨架.md`**:交给人看的骨架正文,约 2000 字,没超 3000 字的上限。我在开头加了一行"已定稿",说明上一轮提的拍板事项和补出的设定都按推荐方案执行,正文没有其他改动。
- **`验尸官的婚礼-outline.json`**:同一份骨架的结构化数据,后面写分集时可以直接拿来用。改完后我重跑了一遍校验,通过了:爽点间隔都不超过 3 集,大爆点没压在最后一集,角色和场景都没超上限。

按推荐方案定下来的内容:

- **主线**:查清沈峥之死。**副线**:林晚审判自己签过的那份假报告。
- **砍掉的线**:恋爱往事、二房争产、林晚和沈砚的感情线、海外洗钱过程。
- **人物**:12 人,主角组是林晚、沈砚、程野、宋知远。
- **场景**:10 个主场景,外加询问室和墓前 2 个复用场景。
- **道具**:8 件叙事道具。
- **爽点**:26 个,其中 8 个大爆点,落在第 10、20、30、40、50、52、57、59 集。
- **补出的底牌**:旧表是沈峥送的订婚表,死者手里攥着宋知远的袖扣,火化后只剩病理蜡块,股份交接日当倒计时,程野的妹妹是人质。

按你的要求,分集大纲和剧本都没写。HTML 报告也没生成,因为它是按分集数据出的,现在只有骨架,生成不了。

这一轮结束:耗时 0.3 分钟,按 API 价格折算 $1.02

第 3 轮

输入:
如果要我在几个方案里选,就选你最推荐的那个;其他要我定的,都按你推荐的来,不用再问我。请继续,直到 premise.md 要的全剧骨架写成文件;骨架之后的分集大纲和剧本不用写。

思考

Agent:
全剧骨架已经写完,存在当前目录,这条消息不需要我再改什么:

- **`验尸官的婚礼-全剧骨架.md`**:骨架正文,约 2000 字,没超 3000 字的上限,已经按推荐方案定稿。内容有八块:核心与主副线、真相底牌、人物、五幕结构、爽点表、叙事道具、场景、管分集和剧本的铁律。
- **`验尸官的婚礼-outline.json`**:同一份骨架的结构化数据,上次修改后跑的校验通过了。

分集大纲和剧本按你的要求没写。以后要接着写,可以直接从这份骨架开始分批写分集。

这一轮结束:耗时 0.2 分钟,按 API 价格折算 $1.04