Mirrorの猫窝
首页
笔记
项目
更多
关于
叙事引擎世界存储与版本管理设计

叙事引擎世界存储与版本管理设计

2026-07-20 18:00:00
✨ 心情:设计
# 叙事引擎
# 架构设计
# 世界图
# 版本管理

1. 整体架构:双图分离

核心决策:整体是两张独立的图,事件节点作为单独的节点,不和世界图一起放。

图 职责 内容
世界图 记录一切故事需要的背景信息 角色、设定、物品、地点、关系、规则等
事件图 记录整个叙事的流程 事件节点、事件间的因果关系、什么发生了什么变化

双图分离的工程意义

  1. 职责清晰:世界图是"状态",事件图是"历史"
  2. 回退独立:事件图回退不影响世界图的稳定节点
  3. 检索分离:调度器查世界图获取角色信息,渲染器/用户查事件图获取叙事流程
  4. 可视化分离:世界图可视化看到的是关系网络,事件图可视化看到的是时间线/因果链
  5. 写入分离:世界图接受引擎扩散+用户编辑两条路径;事件图只接受引擎事件写入

与 Graphiti 的关键差异

  • Graphiti 的 Episodic 节点和 Entity 节点在同一个图内,通过 MENTIONS 边连接
  • 本架构将 Episodic 完全独立为"事件图",与世界图物理分离
  • 这是比 Graphiti 更彻底的解耦

2. 世界图:当前状态快照

2.1 世界图的角色定义

世界图职责:

  • 作为唯一状态中心,存储所有世界背景条目
  • 接收并处理两条独立写入路径:
  1. 引擎自然生产:角色扮演产生的数据写回世界图,更新对应节点状态(扩散写入)
  2. 非引擎自然生产:主会话工具修改 + 图 UI 修改(用户直接编辑) 核心写入流程(引擎自然生产):
  • 子代理扮演 → 产生结构化数据 → 写回世界图 → 更新对应节点状态
  • 这是"扩散"的工程实现:事件 → 子代理行为 → 状态变化 → 写回节点

2.2 节点分层

四层节点分类:

层级 说明 示例
角色层 剧情中扮演行动主体的节点 主角、配角、反派
角色互动物层 和角色直接交互的物品 角色持有的武器、钱袋、信件
非角色互动物层 不和角色交互的物品 场景氛围元素:天气、人群、装饰
背景层 世界规则和宏观背景 魔法体系、社会制度、历史事件

2.3 节点生命周期

核心机制:每个节点有生命周期,临时性的节点会在演出后消失。 工程意义:

  • 解决长篇节点爆炸问题——不是所有节点都需要永久保留
  • 临时节点的例子:
  • 角色互动物层:临时拿起的物品(吃完的苹果、丢掉的纸条)
  • 非角色互动物层:场景氛围(某场雨、某群路人)
  • 可能也包括临时角色(路人甲、酒馆老板)
  • 永久节点的例子:
  • 角色层:主要角色
  • 背景层:世界规则
  • 角色互动物层:角色的标志性物品(主角的佩剑)

2.4 世界图的存储原则

世界图只存储当前事件下世界的状态:

  • 世界图 = 当前时刻的状态快照
  • 每个事件对应世界图的一个状态
  • 事件 = 两个世界图状态之间的变化(diff)
  • 世界图永远只存当前状态,不需要保留历史版本(历史在事件链里) 每次事件扩散写入:
  • 直接覆盖世界图字段
  • 在事件图中记录变化(diffusions) 存储格式:
  • JSON 文件存储(每节点一文件)
  • 不依赖图数据库(Neo4j 等不要)
  • 白盒可见,符合"用户可见、可追变动"约束
  • 向量查询作为辅助检索手段,但不作为主存储

3. 事件图:状态变化记录

3.1 事件即状态变化记录(核心机制)

核心澄清:事件不仅是用户口述的内容时间线记录,也是整个世界图的状态变化记录。 类比:

  • 事件图 = Git commit history(每个 commit 是一次状态变化)
  • 世界图 = Working directory(当前文件状态)
  • 回退 = git reset(移动 HEAD,working directory 相应变化)

3.2 事件节点的核心字段

- uuid:事件唯一标识(commit hash)
- parent:上一个事件的 uuid(对应 commit 的 parent)
- content:用户口述的事件内容
- diffusions:状态变化记录列表(对应 commit 的 diff)
- target_uuid:受影响的世界图节点
- field:被修改的字段
- old_value:修改前的值
- new_value:修改后的值
- narrative_text:渲染器生成的故事文本(渲染层产物)
- timestamp:事件发生时间(故事时间)
- created_at:事件写入时间(事务时间)

3.3 事件图的核心原则

  • 事件 = 状态变化的 diff,不是完整状态 → 事件节点轻量
  • 一个事件内不可能变了几千个节点——单事件影响范围有限
  • 事件图只存储每个事件中状态的变化(diff),不是完整状态

