Project Infinity 静态分析:模块设计与工作原理

对象:electronistu/Project_Infinity · 方法:纯静态分析

1. 总体架构

┌──────────────────────────────────────────────────────────────┐
│ 终端 TUI(Rich + prompt_toolkit)                              │
│  play.py / play_with_{deepseek,gpt,gemini,claude,nano}.py     │
│  → create_*_chat_fn() 各后端适配器(统一 chat_fn 接口)        │
└───────────────┬──────────────────────────────────────────────┘
                │ game_engine.run_game() 编排循环(game_engine.py)
┌───────────────▼──────────────────────────────────────────────┐
│ game_engine.py —— 会话编排层                                   │
│  选世界→注入系统提示词+世界文件→MCP 客户端→工具调用循环→         │
│  斜杠命令→每 5 回合 timeline 摘要→/save 落盘                    │
└───────┬──────────────────────────────────────────────────────┘
        │ MCP stdio(game_engine.py:130 启动子进程 dice_server.py)
┌───────▼──────────────────────────────────────────────────────┐
│ dice_server.py —— 规则引擎(FastMCP,3001 行)                 │
│  @mcp.tool 暴露:modify_player_numeric / update_player_list / │
│  dump_player_db / rest / roll_dice / perform_check /          │
│  register_combatants / resolve_attack / resolve_magic         │
│  ┌────────────┐ ┌────────────┐ ┌──────────────────────────┐   │
│  │内存 SQLite  │ │战斗注册表    │ │config/spells.yml(5619行) │   │
│  │(玩家状态)   │ │_COMBAT_REGISTRY│ │SRD 5.1 法术库          │   │
│  └────────────┘ └────────────┘ └──────────────────────────┘   │
└───────┬──────────────────────────────────────────────────────┘
        │ level_up.py(等级/法术位表 单一事实源)
