Claude-of-Duty像素级回归测试指南:用baseline与imagediff打造位图一致的确定性截图门
【免费下载链接】Claude-of-DutyA Call of Duty-quality FPS in Three.js, built from a single prompt.项目地址: https://gitcode.com/gh_mirrors/cl/Claude-of-Duty
Claude-of-Duty 是一个用 Three.js 从零构建的《使命召唤》级浏览器 FPS,它附带一套专业的像素级回归测试体系:baseline.mjs负责可复现地拍摄 11 张固定镜头截图,imagediff.mjs则逐像素比对两组截图,任何一像素变动都会让测试门失败(非零退出码)。这套工具曾在一次性能优化中被用来强制执行"零视觉变化"——优化后的构建与优化前参考图在所有镜头上逐位一致(bit-identical)。本指南带你快速上手这套截图门。
1. 为什么需要"像素级"的回归门?
这个项目最有意思的部分不是游戏本身,而是围绕它的测试框架(harness)。项目 README 明确记录了两个关键教训:
- 中位数帧时间会掩盖真实问题:静态相机基准测出 94 fps,但实际游玩只有 12–17 fps,根因是 34+ 个 WebGL 着色器在帧中途惰性编译导致 728–1236 ms 的卡顿。
- 截图曾经不可复现:
shotset.mjs在同一个页面里连拍 11 个镜头,粒子年龄、贴花缓冲、曝光状态会"泄漏"到后续镜头——两次完全相同的运行有 10/11 张截图不一致。
也就是说,如果截图本身不稳定,"视觉回归"就无从谈起。于是有了 tools/baseline.mjs 与 tools/imagediff.mjs 这对组合。
2. 回归工具链一览
| 工具 | 用途 | 是否用于回归门 |
|---|---|---|
| tools/capture.mjs | GPU 驱动的无头 Chromium 拍摄单个命名镜头 | 快速验证用 |
| tools/shotset.mjs | 单会话拍全部 11 个镜头,供人工/评审快速浏览 | ❌ 不可复现 |
| tools/baseline.mjs | 可复现采集:每个镜头独立页面 + 固定帧预算 | ✅ 生成基线 |
| tools/imagediff.mjs | 逐像素比对,有像素变动则退出码非零 | ✅ 回归门 |
11 个镜头全部定义在 src/dev/shots.js 中,涵盖环境光照(hero/interior/detail/sunset/night)、武器视角(weapon/ads/muzzle)与战斗特效(combat/impacts/hud)三大类,每个镜头都是冻结输入后的固定相机位姿 + 固定时刻,保证每次评审看到的构图完全一致。
3. baseline.mjs:确定性截图的三根支柱
打开 tools/baseline.mjs 的文件头注释,作者把可复现性拆解为三条设计,理解它们就能理解整套截图门:
- 隔离(ISOLATION)——每个镜头开一个全新页面。避免粒子年龄、贴花缓冲、动画相位、自动曝光状态在镜头之间泄漏;
- 固定帧预算(FIXED FRAME BUDGET)——镜头在已知的帧索引处应用,随后精确推进
settle(默认 90)帧,使 TAA 抖动相位、曝光自适应等时间累积器总从同一相位收敛; - 时间重置(TEMPORAL RESET)——开拍前请求渲染器丢弃 TAA 历史并重置曝光,从已知相位开始累积。
更深层的确定性来自lockstep 模式。引擎自身的 rAF 循环会在测试框架做各种等待往返时继续推进帧数,导致"快门按下时的帧索引"每次漂移 10–20 帧,所有与帧索引锁相的效果(TAA 抖动、噪声旋转frame % 64、曝光自适应)都会解算出不同结果。修复方案写在 src/dev/shots.js 的注释里:lockstep 下引擎从不自己调度帧,帧只发生在测试框架显式调用的__PUMP__(n)内——精确推进 n 帧,快门时刻的帧索引成为常量,截图期间仿真完全静止。
配套的还有固定步长时钟:capture 模式下每帧 dt 被强制为精确的 1000/60 毫秒,与启动路径无关(见 src/dev/shots.js)。再加上 ARCHITECTURE.md 的硬性规则——游戏逻辑与视觉中禁止使用Math.random(),必须用确定性随机源 src/core/rng.js——采集可复现性在代码层面被制度性地兜住。
4. imagediff.mjs:逐像素的回归门
tools/imagediff.mjs 用 pngjs 逐字节读取两组 PNG,按通道取最大差值统计:
tol(默认 0):每通道 0–255 差值容差,低于它的像素视为未变化;- 输出指标:每张截图的
changedPct(变化像素百分比)、maxDelta(最大通道差)、meanDelta(平均通道差); - 退出码即门禁:tools/imagediff.mjs 中,只要所有镜头
changedPct为 0(或整体在明确论证过的 epsilon 内:变化 <0.05% 且maxDelta ≤ max(2, tol))才返回 0,否则退出码为 1——可直接接入 CI 流水线; --write-diff:把发生变化的像素以亮品红色高亮、原图压暗输出.diff.png,方便肉眼定位差异区域。
项目 README 记录了它的实战价值:一次大规模优化被约束为"零视觉变化",且这一约束由imagediff.mjs强制执行而非口头声明——交付构建在全部 11 张镜头上与优化前参考位图一致。
5. 快速上手:三步建立你自己的截图门
环境要求:Node.js + 依赖中的playwright、pngjs、vite(见 package.json)。
第一步:安装依赖
git clone https://gitcode.com/gh_mirrors/cl/Claude-of-Duty cd Claude-of-Duty npm install npx playwright install chromium第二步:采集基线
node tools/baseline.mjs --out=shots/base --port=8080工具会自动拉起 vite(若端口未占用)、以 1920×1080 视口、deviceScaleFactor=1、sRGB 色彩配置启动无头 Chromium,逐一采集 11 张 PNG 并写出report.json。常用参数:
--shots=hero,detail只拍子集,迭代更快;--settle=90调整收敛帧数;--query=prewarm=0向所有镜头 URL 追加查询参数,可做 A/B 对照(例如对比预热的开/关)。
第三步:改动代码后做像素比对
# 重新拍一组(例如优化后的构建) node tools/baseline.mjs --out=shots/opt --port=8080 # 逐像素回归门 node tools/imagediff.mjs --a=shots/base --b=shots/opt [--tol=1] [--write-diff]退出码为 0 即"视觉无变化"通过门禁;为 1 时终端 JSON 会指出worst(最差镜头)与每张镜头的统计行,加--write-diff后还能直接看高亮差异图。
6. 实践要点与常见陷阱 🎯
- 不要拿
shotset.mjs的结果做回归比对。它快,但同页连拍导致时间状态泄漏,两次运行 10/11 张不同。它只适合人工评审。 - 锁相帧索引是复现性的核心。TAA 抖动相位、GTAO/SSR/接触阴影噪声旋转都按
frame % 64走,帧索引差 1 帧,输出就差一截。这正是 src/dev/shots.js 中 lockstep 模式存在的原因。 - 预热(pre-warm)也会"看起来"改变画面:它消耗约 1.4 s 墙钟时间,若系统动画依赖
performance.now()而非引擎时钟,启动时长变化就会平移一切输出。项目修复方法是让相关子系统改用引擎时钟,配合 src/core/prewarm.js 的着色器预热实现"可证明的像素中性"。 - 跨机器位图一致有前提:同一 GPU 后端与驱动下可 bit-identical;换显卡驱动后可能出现系统性微小差异,此时应重新采集基线,并用
--tol明确论证 epsilon。 - 报告即诊断:tools/baseline.mjs 会把页面
pageerror与 console error 收进report.json并影响退出码——截图失败不会静默通过。
7. 延伸阅读
- 回归门背景与设计权衡:README.md 中 "Tooling" 与 "Performance" 章节
- 引擎契约与确定性硬规则(禁用
Math.random()):ARCHITECTURE.md - 镜头定义与 lockstep 实现:src/dev/shots.js
- 单镜头快速截图:tools/capture.mjs
- 确定性随机源:src/core/rng.js
- 着色器预热的像素中性约束:src/core/prewarm.js
一句话总结:先让截图可复现(baseline 的隔离 + 锁相 + 固定帧预算),再谈逐像素比对(imagediff 的门禁语义)——这就是 Claude-of-Duty 把"优化不许改画面"从口号变成可执行退出码的完整方法。
【免费下载链接】Claude-of-DutyA Call of Duty-quality FPS in Three.js, built from a single prompt.项目地址: https://gitcode.com/gh_mirrors/cl/Claude-of-Duty
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考