MOD A 把酒馆铁匠的生命值改成 300,MOD B 把同一条角色记录的生命值改成 80,并顺手给他加了一个技能。两个 MOD 各自测试都没问题,玩家把它们同时装上,进游戏发现铁匠只有 80 点生命,但技能是 B 的、对话是 A 的。谁生效取决于加载顺序,而玩家对此一无所知。多个 MOD 改同一个角色,是 MOD 生态里最常见的一类问题,也是 AI 最应该接手的检查工作。

冲突不是一种,而是四层

笼统地说“MOD 冲突”没有办法自动化,需要先分层。数据层:多个 MOD 修改同一条记录的同一个字段。脚本钩子层:多个脚本订阅或替换同一个事件、函数。资源层:多个 MOD 提供了同路径或同 ID 的贴图、模型、音频。加载顺序层:以上三类冲突谁生效,由顺序决定,而顺序本身也可能与依赖关系矛盾。每一层要用不同的检测方法,报告方式也不同。

数据层:按字段比对,而不是按文件比对

如果只比较文件,两个 MOD 都改了 characters.json 就算冲突,误报会多到没人看。更有效的做法是先把每个 MOD 解析成对基础游戏的“补丁集合”:改了哪条记录、哪个字段、从什么值改到什么值。然后按(记录 ID,字段)做交集。只有同一个键被写了不同值才是真冲突;A 改生命值、B 加技能,是可以并存的。

数据层还有一种隐蔽冲突:引用悬空。MOD A 删除了一个技能,MOD B 的角色却引用了它。检测办法是把所有补丁应用之后,做一次完整的引用完整性检查,看有没有指向不存在记录的 ID。

脚本钩子层:谁在拦截同一个事件

脚本冲突比数据难得多,因为函数体里的逻辑不像字段那样有清晰的键。可执行的办法是先提取每个脚本的“接触面”:订阅了哪些事件,重写了哪些函数,读写了哪些全局状态。当两个 MOD 都替换同一个函数,且不调用原函数,后加载的会把先加载的直接盖掉;当两个 MOD 都订阅“角色死亡”事件,则可能出现执行顺序引起的行为差异,比如先发奖励还是先移除队伍成员。这些信息用静态分析就能抽出来,AI 的价值在下一步:读两段脚本,判断它们对同一个状态的修改是相加、覆盖还是互相矛盾,并用人话写出来。

资源层与加载顺序

资源层最简单,也最容易被忽略:按路径和资源 ID 建索引,同键出现两次就是候选。但要注意区分“覆盖”和“并存”,替换头像是覆盖,新增头像则不冲突。加载顺序(Load Order)则要和依赖(Dependency)一起看:清单文件里声明了“我依赖 MOD X”,就意味着 X 必须先加载;声明了“与 Y 冲突”,就不该同时出现。把这些关系画成有向图,检查有没有环、有没有违背声明的排序,就能自动给出一个可行的排序,或明确告诉玩家不存在可行排序。清单里的这些字段从哪里来,参见一句话怎样变成可安装 MOD里的打包环节。

合并策略:能自动的自动,不能的交还

发现冲突只是一半,另一半是怎么处理。可以按可信度分成三级。第一级,无冲突并存:字段不相交,直接合并。第二级,可推导的合并:数值类字段,两个 MOD 都在基础值上做增量,就应用叠加而不是覆盖;列表类字段做并集;都需要规则明确、可以复现。第三级,语义冲突:两个 MOD 对同一角色给出了相互矛盾的设计,比如一个让他成为队友,一个让他成为必须击杀的敌人。这一级不应该由 AI 悄悄选择,而是给玩家两到三个选项和各自的后果,由玩家决定,选择结果写进一个“合并补丁”,作为独立的 MOD 存在,方便之后分享和撤销。

AI 在这条链路中的位置

把职责画清楚,能避免最大的坑:让模型直接“看一眼”所有 MOD 再判断有没有冲突,既慢又不可复现。更可靠的分工是:确定性工具产出候选(字段交集、钩子交集、资源交集、依赖图检查),AI 负责三件事:把候选归类并排序,把技术细节翻译成玩家能看懂的说明,为可合并的情况生成补丁。最后一定要把合并后的整体放进沙盒跑一遍,这一步与MOD 兼容性为什么比写脚本更难所讨论的验证是同一件事。

如果要给一个检查清单:每个 MOD 是否声明了修改的记录与钩子;候选冲突是否按四层分类;每个冲突是否有“自动合并 / 需要选择 / 不兼容”的明确结论;合并结果是否经过读档测试;游戏更新后这份冲突报告是否会被重新生成,这一点则会在旧 MOD 的版本修复中继续展开。