From e022faf975f7e055714ac9b6740a566c22ca115d Mon Sep 17 00:00:00 2001 From: Jayway Liu <14022498+bluejayway@user.noreply.gitee.com> Date: Sun, 4 Oct 2026 07:42:47 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=95=B0=E6=8D=AE=E5=88=A0=E9=99=A4?= =?UTF-8?q?=E6=9C=BA=E5=88=B6=EF=BC=88purge=20=E7=89=A9=E7=90=86=E6=B8=85?= =?UTF-8?q?=E7=A9=BA=20+=20=E6=8A=A5=E5=91=8A=E9=A1=B5=E9=9A=90=E7=A7=81?= =?UTF-8?q?=E5=8C=BA=E5=9D=97=20+=20=E5=88=A0=E5=90=8E=E5=9B=9E=E5=90=8C?= =?UTF-8?q?=E6=84=8F=E9=97=A8=EF=BC=89+=20=E6=B5=8B=E8=AF=95=E6=A1=A9?= =?UTF-8?q?=E8=A1=A5=20purge?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- content/chapters/chapter2.json | 4 +- content/chapters/chapter3.json | 2 +- docs/script/act1.md | 17 ++- .../coherence-literary-review-2026-10-04.html | 121 ++++++++++++++++++ ...冢-Act1-连贯性与文学性评审-2026-10-04.docx | Bin 0 -> 21169 bytes docs/story-framework.md | 52 ++++++++ scripts/verify-adaptive.ts | 1 + scripts/verify-live.ts | 3 + scripts/verify.ts | 32 ++++- src/App.tsx | 9 ++ src/engine/eventStore.ts | 18 ++- src/engine/progress.ts | 13 ++ src/engine/sqliteEventStore.ts | 11 +- src/ui/report/ReportPage.tsx | 61 ++++++++- 14 files changed, 330 insertions(+), 14 deletions(-) create mode 100644 docs/script/coherence-literary-review-2026-10-04.html create mode 100644 docs/script/苍梧疑冢-Act1-连贯性与文学性评审-2026-10-04.docx diff --git a/content/chapters/chapter2.json b/content/chapters/chapter2.json index e7a82a5..8cdd7ca 100644 --- a/content/chapters/chapter2.json +++ b/content/chapters/chapter2.json @@ -16,7 +16,7 @@ "id": "c2-d1v", "type": "dialogue", "speaker": "旁白", - "text": "【若 c1_06_landing = b_precise】黄昏,苍梧城内西市。西市正收摊,灯笼却没亮——满城无灯。落点舱的余震还在耳膜里,你已经站在了一千年前的苍梧城里。没有人看见你出现,但你觉得整座城都看见了:家家户户门窗紧闭,街上的人走路都贴着墙根。一个卖炊饼的老汉收摊时看了你一眼,那一眼不是好奇,是数你。天上最后一线光里,你忽然明白资料里那句话是什么意思:「苍梧最后一个月,夜不举火。」人在怕。只是不知道在怕什么。", + "text": "【若 c1_06_landing = b_precise】黄昏,苍梧城内西市。西市正收摊,灯笼却没亮——满城无灯。落点舱的余震还在耳膜里,你已经站在了一千年前的苍梧城里。没有人看见你出现,但你觉得整座城都看见了:家家户户门窗紧闭,街上的人走路都贴着墙根。一个卖炊饼的老汉收摊时看了你们一眼——不是看生人的好奇:他的目光挨个儿从你们五张脸上过了一遍,像点货,点完便低头继续收他的摊。夜禁的城里,生面孔是稀罕物,多一张,街坊都要记挂几天。天上最后一线光里,你忽然明白资料里那句话是什么意思:「苍梧最后一个月,夜不举火。」人在怕。只是不知道在怕什么。", "sprite": "scene", "next": "c2-d1a" }, @@ -212,7 +212,7 @@ { "signature": "遗憾但坦然", "variantId": "intel_mid", - "label": "【同路】界碑前他把方位时辰又对了一遍,才肯动身。你们自己走进月下的封土群,他跟在后头一路没再多话,找到那块「会唱歌的石头」时才嗯了一声。到暗门口,他多说一句:「放得下的,才叫过去。」门缝开了才走。基调:得体的距离。", + "label": "【同路】界碑前他把方位时辰又对了一遍,才肯动身。你们自己走进月下的封土群,他跟在后头一路没再多话。路过那块「会唱歌的石头」时,夜风正巧从石窍里过,石头低低地呜了一声,像谁在很远的地方应了个声——他嗯了一下,算是跟它打了招呼。到暗门口,他多说一句:「放得下的,才叫过去。」门缝开了才走。基调:得体的距离。", "tint": "#a0964b" }, { diff --git a/content/chapters/chapter3.json b/content/chapters/chapter3.json index 92141e2..52d17f6 100644 --- a/content/chapters/chapter3.json +++ b/content/chapters/chapter3.json @@ -8,7 +8,7 @@ "id": "c3-d1", "type": "dialogue", "speaker": "旁白", - "text": "暗门后的台阶比想象中长。走到一半你就明白了:这不是墓。墓是让人安息的地方,这里是让人等待的地方。祭祀坑一层压着一层,器物成组地朝着南方,像一片朝着太阳倒伏的麦田。考古报告里那个你标注过无数次的词,此刻从纸上站了起来——禳。这不是下葬。这是祈福。整座宫墟,是苍梧人修给上天的、一封加急的信。", + "text": "探灯的光最后扫过暗门的门楣——上面凿着一个字,校准室里那块残碑上的同一个:「安」。碑说,它镇的方向就是安门。你们到了。暗门后的台阶比想象中长。走到一半你就明白了:这不是墓。墓是让人安息的地方,这里是让人等待的地方。祭祀坑一层压着一层,器物成组地朝着南方,像一片朝着太阳倒伏的麦田。考古报告里那个你标注过无数次的词,此刻从纸上站了起来——禳。这不是下葬。这是祈福。整座宫墟,是苍梧人修给上天的、一封加急的信。", "sprite": "scene", "next": "c3-d1a" }, diff --git a/docs/script/act1.md b/docs/script/act1.md index 9c3bd3f..575b548 100644 --- a/docs/script/act1.md +++ b/docs/script/act1.md @@ -530,7 +530,9 @@ score。对 `confidence < 0.6` 的维度,在第 2 章插入对应支线 > > 没有人看见你出现。但你觉得整座城都看见了——家家户户门窗 > 紧闭,街上的人走路都贴着墙根。一个卖炊饼的老汉收摊时看了 -> 你一眼,那一眼不是好奇,是**数你**。 +> 你们一眼——不是看生人的好奇:他的目光挨个儿从你们五张脸上 +> 过了一遍,像点货,点完便低头继续收他的摊。夜禁的城里, +> 生面孔是稀罕物,多一张,街坊都要记挂几天。 > > 天上最后一线光里,你忽然明白资料里那句话是什么意思: > 「苍梧最后一个月,夜不举火。」人在怕。只是不知道在怕什么。 @@ -539,7 +541,9 @@ score。对 `confidence < 0.6` 的维度,在第 2 章插入对应支线 > 沈铎分派:明日采集开始。 【工程】两段变体文本等长等信息量(A 给城外情报线,B 给城内氛围 -素材),汇合后主线完全一致。变体 B 的「数你」细节为 §3-7 钩子埋线。 +素材),汇合后主线完全一致。变体 B 的老汉细节在本段内自释 +(夜禁城的生面孔焦虑),不向后续章节悬置;「被注视」主题由 +§3-7 通话正面承担,跨幕伏笔一律登记于母本 §9。 --- @@ -719,7 +723,7 @@ score。对 `confidence < 0.6` 的维度,在第 2 章插入对应支线 | 情绪签名 | 演出档位 | 夜游场景基调稿 | |---|---|---| | 自责(悔指向自己) | **絮说**:一路举着火把走在前面,话不断 | 月色下的陵区,石像生的影子很长。老人举火把走在前面,脚步轻得不像老人——"陵里的路,闭眼走比睁眼走直"。行至暗门,他忽然说起自己年轻时也悔过一件事,说了半句,摆摆手不说了。摸一遍门石,等门缝开了才走。基调:**被信任的厚重** | -| 遗憾但坦然 | **同路**:话在界碑前说完,路上安静 | 界碑前他把方位时辰又对了一遍,才肯动身。你们自己走进月下的封土群,他跟在后头一路没再多话,找到那块"会唱歌的石头"时才嗯了一声。到暗门口,他多说一句:"放得下的,才叫过去。"门缝开了才走。基调:**得体的距离** | +| 遗憾但坦然 | **同路**:话在界碑前说完,路上安静 | 界碑前他把方位时辰又对了一遍,才肯动身。你们自己走进月下的封土群,他跟在后头一路没再多话。路过那块"会唱歌的石头"时,夜风正巧从石窍里过,石头低低地呜了一声,像谁在很远的地方应了个声——他嗯了一下,算是跟它打了招呼。到暗门口,他多说一句:"放得下的,才叫过去。"门缝开了才走。基调:**得体的距离** | | 愤怒指向他人 | **缄默**:方位时辰一句不落指给你,然后沉默地走在队尾 | 老人听完,编草绳的手停了很久。动身时该指的路、该说的时辰,一句没少——说完就闭了嘴,走在队尾,没再多看你一眼。可你们走出十几步,他在身后忽然开口:"火气这东西,老头子也有过一回。"没头没尾,再没下文。门缝开了,他转身就走。基调:**话到,情未到**——可那一句话,他自己也未必舍得讲给旁人 | | 沉默 | **草绳**:一下午并肩编草绳,黄昏同样起身送到门口 | 一下午你们并肩搓绳,他絮絮地说陵里的事,你默默地听。日头偏西,他拍拍手上的草屑起身,一样把你们送到暗门口。等门缝开了,他说:"不说的人,心里的事最重。重的别久揣。"基调:**笨拙的善意** | @@ -1127,6 +1131,9 @@ score。对 `confidence < 0.6` 的维度,在第 2 章插入对应支线 【正文·旁白】 +> 探灯的光最后扫过暗门的门楣——上面凿着一个字,校准室里那块 +> 残碑上的同一个:**安**。碑说,它镇的方向就是安门。你们到了。 +> > 暗门后的台阶比想象中长。走到一半你就明白了: > 这不是墓。墓是让人安息的地方,**这里是让人等待的地方**。 > @@ -1810,6 +1817,10 @@ C-2049 的燃料质量比预估高,是的,**那扇门开得很顺**。 - 叙述纪律:旁白只写可见动作与台词,不替玩家或 NPC 的心理下结论; C3-03 伤员获同等救护,差异落在安全区域的记录任务与遗存损失, NPC 不做"选得对/处置正确"式裁判(C3-03-B/C 台词已清除)。 +- 伏笔纪律:跨幕悬念一律登记于母本 §9 伏笔登记表(写明已交代/ + 待回收/建议章节);段内承诺必须在 Act 1 内兑现——C1-05"安"字 + 已于 §3-1 暗门门楣回收,"会唱歌的石头"已在 C2-02 同路档内 + 出声,§2-1 老汉细节已改为段内自释(2026-10-04)。 - 支线铁律:所有分支差异收敛于变量与文本换色,主线事件序列 对所有玩家一致(三通道原则);自适应支线仅留挂点(§2-4、§3-2 前), 支线剧本另册;支线须遵守 `wenjian_health` 贯穿约束(§2-4 末, diff --git a/docs/script/coherence-literary-review-2026-10-04.html b/docs/script/coherence-literary-review-2026-10-04.html new file mode 100644 index 0000000..4d87267 --- /dev/null +++ b/docs/script/coherence-literary-review-2026-10-04.html @@ -0,0 +1,121 @@ + + + + +《苍梧疑冢》Act 1 剧本 · 连贯性与文学性评审 + + + + +

《苍梧疑冢》Act 1 剧本 · 连贯性与文学性评审

+ +

评审对象:docs/script/act1.md、docs/script/adaptive-branches.md(2026-10-04 安全合规修订版),并对照已同步的章节与支线 JSON。
+评审口径:只看叙事质量——连贯性(有发必有收、人物与设定有出处、时间线单一事实源)与文学性(张力、意象、声口、沉浸感)。测量与合规口径不在此评。
+结论速览:剧本骨架是好的——大火星倒计时、空璧座、问心石三条意象主线贯穿有力,NPC 声口分明,章末钩子结构成熟。当前最主要的问题集中在两处:其一,上一轮安全化修订把三场高潮戏(C3-03 塌方、B-EMO-A 沙暴、B-EMO-B 压板)的动作全部换成"清点/坐标/记录/警戒线",并引入了没有出处的"接应人员",连贯与文学同时受损;其二,若干伏笔有发无收("数你"的老汉、"安"字碑、"会唱歌的石头"),正是"老人说要带你去看个东西、后文再无下文"这一类断裂。

+ +

一、连贯性问题(按严重度排序)

+ +

P1-1 "接应人员/受训人员"凭空出现,队伍里没有这个人

+

穿越小队自始至终只有五个人:沈铎、阿棠、温简、老鬼、你。但 C3-03 的三条后续里六次出现"接应人员"("传给接应人员""由接应人员送回营地""接应人员护送"),B-EMO-B 里出现"受训人员""受训接应人员"。读者会立刻问:这些人是哪来的?——这是当前全剧最刺眼的一处设定破绽。

+

值得注意的是,C3-03 的场景段其实已经把救护落在了正确的人身上("沈铎和受过救护训练的老鬼已在安全线内接管伤情"),后文的"接应人员"是改写时漏网的旧词,同一场景内自相矛盾。

+

修改意见:全剧统一口径——队内急救员就是老鬼(可以在 §1-5 集结甲板的装备清单处补半句"老鬼清点着急救包——队里指定急救员从来是他",让这层身份有出处)。然后把 C3-03、B-EMO-B、附录 D 里所有"接应人员/受训人员"替换为"老鬼"或"沈铎与老鬼";"送回营地"改为"老鬼按外伤规程处置后,陪她随队撤回"。

+ +

P1-2 悬空伏笔:说过要给的东西,后文没给

+

这是本次评审按您举的"守墓老人说带你看个东西"一类问题专项排查的结果。三处确认有发无收:

+
    +
  1. "数你"的老汉(§2-1 变体 B)。原文:"一个卖炊饼的老汉收摊时看了你一眼,那一眼不是好奇,是数你",且工程注明确承诺"变体 B 的「数你」细节为 §3-7 钩子埋线"——但 §3-7 的馆长通话通篇没有回收这条线。读者(尤其是选了精确落点的玩家)记住了这个寒意,剧本却忘了。
    修改意见:在 §3-7 通话里补半句——馆长对"另一方"低声说:"城里已经有人在替你们数着人数了。"一句即收;若不愿加,则删掉工程注里"§3-7 钩子埋线"的承诺,不要立字据又不兑现。
  2. +
  3. "安"字碑(C1-05)。沈铎说:"这个字是'安'。记住它,下去之后你会再见到它。"这是标准的契诃夫之枪——但主线后文"安"再未出现(§3-1 只写"穿过守墓老人指点的暗门")。
    修改意见:在 §3-1 开场加一句换色:"暗门的门楣上凿着一个字——你在校准室见过的那个:安。"一句话完成回收,且让 C1-05 的解谜与第三章的空间产生扣合。
  4. +
  5. "会唱歌的石头"(C2-02 同路档)。老人指给你们的路标,提了名字,石头一声没响。
    修改意见:要么让它在夜游正叙里"呜"地响一声(风过石窍),与 §2-8"这座城安静得像屏着一口气"形成对位——全城唯一出声的是一块石头,文学上是好句子;要么删掉这个命名,改成普通的界石。
  6. +
+

同时核对确认已经闭环、不必动的伏笔,列出备查:谶语"不是人走了,是请走的"→§3-1"禳/祈福";温简板子配给疑云→C3-06 引导语;哨子/绳包扣→§3-7"攥出了汗";阿棠崴脚→少年赠药;空璧座→§3-7"璧的事一个字都不许提";大火星倒计时→§3-8 终幕。

+ +

P1-3 阿棠伤情因果链断裂:擦伤怎么变成了骨裂

+

C3-03 现场写"阿棠小腿被擦伤",三条后续却都写"夜里疼醒三次,次日复查为小腿骨裂"。两个问题:其一,擦伤与骨裂是两种伤,现场能走动、能喊,伤情升级缺少中间描写;其二,苍梧宫墟里不存在"复查"的手段,这是现代医院词汇穿越回了古代现场。

+

修改意见:统一为一条可信的伤情线——"塌落的夯土擦过小腿,当时疑为挫伤;回营后老鬼按外伤规程检查,判骨裂、上夹板"。三线共用这一句,把"复查"改为"老鬼检查后确认"。这样 P1-1 的急救员设定也顺势得到第二次使用。

+ +

P2-4 时间线出现两个事实源;终幕算术太绕

+

§2-8 结尾写"第 6 日黄昏,全队拔营……第 7 日清晨入宫墟",§3-1 场景又独立标注"【场景】第七日,宫墟"。同一信息两处维护,将来任何一侧改动都会漂移。
修改意见:绝对日历只在 §2-8(或附录 D)说一次,§3-1 场景删"第七日",改"清晨,宫墟"。另外 §3-8"那一天是九天后/回程八天后"的数字算术对玩家偏抽象,建议换成形象:"大火星再走七夜,就到正中;你们的回程,在第六夜。"——数字少一个,意象多一层。

+ +

P2-5 守墓老人的"半句悔"在四档中存在性不一致

+

自责档里老人"忽然说起自己年轻时也悔过一件事,说了半句,摆摆手不说了",其余三档没有这半句。单周目玩家看不到差异,但这是同一人物的心事,作为文案资产应当要么四档都有(措辞不同:同路档是沉默更久、缄默档是那句"火气这东西,老头子也有过一回"——这条已经有了)、要么注明自责档独有是设计而非遗漏。
修改意见:把"悔"的痕迹定为老人的固定底色:絮说档=半句悔;同路档=话到界碑前说完、其中夹一句"老头子也有过一回";缄默档=已有;草绳档=搓绳时"绳结打错了三次"这类动作化暗示。四档同源,深浅不同。

+ +

二、文学性问题

+ +

P1-6 高潮戏"去张力":合规之后,紧张感没有家可回(最主要的文学问题)

+

安全化修订正确地移除了危险动作示范,但把三场高潮戏的选项与后续几乎全部替换成了管理动作。试读 C3-03 的三个选项:

+
A.留在阿棠这边。接手伤情记录和撤离联络,替她把疼痛与需求传给接应人员……
+B.留在温简这边。在安全线内核对远距影像、方位与已抄铭文……
+C.调配两边的记录任务。让沈铎确认交接、温简退回安全线,先保全已有资料……
+

以及 B-EMO-A 沙暴中的选项:"先点人""先报坐标""先安顿队伍"。这是 Act 1 的情绪最高点与两处突发高压,但现在读起来像工地交接单。玩家在高潮处拿到的是职位描述,不是抉择。

+

需要说明:问题不在安全边界本身——"人不进危险区、无操作示范"这条红线应当保留——而在张力被错误地从"动作"上拿走后,没有搬进"感官与人物"。紧张感完全可以由不涉及任何危险操作的笔墨承载:探灯光柱里暴涨的尘埃、鼎簋滑向缝口时那一寸一寸的慢、温简数拍子的声音、阿棠数自己呼吸的声音、你手心里汗把记录板洇出的印子。

+

修改意见(原则:动词换体温,边界不越线)——把选项动词从"记录/核对/调配"换成玩家身体在场的动作:"把她的手攥住,替她把疼喊出来""替温简举稳灯,让他盯住那行字""什么也不做,就站在两边的中间,喊出每个人的名字"。这些动作同样零危险,但读者能借位置与触觉进入现场。文末第五节给出 C3-03 的整段示范改写。

+ +

P1-7 馆长台词泄漏产品术语,世界观破功

+

C3-02 出石室的馆长台词:"室内不录音、不留讲述文本、不入记忆展厅、不作画像输入;仅保留你完成、折返或离开的路径用于剧情接续。"——"画像输入""剧情接续"是工程与产品语言,从一个自称"收藏人怎么活过"的千年档案馆馆长嘴里说出来,等于穿帮。类似地,"路径""节点""触发"这类词都应避免进入台词层。
修改意见:台词只说世界观内的话:"你在门里说的每一个字,不记进任何册页,不算进任何数——门里的事,归你。"隐私承诺的精确边界(无文本、无画像输入)保留在工程注里,由实际数据行为兑现,不必让馆长替产品文档背书。

+ +

P2-8 C2-02 四档变体表复读:"送到暗门口"写了四遍

+

四档等价是测量上的硬要求,但文学呈现上,"送到暗门口、摸一遍门石、等门缝开了才走"在表格里逐档重复,读感是四段同构的说明文,老人的质感反而被冲淡。
修改意见:把等价的公共动作抽出为一段夜游正叙(放在即时回报之后,只写一遍:"无论你讲了什么,他都在黄昏起身,把你们送到暗门口,摸一遍门石,等门缝开了才转身回去"),四档表格只保留差异列——他一路说不说话、说哪句体己话。表格瘦身后,每档那句台词的分量会更清楚。

+ +

P2-9 中段空转:第 4–6 日,城不呼吸

+

§2-8 用一句"例行采集"带过两整天。可全剧最好的悬念恰是"这座城在等那一天"——等待期的城市应当有涨落,两日真空让"城在等"变成了旁白的口头禅。
修改意见:在 §2-8 桥段加一个五十字级的轻意象事件:水门的商队开始收铜镜("镜子照天,收了天上就看不见了"),或夜里家家磨刀、声音像雨。一个细节即可让焦虑继续生长,顺便为 B-CON-A 的市集氛围提供换色锚点。

+ +

P2-10 阿棠的伤是设定,不是感受

+

骨裂次日(C3-05),她"忽然大笑,笑得咳嗽",通场没有一处疼的痕迹。伤情变量记进了附录,身体却没记进文字。
修改意见:给她两处微动作——坐在物资堆上时"撑着膝盖,先把伤腿搁平了才敢坐";大笑笑到一半"吸了口冷气,把后半截笑咽回去,改成拍你的手背"。疼不必说出口,动作替她说。

+ +

P3-11 "璧"的钩子缺一个前置抓点

+

§3-7 馆长那句"壁的事,一个字都不许提"是全季级钩子,但玩家对"璧"的全部认知只是"璧座是空的"。空与禁言之间的重量差,靠玩家自行脑补。
修改意见:§3-2 解题后,温简补一句低声的自问:"礼都备到这一步了……璧呢?"给钩子一个三小时前的抓点,禁言落下来时才有"啪"的一声。

+ +

P3-12 零星措辞

+

旁白偶有说明文腔("接应人员在安全线内备好急救器材"是任务简报不是小说);个别残留短语与安全口径打架("三双手合力"之类应随 P1-1 一并再扫一遍)。建议做一次纯文字层的通读,凡是"主语+配置了+资源"式的句子,都改成"某人在做某个看得见的动作"。

+ +

三、修改优先级清单

+ + + + + + + + + + + + + + + +
优先级条目位置一句话改法
必改 P1接应人员无出处C3-03/B-EMO-B/附录 D急救员定为老鬼(§1-5 补出处),全量替换
必改 P1三处悬空伏笔§2-1/C1-05/C2-02"数你"入 §3-7 通话;门楣见"安";石头出声或删名
必改 P1擦伤→骨裂因果C3-03 三线后续"疑为挫伤,老鬼查后判骨裂上夹板"
必改 P1高潮戏去张力C3-03/B-EMO-A/B选项动词换体温,感官承载张力(见示范)
必改 P1馆长术语泄漏C3-02 出室台词"不记进任何册页,不算进任何数"
应改 P2时间双源§2-8/§3-1日历只留一处;§3-1 删"第七日"
应改 P2四档复读C2-02 变体表公共夜游正叙抽出,表格只留差异
应改 P2中段空转§2-8加一个轻意象事件(收铜镜/磨刀声)
应改 P2阿棠痛感缺失C3-05两处微动作
可改 P3"璧"钩子前置§3-2 结尾温简自问一句"璧呢?"
可改 P3老人半句悔四档统一C2-02四档同源、深浅不同
可改 P3零星措辞全剧通读扫"配置资源"式句子
+ +

四、值得保留的优点(改稿时请勿误伤)

+ + +

五、示范改写:C3-03(安全边界不变,张力回家)

+

以下改写不含任何危险操作示范(无人进入危险区、无技术动作、无专业背书),仅以感官与人物动作承载张力,可直接替换现有正文与选项:

+
+

【旁白】话音未落,前方传来阿棠的喊声和一声闷响。甬道中段的夯土塌了一角。她连滚带爬退到安全线内,小腿的裤管渗了血,脸白得像甬道里的尘土。沈铎和老鬼已经蹲在她身边——老鬼是队里的急救员,剪开裤管的手很稳。沈铎头也不回:"这里够了。别过来。"

+

甬道另一侧,你们今早清理出的工作面上,那组七鼎六簋正随着台基的颤动,一寸一寸滑向裂开的地缝。温简一个人守在安全线内,探灯举得笔直,光柱里尘埃像倒着下的雪。他不回头,声音是从牙缝里挤出来的:"最长的那件……腹铭三百字。我今晨只抄到第七行。"

+

头顶,第二声闷响在逼近。尘土簌簌落在你的记录板上。你站在两条安全线中间——一边是阿棠压着嗓子的抽气声,一边是鼎足蹭过石缝的、指甲刮石般的声音。你只有一副眼睛、一双手。

+

【选项】
+A.到阿棠那边去。蹲下来,把她的手攥住——老鬼处置伤,你替她把疼喊出来。
+B.到温简那边去。替他举稳备用灯,让那三百个字在他抖之前,多进镜头一行是一行。
+C.站在中间,喊出每个人的名字,一边一句,把两边都钉在原地。

+
+

说明:A/B/C 三个动作全部零危险(攥手、举灯、喊名字),合规审查不会受阻;但"指甲刮石般的声音""把疼喊出来""多进一行是一行"把抉择的重量放回了读者的身体里。后续演出可沿用现有三线结果,仅需把"接应人员"替换为"老鬼"(见 P1-1)。

+ +

六、结语

+

这份剧本的骨架——意象系统、声口、钩子结构——已经是成品水准。当前的问题集中而非弥散:连贯性上,一处设定破绽(接应人员)、一处伤情断链、三枚哑火的伏笔;文学性上,三场高潮戏在合规改写中失去了张力载体,外加一处术语穿帮。按第三节清单从 P1 顺次处理,预计两处半天工作量即可完成主体修复;修复时务必守住第四节列出的既有优点——那些是这出戏真正值钱的东西。

+ + + diff --git a/docs/script/苍梧疑冢-Act1-连贯性与文学性评审-2026-10-04.docx b/docs/script/苍梧疑冢-Act1-连贯性与文学性评审-2026-10-04.docx new file mode 100644 index 0000000000000000000000000000000000000000..06951114117ccd3a19d50e41b7441ea050675d5d GIT binary patch literal 21169 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 yA})=4YM;|qD!e)+o)rrK literal 0 HcmV?d00001 diff --git a/docs/story-framework.md b/docs/story-framework.md index 4dae612..f132ffb 100644 --- a/docs/story-framework.md +++ b/docs/story-framework.md @@ -143,3 +143,55 @@ score 问题。对 `confidence < 0.6` 的维度,在下一章插入对应支线 交付:第 1-3 章(本文档定义的全部选择点与讲述点)+ 画像报告 + 分享卡。 不含:匹配、聊天、Act 2/3、实时生成(全部走章节间预生成)。 + +## 9. 伏笔登记表(Act 1 已埋、后续章节回收清单) + +> 纪律(2026-10-04 立):跨幕悬念必须在本表登记,登记时写明 +> 「已交代什么/待回收什么/建议回收章节」;Act 1 文本内做出的 +> 段内承诺(如"你会再见到它")必须在 Act 1 内兑现,不得向 +> Act 2 之后直接借债——Act 2 必须过一手。新增伏笔不登记者, +> 视为写作事故。 + +### 9.1 主线悬念(Act 2 必收) + +| # | 伏笔 | 埋设点(旗标) | 已交代 | 待回收 | 建议章节 | +|---|---|---|---|---|---| +| 1 | 璧的下落 | §3-2 空璧座;§3-7"璧的事一个字不许提" | 礼备到最后一步、璧始终缺席、档案馆禁言 | 璧是什么、去了哪、为何禁言 | Act 2 中层真相(章末) | +| 2 | 档案馆隐瞒与"那一天" | §3-6/§3-7 通话;§3-8 早走一天(`flag_arch_doubt`) | 计划无恶意、问心石是放的、"来不及回头" | 那一天会发生什么;早走一天的正面答案 | Act 2 总线(持续) | +| 3 | 问心石的用途延伸 | §3-7"问心是我们放的";C-2049"燃料质量" | 石头非遗迹、采集数据外送 | 问心数据去向、"燃料"说法的含义 | Act 2(配合 #4) | +| 4 | 温简板子疑云 | C2-05 浅暴露线;C3-06-C-温简"情绪回收率" | 配给多于记录、多出一行回收率 | 双账本线的爆发与站队 | Act 2 温简线 | +| 5 | 沈铎隐瞒线 | C2-06-B 老鬼"不止我一个"(`flag_laogui_hint`);C3-04-B"这次我背"(`flag_shenduo_cover`) | 沈铎有所瞒、求情成把柄 | 他瞒的是什么、把柄如何爆 | Act 2 信任危机主线 | +| 6 | 老鬼动机与私藏线 | C2-06-C"急什么";C1-04-D(`flag_laogui_thread`);C3-04 事发后续 | 放回玉佩有隐情、被限行动 | 他的真实来路与交换条件 | Act 2 老鬼线 | +| 7 | 撤离后开局站位 | C3-06 三条 C 线(沈铎提前/阿棠结盟/温简双账) | 三种知情盟友 | 各自把 Act 2 开场带向何处 | Act 2 第 4 章开场 | + +### 9.2 支线旗标(Act 2 换色或情报回收) + +| # | 伏笔 | 埋设点(旗标) | 待回收 | 建议章节 | +|---|---|---|---|---| +| 8 | 城备净水 | B-CON-A-C(`flag_water_intel`) | "那一天"与水的关系佐证 | Act 2 推算线 | +| 9 | 市令记脸 | B-CON-A-D(`flag_shiling_noticed`) | 城内有人盯上小队 | Act 2 城内风险 | +| 10 | 鱼牙人渠道 | B-CON-B-02-A(`flag_fish_channel`) | 灰色情报网的启用与代价 | Act 2 情报线 | +| 11 | 封存的陶范密室 | B-EMO-B-B/C(`flag_sealed_vault`,"账先欠着") | 重回宫墟开启 | Act 2 重访段 | +| 12 | 徙南垣铭文 | B-CUL-A(`flag_wall_relocation`) | 城为何迁走南墙 | Act 2/3 真相链 | +| 13 | 第十位先王 | B-CUL-B(`flag_tenth_king`) | "等一位王变成先王"的现在进行时 | Act 2/3 宫廷线 | +| 14 | 守陵祖孙 | B-VAL-B(`rapport.shouling`、`flag_herb_poultice`) | 古代侧人情回响 | Act 2 古代侧 | +| 15 | 阿棠的家事 | B-ATT-B(她关频道独处) | 她在守什么私事 | Act 2 阿棠线 | +| 16 | 温简亲密度三档 | C2-05(`c2_05_depth`,沉默档明示 Act 2 兑现) | 慢线情感的兑现 | Act 2 情感线 | +| 17 | 问心幻境余波 | C3-02(`c3_02_mode`/`density`) | 幻境再入与其后果 | Act 2 幻境戏 | +| 18 | "托付那类,最不好办" | C1-03-D(`d_trust`) | 馆长对托付型玩家的态度 | Act 2 换色(小) | + +### 9.3 可选(低优先,Act 2 顺手可用,不收不算事故) + +- CW-0322 银镯(`flag_silver_bracelet`):记忆展厅陈列/征集品册复现。 +- C2-03-B 守陵人换岗观察:Act 2 陵区布控参考。 +- B-ATT-A 侧阶(`flag_side_stairs`):Act 2 重登观星台的路径换色。 + +### 9.4 Act 1 内已闭环(备查,无需后续动作) + +谶语→§3-1"禳";大火星倒计时→§3-8;问心石放置→§3-7 揭露; +哨子/绳包扣→§3-7"攥出了汗";阿棠崴脚→少年赠药;温简板子疑云→C3-06 +(深层入 #4);C-沈铎"撤离提前"→§3-8 换色。 + +> 2026-10-04 修订记录:「数你」老汉改为段内自释(夜禁城生面孔 +> 焦虑,不再跨章悬置);C1-05"安"字于 §3-1 暗门门楣回收; +> 「会唱歌的石头」在同路档内出声收口。 diff --git a/scripts/verify-adaptive.ts b/scripts/verify-adaptive.ts index d8a8c68..1320555 100644 --- a/scripts/verify-adaptive.ts +++ b/scripts/verify-adaptive.ts @@ -50,6 +50,7 @@ class MemoryStore implements EventStore { events: StoryEvent[] = []; async list(sessionId?: string) { return this.events.filter((e) => !sessionId || e.sessionId === sessionId); } async append(e: StoryEvent) { this.events.push(e); } + async purge() { this.events = []; } } class FixedJev extends MockJevClient { dimension: ProfileDimension; diff --git a/scripts/verify-live.ts b/scripts/verify-live.ts index 8bc77aa..907ef3b 100644 --- a/scripts/verify-live.ts +++ b/scripts/verify-live.ts @@ -34,6 +34,9 @@ class MemoryStore implements EventStore { async list(sessionId?: string) { return sessionId ? this.events.filter((e) => e.sessionId === sessionId) : [...this.events]; } + async purge() { + this.events = []; + } } const key = loadApiKey(); diff --git a/scripts/verify.ts b/scripts/verify.ts index 367e2d7..e709acc 100644 --- a/scripts/verify.ts +++ b/scripts/verify.ts @@ -7,7 +7,9 @@ * 5. EventStore 各实现(含 SqliteEventStore)的 append-only 性质(接口层面断言无 update/delete; * SQLite 实连只能在 Tauri 运行时内进行,Node 下不实例化)。 * 6. 同意门(ADR-0004):consent 事件存证、幂等、跨会话跳过; - * 7. 条件文本:flag 推导(附录 A)、【若】标记零泄漏、差分显隐、条件不满足节点跳过无死路。 + * 7. 条件文本:flag 推导(附录 A)、【若】标记零泄漏、差分显隐、条件不满足节点跳过无死路; + * 8. 删除全部个人数据(ADR-0004 数据删除权):purge 后事件列表为空、进度清空、 + * consent 存证删除后回到未同意态、可重新同意并继续走剧情。 * 运行:pnpm verify */ import { readFileSync } from 'node:fs'; @@ -53,6 +55,9 @@ class MemoryStore implements EventStore { async list(sessionId?: string) { return sessionId ? this.events.filter((e) => e.sessionId === sessionId) : [...this.events]; } + async purge() { + this.events = []; + } } const chapters = [1, 2, 3].map( @@ -550,6 +555,31 @@ for (const mode of ['farewell_done', 'farewell_partial', 'exit_room'] as const) check(restored.c3_02_mode === mode && restored.c3_02_fantasy_density === e.state.flags.c3_02_fantasy_density, `问心之室 ${mode}:恢复后叙事状态一致`); } +// --- 14. 删除全部个人数据(ADR-0004 数据删除权):purge 整库物理清空 + 进度清空 + 回到未同意态 --- +{ + const s = new MemoryStore(); + const progress = new MemoryProgressStore(); + const eng = new StoryEngine(chapters, s, new MockJevClient(), undefined, progress); + await eng.grantConsent(); + await walkTo(eng, 'c1-d5'); // 途经 C1-01 身份点、C1-02 讲述点、C1-03 选择点 + check((await s.list()).length > 0 && progress.load() !== null, '删除前:事件流与进度快照均非空'); + await s.purge(); + progress.clear(); + check((await s.list()).length === 0, 'purge 后事件列表为空(全部事件物理删除)'); + check(progress.load() === null, 'clear 后进度快照为空'); + check(!(await eng.hasConsent()), 'purge 后 consent 存证一并删除 → 回到未同意状态(同意门重新出现)'); + await s.purge(); + check((await s.list()).length === 0, 'purge 幂等:重复清空不报错且仍为空'); + // 删除后可重新同意并正常继续走剧情(存储清空后仍可 append,事件不串旧轨迹) + await eng.grantConsent(); + check(s.events.filter((e) => e.kind === 'consent').length === 1, '重新同意:新 consent 存证落库(只存证一次)'); + await walkTo(eng, 'c1-05'); // 从 c1-d5 出发途经 C1-04 选择点 + check( + s.events.some((e) => e.kind === 'choice') && !s.events.some((e) => e.kind === 'profile'), + '删除后可正常继续走剧情并落新事件,旧轨迹事件不残留(无已删除的 profile 事件)', + ); +} + if (failures > 0) { // 主线失败先退出,避免掩盖原有回归。 console.error(`\n${failures} 项校验失败`); diff --git a/src/App.tsx b/src/App.tsx index d0ccfb1..0dbed54 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -65,6 +65,15 @@ export default function App() { engine.replay('chapter1'); setScreen('story'); }} + onPurge={async () => { + // ADR-0004 数据删除权:事件流(含 consent 存证)物理删除、进度清空, + // 应用回到未同意状态——同意门重新出现,重新同意后从第一章开头开始。 + await store.purge(); + engine.replay('chapter1'); + progress.clear(); + setScreen('story'); + setConsent('needed'); + }} /> )} diff --git a/src/engine/eventStore.ts b/src/engine/eventStore.ts index df22887..e7f4cf4 100644 --- a/src/engine/eventStore.ts +++ b/src/engine/eventStore.ts @@ -1,14 +1,18 @@ import type { ChoiceBehavior, DimensionTag, FollowUpDimension, IdentityProfile } from './types'; /** - * 选择/讲述事件存储(ADR-0004:历史不可涂改)。 - * 接口只有 append 和 list —— 刻意不提供 update/delete, + * 选择/讲述事件存储(ADR-0004:历史不可涂改,删除权完整保留)。 + * 接口只有 append / list / purge —— 刻意不提供单条 update/delete, * 让"不允许单独编辑历史选择"成为类型层面的约束,而非约定。 + * purge 是《个人信息保护法》数据删除权(账号注销语义)下的整库物理清空, + * 不是对单条历史的修改;append-only 纪律不变。 */ export interface EventStore { append(event: StoryEvent): Promise; /** 列出事件;传 sessionId 则只看该轨迹 */ list(sessionId?: string): Promise; + /** 整库物理清空(含 consent 存证);删除后应用回到未同意状态 */ + purge(): Promise; } /** 一次重玩 = 一条新轨迹(新 sessionId),旧事件原样保留 */ @@ -72,10 +76,18 @@ export class LocalStorageEventStore implements EventStore { const all: StoryEvent[] = raw ? (JSON.parse(raw) as StoryEvent[]) : []; return sessionId ? all.filter((e) => e.sessionId === sessionId) : all; } + + async purge(): Promise { + try { + localStorage.removeItem(STORAGE_KEY); + } catch { + // 隐私模式等场景:静默降级(与 list 一致) + } + } } /** * Tauri 原生端实现:见 ./sqliteEventStore.ts(tauri-plugin-sql + SQLite, - * 同一 EventStore 接口,SQL 层同样只写 INSERT/SELECT)。 + * 同一 EventStore 接口,SQL 层同样无单条 UPDATE/DELETE;整库清空只走 purge)。 * 运行时选择在 App.tsx:window.__TAURI__ 存在用 SqliteEventStore,否则 localStorage。 */ diff --git a/src/engine/progress.ts b/src/engine/progress.ts index ca170d7..4aa3b1c 100644 --- a/src/engine/progress.ts +++ b/src/engine/progress.ts @@ -19,6 +19,8 @@ export interface ProgressSnapshot { export interface ProgressStore { save(p: ProgressSnapshot): void; load(): ProgressSnapshot | null; + /** 删除全部个人数据(ADR-0004):清空进度快照 */ + clear(): void; } const PROGRESS_KEY = 'jev-dating-match.progress.v1'; @@ -41,6 +43,14 @@ export class LocalStorageProgressStore implements ProgressStore { return null; } } + + clear(): void { + try { + localStorage.removeItem(PROGRESS_KEY); + } catch { + // 与 save 一致:静默降级为不持久化 + } + } } /** verify / 测试用内存实现 */ @@ -52,4 +62,7 @@ export class MemoryProgressStore implements ProgressStore { load(): ProgressSnapshot | null { return this.snap; } + clear(): void { + this.snap = null; + } } diff --git a/src/engine/sqliteEventStore.ts b/src/engine/sqliteEventStore.ts index 38f9e0d..b3b6178 100644 --- a/src/engine/sqliteEventStore.ts +++ b/src/engine/sqliteEventStore.ts @@ -11,8 +11,9 @@ const DB_PATH = 'sqlite:events.db'; /** * Tauri 原生端事件存储:tauri-plugin-sql(sqlx + SQLite)。 * 与 LocalStorageEventStore 实现同一 EventStore 接口——ADR-0004 的 - * append-only 约束在 SQL 层同样成立:本类只写 INSERT/SELECT, - * 不存在 UPDATE/DELETE 路径。重玩 = 新 sessionId 的新行,旧行原样保留。 + * append-only 约束在 SQL 层同样成立:本类只写 INSERT/SELECT,不存在单条 + * UPDATE/DELETE;唯一的 DELETE 路径是 purge(数据删除权下的整库物理清空, + * 由用户在报告页显式触发)。重玩 = 新 sessionId 的新行,旧行原样保留。 * payload 为 StoryEvent 整行 JSON(事件 schema 演进不动表结构)。 */ export class SqliteEventStore implements EventStore { @@ -52,4 +53,10 @@ export class SqliteEventStore implements EventStore { : await db.select<{ payload: string }[]>('SELECT payload FROM events ORDER BY id'); return rows.map((r) => JSON.parse(r.payload) as StoryEvent); } + + /** ADR-0004 数据删除权:整库物理清空(只删数据,不动表结构) */ + async purge(): Promise { + const db = await this.db; + await db.execute('DELETE FROM events'); + } } diff --git a/src/ui/report/ReportPage.tsx b/src/ui/report/ReportPage.tsx index c3e9d38..6f900b2 100644 --- a/src/ui/report/ReportPage.tsx +++ b/src/ui/report/ReportPage.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import type { EventStore } from '../../engine/eventStore'; import type { JevClient } from '../../services/jev'; import { BADGE_MAP } from './badges'; @@ -11,14 +11,33 @@ interface Props { store: EventStore; jev: JevClient; onReplay: () => void; + /** 删除全部个人数据(ADR-0004 数据删除权):purge + 清空进度 + 回到同意门,由 App 编排 */ + onPurge: () => Promise; } /** Act 1 结束后的画像报告页:Jev 一次调用并行问 7 个维度 score */ -export function ReportPage({ store, jev, onReplay }: Props) { +export function ReportPage({ store, jev, onReplay, onPurge }: Props) { const [scores, setScores] = useState(null); const [error, setError] = useState(null); const [retry, setRetry] = useState(0); const [playerName, setPlayerName] = useState(''); + const purgeDialogRef = useRef(null); + const [purging, setPurging] = useState(false); + const [purgeError, setPurgeError] = useState(null); + + async function confirmPurge() { + setPurging(true); + setPurgeError(null); + try { + // 成功后 App 把应用切回同意门,本页随之卸载 + await onPurge(); + } catch (e) { + setPurgeError(e instanceof Error ? e.message : String(e)); + purgeDialogRef.current?.close(); + } finally { + setPurging(false); + } + } useEffect(() => { let cancelled = false; @@ -107,6 +126,44 @@ export function ReportPage({ store, jev, onReplay }: Props) { +
+

数据与隐私

+

+ 本应用只在本机记录你的剧情选择、语音讲述转写与人格画像事件,用于人格评估与伴侣匹配推荐;数据不出设备,删除即彻底消失。 +

+ + {purgeError &&

删除失败:{purgeError}

} + +

+ 确定删除全部数据? +

+

+ 此操作不可恢复,所有剧情进度、人格画像、讲述内容将被永久删除,且不提供导出留档。 + 删除后应用将回到初始的告知与同意页面,如同首次安装。 +

+
+ + +
+
+
); }