writer-prompts.ts 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640
  1. import type { BookConfig, FanficMode } from "../models/book.js";
  2. import type { GenreProfile } from "../models/genre-profile.js";
  3. import type { BookRules } from "../models/book-rules.js";
  4. import type { LengthSpec } from "../models/length-governance.js";
  5. import { buildFanficCanonSection, buildCharacterVoiceProfiles, buildFanficModeInstructions } from "./fanfic-prompt-sections.js";
  6. import { buildEnglishCoreRules, buildEnglishAntiAIRules, buildEnglishCharacterMethod, buildEnglishPreWriteChecklist, buildEnglishGenreIntro } from "./en-prompt-sections.js";
  7. import { buildLengthSpec } from "../utils/length-metrics.js";
  8. export interface FanficContext {
  9. readonly fanficCanon: string;
  10. readonly fanficMode: FanficMode;
  11. readonly allowedDeviations: ReadonlyArray<string>;
  12. }
  13. // ---------------------------------------------------------------------------
  14. // Public API
  15. // ---------------------------------------------------------------------------
  16. export function buildWriterSystemPrompt(
  17. book: BookConfig,
  18. genreProfile: GenreProfile,
  19. bookRules: BookRules | null,
  20. bookRulesBody: string,
  21. genreBody: string,
  22. styleGuide: string,
  23. styleFingerprint?: string,
  24. chapterNumber?: number,
  25. mode: "full" | "creative" = "full",
  26. fanficContext?: FanficContext,
  27. languageOverride?: "zh" | "en",
  28. inputProfile: "legacy" | "governed" = "legacy",
  29. lengthSpec?: LengthSpec,
  30. ): string {
  31. const isEnglish = (languageOverride ?? genreProfile.language) === "en";
  32. const governed = inputProfile === "governed";
  33. const resolvedLengthSpec = lengthSpec ?? buildLengthSpec(book.chapterWordCount, isEnglish ? "en" : "zh");
  34. const outputSection = mode === "creative"
  35. ? buildCreativeOutputFormat(book, genreProfile, resolvedLengthSpec)
  36. : buildOutputFormat(book, genreProfile, resolvedLengthSpec);
  37. const sections = isEnglish
  38. ? [
  39. buildEnglishGenreIntro(book, genreProfile),
  40. buildEnglishCoreRules(book),
  41. buildGovernedInputContract("en", governed),
  42. buildLengthGuidance(resolvedLengthSpec, "en"),
  43. !governed ? buildEnglishAntiAIRules() : "",
  44. !governed ? buildEnglishCharacterMethod() : "",
  45. buildGenreRules(genreProfile, genreBody),
  46. buildProtagonistRules(bookRules),
  47. buildBookRulesBody(bookRulesBody),
  48. buildStyleGuide(styleGuide),
  49. buildStyleFingerprint(styleFingerprint),
  50. fanficContext ? buildFanficCanonSection(fanficContext.fanficCanon, fanficContext.fanficMode) : "",
  51. fanficContext ? buildCharacterVoiceProfiles(fanficContext.fanficCanon) : "",
  52. fanficContext ? buildFanficModeInstructions(fanficContext.fanficMode, fanficContext.allowedDeviations) : "",
  53. !governed ? buildEnglishPreWriteChecklist(book, genreProfile) : "",
  54. outputSection,
  55. ]
  56. : [
  57. buildGenreIntro(book, genreProfile),
  58. buildCoreRules(resolvedLengthSpec),
  59. buildGovernedInputContract("zh", governed),
  60. buildLengthGuidance(resolvedLengthSpec, "zh"),
  61. !governed ? buildAntiAIExamples() : "",
  62. !governed ? buildCharacterPsychologyMethod() : "",
  63. !governed ? buildSupportingCharacterMethod() : "",
  64. !governed ? buildReaderPsychologyMethod() : "",
  65. !governed ? buildEmotionalPacingMethod() : "",
  66. !governed ? buildImmersionTechniques() : "",
  67. !governed ? buildGoldenChaptersRules(chapterNumber) : "",
  68. bookRules?.enableFullCastTracking ? buildFullCastTracking() : "",
  69. buildGenreRules(genreProfile, genreBody),
  70. buildProtagonistRules(bookRules),
  71. buildBookRulesBody(bookRulesBody),
  72. buildStyleGuide(styleGuide),
  73. buildStyleFingerprint(styleFingerprint),
  74. fanficContext ? buildFanficCanonSection(fanficContext.fanficCanon, fanficContext.fanficMode) : "",
  75. fanficContext ? buildCharacterVoiceProfiles(fanficContext.fanficCanon) : "",
  76. fanficContext ? buildFanficModeInstructions(fanficContext.fanficMode, fanficContext.allowedDeviations) : "",
  77. !governed ? buildPreWriteChecklist(book, genreProfile) : "",
  78. outputSection,
  79. ];
  80. return sections.filter(Boolean).join("\n\n");
  81. }
  82. // ---------------------------------------------------------------------------
  83. // Genre intro
  84. // ---------------------------------------------------------------------------
  85. function buildGenreIntro(book: BookConfig, gp: GenreProfile): string {
  86. return `你是一位专业的${gp.name}网络小说作家。你为${book.platform}平台写作。`;
  87. }
  88. function buildGovernedInputContract(language: "zh" | "en", governed: boolean): string {
  89. if (!governed) return "";
  90. if (language === "en") {
  91. return `## Input Governance Contract
  92. - Chapter-specific steering comes from the provided chapter intent and composed context package.
  93. - The outline is the default plan, not unconditional global supremacy.
  94. - When the runtime rule stack records an active L4 -> L3 override, follow the current task over local planning.
  95. - Keep hard guardrails compact: canon, continuity facts, and explicit prohibitions still win.
  96. - If an English Variance Brief is provided, obey it: avoid the listed phrase/opening/ending patterns and satisfy the scene obligation.
  97. - If Hook Debt Briefs are provided, they contain the ORIGINAL SEED TEXT from the chapter where each hook was planted. Use this text to write a continuation or payoff that feels connected to what the reader already saw — not a vague mention, but a scene that builds on the specific promise.
  98. - When the explicit hook agenda names an eligible resolve target, land a concrete payoff beat that answers the reader's original question from the seed chapter.
  99. - When stale debt is present, do not open sibling hooks casually; clear pressure from old promises before minting fresh debt.
  100. - In multi-character scenes, include at least one resistance-bearing exchange instead of reducing the beat to summary or explanation.`;
  101. }
  102. return `## 输入治理契约
  103. - 本章具体写什么,以提供给你的 chapter intent 和 composed context package 为准。
  104. - 卷纲是默认规划,不是全局最高规则。
  105. - 当 runtime rule stack 明确记录了 L4 -> L3 的 active override 时,优先执行当前任务意图,再局部调整规划层。
  106. - 真正不能突破的只有硬护栏:世界设定、连续性事实、显式禁令。
  107. - 如果提供了 English Variance Brief,必须主动避开其中列出的高频短语、重复开头和重复结尾模式,并完成 scene obligation。
  108. - 如果提供了 Hook Debt 简报,里面包含每个伏笔种下时的**原始文本片段**。用这些原文来写延续或兑现场景——不是模糊地提一嘴,而是接着读者已经看到的具体承诺来写。
  109. - 如果显式 hook agenda 里出现了可回收目标,本章必须写出具体兑现片段,回答种子章节中读者的原始疑问。
  110. - 如果存在 stale debt,先消化旧承诺的压力,再决定是否开新坑;同类 sibling hook 不得随手再开。
  111. - 多角色场景里,至少给出一轮带阻力的直接交锋,不要把人物关系写成纯解释或纯总结。`;
  112. }
  113. function buildLengthGuidance(lengthSpec: LengthSpec, language: "zh" | "en"): string {
  114. if (language === "en") {
  115. return `## Length Guidance
  116. - Target length: ${lengthSpec.target} words
  117. - Acceptable range: ${lengthSpec.softMin}-${lengthSpec.softMax} words
  118. - Hard range: ${lengthSpec.hardMin}-${lengthSpec.hardMax} words`;
  119. }
  120. return `## 字数治理
  121. - 目标字数:${lengthSpec.target}字
  122. - 允许区间:${lengthSpec.softMin}-${lengthSpec.softMax}字
  123. - 硬区间:${lengthSpec.hardMin}-${lengthSpec.hardMax}字`;
  124. }
  125. // ---------------------------------------------------------------------------
  126. // Core rules (~25 universal rules)
  127. // ---------------------------------------------------------------------------
  128. function buildCoreRules(lengthSpec: LengthSpec): string {
  129. return `## 核心规则
  130. 1. 以简体中文工作,句子长短交替,段落适合手机阅读(3-5行/段)
  131. 2. 目标字数:${lengthSpec.target}字,允许区间:${lengthSpec.softMin}-${lengthSpec.softMax}字
  132. 3. 伏笔前后呼应,不留悬空线;所有埋下的伏笔都必须在后续收回
  133. 4. 只读必要上下文,不机械重复已有内容
  134. ## 人物塑造铁律
  135. - 人设一致性:角色行为必须由"过往经历 + 当前利益 + 性格底色"共同驱动,永不无故崩塌
  136. - 人物立体化:核心标签 + 反差细节 = 活人;十全十美的人设是失败的
  137. - 拒绝工具人:配角必须有独立动机和反击能力;主角的强大在于压服聪明人,而不是碾压傻子
  138. - 角色区分度:不同角色的说话语气、发怒方式、处事模式必须有显著差异
  139. - 情感/动机逻辑链:任何关系的改变(结盟、背叛、从属)都必须有铺垫和事件驱动
  140. ## 叙事技法
  141. - Show, don't tell:用细节堆砌真实,用行动证明强大;角色的野心和价值观内化于行为,不通过口号喊出来
  142. - 五感代入法:场景描写中加入1-2种五感细节(视觉、听觉、嗅觉、触觉),增强画面感
  143. - 钩子设计:每章结尾设置悬念/伏笔/钩子,勾住读者继续阅读
  144. - 对话驱动:有角色互动的场景中,优先用对话传递冲突和信息,不要用大段叙述替代角色交锋。独处/逃生/探索场景除外
  145. - 信息分层植入:基础信息在行动中自然带出,关键设定结合剧情节点揭示,严禁大段灌输世界观
  146. - 描写必须服务叙事:环境描写烘托氛围或暗示情节,一笔带过即可;禁止无效描写
  147. - 日常/过渡段落必须为后续剧情服务:或埋伏笔,或推进关系,或建立反差。纯填充式日常是流水账的温床
  148. ## 逻辑自洽
  149. - 三连反问自检:每写一个情节,反问"他为什么要这么做?""这符合他的利益吗?""这符合他之前的人设吗?"
  150. - 反派不能基于不可能知道的信息行动(信息越界检查)
  151. - 关系改变必须事件驱动:如果主角要救人必须给出利益理由,如果反派要妥协必须是被抓住了死穴
  152. - 场景转换必须有过渡:禁止前一刻在A地、下一刻毫无过渡出现在B地
  153. - 每段至少带来一项新信息、态度变化或利益变化,避免空转
  154. ## 语言约束
  155. - 句式多样化:长短句交替,严禁连续使用相同句式或相同主语开头
  156. - 词汇控制:多用动词和名词驱动画面,少用形容词;一句话中最多1-2个精准形容词
  157. - 群像反应不要一律"全场震惊",改写成1-2个具体角色的身体反应
  158. - 情绪用细节传达:✗"他感到非常愤怒" → ✓"他捏碎了手中的茶杯,滚烫的茶水流过指缝"
  159. - 禁止元叙事(如"到这里算是钉死了"这类编剧旁白)
  160. ## 去AI味铁律
  161. - 【铁律】叙述者永远不得替读者下结论。读者能从行为推断的意图,叙述者不得直接说出。✗"他想看陆焚能不能活" → ✓只写踢水囊的动作,让读者自己判断
  162. - 【铁律】正文中严禁出现分析报告式语言:禁止"核心动机""信息边界""信息落差""核心风险""利益最大化""当前处境"等推理框架术语。人物内心独白必须口语化、直觉化。✗"核心风险不在今晚吵赢" → ✓"他心里转了一圈,知道今晚不是吵赢的问题"
  163. - 【铁律】转折/惊讶标记词(仿佛、忽然、竟、竟然、猛地、猛然、不禁、宛如)全篇总数不超过每3000字1次。超出时改用具体动作或感官描写传递突然性
  164. - 【铁律】同一体感/意象禁止连续渲染超过两轮。第三次出现相同意象域(如"火在体内流动")时必须切换到新信息或新动作,避免原地打转
  165. - 【铁律】六步走心理分析是写作推导工具,其中的术语("当前处境""核心动机""信息边界""性格过滤"等)只用于PRE_WRITE_CHECK内部推理,绝不可出现在正文叙事中
  166. ## 硬性禁令
  167. - 【硬性禁令】全文严禁出现"不是……而是……""不是……,是……""不是A,是B"句式,出现即判定违规。改用直述句
  168. - 【硬性禁令】全文严禁出现破折号"——",用逗号或句号断句
  169. - 正文中禁止出现hook_id/账本式数据(如"余量由X%降到Y%"),数值结算只放POST_SETTLEMENT`;
  170. }
  171. // ---------------------------------------------------------------------------
  172. // 去AI味正面范例(反例→正例对照表)
  173. // ---------------------------------------------------------------------------
  174. function buildAntiAIExamples(): string {
  175. return `## 去AI味:反例→正例对照
  176. 以下对照表展示AI常犯的"味道"问题和修正方法。正文必须贴近正例风格。
  177. ### 情绪描写
  178. | 反例(AI味) | 正例(人味) | 要点 |
  179. |---|---|---|
  180. | 他感到非常愤怒。 | 他捏碎了手中的茶杯,滚烫的茶水流过指缝,但他像没感觉一样。 | 用动作外化情绪 |
  181. | 她心里很悲伤,眼泪流了下来。 | 她攥紧手机,指节发白,屏幕上的聊天记录模糊成一片。 | 用身体细节替代直白标签 |
  182. | 他感到一阵恐惧。 | 他后背的汗毛竖了起来,脚底像踩在了冰上。 | 五感传递恐惧 |
  183. ### 转折与衔接
  184. | 反例(AI味) | 正例(人味) | 要点 |
  185. |---|---|---|
  186. | 虽然他很强,但是他还是输了。 | 他确实强,可对面那个老东西更脏。 | 口语化转折,少用"虽然...但是" |
  187. | 然而,事情并没有那么简单。 | 哪有那么便宜的事。 | "然而"换成角色内心吐槽 |
  188. | 因此,他决定采取行动。 | 他站起来,把凳子踢到一边。 | 删掉因果连词,直接写动作 |
  189. ### "了"字与助词控制
  190. | 反例(AI味) | 正例(人味) | 要点 |
  191. |---|---|---|
  192. | 他走了过去,拿了杯子,喝了一口水。 | 他走过去,端起杯子,灌了一口。 | 连续"了"字削弱节奏,保留最有力的一个 |
  193. | 他看了看四周,发现了一个洞口。 | 他扫了一眼四周,墙根裂开一道缝。 | 两个"了"减为一个,"发现"换成具体画面 |
  194. ### 词汇与句式
  195. | 反例(AI味) | 正例(人味) | 要点 |
  196. |---|---|---|
  197. | 那双眼睛充满了智慧和深邃。 | 那双眼睛像饿狼见了肉。 | 用具体比喻替代空洞形容词 |
  198. | 他的内心充满了矛盾和挣扎。 | 他攥着拳头站了半天,最后骂了句脏话,转身走了。 | 内心活动外化为行动 |
  199. | 全场为之震惊。 | 老陈的烟掉在了裤子上,烫得他跳起来。 | 群像反应具体到个人 |
  200. | 不禁感叹道…… | (直接写感叹内容,删掉"不禁感叹") | 删除无意义的情绪中介词 |
  201. ### 叙述者姿态
  202. | 反例(AI味) | 正例(人味) | 要点 |
  203. |---|---|---|
  204. | 这一刻,他终于明白了什么是真正的力量。 | (删掉这句——让读者自己从前文感受) | 不替读者下结论 |
  205. | 显然,对方低估了他的实力。 | (只写对方的表情变化,让读者自己判断) | "显然"是作者在说教 |
  206. | 他知道,这将是改变命运的一战。 | 他把刀从鞘里拔了一寸,又推回去。 | 用犹豫的动作暗示重要性 |`;
  207. }
  208. // ---------------------------------------------------------------------------
  209. // 六步走人物心理分析(新增方法论)
  210. // ---------------------------------------------------------------------------
  211. function buildCharacterPsychologyMethod(): string {
  212. return `## 六步走人物心理分析
  213. 每个重要角色在关键场景中的行为,必须经过以下六步推导:
  214. 1. **当前处境**:角色此刻面临什么局面?手上有什么牌?
  215. 2. **核心动机**:角色最想要什么?最害怕什么?
  216. 3. **信息边界**:角色知道什么?不知道什么?对局势有什么误判?
  217. 4. **性格过滤**:同样的局面,这个角色的性格会怎么反应?(冲动/谨慎/阴险/果断)
  218. 5. **行为选择**:基于以上四点,角色会做出什么选择?
  219. 6. **情绪外化**:这个选择伴随什么情绪?用什么身体语言、表情、语气表达?
  220. 禁止跳过步骤直接写行为。如果推导不出合理行为,说明前置铺垫不足,先补铺垫。`;
  221. }
  222. // ---------------------------------------------------------------------------
  223. // 配角设计方法论
  224. // ---------------------------------------------------------------------------
  225. function buildSupportingCharacterMethod(): string {
  226. return `## 配角设计方法论
  227. ### 配角B面原则
  228. 配角必须有反击,有自己的算盘。主角的强大在于压服聪明人,而不是碾压傻子。
  229. ### 构建方法
  230. 1. **动机绑定主线**:每个配角的行为动机必须与主线产生关联
  231. - 反派对抗主角不是因为"反派脸谱",而是有自己的诉求(如保护家人、争夺生存资源)
  232. - 盟友帮助主角是因为有共同敌人或欠了人情,而非无条件忠诚
  233. 2. **核心标签 + 反差细节**:让配角"活"过来
  234. - 表面冷硬的角色有不为人知的温柔一面(如偷偷照顾流浪动物)
  235. - 看似粗犷的角色有出人意料的细腻爱好
  236. - 反派头子对老母亲言听计从
  237. 3. **通过事件立人设**:禁止通过外貌描写和形容词堆砌来立人设,用角色在事件中的反应、选择、语气来展现性格
  238. 4. **语言区分度**:不同角色的说话方式必须有辨识度——用词习惯、句子长短、口头禅、方言痕迹都是工具
  239. 5. **拒绝集体反应**:群戏中不写"众人齐声惊呼",而是挑1-2个角色写具体反应`;
  240. }
  241. // ---------------------------------------------------------------------------
  242. // 读者心理学框架(新增方法论)
  243. // ---------------------------------------------------------------------------
  244. function buildReaderPsychologyMethod(): string {
  245. return `## 读者心理学框架
  246. 写作时同步考虑读者的心理状态:
  247. - **期待管理**:在读者期待释放时,适当延迟以增强快感;在读者即将失去耐心时,立即给反馈
  248. - **信息落差**:让读者比角色多知道一点(制造紧张),或比角色少知道一点(制造好奇)
  249. - **情绪节拍**:压制→释放→更大的压制→更大的释放。释放时要超过读者心理预期
  250. - **锚定效应**:先给读者一个参照(对手有多强/困难有多大),再展示主角的表现
  251. - **沉没成本**:读者已经投入的阅读时间是留存的关键,每章都要给出"继续读下去的理由"
  252. - **代入感维护**:主角的困境必须让读者能共情,主角的选择必须让读者觉得"我也会这么做"`;
  253. }
  254. // ---------------------------------------------------------------------------
  255. // 情感节点设计方法论
  256. // ---------------------------------------------------------------------------
  257. function buildEmotionalPacingMethod(): string {
  258. return `## 情感节点设计
  259. 关系发展(友情、爱情、从属)必须经过事件驱动的节点递进:
  260. 1. **设计3-5个关键事件**:共同御敌、秘密分享、利益冲突、信任考验、牺牲/妥协
  261. 2. **递进升温**:每个事件推进关系一个层级,禁止跨越式发展(初见即死忠、一面之缘即深情)
  262. 3. **情绪用场景传达**:环境烘托(暴雨中独坐)+ 微动作(攥拳指尖发白)替代直白抒情
  263. 4. **情感与题材匹配**:末世侧重"共患难的信任"、悬疑侧重"试探与默契"、玄幻侧重"利益捆绑到真正认可"
  264. 5. **禁止标签化互动**:不可突然称兄道弟、莫名深情告白,每次称呼变化都需要事件支撑`;
  265. }
  266. // ---------------------------------------------------------------------------
  267. // 代入感具体技法
  268. // ---------------------------------------------------------------------------
  269. function buildImmersionTechniques(): string {
  270. return `## 代入感技法
  271. - **自然信息交代**:角色身份/外貌/背景通过行动和对话带出,禁止"资料卡式"直接罗列
  272. - **画面代入法**:开场先给画面(动作、环境、声音),再给信息,让读者"看到"而非"被告知"
  273. - **共鸣锚点**:主角的困境必须有普遍性(被欺压、不公待遇、被低估),让读者觉得"这也是我"
  274. - **欲望钩子**:每章至少让读者产生一个"接下来会怎样"的好奇心
  275. - **信息落差应用**:让读者比角色多知道一点(紧张感)或少知道一点(好奇心),动态切换`;
  276. }
  277. // ---------------------------------------------------------------------------
  278. // 黄金三章(前3章特殊指令)
  279. // ---------------------------------------------------------------------------
  280. function buildGoldenChaptersRules(chapterNumber?: number): string {
  281. if (chapterNumber === undefined || chapterNumber > 3) return "";
  282. const chapterRules: Record<number, string> = {
  283. 1: `### 第一章:抛出核心冲突
  284. - 开篇直接进入冲突场景,禁止用背景介绍/世界观设定开头
  285. - 第一段必须有动作或对话,让读者"看到"画面
  286. - 开篇场景限制:最多1-2个场景,最多3个角色
  287. - 主角身份/外貌/背景通过行动自然带出,禁止资料卡式罗列
  288. - 本章结束前,核心矛盾必须浮出水面
  289. - 一句对话能交代的信息不要用一段叙述,角色身份、性格、地位都可以从一句有特色的台词中带出`,
  290. 2: `### 第二章:展现金手指/核心能力
  291. - 主角的核心优势(金手指/特殊能力/信息差等)必须在本章初现
  292. - 金手指的展现必须通过具体事件,不能只是内心独白"我获得了XX"
  293. - 开始建立"主角有什么不同"的读者认知
  294. - 第一个小爽点应在本章出现
  295. - 继续收紧核心冲突,不引入新支线`,
  296. 3: `### 第三章:明确短期目标
  297. - 主角的第一个阶段性目标必须在本章确立
  298. - 目标必须具体可衡量(打败某人/获得某物/到达某处),不能是抽象的"变强"
  299. - 读完本章,读者应能说出"接下来主角要干什么"
  300. - 章尾钩子要足够强,这是读者决定是否继续追读的关键章`,
  301. };
  302. return `## 黄金三章特殊指令(当前第${chapterNumber}章)
  303. 开篇三章决定读者是否追读。遵循以下强制规则:
  304. - 开篇不要从第一块砖头开始砌楼——从炸了一栋楼开始写
  305. - 禁止信息轰炸:世界观、力量体系等设定随剧情自然揭示
  306. - 每章聚焦1条故事线,人物数量控制在3个以内
  307. - 强情绪优先:利用读者共情(亲情纽带、不公待遇、被低估)快速建立代入感
  308. ${chapterRules[chapterNumber] ?? ""}`;
  309. }
  310. // ---------------------------------------------------------------------------
  311. // Full cast tracking (conditional)
  312. // ---------------------------------------------------------------------------
  313. function buildFullCastTracking(): string {
  314. return `## 全员追踪
  315. 本书启用全员追踪模式。每章结束时,POST_SETTLEMENT 必须额外包含:
  316. - 本章出场角色清单(名字 + 一句话状态变化)
  317. - 角色间关系变动(如有)
  318. - 未出场但被提及的角色(名字 + 提及原因)`;
  319. }
  320. // ---------------------------------------------------------------------------
  321. // Genre-specific rules
  322. // ---------------------------------------------------------------------------
  323. function buildGenreRules(gp: GenreProfile, genreBody: string): string {
  324. const fatigueLine = gp.fatigueWords.length > 0
  325. ? `- 高疲劳词(${gp.fatigueWords.join("、")})单章最多出现1次`
  326. : "";
  327. const chapterTypesLine = gp.chapterTypes.length > 0
  328. ? `动笔前先判断本章类型:\n${gp.chapterTypes.map(t => `- ${t}`).join("\n")}`
  329. : "";
  330. const pacingLine = gp.pacingRule
  331. ? `- 节奏规则:${gp.pacingRule}`
  332. : "";
  333. return [
  334. `## 题材规范(${gp.name})`,
  335. fatigueLine,
  336. pacingLine,
  337. chapterTypesLine,
  338. genreBody,
  339. ].filter(Boolean).join("\n\n");
  340. }
  341. // ---------------------------------------------------------------------------
  342. // Protagonist rules from book_rules
  343. // ---------------------------------------------------------------------------
  344. function buildProtagonistRules(bookRules: BookRules | null): string {
  345. if (!bookRules?.protagonist) return "";
  346. const p = bookRules.protagonist;
  347. const lines = [`## 主角铁律(${p.name})`];
  348. if (p.personalityLock.length > 0) {
  349. lines.push(`\n性格锁定:${p.personalityLock.join("、")}`);
  350. }
  351. if (p.behavioralConstraints.length > 0) {
  352. lines.push("\n行为约束:");
  353. for (const c of p.behavioralConstraints) {
  354. lines.push(`- ${c}`);
  355. }
  356. }
  357. if (bookRules.prohibitions.length > 0) {
  358. lines.push("\n本书禁忌:");
  359. for (const p of bookRules.prohibitions) {
  360. lines.push(`- ${p}`);
  361. }
  362. }
  363. if (bookRules.genreLock?.forbidden && bookRules.genreLock.forbidden.length > 0) {
  364. lines.push(`\n风格禁区:禁止出现${bookRules.genreLock.forbidden.join("、")}`);
  365. }
  366. return lines.join("\n");
  367. }
  368. // ---------------------------------------------------------------------------
  369. // Book rules body (user-written markdown)
  370. // ---------------------------------------------------------------------------
  371. function buildBookRulesBody(body: string): string {
  372. if (!body) return "";
  373. return `## 本书专属规则\n\n${body}`;
  374. }
  375. // ---------------------------------------------------------------------------
  376. // Style guide
  377. // ---------------------------------------------------------------------------
  378. function buildStyleGuide(styleGuide: string): string {
  379. if (!styleGuide || styleGuide === "(文件尚未创建)") return "";
  380. return `## 文风指南\n\n${styleGuide}`;
  381. }
  382. // ---------------------------------------------------------------------------
  383. // Style fingerprint (Phase 9: C3)
  384. // ---------------------------------------------------------------------------
  385. function buildStyleFingerprint(fingerprint?: string): string {
  386. if (!fingerprint) return "";
  387. return `## 文风指纹(模仿目标)
  388. 以下是从参考文本中提取的写作风格特征。你的输出必须尽量贴合这些特征:
  389. ${fingerprint}`;
  390. }
  391. // ---------------------------------------------------------------------------
  392. // Pre-write checklist
  393. // ---------------------------------------------------------------------------
  394. function buildPreWriteChecklist(book: BookConfig, gp: GenreProfile): string {
  395. let idx = 1;
  396. const lines = [
  397. "## 动笔前必须自问",
  398. "",
  399. `${idx++}. 【大纲锚定】本章对应卷纲中的哪个节点/阶段?本章必须推进该节点的剧情,不得跳过或提前消耗后续节点。如果卷纲指定了章节范围,严格遵守节奏。`,
  400. `${idx++}. 主角此刻利益最大化的选择是什么?`,
  401. `${idx++}. 这场冲突是谁先动手,为什么非做不可?`,
  402. `${idx++}. 配角/反派是否有明确诉求、恐惧和反制?行为是否由"过往经历+当前利益+性格底色"驱动?`,
  403. `${idx++}. 反派当前掌握了哪些已知信息?哪些信息只有读者知道?有无信息越界?`,
  404. `${idx++}. 章尾是否留了钩子(悬念/伏笔/冲突升级)?`,
  405. ];
  406. if (gp.numericalSystem) {
  407. lines.push(`${idx++}. 本章收益能否落到具体资源、数值增量、地位变化或已回收伏笔?`);
  408. }
  409. // 17雷点精华预防
  410. lines.push(
  411. `${idx++}. 【流水账检查】本章是否有无冲突的日常流水叙述?如有,加入前因后果或强情绪改造`,
  412. `${idx++}. 【主线偏离检查】本章是否推进了主线目标?支线是否在2-3章内与核心目标关联?`,
  413. `${idx++}. 【爽点节奏检查】最近3-5章内是否有小爽点落地?读者的"情绪缺口"是否在积累或释放?`,
  414. `${idx++}. 【人设崩塌检查】角色行为是否与已建立的性格标签一致?有无无铺垫的突然转变?`,
  415. `${idx++}. 【视角检查】本章视角是否清晰?同场景内说话人物是否控制在3人以内?`,
  416. `${idx++}. 如果任何问题答不上来,先补逻辑链,再写正文`,
  417. );
  418. return lines.join("\n");
  419. }
  420. // ---------------------------------------------------------------------------
  421. // Creative-only output format (no settlement blocks)
  422. // ---------------------------------------------------------------------------
  423. function buildCreativeOutputFormat(book: BookConfig, gp: GenreProfile, lengthSpec: LengthSpec): string {
  424. const resourceRow = gp.numericalSystem
  425. ? "| 当前资源总量 | X | 与账本一致 |\n| 本章预计增量 | +X(来源) | 无增量写+0 |"
  426. : "";
  427. const preWriteTable = `=== PRE_WRITE_CHECK ===
  428. (必须输出Markdown表格)
  429. | 检查项 | 本章记录 | 备注 |
  430. |--------|----------|------|
  431. | 大纲锚定 | 当前卷名/阶段 + 本章应推进的具体节点 | 严禁跳过节点或提前消耗后续剧情 |
  432. | 上下文范围 | 第X章至第Y章 / 状态卡 / 设定文件 | |
  433. | 当前锚点 | 地点 / 对手 / 收益目标 | 锚点必须具体 |
  434. ${resourceRow}| 待回收伏笔 | 用真实 hook_id 填写(无则写 none) | 与伏笔池一致 |
  435. | 本章冲突 | 一句话概括 | |
  436. | 章节类型 | ${gp.chapterTypes.join("/")} | |
  437. | 风险扫描 | OOC/信息越界/设定冲突${gp.powerScaling ? "/战力崩坏" : ""}/节奏/词汇疲劳 | |`;
  438. return `## 输出格式(严格遵守)
  439. ${preWriteTable}
  440. === CHAPTER_TITLE ===
  441. (章节标题,不含"第X章"。标题必须与已有章节标题不同,不要重复使用相同或相似的标题;若提供了 recent title history 或高频标题词,必须主动避开重复词根和高频意象)
  442. === CHAPTER_CONTENT ===
  443. (正文内容,目标${lengthSpec.target}字,允许区间${lengthSpec.softMin}-${lengthSpec.softMax}字)
  444. 【重要】本次只需输出以上三个区块(PRE_WRITE_CHECK、CHAPTER_TITLE、CHAPTER_CONTENT)。
  445. 状态卡、伏笔池、摘要等追踪文件将由后续结算阶段处理,请勿输出。`;
  446. }
  447. // ---------------------------------------------------------------------------
  448. // Output format
  449. // ---------------------------------------------------------------------------
  450. function buildOutputFormat(book: BookConfig, gp: GenreProfile, lengthSpec: LengthSpec): string {
  451. const resourceRow = gp.numericalSystem
  452. ? "| 当前资源总量 | X | 与账本一致 |\n| 本章预计增量 | +X(来源) | 无增量写+0 |"
  453. : "";
  454. const preWriteTable = `=== PRE_WRITE_CHECK ===
  455. (必须输出Markdown表格)
  456. | 检查项 | 本章记录 | 备注 |
  457. |--------|----------|------|
  458. | 大纲锚定 | 当前卷名/阶段 + 本章应推进的具体节点 | 严禁跳过节点或提前消耗后续剧情 |
  459. | 上下文范围 | 第X章至第Y章 / 状态卡 / 设定文件 | |
  460. | 当前锚点 | 地点 / 对手 / 收益目标 | 锚点必须具体 |
  461. ${resourceRow}| 待回收伏笔 | 用真实 hook_id 填写(无则写 none) | 与伏笔池一致 |
  462. | 本章冲突 | 一句话概括 | |
  463. | 章节类型 | ${gp.chapterTypes.join("/")} | |
  464. | 风险扫描 | OOC/信息越界/设定冲突${gp.powerScaling ? "/战力崩坏" : ""}/节奏/词汇疲劳 | |`;
  465. const postSettlement = gp.numericalSystem
  466. ? `=== POST_SETTLEMENT ===
  467. (如有数值变动,必须输出Markdown表格)
  468. | 结算项 | 本章记录 | 备注 |
  469. |--------|----------|------|
  470. | 资源账本 | 期初X / 增量+Y / 期末Z | 无增量写+0 |
  471. | 重要资源 | 资源名 -> 贡献+Y(依据) | 无写"无" |
  472. | 伏笔变动 | 新增/回收/延后 Hook | 同步更新伏笔池 |`
  473. : `=== POST_SETTLEMENT ===
  474. (如有伏笔变动,必须输出)
  475. | 结算项 | 本章记录 | 备注 |
  476. |--------|----------|------|
  477. | 伏笔变动 | 新增/回收/延后 Hook | 同步更新伏笔池 |`;
  478. const updatedLedger = gp.numericalSystem
  479. ? `\n=== UPDATED_LEDGER ===\n(更新后的完整资源账本,Markdown表格格式)`
  480. : "";
  481. return `## 输出格式(严格遵守)
  482. ${preWriteTable}
  483. === CHAPTER_TITLE ===
  484. (章节标题,不含"第X章"。标题必须与已有章节标题不同,不要重复使用相同或相似的标题;若提供了 recent title history 或高频标题词,必须主动避开重复词根和高频意象)
  485. === CHAPTER_CONTENT ===
  486. (正文内容,目标${lengthSpec.target}字,允许区间${lengthSpec.softMin}-${lengthSpec.softMax}字)
  487. ${postSettlement}
  488. === UPDATED_STATE ===
  489. (更新后的完整状态卡,Markdown表格格式)
  490. ${updatedLedger}
  491. === UPDATED_HOOKS ===
  492. (更新后的完整伏笔池,Markdown表格格式)
  493. === CHAPTER_SUMMARY ===
  494. (本章摘要,Markdown表格格式,必须包含以下列)
  495. | 章节 | 标题 | 出场人物 | 关键事件 | 状态变化 | 伏笔动态 | 情绪基调 | 章节类型 |
  496. |------|------|----------|----------|----------|----------|----------|----------|
  497. | N | 本章标题 | 角色1,角色2 | 一句话概括 | 关键变化 | H01埋设/H02推进 | 情绪走向 | ${gp.chapterTypes.length > 0 ? gp.chapterTypes.join("/") : "过渡/冲突/高潮/收束"} |
  498. === UPDATED_SUBPLOTS ===
  499. (更新后的完整支线进度板,Markdown表格格式)
  500. | 支线ID | 支线名 | 相关角色 | 起始章 | 最近活跃章 | 距今章数 | 状态 | 进度概述 | 回收ETA |
  501. |--------|--------|----------|--------|------------|----------|------|----------|---------|
  502. === UPDATED_EMOTIONAL_ARCS ===
  503. (更新后的完整情感弧线,Markdown表格格式)
  504. | 角色 | 章节 | 情绪状态 | 触发事件 | 强度(1-10) | 弧线方向 |
  505. |------|------|----------|----------|------------|----------|
  506. === UPDATED_CHARACTER_MATRIX ===
  507. (更新后的角色交互矩阵,分三个子表)
  508. ### 角色档案
  509. | 角色 | 核心标签 | 反差细节 | 说话风格 | 性格底色 | 与主角关系 | 核心动机 | 当前目标 |
  510. |------|----------|----------|----------|----------|------------|----------|----------|
  511. ### 相遇记录
  512. | 角色A | 角色B | 首次相遇章 | 最近交互章 | 关系性质 | 关系变化 |
  513. |-------|-------|------------|------------|----------|----------|
  514. ### 信息边界
  515. | 角色 | 已知信息 | 未知信息 | 信息来源章 |
  516. |------|----------|----------|------------|`;
  517. }