┌───────▼──────────────────────────────────────────────────────┐
│ 持久化:output/*.wwf(世界)+ *.player(角色 JSON)+ *.timeline.md │
└──────────────────────────────────────────────────────────────┘

关键解耦dice_server.py 是独立进程(MCP stdio server),game_engine.py 通过 mcp.ClientSession 作为客户端连入,二者只靠 JSON 工具调用通信。所有随机数在 server 侧用 random.randint 产生,LLM 只发工具调用、读返回的 narrative_format,无法自造数值。

2. 核心工作流:玩家命令 → 规则判定 → 骰子结算 → LLM 叙事 → 状态持久化

这是项目最重要的一条链路,贯穿 game_engine.pydice_server.py

  1. 启动装配(game_engine.py:124-166):读入 GameMaster_MCP.md 作为 system prompt、.wwf 世界文件、.timeline.md 历史;stdio_client 启动 dice_server.py 子进程,session.list_tools() 把 server 的 @mcp.tool 转成 LLM 的 function-calling schema(game_engine.py:137-147)。
  2. 玩家输入(game_engine.py:434):prompt_async 收取文本,非 / 开头即作为 action 交给 chat_with_tools
  3. LLM 产出工具调用(game_engine.py:170-276):chat_fn 返回 message.tool_calls,逐个 session.call_tool(name, arguments) 执行,结果以 role:"tool" 追加回 messages;若 LLM 输出 {{_NEED_AN_OTHER_PROMPT}} 同步令牌则暂停(game_engine.py:271-274)。
  4. 规则判定与骰子结算(dice_server.py):如 resolve_attack(1504-1787)一次完成「d20 攻击掷骰→命中判定(优势/强击/暴击)→伤害掷骰(暴击翻倍主骰、附加骰不翻倍)→HP 结算→击杀判定→XP 奖励」;resolve_magic(1990-2992)完成「法术位校验与消耗→攻击/豁免→伤害/治疗→AoE 多目标→临时HP/状态→击杀与 XP」。
  5. 两阶段协议(GameMaster_MCP.md:32-67):机械阶段 LLM 只发工具调用、不发叙事,审计清单跑满后发同步令牌;叙事阶段才输出 prose,并把每个工具返回的 narrative_format 逐条嵌入「Mechanics:」块(GameMaster_MCP.md:59-67)。
  6. 持久化:数值改动落在内存 SQLite(modify_player_numeric),/save(game_engine.py:353-392)把 dump_player_db 结果回写 .player;每 5 回合触发 timeline 摘要落盘(game_engine.py:485-506)。

第二条链路:战斗结算(register_combatants → resolve_attack/resolve_magic)

  1. GM 开战先调 register_combatants(dice_server.py:1345):玩家从 DB 自动注册(名/HP/AC/DEX 先攻修正,1389-1424),NPC 逐个 d20+init 掷先攻并按总分排序返回(1426-1493)。
  2. 随后每次 resolve_attack/resolve_magic 若省略 target_current_hp 则从 _COMBAT_REGISTRY 查表(1305-1342,1740-1745),命中后 _registry_update_hp 写回减伤后的 HP——同轮多人集火自动串联扣血。
  3. 击杀判定用「剩余 HP ≤ 0」而非原始值,击杀后 _registry_kill 标记并按 CR_XP_TABLE 奖励 XP(1767-1776);法术的 AoE 豁免分支同样逐目标更新注册表(2722-2825)。
  4. 治疗法术(Cure Wounds 等)经同一 resolve_magicsp_healing 分支,写入注册表并 min(..., max_hp) 封顶(2544-2593),实现「玩家↔NPC↔NPC 三向治疗」。

3. 模块逐一分析

3.1 dice_server.py —— 5e 规则引擎(MCP server,3001 行)

3.2 game_engine.py —— 会话编排(515 行)

3.3 level_up.py —— 升级与法术位表(305 行)

3.4 多后端 LLM 抽象

统一契约即 run_game 的 docstring(game_engine.py:92-105):chat_fn(messages, tools, model, context_window) -> {'prompt_eval_count', 'message': {'content', 'tool_calls'}}。各 play_with_*.py 只实现该闭包:

3.5 forge/ —— World Forge(世界生成器)

3.6 display.py —— TUI 渲染(206 行)

3.7 GameMaster_MCP.md —— 系统提示词(168 行)

4. 数据模型与持久化

5. 与 Z.R.I.C 的功能对照表

Z.R.I.C 能力 Project Infinity 对应实现 位置 结论
剧本解析 无剧本文件;世界由 World Forge 程序化生成(4 王国/公会/NPC) forge/population_generator.py:224 部分(生成而非解析)
推演分支 无分支系统;LLM 自由叙事,靠系统提示词约束 GameMaster_MCP.md 未实现(无结构化分支)
自动判定 全部机械判定走 MCP 工具(豁免/命中/HP/击杀) dice_server.py resolve_attack/magic
世界状态 .wwf 只读注入 + 玩家状态 SQLite game_engine.py:124-127, dice_server.py 强(偏玩家侧)
NPC 情绪记忆 无情绪状态机;NPC 仅静态数据 + reputation(玩家侧) forge/models.py NPC 未实现
地图 无空间地图/移动系统 未实现
触发器 无触发器表;靠 GM 系统提示词手动遵循流程 GameMaster_MCP.md 未实现
时间线 .timeline.md 每 5 回合自动摘要 + 下次注入 game_engine.py:16,485-506 有(轻量版)
记忆系统 无 RAG/向量记忆;仅 timeline 文本回灌 + SQLite 状态 game_engine.py:157-164 部分
本地模型 Ollama 后端(play.py) play.py:63
许可 源码 MIT + SRD 内容 CC-BY-4.0 双许可 README:363-380, LICENSE

6. 设计评价

优点

  1. 「AI 不生成骰子」落实彻底:随机数只在独立 MCP server 进程内 random.randint 产生(dice_server.py:271,1232,1277,1578,2414),LLM 只能读工具返回的 narrative_format;且系统提示词强制「每次调用必须对应叙事行」(GameMaster_MCP.md:67),从进程边界到提示词双重兜底。
  2. 单次工具调用完成全链路结算resolve_attack/resolve_magic 把命中→伤害→HP→击杀→XP 一次返回,避免多次往返的数值漂移,是防幻觉的关键设计(dice_server.py:1504,1990)。
  3. 战斗注册表 + 顺序命中共享 HP_COMBAT_REGISTRY 让同轮多次命中自动携带上次扣血后的 HP(dice_server.py:1305-1342,1740-1751),README 255-258 所述「注册一次、逐击扣减」有对应实现。
  4. 两阶段协议防「边算边编」:机械阶段与叙事阶段用 {{_NEED_AN_OTHER_PROMPT}}/{{_CONTINUE_EXECUTION}} 令牌硬隔离,还枚举 10 种失败模式(GameMaster_MCP.md:32-67,126-146)。
  5. 可逆副作用:buff 用 _active_buff_data 记录增量,移除/长休时按 -delta 回滚(dice_server.py:837-858,1104-1114),不产生脏状态。

缺陷

  1. 世界/NPC 状态是只读的.wwf 只注入一次(game_engine.py:126-127),NPC 死亡/关系变化不写回,dump_player_db 也不含世界状态——「持久世界」只停留在玩家侧。
  2. NPC-vs-NPC 与部分路径不落库is_npc_vs_npc 分支只更新内存注册表、明确「no player HP modified, no XP auto-awarded」(dice_server.py:1536,1707-1737),且注册表是进程内 dict(dice_server.py:20),重启即失。
  3. 死亡豁免只是标记_apply_hp_change 归零仅返回 "death_saves": True 提示(dice_server.py:337-348),未实现 3 成功/3 失败的真实死亡豁免计数——README 275 的「triggers death saves」被高估。
  4. 法术库依赖外部 YAML 的字段约定resolve_magic 大量硬读 spell.get(...) 字段名(dice_server.py:2156-2179),字段缺失即静默走默认分支,与 config/spells.yml(5619 行)强耦合且无 schema 校验。
  5. HP 池/治疗对 targets 依赖参数传 HP:Sleep 按 t.get("current_hp") 排序而非必查注册表(dice_server.py:2622-2629),GM 若传错 HP 会得到错误受影响列表。
  6. 经验表/阈值是硬编码常量XP_THRESHOLDSCR_XP_TABLE 手写于模块顶(dice_server.py:22-43),与 level_up.py 的表分属两处,改规则需两处同步。

7. 结论

Project Infinity 是「MCP 规则引擎 + LLM 叙事」范式里完成度较高、把「公平骰子」落实得最彻底的一个:独立骰子进程 + 单调用全链路结算 + 两阶段协议三管齐下,使战斗/法术/豁免/休息/升级的数值几乎不可能被 LLM 捏造。其边界也很清晰——它解决的是机械正确性,而非叙事结构化:分支、地图、触发器、NPC 情绪记忆等 Z.R.I.C 级别的叙事引擎能力基本缺失,世界状态只读、NPC 关系与死亡豁免等也停在「提示 GM 手动处理」的层面。因此它更适合作为「有真骰子的单人 AI 跑团 GM」,而非完整的世界推演引擎;若需对标 Z.R.I.C,最值得借鉴的是它的「骰子权威进程 + 两阶段结算」设计,而它缺失的部分恰是 Z.R.I.C 的主场。