一名玩家在集市里把一盏油灯递给守卫,转身走进西侧巷道,三分钟后回来,守卫对这盏灯毫无印象,巷口的一个摊位还凭空消失了。排查下来,模型并没有“胡说”,它只是各说各话:对话模块以为灯在守卫手里,背包模块以为灯还在玩家身上,地图模块在重新加载分区时把摊位当成了可以回收的临时对象。三个模块各自维护了一份世界的样子,谁也没错,世界却裂开了。

PA真人游戏世界智能研究组在设计PA真人AI的世界状态层时,第一个决定并不是选哪个模型,而是规定一条纪律:世界里发生的任何事,只在一个地方有权威记录,其他模块看到的都是它的投影。这就是常说的“唯一事实源”(Single Source of Truth)。至于语言模型怎样从聊天走向完整的世界模型,可以先读《一个真正的AI游戏大模型为什么不能只会聊天》,本文只讨论其中最底层的一块:状态到底怎样存、怎样读、怎样改。

实体:先把“世界里有什么”说清楚

世界状态里的东西统一叫实体(Entity)。研究组把它们归为六类:NPC、玩家、地图区块、物品、任务、关系。关系单独成类是一个有意的取舍,因为“守卫欠玩家一个人情”既不属于守卫,也不属于玩家,硬塞进任一方的属性里,另一方读取时就会出现信息不对称。每个实体有稳定的编号、类型、版本号和一组受模式(Schema)约束的字段,模型可以在文本里随便称呼它,但在状态里它只有一个编号。

entity: item#lamp_0142
  type: item
  owner: npc#guard_07        // 谁持有,只能有一个
  location: region#market_a
  flags: [lit, quest_related]
  version: 18                // 每次提交递增
  last_event: evt#88213      // 指向使这一版生成的事件

entity: relation#guard_07-player_2
  kind: favor_owed
  strength: 0.4              // 示例参数
  source_event: evt#88213

每个状态都能追到造成它的事件,而不是“模型说的”。

画面中心是世界状态数据库,四条总线分别连向NPC、玩家、地图和任务模块,外圈的事件日志环记录每次状态变化。
这张图说明:大模型让NPC、玩家、地图与任务共用同一份世界状态,并以事件日志保持一致。

模型只读视图,只提提案

语言模型在这套结构里没有写权限。它拿到的是一份“视图”(View),也就是为当前任务裁剪出来的只读快照,输出的是“提案”(Proposal),比如“守卫接过油灯并表示感谢,建议登记一条人情关系”。提案交给规则引擎,引擎依次检查:这个实体是否存在,前置条件是否满足,数值是否越界,与已有事实是否冲突。通过的才会被提交,产生新版本的实体并写入事件日志;被拒的会带着原因退回,模型可以改写后再提交,或者干脆把这次拒绝写成一句合理的台词。

这样分工之后,几类常见事故会自然消失。模型说“守卫把灯扔进了河里”,但河边并没有守卫的行动能力,提案被拒;模型在同一轮里两次转移同一件物品,第二次因为版本号过期被拒。

事件日志与快照:可回放才算可信

状态可以直接覆盖,也可以只追加事件、再从事件推出当前状态。研究组的方案是两者并用:事件日志只增不改,定期生成整体快照,快照相当于“存档点”。示例参数:每200条事件或每5分钟游戏时间存一次快照,保留最近三份。任何一次异常,都可以取最近快照、重放其后的事件,逐条对照是哪一步让世界变了样。

这也让“撤销”与“回滚”变成合理的操作。玩家投诉“我的灯被吞了”,工作人员不需要猜测,直接查看灯的实体在哪个版本发生了转移,由哪个提案触发。与之相邻的思路,生存类玩法里如何让因果可复盘,在生存游戏的世界规则一文里有更具体的展开。

读取视图模型提案规则校验提交状态事件日志同步各端
本文讨论的世界状态写入路径:模型不直接改世界,任何变化都要经过校验与提交,并留下可回放的记录。

多人同步:冲突在提交处解决

两名玩家几乎同时去拿同一把钥匙,两个客户端都在本地看到“拿到了”,这是多人游戏里最典型的冲突。做法是让所有写操作汇入同一个提交点,按版本号判定先后:先到的提案提交成功,实体版本从7变成8;后到的提案带着旧的版本号7,会被拒绝并收到最新状态,客户端据此回滚本地表现。

玩家之间的可见性也在同一层处理。某个秘密只有一名玩家知道,就在实体上标注可见范围,视图生成时按接收者过滤,模型不会“无意间”把它说给另一个人。

分区与视图裁剪:上下文是预算,不是仓库

整个世界不可能一次塞给模型。地图被切成分区,玩家附近的分区处于活跃状态,远处的只保留摘要;每个模块拿到的视图也不同,如下表所示。

模块 读取的视图可提交的提案示例上限
对话 说话者、听者、二者关系、当前区块摘要关系变动、口头承诺 约30个实体
任务任务图、相关NPC、关键物品任务状态迁移约40个实体
地图事件活跃分区、资源与天气 生成、移除、移动实体 约60个实体
玩家行动 背包、位置、邻近可交互物 使用物品、移动约25个实体

裁剪规则不复杂,难的是漏掉什么。研究组的经验是宁可让模块通过“查询接口”按需追加读取,也不要预先塞满。不同任务由哪种模型、在哪里执行,则属于另一个层面,可参见模型路由一文。

这一层还没有好办法的地方

两件事目前依然是取舍而不是答案。其一,模式约束越严,模型越难表达意外的创意,比如玩家把灯挂在桥栏上当路标,模式里没有“悬挂”这个状态时怎么办;现在的折中是允许提案携带一个待审核的“自由备注”,由后续规则版本决定是否升级为正式字段。其二,多人跨地域的延迟会让“先到先得”变得不那么公平,需要在体验层面另外补偿。

把世界状态做成一个不会说话、也不会撒谎的底座,语言模型才有资格去讲故事。栏目里的其他内容可以从PA真人AI大模型栏目继续阅读。