过去一年里,让 Claude Code 或 Codex 写游戏代码,已经从新鲜尝试变成了日常习惯。重构、整套系统的实现、着色器和玩法的调整,智能体产出 diff 的速度不是人能比的。现在的问题不再是这个 diff 能不能跑,而是它是不是还一样快。
AI 写代码时代的瓶颈
写代码的工夫一降下来,瓶颈就从「写」挪到了「确认」。功能能不能跑,测试会自己给出答案。可这段代码有没有把真机帧时间悄悄抬高几毫秒,一片绿的测试是不会告诉你的。
一旦智能体一天提交几十个 diff,逐个用手去剖析就不现实了。渲染线程上不知不觉多了一次分配,更新循环的计算量悄悄变差,GC 触发得更频繁。这些都能通过单元测试,也躲得过肉眼的代码评审,直到放上真机才作为体感上的劣化显现出来。
用智能体在写这一头提上来的速度,把确认这道工序变成了新的瓶颈。
所以不能再把性能确认留成一道靠评审者的警觉、靠老手直觉的工序。它得变成一个和智能体同速运转、由机器给出的判定。
把回归门禁放到机器这一侧
要做的事很简单。给每个构建收集性能遥测,和上一个已知良好的构建相比,一旦超过阈值变差,就让 CI 挂掉。不是由人盯着数字来判断,而是由机器判定是否发生回归,并作为流水线的通过/失败返回。
Framedash 的 framedash perf-diff 会给出两个构建在帧时间、内存、GPU 时间上的 P50 和 P95。加上 --fail-on-regression,当候选构建的 P50 超过阈值变差时,它会以退出码 1 挂掉(门禁按 P50 的比较来判定,P95 作为参考一并列出,不参与判定)。
framedash perf-diff --baseline "$BASE_SHA" --candidate "$GITHUB_SHA" \
--threshold 5 --fail-on-regression
比较的对象除了帧时间、内存、GPU 时间,还包括地图加载时间(load_time_ms)和磁盘 IO(io.*),都按越小越好的指标处理。你可以用 --metric 把门禁收窄到某个指标,或者用 --map 和 --platform 限定比较范围。
这里的关键是作为比较单位的 build_id。如果一个数字属于哪个构建都定不下来,构建之间的比较本身就无从谈起。
工作流全貌
我们来跟一遍智能体写的代码走到这道回归门禁前的流程。
门禁一挂,智能体就自己读取遥测数据来缩小原因范围。
在 CI 里把这条流程一把跑完的是 framedash run-profile-test。这条命令会写出自动会话用的 FRAMEDASH_* 环境变量,启动你指定的剖析构建,等它的遥测被收集进来,再对着基线跑 perf-diff 门禁。
framedash run-profile-test \
--command "./Build/Game.exe -nullrhi -ExecCmds='Automation RunTest Perf'" \
--scenario nightly --api-key-file ci-read.key \
--baseline "$BASE_SHA" --threshold 5 --fail-on-regression
被启动的游戏这一侧,在自动测试入口处调用一次自动会话 API。当 SDK 调用 BeginAutomatedSessionFromEnvironment(),它会读取 run-profile-test 写出的 FRAMEDASH_BUILD_ID / FRAMEDASH_GIT_BRANCH / FRAMEDASH_GIT_COMMIT / FRAMEDASH_TEST_SCENARIO,此后每个事件都自动带上 CI 构建以及它的分支、提交、场景。不需要逐个事件打标签的代码。
TelemetrySDK.Instance.BeginAutomatedSessionFromEnvironment();
// ... 运行剖析场景 ...
TelemetrySDK.Instance.EndAutomatedSession();
这里有一个运维上的注意点:短构建或无头运行可能在 SDK 的定期刷新之前就退出。请让运行一直存活到遥测真正发出为止(各 SDK 的 CI 与无头运行指南里有做法);随后 run-profile-test 会等遥测被收集进来,再执行门禁。
build_id 记为顶层字段,分支、提交、场景则挂在 ci.branch / ci.commit / ci.scenario 属性上。这样,智能体提交的 build_id 就直接成了回归比较的候选。
只有密钥的区分要留意。门禁用 analytics:read 密钥读取遥测,被启动的游戏则用另一把 events:write 采集密钥发送遥测。为避免名字冲突,门禁那把密钥用 --api-key-file 传,而不是 FRAMEDASH_API_KEY 环境变量,让 FRAMEDASH_API_KEY 留给游戏当采集密钥。
门禁挂了,该看哪里
perf-diff 挂掉时,你能知道的只到哪个指标变差了、差了多少。在哪里变差,光凭这个数字看不出来。
这时候用的是性能热力图。它把 FPS、帧时间、GPU 时间、内存占用按格子叠加在你的游戏地图上。你可以按设备或构建配置筛选,于是能把回归构建里哪张地图哪一带变重了,定位成地图上的一个位置。
地图上的分布,是由带位置的事件画出来的。当你的剖析场景把玩家位置连同一个已注册的地图 ID 一起上报,SDK 就会给这些事件自动附上相机朝向(偏航和俯仰)。有了这些,「build 1042 的 desert_ruins,朝北看的一个角落里 P95 会跳」这种粒度你就能追下去。回归就此成了可复现的调查对象。
让智能体自己读取遥测
上面这条流程里还剩一处有人:打开热力图、缩小原因、写下修复代码。
这道工序也能交给智能体。Framedash 的 MCP 服务器把针对遥测的只读工具和资源开放给 LLM。热力图网格数据、仪表盘 KPI,乃至原始 SQL 查询,智能体都能从一句自然语言指令直接拉出来。
claude mcp add framedash \
-e FRAMEDASH_API_KEY=fd_xxx \
-e FRAMEDASH_PROJECT_ID=your-project-uuid \
-- npx -y @framedash/mcp-server
这样就能搭出一个闭环:让制造了回归的智能体自己去读它弄坏的那处,再把它修好。用 get_heatmap 俯瞰地图上变重的地方,用按 build_id 过滤的 query 把候选构建的热点单独摘出来,形成对原因的判断,然后改代码。工具是只读的,所以智能体不会改写或删除你的遥测。改好的代码有没有真正消除回归,会在下一个构建里由同一道 CI 门禁再客观判定一次。
判定交给机器,读原因交给智能体。留给人的,是阈值划在哪里,以及最后看一眼修法是否稳妥。
如何开始
你需要做的,只是给游戏接入 SDK、从 CI 传入 build_id,再往流水线里加一道门禁。SDK 接入几行就够,支持的引擎是 Unity、UE5、Godot (C#)。
claude plugin marketplace add crane-valley/framedash-claude-plugin
claude plugin install framedash@framedash