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 剪枝(本项目最精巧的确定性逻辑)

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 的三个机制:

  1. 同向阻尼:同方向堆叠时越接近极值阻尼越强(饱和曲线 damping × saturation)——防止刷好感线性到顶;
  2. 烈度穿透magnitude = min(|delta|/20, 1)——大事件(|Δ|≥20)几乎无视阻尼(救命之恩必须入账);
  3. 背叛放大: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)


8. 多时间线(timeline.py)


9. RAG 知识库(rag.py)


10. 存档 / 回滚(main.py)


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. 设计评价

优点(可借鉴)

  1. 「副作用引擎」思想:AI 只产出结构化意图(JSON),一切数值/状态变化由代码结算——LLM 幻觉被挡在数值层之外。这是与 loreweaver「确定性/生成性分层」同构的设计,只是载体不同(JSON schema vs 文档投影)。
  2. 三阶段流水线(推演→扩写→结算):60 字骨架保留因果链、完整叙事按需生成——省 token 且让「叙事」与「结算」解耦。
  3. 三值逻辑 + AI 剪枝:确定性条件零成本短路,AI 只在必要时批量出场——触发器高频调用下的成本控制典范。
  4. 情绪状态机的阻尼/烈度穿透/背叛放大:三个数学机制就把「NPC 态度」做出真实感,且纯确定性、可单测(模块自带自测)。
  5. 记忆三级的「记忆即知识」统一:L1 快照 → AI 压缩 → 向量入库 → 检索召回,长期记忆与规则知识走同一条 RAG 管线。
  6. 降级无处不在:RAG 失败降级、embedding 失败降级、AI 摘要失败降级、合并失败降级——单点故障不阻塞核心推演。
  7. 零构建前端:Vue3 CDN + 单文件 HTML,Windows 双击 bat 即玩——分发成本极低。

缺陷(要警惕)

  1. 端点/模型硬编码(api.deepseek.com、"deepseek-chat" 散落 6 个文件)——本机实测需 3 处补丁才能接本地模型。
  2. 结构化输出脆弱:JSON Schema 极深(分支×副作用×嵌套可选字段),小模型高频复读/截断;json_repair 只是兜底。本地 7B 需降到温度 0.3 才稳定。
  3. 同步阻塞:主推演/触发器 AI 判定/时间线合并都是同步 HTTP 调用(SSE 只覆盖推演一条路径),多人/多时间线并发时无队列编排。
  4. L2 实体档案弱:README 宣传「L2 实体档案」,代码中 entity 只是「AI 从行动文本提取并 upsert」,无独立检索注入路径(实际是 L1/L3 两级 + 实体表)。
  5. 单机 SQLite:无鉴权、无租户,投屏靠局域网直连——演示可用,生产不行。
  6. GPL-3.0 传染性许可,商业复用需注意。
  7. 时间线/触发器/地图三套概念叠加后,GM 的学习成本不低(UI 也复杂)。

13. 结论:它是什么、不是什么

同类对比与功能矩阵见 22-zric-similar-frameworks.md;实测记录见 23-zric.md