Z.R.I.C 架构静态分析:模块设计与工作原理
对象:zRICGao/ZRIC-AI-TRPG-Engine(GPL-3.0,FastAPI + SQLite + Vue3 CDN,约 9000 行 Python) 方法:纯静态分析(不运行),逐模块阅读 main.py(2437) / agent.py(1814) / trigger.py(1189) / rag.py(665) / entity.py(578) / map.py(500) / memory.py(355) / timeline.py(213) / logger.py 与实测结论对照:3 处本地化补丁(端点环境变量、端口、温度/列表解包)已在本机验证
1. 总体架构
┌─────────────────────────────────────────────────────────────┐
│ 前端(零构建,同源静态服务) │
│ index.html GM 控制台(Vue3 4250行) · player.html 投屏端(WS) │
│ phone.html NPC 短信界面 │
└───────────────┬─────────────────────────────────────────────┘
│ REST + SSE + WebSocket(投屏)
┌───────────────▼─────────────────────────────────────────────┐
│ main.py —— 编排层(App 装配 / 剧本导入导出 / 角色 / 节点 / 存档 │
│ / 检查点回滚 / 投屏广播 / 依赖注入到各模块) │
├──────────────────────────────────────────────────────────────┤
│ agent.py —— AI 推演核心(上下文装配 / 模型路由 / 三阶段流水线) │
│ trigger.py 触发器 · memory.py 三级记忆 · entity.py 世界实体 │
│ map.py 空间地图 · timeline.py 多时间线 · rag.py 向量知识库 │
├──────────────────────────────────────────────────────────────┤
│ SQLite(WAL) —— 单库多表,全部业务状态持久化 │
└──────────────────────────────────────────────────────────────┘
│ OpenAI 兼容协议(可多模型,请求级回退)
DeepSeek / Claude / 任意 OpenAI 兼容端点(本机 KoboldCpp 已验证)
模块间解耦方式:main.py 启动时把 DB 路径、LLM 客户端、embedding 函数「依赖注入」给各模块(configure_agent / configure_trigger / configure_memory / configure_entity / configure_rag / map_set_db_file),各模块独立维护自己的 FastAPI router 与表结构——典型「单机服务 + 模块化 router」布局,无共享框架约束。
2. 核心工作流:三阶段推演流水线(最重要设计)
README 称之为 Phase 1/2/3,代码里刻意乱序编号(Phase3 在 Phase2 之前执行),这是理解全引擎的关键:
玩家输入行动
│
▼
【Phase 1】/api/ai/dynamic-options → agent.py dynamic_options_handler
│ 1. build_system_context() 装配「全知上下文」(见 §3)
│ 2. 组装 system prompt(世界观+队伍状态+百科+记忆+实体+RAG+地图+JSON Schema)
│ 3. _call_ai(json_mode=True) → 模型返回 branches JSON(含 60 字骨架+副作用)
│ 4. json_repair 容错解析 → _post_process_dynamic_result
│ · 每个 branch 创建 nodes + options 记录
│ · 副作用 payload 存入 pending_effects 表(冻结,不立即执行!)
│ · 写 L1 记忆快照(场景/行动/摘要/思考过程/实体变化,按时间线隔离)
▼
【玩家点击某个分支】
│
├──【Phase 3】/api/ai/expand-branch/stream(先扩写)
│ 读 pending_effects + 60 字骨架 + 最新状态 → AI 润色为完整叙事
│ (扩写时副作用尚未执行——叙事与结算解耦)
│
▼
【Phase 2】/api/ai/apply-branch-effects(后结算)
│ 从 pending_effects 读 payload:
│ · stat_changes → 改 characters 表 HP/SAN/物品(名字匹配角色)
│ · npc → 生成或更新 NPC(去重 upsert)+ 写登场记忆
│ · map_actions → 移动 / 新房间自动生长 / 解锁边(_process_map_actions)
│ · emotion_deltas→ 情绪状态机结算(§5)
│ · npc_memories → NPC 独立记忆
│ · entity_updates→ 世界实体档案更新
│ 执行完毕删除 pending_effects(防重复执行)
▼
【场景进入时】/api/game/check-triggers → 触发器判定循环(§4)
为什么「先扩写后结算」:副作用是「玩家的选择结果」,叙事扩写需要最新状态但不应受结算污染;而选择一旦做出,副作用必须精确执行一次(pending_effects 表 + 执行即删 = 恰好一次语义)。这是该引擎比「纯聊天式 AI 跑团」结构化程度更高的根本原因。
3. 上下文装配管线(agent.py build_system_context,8 元组)
每次推演注入 8 段「全知上下文」,全部有独立降级路径(缺哪块都不阻塞推演):
| # | 上下文 | 来源 | 工作原理 |
|---|---|---|---|
| 1 | 世界观 | system_state.worldview | GM 手写的世界规则与主线(静态) |
| 2 | 队伍状态 | characters 表 | 活跃/暂离角色(HP/SAN/物品/性格,MBTI 描述用正则去括号) |
| 3 | 相关百科 | lorebook 表 | 关键词命中注入:把行动文本分词后与每条百科的 keywords 做子串匹配 |
| 4 | 会话记忆 | system_state.session_memory | 全局滚动摘要 |
| 5 | L1 工作区 | memory_l1 | 最近 8 条推演快照,格式化后注入(按时间线隔离) |
| 6 | 世界实体 | world_entities | 按行动文本相关度检索实体档案(entity.py) |
| 7 | RAG 检索 | rag_chunks | 地图驱动增强:检索 query 自动拼接当前房间名+描述再检索 |
| 8 | 空间感知 | map_rooms/edges | 当前位置 + 可通往房间(楼层/方向/上锁/探索状态)+ 已知地图状态 |
设计亮点:百科用确定性关键词注入(快),RAG 用向量检索(慢但语义)——两种知识注入按成本分层;RAG 检索查询被「地图上下文」增强,让规则怪谈类剧本的「位置相关规则」能被检索到。
4. 触发器系统(trigger.py——收束叙事的关键)
4.1 条件树:三值求值 + AI 剪枝(本项目最精巧的确定性逻辑)
- 条件类型四种:
scene(场景 ID 相等)、item(物品子串匹配)、stat(HP/SAN 比较运算,支持>=,<=,>,<,==,作用于全体 PC)、ai(自然语言条件,交给 AI 批判)。 - 条件树支持
and/or/not递归组合,深度上限 20 防爆栈。 - AI 剪枝(
_eval_tree_3val):调用 AI 之前先用三值逻辑(True/False/_MAYBE)按短路规则求值——非 AI 条件能确定结果就不花 AI 调用;只有剩余 _MAYBE 叶子才批量打包给 AI 一次判断(_batch_judge_ai,批量省 token)。纯确定性的部分零成本,AI 只做自然语言部分。
4.2 动作系统(11 种副作用动作)
set_flag(全局标志位)/ mod_stat(HP/SAN 增减,target="PC" 作用全体)/ add_memory / fire_trigger(链式激活另一触发器,不递归防循环)/ inject_text(向节点追加叙事)/ inject_option(注入选项)/ gen_node(AI 即时生成新节点)/ mod_inventory / mod_npc(改实体状态/情绪/记忆)/ entity_script(appear/leave/die/attack)。
触发模式分 hard(强制跳转/执行)与 soft(仅提示)——README 明确「触发器是收束时间线的关键」,在关键节点把 AI 的自由发散拉回主线。
5. NPC 情绪状态机(entity.py——数值化关系)
三轴:trust(信任)/ fear(恐惧)/ irritation(烦躁),取值 [-100, +100],每轴带阻尼系数。apply_emotion_delta 的三个机制:
- 同向阻尼:同方向堆叠时越接近极值阻尼越强(饱和曲线
damping × saturation)——防止刷好感线性到顶; - 烈度穿透:
magnitude = min(|delta|/20, 1)——大事件(|Δ|≥20)几乎无视阻尼(救命之恩必须入账); - 背叛放大:trust 反向(信任被辜负)时伤害放大,
trust=+100 时系数 3.0——信任越深,背叛越痛(最受信任者的背叛能从 +100 直接砸穿)。
tick_emotion_decay:fear/irritation 正值自然衰减,trust 永不衰减、负值永不衰减(记仇与信任一样是永久资产)。NPC 记忆(npc_memories)按 NPC 视角独立存储——「从该 NPC 视角记住的一句话」。
6. 三级记忆(memory.py)
| 层 | 存储 | 容量 | 工作原理 |
|---|---|---|---|
| L1 工作区 | memory_l1 表 | 每时间线 8 条(L1_MAX_ENTRIES) | 每次推演追加快照(场景/行动/摘要/思考/实体变化),注入下轮 prompt |
| L1→L3 折叠 | 后台线程 | — | 溢出时异步淘汰:AI 压缩为 ≤N 字摘要 → embedding → 写入 rag_chunks(成为可检索长期记忆);embedding 失败降级为纯文本追加 session_memory。所有 IO 先于写库(不在 AI 调用期间持有 SQLite 写锁) |
| L3 长期 | rag_chunks | 向量库 | 与知识库同表,推演时随 RAG 检索召回——「记忆即知识」的统一设计 |
fold_memory_with_ai(备用折叠)+ _async_fold_memory_task 提供了第二条路径:手动触发记忆折叠时后台执行,不阻塞主线程。
7. 空间地图(map.py)
- 拓扑模型:
map_rooms(x/y/w/h 画布坐标 + floor 楼层 + state:unknown/explored/locked/active + 关联 node_id)+map_edges(双向边 + locked/key_item 上锁条件 + edge_type:楼梯/电梯/传送门)。 - 空间感知文本(get_map_context):当前位置描述 + 「可通往:A[楼梯(上至2层)](已探索)/ B(上锁,需:铜钥匙)」+ 已知地图状态——AI 推演时天然知道「你在哪、旁边有什么、怎么去」。
- 自动生长(auto_place_room):推演产生新房间时按「右→下→左→上」四方向试放,40px 网格对齐 + 碰撞检测,放不下静默失败——地图随剧情自动扩展而不重叠。
- 数据可独立导出 map.json(剧本文件夹格式的一部分)。
8. 多时间线(timeline.py)
- 每条时间线:独立 current_node_id、current_room_id、memory 文本流、char_ids 集合、status(active/merged)。
- L1 记忆按
timeline_id隔离(同一引擎并行推演多条支线互不污染)。 - 合并(merge_timelines):AI 把两条支线记忆流合并为「时间线汇合摘要」(标注事件发生在哪条支线),source 标记 merged——「平行推演、独立记忆、可合并」的完整闭环。
9. RAG 知识库(rag.py)
- 切片:600 字/块、0 重叠(chunk_text);embedding 用硅基流动 BGE-M3(1024 维,可换任意 OpenAI 兼容 embedding 端点——
configure_rag接受 base_url)。 _VectorCache:全库向量常驻内存缓存(启动加载、变更刷新),检索不用每次现算。_hybrid_retrieve:向量相似度 + 文本匹配混合检索,top_k 截断后格式化注入 prompt。- 剧本文件夹的
knowledge/*.txt在加载剧本时自动入库;文档支持 hidden 标记(GM 可见/不参与检索)。
10. 存档 / 回滚(main.py)
- 剧本格式:
campaigns/<名字>/campaign.json + map.json + knowledge/*.txt——人类可读、可版本管理。 - 导出:全库业务表 + RAG 向量索引整体导出(加载即用,无需重建向量)。
- 检查点/回滚:
/api/game/checkpoint存快照,/api/game/rollback恢复——配合多时间线实现「试错式推演」。
11. 数据模型一览(SQLite 单库)
| 表 | 用途 |
|---|---|
| nodes / options | 场景节点(60 字骨架/完整叙事)+ 选项(node→next_node) |
| characters | 角色(PC/NPC:HP/SAN/物品/性格/MBTI/状态) |
| pending_effects | 副作用暂存(Phase1 冻结 → Phase2 执行即删) |
| system_state | key-value:worldview / session_memory / current_room_id / stat_labels |
| lorebook | 关键词触发的百科条目 |
| triggers | 触发器(条件树 JSON + 动作 JSON + fire_count/cooldown/max_fire_count) |
| timelines | 时间线(记忆流/角色集/状态) |
| memory_l1 | L1 短期工作区(按时间线隔离) |
| rag_documents / rag_chunks | 知识库文档 + 向量块(L3 记忆同表) |
| world_entities | 世界实体档案(含情绪三轴 JSON、persona、房间绑定) |
| map_rooms / map_edges | 地图拓扑 |
| game_flags | 触发器标志位 |
| npc_chat_logs | NPC 对话日志 |
12. 设计评价
优点(可借鉴)
- 「副作用引擎」思想:AI 只产出结构化意图(JSON),一切数值/状态变化由代码结算——LLM 幻觉被挡在数值层之外。这是与 loreweaver「确定性/生成性分层」同构的设计,只是载体不同(JSON schema vs 文档投影)。
- 三阶段流水线(推演→扩写→结算):60 字骨架保留因果链、完整叙事按需生成——省 token 且让「叙事」与「结算」解耦。
- 三值逻辑 + AI 剪枝:确定性条件零成本短路,AI 只在必要时批量出场——触发器高频调用下的成本控制典范。
- 情绪状态机的阻尼/烈度穿透/背叛放大:三个数学机制就把「NPC 态度」做出真实感,且纯确定性、可单测(模块自带自测)。
- 记忆三级的「记忆即知识」统一:L1 快照 → AI 压缩 → 向量入库 → 检索召回,长期记忆与规则知识走同一条 RAG 管线。
- 降级无处不在:RAG 失败降级、embedding 失败降级、AI 摘要失败降级、合并失败降级——单点故障不阻塞核心推演。
- 零构建前端:Vue3 CDN + 单文件 HTML,Windows 双击 bat 即玩——分发成本极低。
缺陷(要警惕)
- 端点/模型硬编码(api.deepseek.com、"deepseek-chat" 散落 6 个文件)——本机实测需 3 处补丁才能接本地模型。
- 结构化输出脆弱:JSON Schema 极深(分支×副作用×嵌套可选字段),小模型高频复读/截断;json_repair 只是兜底。本地 7B 需降到温度 0.3 才稳定。
- 同步阻塞:主推演/触发器 AI 判定/时间线合并都是同步 HTTP 调用(SSE 只覆盖推演一条路径),多人/多时间线并发时无队列编排。
- L2 实体档案弱:README 宣传「L2 实体档案」,代码中 entity 只是「AI 从行动文本提取并 upsert」,无独立检索注入路径(实际是 L1/L3 两级 + 实体表)。
- 单机 SQLite:无鉴权、无租户,投屏靠局域网直连——演示可用,生产不行。
- GPL-3.0 传染性许可,商业复用需注意。
- 时间线/触发器/地图三套概念叠加后,GM 的学习成本不低(UI 也复杂)。
13. 结论:它是什么、不是什么
- 是:一个「DM 在环的演出台」——人定规则与主线,AI 推演分支与细节,触发器收束,投屏演出。单人/单 GM 的规则怪谈、恋爱模拟、剧情推演工具。
- 不是:全自动 DM(需要人持续操作 GM 控制台)、多人在线跑团平台(无账户/权限体系)、规则引擎(HP/SAN 只是数值,无 CoC/D&D 规则深度——对比 5e-mcp/loreweaver)。
同类对比与功能矩阵见 22-zric-similar-frameworks.md;实测记录见 23-zric.md。