4. 版本管理:Git 模式

4.1 Git 对象模型映射

直接参考 Git 的对象模型和逻辑,针对双图场景实现最简版本。

Git 概念 引擎对应实现
Blob(文件内容) 世界图节点/边的当前状态
Tree(目录结构) 世界图的整体状态快照
Commit(提交) 事件节点(含 diffusions + 指向上一事件的 parent)
Ref(分支/HEAD) 当前事件指针(指向事件链的 HEAD)
HEAD~N 回退 N 个事件
git reset 移动 HEAD 指针 + 反向撤销 diffusions
git revert 保留 HEAD,生成新的反向事件
git reflog 事件链历史(所有 HEAD 移动记录)

4.2 核心组件

  1. 事件节点 = Git Commit
  • uuid(commit hash)
  • parent(上一个事件的 uuid)
  • content(用户口述)
  • diffusions(状态变化)
  • narrative_text(渲染文本)
  • timestamp / created_at
  1. 世界图 = Working Directory
  • 只存当前状态
  • 每次事件扩散写入 = 直接覆盖字段
  • 不保留历史版本(历史在事件链里)
  1. HEAD 指针
  • 一个字段,指向当前事件链的最新事件
  • 回退 = 移动 HEAD + 反向撤销 diffusions
  • 重放 = 从 HEAD 开始追加新事件
  1. 节点创建事件 = created_by_event 字段
  • 世界图节点带 created_by_event: event_uuid
  • 反向撤销时,查此字段判断是否到达创建位置
  1. 旧事件链保留
  • 和 Git 一样,旧 commit 永久保留
  • 通过 HEAD 移动实现"删除",不物理删除事件节点

5. 回退机制

5.1 回退与重放的统一逻辑

回退和重放是连续的两步操作,不是独立的场景 完整流程:

  1. 用户发起回退(重写某事件 / 改某节点)
  2. 引擎按事件链反向撤销 diffusions(new_value → old_value)
  3. 一直撤销到目标节点创建的位置(该节点首次出现的事件)
  4. 从该位置重新开始叙事(重新跑子代理,生成新的事件链) 关键含义:
  • 回退不是"回到过去就停下",而是"回到过去 + 重新开始"
  • 重放的起点 = 受影响节点的创建事件
  • 重放 = 重新跑子代理,生成新的事件和扩散

5.2 反向撤销的工程实现

  • 每个事件节点的 diffusions 字段已经记录 old_value 和 new_value
  • 回退到事件 E = 从当前事件开始,依次将 new_value 还原为 old_value,直到 E
  • 时间复杂度 O(M),M = 要撤销的事件数(通常很小)

5.3 Git 操作对照

Git 操作 引擎对应操作
git reset HEAD~N 反向撤销 N 个事件的 diffusions
Working directory 变化 世界图回到目标事件时的状态
旧 commit 保留 旧事件节点保留(不物理删除)
新 commit 覆盖 用户新输入事件后,新事件覆盖旧事件
git revert 用户告诉引擎"按原来的事件重新跑"

5.4 两种重放模式

  1. 手动重放(默认):
  • 引擎回退到目标事件后,等待用户输入新事件
  • 用户新输入的事件覆盖旧事件链
  • 类似 git reset 后重新 commit
  1. 自动重放:
  • 用户告诉引擎"按原来的事件重新跑"
  • 引擎按旧事件链的内容,依次重新执行
  • 类似 git revert(保留历史,生成新的反向 commit)

5.5 节点创建事件的识别

  • 节点创建时,记录是在哪个事件下创建的(created_by_event: event_uuid)
  • 不需要复杂标记——一个字段就够了
  • 反向撤销到某节点时,查 created_by_event 就知道是否到达创建位置
  • 节点本身的删除 = 不需要特殊处理,反向撤销到创建位置时,节点的 old_value = null(创建前不存在),撤销后自然消失

6. 用户编辑融合机制

6.1 用户编辑的语义

  • 用户编辑影响的是当前世界的状态(直接修改世界图)
  • 用户编辑产生的扩散需要到下一个事件才生效
  • 即:用户编辑不立即触发扩散,扩散在下一次引擎事件时由子代理处理
  • 用户编辑不被记录为事件(它是非引擎写入)

6.2 用户编辑优先原则

用户编辑影响当前世界状态,但扩散到下一个事件才生效。具体实现按"用户编辑优先"原则:

  • 用户编辑直接覆盖世界图字段(当前状态立即变化)
  • 下一个引擎事件 E2 的子代理看到的是用户编辑后的状态
  • E2 的 diffusions 记录 old_value=用户编辑后的值, new_value=E2扩散后的值
  • 回退 E2 时,世界图字段恢复为"用户编辑后的值"(即 old_value)

