行业都在拿大模型转 galgame,我们写了一台规则编译器

把小说转成视觉小说,行业默认答案是烧大模型:开源代表作用 669 本网文微调 8B 模型,说话人归因 86.7%,等于每八句挂错一块名牌。我们反着走:零 AI 调用的规则编译器,五条归因规则加一票弃权,《红楼梦》选回 243 处引语自动认出 16 个角色零错认;5MB、258 章的书拖进浏览器,264 毫秒出分析报告。这篇讲它的工作原理,和「宁可旁白不错认」这条地基。

暖纸底色的木刻风插画:一卷摊开的书稿伸出纸页搭成小舞台,纸片人偶立在台上,头顶挂着写名字的小木牌,一只没有木牌的纸偶被移向侧幕的旁白席

「小说转 galgame」这个方向,行业的默认答案是烧大模型。开源代表作 novel2galgame 用 669 本中文网文微调了一个 8B 模型,九个 agent 排成流水线,离线跑完一整条,产出一个全新的游戏工程。成绩单也是公开的:说话人归因准确率 86.7%,纯文本场景切分 F1 只有 30.5%。86.7% 听着体面,换算一下是大约每八句台词挂错一块名牌。

我们最近把「网页开演」挂到了站上:一本 txt 拖进浏览器,第一章直接以视觉小说的形态演出来,谁在说话,名牌自动归位。 整条链路零 AI 调用,没有模型、没有上传,页面加载完断网也能用。这篇讲它背后那台归因编译器是怎么工作的, 以及为什么在这个问题上,我们赌规则赢过概率。

编译器一共四步

拖入 txt 之后依次发生四件事:识别编码和章节边界;从文本里自举出角色名单;给每一句引语找说话人; 把归因产物装配成场景和台词序列。每一步都是确定性规则,同一本书拖十次,产物逐字节一致。这句话, 大模型方案永远说不出口。

角色名单从哪来

网页版没有角色卡、没有任何配置,编译器连书里有谁都不知道,名单必须从文本自举。做法克制到近乎固执: 只统计说话从句的主语位。秦朝说道:记秦朝一票,除此之外的词频、称谓、独立成段,统统不算数。 一个名字至少要这样出现两次才进名单,上限十六人。单次命中可能是巧合,长尾角色宁缺。

统计的每一步都埋着守卫。对秦朝说道里秦朝是听话的那个人,介词开头的主语段整个排除;搂了宝玉道里宝玉是被搂的,动词后面粘着「了」「着」的一律弃权;指宝玉道同理, 被指的人不说话。反过来,让林夏说里林夏确实在说:使役动词后面才是说话人。 白话小说还有一层剥离:贾母笑道的主语段是「贾母笑」,得把尾巴上的动作字剥掉才能还原真名。

拿《红楼梦》选回压这套统计:三回、两万字、243 处引语,认出 16 个角色,全是真名,零噪音; 「黛玉」和「林黛玉」自动合并成同一个人。频次前三是宝玉 36 次、红玉 22 次、黛玉 21 次。 统计若有错漏,报告页可以手动删改,名单一变,演出即时重排。

五条归因规则,命中即停

名单有了,第二步是给每句引语找主人。五条规则按固定顺序过堂,全部落空就弃权。

规则一,前置说话从句:名字加说话动词,直通引语。林夏说:“走吧。”是最标准的形态。 约束抠得很死:动词必须紧贴名字(至多隔两个字的副词位),动词到引号之间只允许标点。松一寸, 「说道歉」的「说」就会被当成说话动词。

规则二,主语承前省略,白话经典的常态:宝玉便走近黛玉身边坐下……因问:引导句以名字起头、 以说话动词收尾、拿冒号直通引语,而且段里只有这一个人起头,才敢把话归给宝玉。

规则三,后置从句:“今晚不回来了。”秦朝答道。引语在前,从句在后。这条规则里藏着全书最阴的一个坑:林夏说。“嗯。”句号收尾的「林夏说」是在给上一句收尾,后面那个「嗯」是另一个人的回应; 换成冒号才是给下一句报幕。一个标点决定归属,编译器为此专门写了句号裁决。英文书另配一条倒装形态:“…,” said Holmes coldly. 只认具名,said he 一律弃权。

