diff --git a/docs/design/adaptive-insertion.md b/docs/design/adaptive-insertion.md new file mode 100644 index 0000000..b0e22ab --- /dev/null +++ b/docs/design/adaptive-insertion.md @@ -0,0 +1,99 @@ +# 设计:自适应支线插入引擎 + +> 状态:设计定稿,待实现。关联:docs/story-framework.md §6(路由规则)、 +> docs/script/adaptive-branches.md(支线剧本与三张总表)、ADR-0003(Jev 置信度自适应)。 +> 读者:实现此功能的工程 agent。 + +## 1. 目标 + +每章结束后用 Jev 对用户行为轨迹打 7 维 score;对置信度不足的维度,在下一章的 +固定挂点动态插入一段支线剧情,补充该维度的行为采样。插入必须: + +- **玩家无感**:支线作为剧情独立成立,出口与主线严丝合缝; +- **测量有效**:支线选择点与主线选择点同等规格地落入事件流; +- **成本可控**:每章至多插入 1 条;Jev 故障时不插入(降级,不阻断主线)。 + +## 2. 触发时序 + +``` +chapterEnd(第 N 章结束) + → 汇总本 session 全部事件为 state + → 一次 Jev 调用:7 个维度 score(并行,输出免费) + → 提取每维 confidence + → 插入决策(见 §3)→ 产出第 N+1 章的"插入计划" + → 第 N+1 章开始,引擎带着插入计划运行 +``` + +打分与画像报告页共用同一套 state 构建逻辑(`ReportPage.buildState` 提取为 +`src/engine/scoring.ts`,报告页和插入决策都调它)。章末打分失败(Jev 故障/无 key) +→ 本章不插入任何支线,主线照常(console.warn 记录)。 + +## 3. 插入决策(对照支线册表 2 实现为代码) + +输入:7 维 confidence、本周目已触发支线记录、目标章可用挂点。 + +1. 过滤:confidence < 0.6 的维度为候选;`risk` / `mate_preference` 无支线定义,跳过; +2. 排序:按「维度权重 × 置信度缺口 × 已测稀缺度」取首—— + 维度权重:attachment > values > conflict_style > emotional_stability > culture + (见支线册 §0.5);缺口 = 0.6 − confidence;稀缺度 = 该维度已被采样的选择点数越少越优先; +3. 选皮:该维度的支线 A(挂点 1)未用过→A,用过→B(挂点 2);A、B 都用尽仍低置信 + → 记录 `evidence_insufficient` 标记,不插入(交 Act 2); +4. 每章至多 1 条;挂点被占则顺延到该章另一挂点,两挂点都被占则不插入; +5. 时长红线:插入后该章预估时长 ≤ 45 分钟(支线 5-8 分钟 + 主线 25-35 分钟)。 + +## 4. 数据模型 + +支线包:`content/branches/{branchId}.json`(如 `b-att-a.json`),结构: + +```jsonc +{ + "id": "b-att-a", + "dimension": "attachment", + "anchor": { "chapter": "chapter2", "afterNode": "c2-d4f" }, // 挂点 1:§2-4 后 + "startNodeId": "cb-att-a-01", + "nodes": [ /* 与章节 JSON 同 schema 的 StoryNode 数组 */ ], + // 支线选择点 pointId 用 CB-XXX-Y-ZZ 规则(支线册表 1),维度标签照支线册 +} +``` + +引擎改动: + +- `StoryEngine` 增加 `insertions: BranchPack[]`(本局生效的插入计划); +- 节点索引 `nodeIndex` 在装载插入计划时合并支线节点; +- **挂点重定向**:到达 `anchor.afterNode` 的 `next` 时,若该挂点有插入, + 跳支线 `startNodeId`;支线最后一个节点的 `next` 在 JSON 里写为 + 特殊值 `"$resume"`,引擎解析为"回到挂点原 next"; +- 支线节点参与现有全部机制:条件渲染(flags)、进度持久化、追问流。 + +## 5. 事件与测量 + +- 支线选择点事件正常 append,`pointId`(如 `CB-VAL-A-01`)即变量取值来源; +- 支线 flags(`cb_*`)进现有 flags 系统(`src/engine/flags.ts` 补映射, + 消费方见支线册表 3——注意"回写只许面向未来场景"规则); +- 插入决策本身落一个 `kind: 'branch_inserted'` 事件(branchId、维度、触发置信度), + 供画像层做路径标记(温简 weak 线的公平性问题同款思路)。 + +## 6. 状态与持久化 + +- `ProgressSnapshot` 增加 `activeBranchId?: string` 与 `insertions` 摘要, + 刷新恢复时若正处于支线中,恢复到支线内断点; +- `replay()` 清空插入计划(新周目重新打分重新决策); +- 同一 session 已触发的支线不再重复触发(A→B 换皮由 §3.3 保证)。 + +## 7. 实施拆分(建议一棒完成) + +1. `src/engine/scoring.ts`:7 维打分 + 置信度提取(从 ReportPage 抽出复用); +2. `src/engine/branching.ts`:插入决策纯函数(§3 全部规则,可单测); +3. 引擎装载/挂点重定向/`$resume` 解析/进度快照扩展; +4. `content/branches/`:把支线册 10 条变体 JSON 化(文案照 + `docs/script/adaptive-branches.md`,pointId/变量照表 3); +5. `scripts/verify.ts` 新增断言:决策矩阵表驱动测试(7 种触发情形照表 2)、 + 挂点重定向与 `$resume` 汇合、支线事件落库、重复触发抑制、 + Jev 故障不插入、replay 清空计划。 + +## 8. 未决问题(实现时可自行裁决,报备即可) + +- 挂点锚点的精确 nodeId 以 JSON 现状为准(`afterNode` 需在实现时对照 + chapter2/3.json 落定,支线册的 §2-4 后/§3-2 前是剧本坐标); +- 支线中的 narration 讲述点:本期支线册均为按钮式选择点,无 V 类点, + 若后续支线加讲述点,追问流直接复用现有机制,无新增设计。