6.3 关键含义

  • 用户编辑不会被引擎扩散"吞噬"——它作为 E2 的 old_value 被保留
  • 回退 E2 后,用户编辑仍然存在(世界图回到 E2 之前的状态,即用户编辑后的状态)
  • 用户编辑是"非事件写入",不在事件图中记录,但通过 E2 的 old_value 间接保留

6.4 完整示例

  1. 初始:N.field = "value1"(E1 扩散写入)
  2. 用户编辑:N.field = "value2"(直接覆盖,无事件记录)
  3. E2 事件:子代理看到 N.field = "value2",扩散后改为 "value3"
  • E2.diffusions = [{target: N, field: field, old_value: "value2", new_value: "value3"}]
  1. 回退 E2:N.field 恢复为 "value2"(用户编辑保留)
  2. 再回退 E1:N.field 恢复为 "value1"(用户编辑丢失,因为 E1 的 new_value="value1" 被撤销)

7. 与难题的映射

难题 解决方式 状态
难题 1(世界图记录) 双图分离 + 节点四层分层 + 生命周期机制 ✅ 核心已定
难题 4(数据写回) 事件 diffusions 记录 + 世界图覆盖写入 ✅ 已定
难题 5(事件重写) Git 模式(事件链 + HEAD + 反向撤销 + 重放) ✅ 已定
难题 10(用户编辑融合) 用户编辑优先 + 下个事件 baseline + old_value 保留 ✅ 已定

8. 不采用的方案

  • ❌ Graphiti 的 Bi-temporal Edge(4 个时间戳 + 失效逻辑)——太复杂,Git 模式更简单
  • ❌ 向量数据库作为主存储——黑盒,不可追变动,重建耗时
  • ❌ 周期性快照——不需要,事件链本身就是完整历史
  • ❌ 图数据库依赖(Neo4j/FalkorDB)——纯 JSON 文件存储,白盒可见
  • ❌ RAG 黑盒检索——向量查询可作为辅助检索手段,但不作为主存储

9. 待澄清的子问题(下一阶段研究内容)

节点 schema

  • 节点的具体字段设计(name, type, aliases, summary, lifecycle, created_by_event, ...?)
  • 针对具体的小说场景设计(角色节点、物品节点、背景节点字段不同?)
  • 生命周期字段的具体值?(permanent / temporary / event-scoped?)
  • "演出后消失"的触发时机?(渲染完成后?事件结束后?章节结束后?)
  • 临时节点消失后,相关边怎么处理?(级联删除?保留为"已失效"?)
  • 临时节点的扩散记录是否保留?(节点消失,但它对其他节点的影响是否保留?)

边 schema

  • 边的字段设计(fact + 双图如何引用?)
  • 边的语义化状态如何表达?
  • 事件图与世界图的连接方式?
  • 选项 A:事件节点存储 affected_nodes: [world_node_uuid] 列表
  • 选项 B:事件节点存储 diffusions: [{target_uuid, field, old_value, new_value}](已采用)
  • 选项 C:双向引用——事件节点引用世界节点,世界节点的边记录 source_event: event_uuid

文件目录布局

  • nodes/、edges/、events/、...?
  • 事件链存储格式?(JSON 文件 / JSONL / SQLite)
  • HEAD 指针存储位置?

其他

  • 是否支持多分支叙事?(多个 HEAD 指针,类似 Git branch)
  • 是否需要 reflog(HEAD 移动历史)?
  • 旧事件节点保留多久?(永久?还是有 GC 机制?)
  • 手动重放时,用户是否能看到旧事件链作为参考?
  • 自动重放时,旧事件链的子代理输出是否复用?还是重新跑子代理?
  • 世界图节点的 created_by_event 字段如何更新?(节点重建时更新?)
  • 节点稳定性如何保证?(长篇数千事件,节点 ID 策略)

10. 优势总结

  1. 简单:抄 Git 成熟心智模型,不需要复杂的 Bi-temporal 逻辑
  2. 白盒:JSON 文件存储,用户可见、可追变动
  3. 轻量:事件节点只存 diff,不存完整状态
  4. 可回退:反向撤销 + 重放,支持任意事件重写
  5. 用户编辑友好:用户编辑优先,不被引擎扩散吞噬
  6. 不依赖外部服务:纯 JSON 文件,不需要图数据库或向量数据库
  7. 支持长篇:事件链 + 生命周期机制,避免节点爆炸

本文由 AIGC 调研生成,仅做研究笔记和参考,真实性需要自行考证。

avatar

Mirror

深入探索AIGC叙事生成与AI协作创作,记录世界观构建、Vibe Coding实践及原创内容创作的个人博客。

2026年7月

一
二
三
四
五
六
日
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

Recent Studies

酒馆角色扮演提示词工程解析 — 角色卡、世界书与上下文管理

2026-07-19 15:20:00

EmaoNovel:最早的叙事工程探索

2026-07-17 14:20:00

Pi Narrative Engine:从理论到实现

2026-07-16 18:20:00