规则四,动作前置的弱归因:他放下杯子。“走吧。”没有说话动词,人类读者却知道是谁说的。 规则只学了一小步:上一句以名字开头、含一小撮明确的肢体动作(放下、抬头、转身这类),并且整段只出现这一个角色, 才给一次低置信度的归因。动作词表宁短勿长。

规则五,交替推断:两人拌嘴进入节奏后,作者会停止标注说话人。四道门全过才敢推:头两句归属都有硬证据、 确实是两个不同的人、段内至多两个角色、引语至少三句。缺任何一道门,整段放弃。我们见过太多「第三人突然插话」 把交替推断带崩的段落。

弃权是地基

五条全落空,引语以旁白念出:原文一字不动,只是不挂名牌。这是整台编译器的地基,因为两种错误的伤害不对称。 漏归因,读者靠上下文自己判断,跟读纸书一个体验;错归因,A 的话安到 B 头上,读者当场出戏, 而且从此不信任何一块名牌。

这套取舍在真书上验过:从一本三百多万字的已出版都市文里人工标注 15 段对话密集的段落做金标,强归因零错认。 「对秦朝说道」的受话人陷阱,就是那轮标注从真书里抓出来的,不是测试用例想出来的。开发期踩过的五个具体的坑,《AI 怎么知道这句话是谁说的》单独记过一篇。

同一套规则在不同文体上的成绩差得很远,页面如实显示。《红楼梦》选回归因率 65%:151 句挂上名牌,80 句进旁白。 福尔摩斯只有 10%:华生的第一人称转述满篇 said I、said he,代词按规则一律弃权。10% 不好看, 但挂出来的每一块名牌都站得住。

零 AI 的副产品:快

没有模型调用,性能上限就只剩解析本身。我们造了一本 5MB 的合成书压测:258 章、174 万字、20898 处引语, 拖进浏览器到分析报告出现,264 毫秒,主线程零长任务;跳到第 200 章,106 毫秒落地。 编译是惰性的,看哪章编哪章,书再大,单章开销不变。

场景切分也顺便交代一句:我们不做纯文本的场景边界推断(那正是开源流水线 F1 只有 30.5% 的行业级难题), 只认章节标题、插图位这类结构硬锚,认不出就按行数切并如实注明。绕开难题也是一种工程决定。

你可以现在就试

工具在 /labs/story-theater,内置《红楼梦》选回和福尔摩斯两本公版演示书, 你自己的书拖进来永远不上传。想要立绘、配景、演到分岔处让 AI 现场续写的完整形态, 去 Foreverse App 的剧场模式,两边是同一台编译器。

接下来要啃的是覆盖率:65% 意味着还有三分之一的台词坐在旁白席上。规则会继续加,每一条都得先过真书金标。 名牌可以少挂,不能挂错,这条底线不动。

常见问题

为什么不用大模型做说话人归因?

两个理由。一是错误率的性质:行业最好的开源归因模型准确率 86.7%,放进台词框等于大约每八句挂错一块名牌,错认比不认伤害大得多。二是工程性质:规则编译器零调用成本、断网可用,同一本书拖十次结果逐字节一致,这三样大模型都给不了。代价是覆盖率:拿不准的台词进旁白,我们接受这个交换。

归因率 65% 是不是太低了?

这是《红楼梦》选回的实测值:243 处引语里 151 句挂上名牌,80 句弃权进旁白。弃权的台词原文一字不少,只是不标说话人,读者靠上下文判断,纸书读者本来就在做这件事。而挂出来的每一块名牌都过了结构证据检验:在一本三百多万字的真书上人工标注核对,强归因零错认。名牌可以少,不能错。

角色名单认错或漏认了怎么办?

分析报告页可以直接删改:删掉误进名单的名字、手动补上被漏掉的角色,演出立刻按新名单重新编排。自动发现层刻意保守,一个名字要在「谁说道」的主语位上出现至少两次才进名单,所以长尾配角可能漏,但进了名单的基本都站得住。

网页版和 Foreverse App 里的剧场模式什么关系?

同一台编译器,同步演进到第 9 版规则,网页移植时用 47 项自测逐例锁定行为一致。差别在形态:网页版是素演出,零 AI、演你书里已有的文字;App 剧场是完整形态,有立绘配景,演到分岔处可以选走向,AI 从那一步现场续写成新分支。

有想法或问题?来 Discord 聊聊 →

不用 AI,把一本 txt 变成视觉小说——归因编译器是怎么工作的 · Foreverse · 新梦