我正在构建一个基于网页的策略引擎,过程中遇到了需要拆解大量核心模块(动画、战斗节奏、AI时序)的情况,却无法清晰判断改动是否影响了实际表现。因此在动手重构前,我花时间先搭建了一个安全网。本文将讲述其工作原理——因为最终这套方案的重要性甚至超过了它所支持的重构工作本身。

核心举措是将引擎中的所有时间控制权赋予同一个Clock对象。此前,动画、战斗节奏、AI延迟和状态机各自独立调用performance.now()setTimeout,导致无法统一暂停或回放。现在,时钟在正式发布的游戏中运行于实时模式,同时提供手动模式——仅以我控制的固定步长推进。配合以种子的mulberry32随机数生成器替代Math.randomenterDeterministicMode(seed)结合逐帧推进,使得即便是随机战斗场景,每次运行也能生成字节完全相同的帧。这种确定性是整个体系的基础,因为它让截图拥有了可比较的意义。

在此基础上,我们构建了包含十六个测试用例的视觉回归测试套件,分为夹具驱动的渲染测试和控件驱动的编辑-保存测试。每个测试用例都会将捕获的帧与已提交的基准PNG进行对比,只要有一个像素偏移就判定失败,并通过pre-push钩子强制执行。这套机制很快展现了价值:当粒子库升级(https://three-nebula.org 版本11到12)悄然改变粒子渲染效果时,测试套件精准标出了哪些帧发生了变化,使我能够确认这一变动符合预期并有目的地重新生成基准,而不是在几周后上线的游戏中才发现问题。

完整文章(包含时钟模式与确定性模式流程)请见:https://projectgoldscript.com/development/becoming-a-time-mage/

我很好奇其他人如何处理字节完全一致的问题。让实时引擎每次运行都能逐像素一致地渲染,意味着需要追踪每个隐藏的非确定性来源——我相信还有更多潜伏的问题。对于在实际引擎上运行视觉回归测试的各位,你们如何应对基准不稳定以及不同机器间GPU/驱动差异的问题?