一键开通华为云码道 CodeArts 代码智能体:https://developer.huaweicloud.com/codeartsco.html?source=dmzntgwatomgit1&sourcead=dmzntgwatomgithd
摘要:本文是华为云码道 AI 编程挑战赛第 5 期收官期的参赛实测记录。我用 CodeArts 代码智能体独立开发了一款纯浏览器 Canvas 策略小游戏「熵减特勤局」:在网格化机房部署守护进程,抵御熵潮(错误数据流)对核心服务的侵蚀。技术上是零运行时依赖的 TypeScript + 自研 ECS + 固定步长确定性模拟,核心亮点是回放即测试:一整局游戏就是一个回归测试用例。文章完整记录架构设计(ECS + 双循环 + 关卡即数据)、AI 擅长与不擅长的分层实测、功能演示与踩坑记录,源码已在 AtomGit 开源。这是五期系列的最后一篇:Web 全栈、小程序、鸿蒙原生、桌面应用之后,第五种端形态压轴。
技术栈版本:码道 CodeArts 代码智能体(2026-09 挑战赛版本)| TypeScript strict(运行时零 npm 依赖)| Canvas 2D | Vite(仅构建)| Vitest + 确定性回放回归 | 实测时间: 2026-10
一、项目背景:压轴为什么选游戏,以及一个运维皮肤
五期的最后一期,我选了与前四期反差最大的品类。理由有三:
- 游戏是五类赛题里工程差距最大的品类。多数 AI 一次性生成的游戏 demo,逻辑糊在渲染循环里、魔法数字满天飞、不可测试,工程化叙事在这里几乎没有竞争;
- 确定性模拟能把「测试」带进游戏。塔防玩法(确定性、回合结构清晰)恰好是最适合确定性模拟的品类,回放一整局就能做回归测试,这是其他参赛作品很难有的代码质量证据;
- 题材可以致敬我的真实身份。世界观借用了我的日常:守护进程、熵潮、核心服务。第 2 期做运维巡检工具时 defend 的那些服务器,在这期变成了一款游戏要守护的机房,这个「彩蛋式致敬」会在系列收官时成为叙事闭环。
先划清边界:熵减特勤局与第 2 期巡哨是完全不同的产品——一个是分析工具(小程序),一个是游戏(浏览器 Canvas),零代码关系,只有世界观的呼应。这也是对「旧作品翻新」红线最干净的回应:连运维皮肤都是新的创作。
1.1 作品功能一览
| 模块 | 功能 |
|---|---|
| ECS 引擎 | 实体-组件-系统架构,逻辑零渲染依赖,可无头运行 |
| 双循环 | 固定 60Hz 步长模拟 + 可变步长渲染插值 + 追帧上限 |
| 玩法 | 网格部署改变流场,4 种守护进程 × 4 种错误数据包 × 6 关 |
| 回放系统 | 指令流 + 种子完整重放任意一局,一局 = 一个测试用例 |
| 失败复盘 | 防线击穿时间轴:定位核心服务首次被击穿的 tick 与位置 |
| 本地存档 | localStorage 存关卡进度与最佳评分(唯一存储) |
技术栈:TypeScript strict(运行时零 npm 依赖)+ Canvas 2D + 自研 ECS + Vitest。构建用 Vite(devDependency),产物纯静态,任意静态服务器可玩。
二、架构设计:三个核心决策
老规矩,架构决策在人、实现在 AI。本期三个决策全部指向同一个目标:让模拟可测试。
2.1 决策一:ECS——实体是 ID,行为在 System,数据在 Component
直觉写法是每个「塔/敌人/子弹」一个 class,互相持有引用。但塔防的实体关系是网状的:子弹找敌人、光环影响塔、干扰体禁用塔——class 网会迅速纠缠成不可测的泥球。动手前比过两条路:
| 方案 | 关系解耦 | 可测性 | 扩展新实体 | 结论 |
|---|---|---|---|---|
| OOP 继承树(Tower/Enemy 基类) | 差:交叉引用成网 | 差:依赖完整对象图 | 改继承树 | 否决 |
| ECS(组件 + 系统) | 好:系统按需查询组件,无相互持有 | 好:System = (组件集) → 副作用 | 新组件 + 新系统 | ✅ 采用 |
// src/engine/World.ts(节选)——ECS 内核,零游戏知识/** * 实体销毁是惰性的(标记后统一回收),query 不返回已销毁实体。 * 组件存储 Map<EntityId, T>:300 实体上限下不追求极致性能,可测性优先。 */exportclassWorld{registerComponent<T>(name:string):void;createEntity():EntityId;addComponent<T>(id:EntityId,name:string,data:T):void;getComponent<T>(id:EntityId,name:string):T|undefined;query(...names:string[]):EntityId[];// 含全部指定组件的存活实体destroyEntity(id:EntityId):void;}分层边界是一条硬纪律:sim/**不 importrender/**与任何 DOM API——同一份模拟状态,渲染层只是读取者。这让「无头跑一整局」成为可能,也是回放测试的地基。
2.2 决策二:固定步长模拟 + 可变步长渲染 + 追帧上限
requestAnimationFrame一步一模拟的写法,在 144Hz 屏上游戏快 2.4 倍、掉帧时变慢、切后台回来瞬移。三种常见方案摆在一起比:
| 方案 | 跨帧率一致性 | 切后台表现 | 结论 |
|---|---|---|---|
| rAF 每帧模拟一步 | 差:144Hz 屏快 2.4 倍 | 瞬移 | 否决 |
| 可变步长(按 dt 模拟) | 中:浮点 dt 累计误差,不可回放 | dt 巨大时数值爆炸 | 否决 |
| 固定步长 + 累加器 + 插值 + 追帧上限 | 好:模拟与帧率解耦 | 补步上限 + 超限快进 | ✅ 采用 |
// src/engine/loop.ts(节选)——纯逻辑部分,可单测exportconstFIXED_DT_MS=1000/60;/** * 模拟恒定 1/60s 步长;渲染每帧用 alpha 做状态插值; * 后台返回最多补 10 步,超出按快进丢弃积压(显性架构决策,写进注释)。 * 【确定性纪律】sim 层禁止读时钟——performance.now 只允许出现在 rAF 壳一处。 */exportfunctionplanTick(accumulatorMs:number,maxCatchupSteps=10):LoopTick{constraw=Math.floor(accumulatorMs/FIXED_DT_MS);constsimSteps=Math.min(raw,maxCatchupSteps);constcatchingUp=raw>maxCatchupSteps;constconsumed=catchingUp?0:raw*FIXED_DT_MS;// 快进时清空积压return{simSteps,catchingUp,interpolationAlpha:(accumulatorMs-consumed)/FIXED_DT_MS,};}2.3 决策三:确定性四纪律——回放即测试的地基
「同一份指令流喂给同一版本引擎,必得同一结果」——这个性质不会自然发生,靠四条纪律硬性保证,写进了 README 与架构文档:
| 纪律 | 实现 | 破坏的后果 |
|---|---|---|
一切随机走Rng | 种子化实现,sim 层禁Math.random(ESLint 自定义规则 + CI) | 回放漂移 |
| 系统执行顺序固定 | 调度器注册序即执行序,禁止动态排序 | 回放漂移 |
| 模拟层禁用时钟 | 禁Date.now/performance.now直调 | 回放漂移 |
| 整数结算 | 资源与伤害全整数,禁浮点累计 | 跨次运行结果不一致 |
这四条纪律的直接产出是本期的招牌能力回放即测试:预录三条标准局(胜利 / 失败 / 极限翻盘),CI 里无头回放并断言终局状态哈希。一整局游戏的运行正确性,被压缩成一条npx vitest run src/replay的输出。
三、用 CodeArts 开发:AI 擅长与不擅长的分层
3.1 开发前的准备
打开 CodeArts 之前的人肉准备:AtomGit 公开仓库entropy-rangers(LICENSE + README 骨架)、Vite + TS 模板跑通,以及本期最重要的设计底稿——三个数值表初稿(塔/敌/关卡的 JSON)。数值不用精确,先「能跑起来」:平衡调整全部发生在回放工具上,这是「调参不改代码」工作流的起点。
七天里程碑:D1 ECS 内核与双循环 → D2 网格流场与最小可玩 → D3 回放与全部单位 → D4 渲染与交互 → D5 关卡内容与收口 → D6 文章 → D7 投稿(收官期无平移空间,D5 即达可投状态)。
3.2 AI 擅长什么:结构良好的纯逻辑
五期下来最稳定的一个观察:AI 对「输入类型明确、输出类型明确、规则可穷举」的模块生成质量极高。本期最典型的是流场寻路——话术写死「BFS 从核心格出发、塔格视为临时阻挡、被围死时自毁、遍历顺序按 idx 升序(确定性)」,AI 一次生成即通过全部 19 个用例,包括两条我最担心的:完全围死触发自毁、同输入两次计算逐位一致。
(D2 话术节选——把「确定性」写成可验收的断言) …… ## 三、确定性要求 BFS 遍历顺序固定(按 idx 升序入队),禁止任何哈希序依赖。 ## 四、测试 ≥ 15 用例 - 直线路径 / 绕开单个阻挡 / 完全围死触发自毁 - 塔部署前后流场变化(某格方向被改写) - 同输入两次计算结果逐位一致(确定性断言) ……3.3 AI 不擅长什么:自觉维持约束
AI 不会主动维持你没盯着的全局约束。D3 期间我抓到两次「确定性破坏」:一次是分裂体敌人的死亡分裂用了Math.random(AI 觉得「就一个随机数」),一次是某个系统里顺手写了Date.now()做耗时统计。两处都能跑、肉眼看不出版本差异,但回放立刻漂移。
处理方式固化为一条修错话术的追加约束:
【约束】不得通过删减确定性断言/放宽回放比对来让测试通过。 修复方向只能是:把随机改经注入的 Rng、把时间来源改为注入。 修复后回放回归必须全绿。这段「AI 生成能跑但不满足确定性 → 回放测试抓住 → 重构收敛」的完整过程,是本期博文最有说服力的素材。
四、功能演示:从部署到复盘的完整闭环
构建产物纯静态:JS 81.78 kB(gzip 25.85 kB)+ CSS 18.73 kB(Vite 实测),npx serve dist打开即玩。
4.1 部署改变流场——玩法核心
选中守护进程进入部署模式:悬停格高亮(合法绿/非法红)+ 射程圈预览。部署 firewall(防火墙)的瞬间,整个熵潮流场被改写——敌人绕行的路径变化在画面上直接可见。这是「部署改变流场,流场决定战局」这个核心策略的具象化。
4.2 UI 优化后的在线效果(v0.0.2)
首轮验收后针对视觉与信息层级做了一轮 UI 优化:开局引导、状态侧栏、胜利结算全面重排,操作提示与胜负期望直接内嵌在面板中。以下为优化后的在线实测截图(可直接体验:https://entropy-rangers.yangchina.cn,欢迎大家体验)。
4.3 回放一整局——「一整局就是一个测试用例」
手玩一局 → 导出回放 JSON(指令流 + 种子 + 数值表哈希)→ 无头重放,结果与实际完全一致。这套机制在仓库里的形态是一条测试命令:
$ npx vitest run src/replay ✓ 回放回归(防退化)(3) ✓ standard-win:outcome=victory,stateHash=581952af ✓ standard-lose:outcome=defeat,stateHash=3f42ed1c ✓ standard-close:outcome=victory,stateHash=604d2feb ✓ 回放确定性(3) ✓ 同一 ReplayData 播放两次 stateHash 一致 ✓ 指令乱序注入(tick 逆序)与顺序播放结果一致 ✓ 空指令回放(纯防守)可跑完 ✓ balanceHash 校验(3) ✓ balanceHash 不匹配 → 抛 BalanceMismatchError Tests18passed(18)这份输出既是一条回归测试(任何 sim 代码变更破坏确定性或平衡,立刻红),也是游戏能完整跑通的运行证据,还是 Bug 复现协议(玩家导出指令流附在 issue 里即可精确复现)。
4.4 失败复盘——防线击穿时间轴
失败之后不止一屏「Game Over」:自动分析定位核心服务首次被击穿的 tick 与位置,时间轴标注三个关键点(首次受击 / 完整度跌破 50% / 终局),点击可跳转回放。最直观的一条提示长这样:「01:35 熵潮自 C7 突破——该通道无拦截单位」。
五、踩坑记录:AI 写对了九成,剩下一成靠人
记录开发过程中真实遇到的问题。
坑一:切后台回来瞬移,积压帧一次全补
现象:标签页切走几分钟后切回,游戏画面瞬间跳到「未来」——敌人位置、波次全部快进,体验像录像带快进卡带。
根因:rAF 在标签页不可见时停摆,visibilitychange 恢复后累积了几千毫秒的 delta,planTick的追帧上限没生效——AI 第一版把「补帧」写成了「补到追上为止」,上限参数形同虚设。
解决:追帧上限 10 步 + 超限快进丢弃积压(planTick已有此设计,rAF 壳没接对);恢复时同时清一次累加器。修复后切后台回来平滑继续,planTick的 18 个边界用例守护住这个行为。
教训:纯逻辑层设计了防御(上限),薄壳层没接上等于没有;在「可单测的纯函数 + 不可单测的薄壳」的边界处,验收必须跨层走一遍。
坑二:浮点累计让回放第一次变红
现象:D3 回放回归首次接入时,同一条标准局两次回放的终局 stateHash 不一致——确定性真的破了。
根因:经济系统的波次奖励结算有一处credits * 0.7(出售返还),浮点乘法在两次运行中的舍入路径不同(累计顺序差异),整数纪律被一行代码破坏。这正是四纪律里「整数结算」要防的事,但它是在写实现时靠人盯的,ESLint 规则当时还没加。
解决:返还改为(credits * 7) / 10的整数运算(先乘后除),补 ESLint 规则禁止 sim 层浮点运算于金额类变量,回放回归转绿。这个坑反过来验证了回放测试的灵敏度——一行浮点都能被 stateHash 抓住。
教训:确定性不是设计出来的,是测出来的——四纪律是必要条件,回放回归才是充分条件。
坑三:Canvas 合批踩坑,实体一多掉帧
现象:第 5 关后期(接近 300 实体上限)帧率明显下跌,性能面板显示瓶颈在绘制——每个实体独立 save/restore + 状态切换,draw call 数量失控。
根因:AI 的渲染实现是「每实体一次完整绘制流程」的直觉写法,没有利用同类型实体可合并的特性;静态网格层也没做离屏预渲染,每帧重画。
解决:两层优化——静态层(网格/阻挡/流场箭头)移入 OffscreenCanvas 仅在变化时重绘;动态实体按类型分组合批绘制,血条只在低于 50% 时渲染。优化后第 5 关后期帧率恢复平稳,draw call 数量从「每实体一次」降为「每类型一次」。
教训:性能优化的前提是「每帧绘制成本」有数:先录火焰图确认瓶颈在绘制而非模拟,再动手;模拟层本就经过了无头一局耗时的性能基线验证,瓶颈果然不在那里。
5.4 小结
三个坑依旧符合系列规律,且本期多了一层结构性的收获:回放测试把「AI 改坏了什么」从人工回归变成了机器断言。四纪律 + 回放回归的组合,让「AI 大量生成代码」这件事在游戏品类里第一次有了自动化的安全网。
质量收口的实测数据(本地实测):
- vitest 全绿:engine 27 / sim 99 / replay 18 用例,共 188 个全绿;行覆盖 sim 87.12%、replay 82.51%、engine 79.53%(–coverage 实测,薄壳层 render/ui 不计入核心口径);
- 回放回归 3 条标准局全绿(胜利 / 失败 / 极限翻盘各一条);
- 确定性禁用规则命中 0;运行时 dependencies 为空;
- 构建产物 JS 81.78 kB(gzip 25.85 kB)+ CSS 18.73 kB;3000 步无头一局模拟 20.46 ms(性能基线用例实测)。
六、提效数据:AI 辅助开发到底快了多少
本次开发的提效记录(基于开发日志的实际耗时统计,为个人单项目样本,仅供参考)。
| 开发阶段 | 纯手写预估 | AI 辅助实际 | 提效来源 |
|---|---|---|---|
| ECS 内核 + 双循环 + 单测 | 约 2 天 | 约 3 小时 | 结构清晰的纯逻辑是 AI 最强区 |
| 网格流场 + 最小可玩闭环 | 约 2.5 天 | 约 4.5 小时 | 算法 + 边界穷举,AI 一次过审率高 |
| 回放系统 + 全部单位 | 约 2 天 | 约 4 小时 | 数据驱动设计让实现即翻译 |
| Canvas 渲染 + 部署交互 | 约 2 天 | 约 5 小时 | AI 写结构,手感调优靠人 |
| 六关调参 + 复盘 + 收口 | 约 1.5 天 | 约 5 小时 | 回放工具让调参可验证 |
| 合计 | 约 10 天 | 约 21.5 小时 | 按 D1~D7 七天收官期排布,开发+调参约 3 天,余量为关卡内容、UI 优化、复盘与成稿 |
开发全程在 CodeArts 的同一个 Agent 会话中连续进行(无跨会话重开),上下文得以延续。
两点说明:一是「纯手写预估」基于经验估算,存在主观误差;二是数值平衡的时间没有省——AI 生成数值草案后,「回放验证 → 人工微调」的循环跑了三轮才达到「直觉部署能险胜」的手感,这部分是游戏品类特有、AI 无法替代的时间。整体看,工期压缩到传统方式的四分之一左右;确定性回放带来的「改代码不心虚」,是五期以来体感最明显的质量护栏。
七、开源地址与运行方式
项目已开源,包含可运行源代码、README(作品介绍)与开源协议:
项目仓库:https://atomgit.com/dickeryang/entropy-rangers.git(AtomGit 公开仓库)
在线演示:https://entropy-rangers.yangchina.cn (打开即玩,欢迎大家体验)
本地运行只需两步(零后端、零运行时依赖):
# 1. 克隆并构建gitclone https://atomgit.com/dickeryang/entropy-rangers.gitcdentropy-rangersnpminstall&&npmrun build# 2. 任意静态服务器打开(或 README 提供的在线演示链接)npx serve dist# 附加:回放回归——"游戏能完整运行"的机器证明npmtest# 无头回放 3 条标准局,断言终局状态哈希仓库内附docs/architecture.md(ECS 分层图 + 双循环时序图 + 回放数据流图 + 被否决方案论证),replays/standard-*.ts三条标准局随仓库分发(standard-win / standard-lose / standard-close,balanceHash 锁定数值表版本)。项目采用 Apache-2.0 协议开源。
八、总结与平台使用建议
8.1 结论
五期收官,第五种端形态:在「人定架构与验收、AI 做实现」的分工下,零依赖 Canvas 小游戏一周完成。
本期给系列补上的最后一块拼图是自动化的质量护栏:前四期的验收靠人工执行(curl / 点页面 / 跑模拟器),本期的回放回归让「AI 改坏了什么」变成机器断言。若说前四期验证了方法的普适性,这期验证了它的可自动化上限。
8.2 给平台的建议
五期连续使用下来,对 CodeArts 代码智能体的最终建议(如实反馈,供官方参考):
- 项目级约定持久化(连续四期提出):模块边界、确定性纪律这类项目级约束若能持久化,重复投喂的成本可以完全省掉——这是五期体验中最大的提效空间;
- 约束违反的主动检测:本项目用 ESLint 自定义规则守确定性,若 Agent 能在生成时感知「该仓库禁用 Math.random」级别的项目规则,这类返工可以从源头消失;
- 长会话的跨天上下文:五期均为一周工程,跨天重投架构约束的损耗真实存在,希望改善。
8.3 五期收官:一套方法的五种验证
| 期次 | 作品 | 端形态 | 技术主线 | 代码分载体 |
|---|---|---|---|---|
| 第 1 期 | ArticlePilot 内容分发管理台 | Web 全栈 | Spring Boot 3 + Vue 3 + MySQL | 状态机与适配器 |
| 第 2 期 | 巡哨 PatrolOps 运维巡检分析 | 微信小程序 | uni-app + 自研 DSL | DSL 解析器 |
| 第 3 期 | 刻循 Kefocus 专注计时 | 鸿蒙原生 | ArkTS + ArkUI | 时基校正 + 状态恢复 |
| 第 4 期 | 仓衡 RepoBalance 仓库体检 | 桌面应用 | Rust + Tauri 2 | Rust 检查器规则 |
| 第 5 期 | 熵减特勤局 塔防小游戏 | 浏览器游戏 | 零依赖 TS + ECS + Canvas | 确定性回放 |
五期下来,端形态换了五种,技术栈互不重叠,但方法论始终是那几条:本地优先、内核与壳分离、纯函数可穷测、关键取舍显性化、演示数据真实不编造。每一期都各自验证了其中适用的部分,这就是这个系列攒下的技术资产。
感谢华为云码道 AI 编程挑战赛的五周。如果你也在用 AI 做完整的工程而不只是 demo,欢迎在评论区交流。
觉得有用就点个赞,有疑问或不同看法欢迎评论区交流。