24 KiB
nana-story V1 开发计划
中文名:《听娜娜讲故事》 计划版本:0.1 制定日期:2026-07-28 目标平台:Windows 桌面端 执行模型:1 个集成人 + 最多 3 个并行子智能体
1. 交付目标
V1 不是“通用 AI Galgame 平台”的完整形态,而是一段能够证明整个产品闭环的可玩纵切:
- 用户通过 LAPP 选择并调用聊天模型;
- 导入或使用内置的娜娜试玩资源包;
- 创建故事并进行 20~30 分钟的自由输入式角色扮演;
- 经历至少一次隐藏的简化 CoC7 判定;
- 建立一项重要许诺,并让行为影响信赖、希望等关系维度;
- 获得、使用或转移一件带隐藏背景的持有物;
- 随时查看自己的持有物、已知线索和已接受许诺;
- 查看模糊化的六维关系雷达图;
- 每轮自动保存,退出后能够恢复;
- 回溯到旧节点继续时创建新分支,并保留原线路。
V1 的成功标准不是资源数量,而是:
从创建故事到完成纵切,全程不需要开发者干预;状态可重放、隐藏信息不泄漏、回溯不破坏旧分支、异常不会留下半轮存档。
2. 已冻结的产品范围
2.1 技术与平台
- 仓库名:
nana-story。 - 首发 Windows 桌面端;安卓以后再考虑。
- 技术栈:
- Tauri 2
- Vue 3
- TypeScript
- Vite
- Naive UI
- Pinia
- Vue Router
- 管理界面可以使用 Naive UI,游玩界面采用自定义视觉实现。
- 管理端、游玩端和运行时位于同一个 Tauri 桌面进程,不引入 daemon、sidecar 或本地网关。
2.2 模型与 LAPP 边界
- 供应商协议、模型选择、凭据解析和网络调用由
lapp-rs负责。 nana-story不直接依赖任何供应商 SDK。nana-story只提供必要的 LAPP 查看、选择、连接测试和简单管理能力。- 本地模型只作为 HTTP 服务接入。
- 不负责安装、下载、启动、停止、更新或管理 Ollama、LM Studio 等本地模型运行器。
- 开发时可使用相邻仓库的 path dependency;发布构建必须固定到明确的
lapp-rs提交,不能复制其协议语义。
2.3 游戏交互
- 自由输入为主。
- 每轮可提供 2~3 个可编辑行动建议;点击后只填入输入框,不直接提交。
- 提供“继续”按钮,表示沉默、观察、等待或允许 NPC 继续行动。
- AI 不得替玩家决定说话、主动行动或内心想法。
- 时间按照实际行动推进,不按消息轮数推进。
- 高风险行动需要先给出叙事风险提示,再执行隐藏的简化 CoC7 判定。
- 玩家默认看不到骰点、技能数值和精确关系变化。
- 回复由若干短演出节拍构成,而不是一整块聊天气泡。
2.4 角色、关系与剧情
- V1 只有一个完整角色:娜娜。
- 剧情模块可以包含会说话的轻量配角,但不为其建立完整关系系统、长期记忆或自主调度。
- 六维关系:
- 好感
affinity - 信赖
trust - 希望
hope - 尊重
respect - 亲密
intimacy - 依恋
attachment
- 好感
- 信赖主要来自已经发生的行为。
- 希望主要来自被接受的许诺和共同未来计划。
- 重要许诺必须保存为独立对象,不能简化成一次关系数值变化。
- 怨恨、恐惧、愧疚、嫉妒等保存为带来源的关系状态,不占六维。
- 创建故事时绑定剧情模块;V1 不支持中途增删模块。
2.5 持有物、知识与分支
- 物品定义、物品实例和角色认知必须分离。
- 所有权
owner与当前持有者holder必须分离。 - 玩家可以随时读取自己当前可访问的持有物。
- NPC 的真实持有物不能直接暴露;只能显示公开可见或玩家已经知道的内容。
- V1 不处理负重、体积、格子或背包容量。
- 每轮自动保存。
- 从旧节点继续时创建新分支。
- V1 不做分支合并,也不建立跨分支记忆。
3. V1 明确不做
- 多个完整角色及多角色发言调度。
- 导演 Agent、自动总结 Agent 或运行时多 Agent 编排。
- 完整 CoC7 职业、成长、战斗、理智、追逐和幸运消费。
- Live2D、语音、口型、动态镜头和复杂动画。
- 分支合并和跨分支记忆。
- 中途增删剧情模块。
- 负重、体积、容量、制作和物品经济。
- 内置资源编辑器或可视化剧情编辑器。
- Steam Workshop 正式接入、DLC 支付、社区付费内容抽成。
- 云同步、账户、多人或跨设备存档。
- Android、Web 和多语言发布。
- 模型下载器、模型启动器和本地模型进程管理。
- 任意脚本、任意插件或任意外部工具执行。
V1 只保证资源包格式未来可以承接 Workshop/DLC,不实现市场本身。
4. 总体架构
4.1 三类事实
| 层级 | 内容 | 权威来源 |
|---|---|---|
| 创作资源 | CharacterCard、WorldBook、Persona、PlotModule、ItemSpec 和素材引用 | 不可变的版本化资源 |
| 分支状态 | 世界状态、关系、许诺、知识、持有物、时钟、判定记录 | 根状态/检查点 + 节点 delta |
| 上下文投影 | 当前意图、命中的世界书、最近演出、提示词 | 每轮临时编译,可重建 |
任何模型推测、检索结果和临时提示词都不是持久事实。需要持续存在的变化必须经过类型化操作,由状态 reducer 验证后写入当前分支。
4.2 单一权威写入路径
flowchart TD
A["玩家输入"] --> B["上下文编译"]
B --> C["单模型工具循环"]
C --> D["暂存工具结果与状态操作"]
D --> E["规则校验与 reducer"]
E --> F["原子提交 StoryNode"]
F --> G["生成 PlayerView"]
G --> H["Vue 演出"]
约束:
- 模型和前端都不能直接修改
RuntimeState。 - reducer 是状态变化的唯一权威入口。
- 一轮生成要么完整提交,要么完全不提交。
- 模型失败、取消、超时或畸形输出不得留下半轮状态。
- 同一玩家行动的重新生成复用原判定记录;推骰属于新行动和新节点。
4.3 前后端安全边界
前端不得得到完整 RuntimeState,只能得到经过投影的 PlayerView:
PlayerView
├─ 当前演出节拍
├─ 可编辑行动建议
├─ 玩家可访问的持有物
├─ 已知线索
├─ 已接受许诺
├─ 模糊化六维关系
└─ 可回溯节点摘要
这条边界用于结构性防止:
- NPC 隐藏物品泄漏;
- 未揭示世界事实泄漏;
- 精确关系数值泄漏;
- 内部骰点和难度泄漏;
- 未触发的剧情节点泄漏。
4.4 推荐持久化
V1 默认采用嵌入式 SQLite,由 Rust 侧独占访问。
- 内容包以经过校验的不可变文件形式安装到应用数据目录。
- SQLite 保存故事、资源绑定、节点、分支、检查点和迁移版本。
- 节点中的状态操作使用版本化、类型化 JSON。
- 每隔固定节点数创建检查点,恢复时使用“最近检查点 + 后续 delta”。
- SQLite 事务同时提交用户输入、模型演出、工具结果、判定记录和状态 delta。
建议最小表:
stories
story_bindings
story_nodes
story_branches
state_checkpoints
installed_packs
schema_migrations
不要在 V1 过早拆出几十张关系、物品、许诺明细表。分支事件流才是事实来源,当前状态是可重建投影。
4.5 模型运行时
V1 游戏运行时仍然只有一个模型,不引入导演 Agent。
每轮流程:
- 读取当前节点、资源绑定和重建后的运行时状态;
- 按优先级编译上下文;
- 调用
lapp-rs选中的聊天模型; - 模型按需调用判定、关系、许诺、知识、持有物、时钟等领域工具;
- Rust 验证工具参数并写入暂存事务;
- 模型根据工具返回继续生成短演出节拍和行动建议;
- 校验演出协议与全部状态操作;
- 原子提交新节点;
- 生成脱敏后的
PlayerView给前端。
上下文优先级:
- 玩家自主权和输出协议;
- 当前角色卡、Persona 与场景;
- 当前关系、许诺、知识、持有物和剧情压力;
- 命中的世界书条目;
- 当前分支最近演出;
- 非关键风格参考。
V1 不做向量记忆库。世界书先采用确定性的标签、关键词和条件触发;长上下文压缩只能作为可丢弃缓存,不能成为新的事实来源。
5. 仓库结构与代码所有权
建议从第一天就使用 Rust workspace,把并行边界变成目录边界:
nana-story/
├─ src/ # Vue 应用
│ ├─ app/ # 路由、全局状态、真实/Mock adapter
│ ├─ features/play/ # 主演出屏
│ ├─ features/inventory/ # 持有物、线索、许诺
│ ├─ features/relationship/ # 关系档案
│ └─ features/history/ # 回溯与分支
├─ src-tauri/ # Tauri 外壳与窄命令
├─ crates/
│ ├─ nana-domain/ # 资源、状态、delta、PlayerView
│ ├─ nana-engine/ # reducer、规则、简化 CoC7
│ ├─ nana-store/ # SQLite、节点、检查点、恢复
│ └─ nana-runtime/ # LAPP、上下文、工具循环、Turn Engine
├─ contracts/ # 生成的 JSON Schema 与 TS 类型
├─ content/nana-demo/ # 试玩纵切资源包
├─ fixtures/ # 稳定的契约和模型响应样例
├─ tests/e2e/ # 跨模块验收
└─ docs/adr/ # 已接受架构决策
单一契约源:
Rust serde 类型
→ 生成 JSON Schema
→ 生成 TypeScript DTO
→ CI 检查生成物是否漂移
禁止同时手写 Rust、Schema 和 TypeScript 三套同名模型。
6. 多子智能体并行方案
6.1 推荐拓扑
并行度维持在 1 个集成人 + 3 个子智能体。
| 角色 | 独占范围 | 主要职责 |
|---|---|---|
| 集成人 | 公共契约、根配置、入口、生成物、跨模块接线、最终验收 | 拆任务、冻结契约、日常合并、拒绝范围扩张 |
| 子智能体 A:Core | nana-domain、nana-engine、nana-store |
类型、reducer、规则、SQLite、分支重放 |
| 子智能体 B:Runtime | nana-runtime |
LAPP、上下文编译、工具循环、Turn Engine |
| 子智能体 C:Experience | src/、content/nana-demo/ |
Vue 界面、演出、官方 Fixture、娜娜纵切内容 |
测试靠近各自模块编写;跨模块 E2E 和契约生成由集成人负责。
不建议同时开启 5~8 个编码子智能体。这个项目的瓶颈是共享语义而不是代码量,过高并行度会把收益变成 Schema、状态提交和 UI DTO 的返工。
6.2 必须先冻结的契约
进入正式并行前必须合并:
ResourceHeaderCharacterCardWorldBookPersonaPlotModuleItemSpec与ItemInstanceStory、StoryBinding、StoryNodeRuntimeState- 所有状态操作的判别联合
PresentationBeatPlayerViewTurnRequest、TurnResult、TurnFailure- 五组 canonical fixtures:
- 普通对话;
- 隐藏判定;
- 许诺建立与履行;
- 物品转移且 NPC 尚未知情;
- 回溯后分叉。
6.3 Worktree 与分支
每个子智能体必须使用独立 Git worktree:
integration/v1
agent/core
agent/runtime
agent/experience
规则:
- 所有分支从同一份已冻结契约提交开始。
- 每个子智能体只修改分配的目录。
- 根
Cargo.toml、package.json、锁文件、CI、Tauri 入口、Vue 入口和生成代码只由集成人修改。 - 子智能体需要新增依赖或修改公共契约时,提交短小的变更请求,不直接跨目录修改。
- 禁止无关重构、全仓格式化和顺手升级依赖。
- 每天至少合并一次小的可验证增量,不在阶段末进行巨型合并。
- 只有集成人解决跨模块冲突。
6.4 子智能体任务包模板
每个任务必须包含:
目标:
基线提交:
允许修改的目录:
输入契约版本:
必须新增的测试:
验收命令:
明确不做:
交付时需要说明的风险:
没有这些信息的任务不应分派给子智能体。
7. 里程碑与并行波次
基准工期为 25 个有效开发日,约 5 周。AI 子智能体可以压缩编码时间,但不能取消契约、纵切联调和 Windows 发布验证。
M0:工程骨架与契约冻结(第 1~2 日)
交付:
- 初始化 Tauri 2 + Vue 3 工程和 Rust workspace;
- 接入相邻
lapp-rs,固定开发基线; - 建立
pnpm verify或等价的一键校验; - 定义 Rust 领域类型、生成 JSON Schema 和 TS DTO;
- 建立 canonical fixtures;
- 内置最小娜娜资源包;
- 写入首批 ADR。
演示:
- Windows 桌面窗口可以启动;
- 前端通过窄命令读取应用版本和一份静态
PlayerView; - 最小娜娜资源包能够加载。
硬门槛:
- CI 可重复运行;
- 非法资源返回稳定错误;
- ID、版本、哈希、依赖和引用可校验;
- Rust、Schema、TS 不存在多个事实来源。
并行:
- 集成人:契约与仓库骨架;
- A:CI 和 Rust workspace;
- B:Fake LAPP Provider/录制模型响应夹具;
- C:按四张原型搭静态 UI 外壳。
M1:确定性状态内核(第 3~6 日)
交付:
RuntimeState;- 纯 reducer;
- 六维关系与单轮限幅;
- 许诺生命周期;
- 知识与可见性;
- 物品 owner/holder、位置、取得方式;
- 时钟;
- 简化 CoC7 判定记录;
PlayerView投影;- SQLite 节点、delta 和检查点。
演示:
- 不调用模型,仅通过固定命令完成:
- 获得物品;
- 作出许诺;
- 改变关系;
- 推进时钟;
- 执行一次判定;
- 保存、关闭、恢复。
硬门槛:
- 同一根状态 + 同一组 delta 必须产生完全相同的
RuntimeState和PlayerView; - delta 重放不能重复结算;
- 关系变化会被规则限幅;
- owner/holder 语义正确;
- 玩家无法读取 NPC 未知持有物;
- 同一行动重新生成时可以复用判定记录。
并行:
- A:domain、engine、store;
- B:基于 fixture 实现 runtime 接口和 Fake Provider;
- C:前端使用同一份
PlayerViewfixture 完成完整假数据交互。
M2:单轮模型运行循环(第 5~10 日,与 M1 后半段重叠)
交付:
- LAPP profile 读取、模型选择和连接测试;
- 非流式与流式聊天适配;
- 上下文编译器;
- 领域工具目录;
- 工具循环和最大轮次保护;
PresentationBeat;- 暂存事务、校验、提交和回滚;
- 取消、超时、畸形输出和一次安全重试。
演示:
- 用户输入一句自由文本;
- 娜娜以短节拍回应;
- 表情发生一次可选变化;
- 生成 2~3 个可编辑建议;
- 一次需要判定的行动经过工具返回后继续叙述;
- 整轮状态一次性提交。
硬门槛:
- 模型和 UI 都不能直接写
RuntimeState; - 工具失败、取消和断流不产生半轮节点;
- 模型不能替玩家决定主动言行和内心;
- 高风险行动先有风险提示;
- 凭据、完整上游错误体和隐藏状态不进入日志或前端 DTO;
- CI 使用 Fake Provider,不依赖真实付费模型。
并行:
- A:补齐 reducer 和 store 的运行时接口;
- B:实现 LAPP adapter、上下文和工具循环;
- C:把主演出屏从 fixture adapter 切到真实 Turn API。
M3:自动存档、回溯和分支(第 8~14 日)
交付:
- 每轮原子自动保存;
- 分支头和节点父子关系;
- 从任意旧节点创建新分支;
- 旧线路保留;
- 故事恢复;
- 同输入重新生成与推骰的不同语义;
- 简洁故事时间线和完整回溯页。
演示:
- 连续玩 5 轮后退出;
- 重启恢复;
- 回到第 2 轮并继续;
- 原分支和新分支同时存在;
- 两条分支的持有物、许诺和关系状态互不污染。
硬门槛:
- 每轮只出现一次有效提交;
- 重启后的状态与提交前一致;
- 回溯不会修改旧节点;
- 重新生成复用原判定;
- 推骰创建新行动和新判定记录;
- 500 节点以内的分支恢复不需要扫描无关分支。
并行:
- A:分支、检查点、恢复和迁移;
- B:重新生成、推骰与运行时事务;
- C:故事回溯 UI 和错误恢复交互。
M4:完整玩法系统与四个界面(第 11~18 日)
交付:
- 主游戏界面;
- 持有物/线索/许诺侧栏;
- 六维关系档案;
- 故事回溯页;
- 角色卡、世界书、Persona、剧情模块和 ItemSpec 的导入校验;
- 轻量配角;
- 场景、立绘、表情和姿态保持;
- “自由输入 / 建议 / 继续”三种操作。
演示:
- 四张界面原型对应的真实交互全部接入运行状态;
- 玩家查看自己的持有物;
- NPC 未知物品不会出现;
- 重要许诺能从 accepted 发展到 fulfilled 或 broken;
- 关系雷达变化但不显示精确数字;
- 轻量配角可以参与一段事件但不会获得完整关系系统。
硬门槛:
- 所有 UI 数据只来自
PlayerView或其他脱敏 DTO; - 不显示骰点、检定难度和精确关系增量;
- UI 不把消息和演出节拍混为一套结构;
- 静态资源加载失败有占位和明确错误;
- 导入包不能通过路径穿越写出目标目录。
并行:
- A:资源导入、规则系统和 PlayerView;
- B:世界书触发、剧情工具和轻量配角上下文;
- C:四个界面和演出状态机。
M5:娜娜 20~30 分钟纵切(第 17~21 日)
纵切至少包含:
- 一个明确开场目标;
- 一次普通自由对话;
- 一次隐藏判定;
- 一次玩家可感知的风险提示;
- 一个被接受的重要许诺;
- 一次用行动证明或破坏许诺;
- 一件具有隐藏背景的物品;
- 一次 owner/holder 不同的物品状态;
- 一次关系路线分化;
- 一个剧情时钟;
- 一次轻量配角发言;
- 一次回溯;
- 至少两个结果方向。
硬门槛:
- 无开发者操作可以从开场玩到结果;
- 条件触发没有死路;
- 角色不泄露玩家尚未获得的知识;
- 不同选择产生可回放的不同状态;
- 引擎规则中不得硬编码
nana或纵切事件 ID; - 使用另一个最小反例资源夹具证明引擎仍然通用。
本阶段停止增加系统功能。所有子智能体只处理纵切暴露的问题。
M6:Windows 发布前加固(第 22~25 日)
交付:
- Windows 安装包;
- 崩溃和断电恢复;
- 存档迁移夹具;
- 模型超时、断网、限流、拒绝、畸形工具调用处理;
- 长对话上下文预算测试;
- 日志脱敏;
- 内容包边界和尺寸限制;
- 用户可理解的错误页;
- 最小发布说明和已知限制。
硬门槛:
- 全新 Windows 环境能够安装、启动和卸载;
- 配置一个 LAPP 模型后能够完成纵切;
- 进程被强制关闭后不会产生半轮节点;
- 旧 Schema 存档夹具能够迁移或给出明确的不兼容提示;
- 凭据不进入前端状态、日志、崩溃报告或存档;
- 所有 canonical fixtures、单元测试和跨模块验收通过。
8. 合并顺序与质量门
每个并行波次按以下顺序合并:
- 契约与 fixtures;
- domain/engine/store;
- runtime;
- client adapter;
- 跨模块 E2E;
- 纵切内容更新。
任何合并都必须跑同一条最小跨模块场景:
创建故事
→ 三轮交互
→ 一次隐藏判定
→ 获得物品
→ 建立许诺
→ 关闭并重启
→ 回溯
→ 从旧节点继续
→ 校验两个分支互不污染
阶段门禁:
- M1 未通过确定性重放,不接正式存档 UI。
- M2 未实现“校验后一次性提交”,不允许模型修改运行时状态。
- M3 未通过分支隔离,不开放回溯入口。
- M5 未跑通完整纵切,不扩展第二完整角色、编辑器、Workshop 或 Live2D。
9. 测试矩阵
| 层级 | 主要测试 |
|---|---|
| Domain | Schema、ID/引用、版本/哈希、资源依赖、反序列化错误 |
| Engine | reducer、限幅、许诺生命周期、知识可见性、物品 owner/holder、CoC7 |
| Store | 原子提交、重放、检查点、分支隔离、迁移、损坏恢复 |
| Runtime | Fake Provider、工具循环、上下文预算、取消、超时、畸形输出 |
| Security | PlayerView 泄密、日志脱敏、内容包路径穿越、凭据边界 |
| Frontend | 演出状态机、建议编辑、继续、侧栏、雷达、回溯 |
| Cross-module | 完整最小场景、重启恢复、回溯继续、真实 LAPP 冒烟 |
| Content | 条件可达性、无死路、隐藏事实、不同结果、角色一致性 |
真实模型测试不作为普通 CI 的前提。CI 使用确定性 Fake Provider;真实模型只运行独立冒烟和录制回归。
10. 主要风险与控制
| 风险 | 控制 |
|---|---|
| 多个子智能体各自发明 Schema | Rust 类型为唯一事实来源;契约目录独占;变更必须更新 fixtures |
| UI 或模型直接修改状态 | 所有修改必须经过类型化工具和唯一 reducer |
| 模型输出不稳定 | 工具调用、结构校验、最大循环次数、一次安全重试、失败不提交 |
| 假数据与真实接口分裂 | UI 和运行时共用 canonical PlayerView fixtures |
| 分支恢复不确定 | 纯 reducer;固定 delta 顺序;每次重放做哈希校验 |
| 重生成刷骰 | 判定绑定玩家行动;重新生成复用,推骰创建新行动 |
| 隐藏信息泄漏 | 后端生成 PlayerView;前端永远不获得完整状态 |
lapp-rs 仍处于 0.1 阶段 |
固定提交;包一层窄 adapter;用三种协议夹具做回归 |
| 本地模型不支持工具调用 | V1 启动前做能力检测并明确提示;不在 V1 增加第二套自由文本解析运行时 |
| 子智能体大范围重构 | 目录所有权、短任务包、禁止无关改动、每日小合并 |
| Windows 问题最后才出现 | M0 即建立 Windows 构建;每个阶段做一次安装/启动冒烟 |
| 纵切内容被写成娜娜专用代码 | 引擎只识别通用标签和资源引用;增加非娜娜反例夹具 |
11. V1 Definition of Done
满足以下全部条件才称为 V1:
- Windows 安装包可用;
- 使用 LAPP 可以选择云端或本地 HTTP 模型并通过连接测试;
- 内置娜娜试玩包可直接创建故事;
- 20~30 分钟纵切可从头玩到至少两个结果方向;
- 自由输入、可编辑建议和“继续”都能稳定完成一轮;
- 六维关系、许诺、知识、持有物、时钟和隐藏判定都在纵切中真实生效;
- 每轮原子自动保存;
- 重启恢复一致;
- 回溯后建立新分支并保留旧线;
- 同一行动重新生成不重新掷骰;
- NPC 隐藏状态、精确关系值、骰点和凭据不泄漏;
- 模型取消、超时和畸形输出不产生半轮状态;
- canonical fixtures、自动测试和 Windows 冒烟全部通过;
- 没有引入 V1 明确不做的功能。
12. 开工顺序
第一批实际开发任务按下面顺序创建:
- 初始化
nana-story仓库、Tauri/Vue 工程和 Rust workspace; - 写入架构 ADR 与目录所有权;
- 定义
ResourceHeader、资源绑定、StoryNode、RuntimeState、状态操作、PresentationBeat和PlayerView; - 生成 JSON Schema、TypeScript DTO 和 canonical fixtures;
- 同时启动三个独立 worktree:
- Core:reducer + SQLite;
- Runtime:Fake Provider + Turn API;
- Experience:静态主演出屏 + fixture adapter;
- 在 M1 确定性重放通过后,接入真实
lapp-rs和第一条完整游戏回合。
第一条集成目标应当非常小:
玩家输入“我答应天亮前回来”,娜娜回应并接受许诺;引擎创建 Promise、提高希望、保存节点;关闭应用后恢复;回溯到许诺之前继续,旧分支仍然存在。
它同时覆盖角色演出、工具调用、许诺、关系、存档、恢复和分支,是最适合作为 nana-story 第一条端到端主线的开发样例。