华为云码道 CodeArts 实测:让 AI 用纯 TypeScript 写一个确定性回放的塔防小游戏「熵减特勤局」

一键开通华为云码道 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

一、项目背景:压轴为什么选游戏,以及一个运维皮肤

五期的最后一期,我选了与前四期反差最大的品类。理由有三:

  1. 游戏是五类赛题里工程差距最大的品类。多数 AI 一次性生成的游戏 demo,逻辑糊在渲染循环里、魔法数字满天飞、不可测试,工程化叙事在这里几乎没有竞争;
  2. 确定性模拟能把「测试」带进游戏。塔防玩法(确定性、回合结构清晰)恰好是最适合确定性模拟的品类,回放一整局就能做回归测试,这是其他参赛作品很难有的代码质量证据;
  3. 题材可以致敬我的真实身份。世界观借用了我的日常:守护进程、熵潮、核心服务。第 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 = (组件集) → 副作用 新组件 + 新系统 ✅ 采用
typescript 复制代码
// src/engine/World.ts(节选)------ECS 内核,零游戏知识
/**
 * 实体销毁是惰性的(标记后统一回收),query 不返回已销毁实体。
 * 组件存储 Map<EntityId, T>:300 实体上限下不追求极致性能,可测性优先。
 */
export class World {
  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/** 不 import render/** 与任何 DOM API ------同一份模拟状态,渲染层只是读取者。这让「无头跑一整局」成为可能,也是回放测试的地基。

2.2 决策二:固定步长模拟 + 可变步长渲染 + 追帧上限

requestAnimationFrame 一步一模拟的写法,在 144Hz 屏上游戏快 2.4 倍、掉帧时变慢、切后台回来瞬移。三种常见方案摆在一起比:

方案 跨帧率一致性 切后台表现 结论
rAF 每帧模拟一步 差:144Hz 屏快 2.4 倍 瞬移 否决
可变步长(按 dt 模拟) 中:浮点 dt 累计误差,不可回放 dt 巨大时数值爆炸 否决
固定步长 + 累加器 + 插值 + 追帧上限 好:模拟与帧率解耦 补步上限 + 超限快进 ✅ 采用
typescript 复制代码
// src/engine/loop.ts(节选)------纯逻辑部分,可单测
export const FIXED_DT_MS = 1000 / 60;

/**
 * 模拟恒定 1/60s 步长;渲染每帧用 alpha 做状态插值;
 * 后台返回最多补 10 步,超出按快进丢弃积压(显性架构决策,写进注释)。
 * 【确定性纪律】sim 层禁止读时钟------performance.now 只允许出现在 rAF 壳一处。
 */
export function planTick(accumulatorMs: number, maxCatchupSteps = 10): LoopTick {
  const raw = Math.floor(accumulatorMs / FIXED_DT_MS);
  const simSteps = Math.min(raw, maxCatchupSteps);
  const catchingUp = raw > maxCatchupSteps;
  const consumed = 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 个用例,包括两条我最担心的:完全围死触发自毁、同输入两次计算逐位一致。

text 复制代码
(D2 话术节选------把「确定性」写成可验收的断言)
......
## 三、确定性要求
BFS 遍历顺序固定(按 idx 升序入队),禁止任何哈希序依赖。

## 四、测试 ≥ 15 用例
- 直线路径 / 绕开单个阻挡 / 完全围死触发自毁
- 塔部署前后流场变化(某格方向被改写)
- 同输入两次计算结果逐位一致(确定性断言)
......

3.3 AI 不擅长什么:自觉维持约束

AI 不会主动维持你没盯着的全局约束。D3 期间我抓到两次「确定性破坏」:一次是分裂体敌人的死亡分裂用了 Math.random(AI 觉得「就一个随机数」),一次是某个系统里顺手写了 Date.now() 做耗时统计。两处都能跑、肉眼看不出版本差异,但回放立刻漂移。

处理方式固化为一条修错话术的追加约束:

text 复制代码
【约束】不得通过删减确定性断言/放宽回放比对来让测试通过。
修复方向只能是:把随机改经注入的 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(指令流 + 种子 + 数值表哈希)→ 无头重放,结果与实际完全一致。这套机制在仓库里的形态是一条测试命令:

bash 复制代码
$ 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

Tests  18 passed (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 (打开即玩,欢迎大家体验)

本地运行只需两步(零后端、零运行时依赖):

bash 复制代码
# 1. 克隆并构建
git clone https://atomgit.com/dickeryang/entropy-rangers.git
cd entropy-rangers
npm install && npm run build

# 2. 任意静态服务器打开(或 README 提供的在线演示链接)
npx serve dist

# 附加:回放回归------"游戏能完整运行"的机器证明
npm test        # 无头回放 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 代码智能体的最终建议(如实反馈,供官方参考):

  1. 项目级约定持久化(连续四期提出):模块边界、确定性纪律这类项目级约束若能持久化,重复投喂的成本可以完全省掉------这是五期体验中最大的提效空间;
  2. 约束违反的主动检测:本项目用 ESLint 自定义规则守确定性,若 Agent 能在生成时感知「该仓库禁用 Math.random」级别的项目规则,这类返工可以从源头消失;
  3. 长会话的跨天上下文:五期均为一周工程,跨天重投架构约束的损耗真实存在,希望改善。

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,欢迎在评论区交流。


觉得有用就点个赞,有疑问或不同看法欢迎评论区交流。

相关推荐
Rosanci17 小时前
AI 写完不等于能用:以“验收标准 + 仓库检查”为中心的 AI 编程质控流程
人工智能·ai 编程
奈落2418 小时前
独立的AI编程助手正在消失?从文心快码并入百度搭子、Qwen Code转向CLI,看编程工具正被吸收进更大的智能体平台
百度·agent·ai编程·ai 编程
mbshqqb19 小时前
TypeScript基础教程之同步生成器
前端·javascript·typescript
林间码客1 天前
SDD 是什么?一文分清软件设计文档与规范驱动开发
软件设计文档·ai 编程·sdd·规范驱动开发
濮水大叔2 天前
Cabloy 双引擎架构详解:Vona 与 Zova 是如何协作的
typescript·node.js·全栈
前端大斗师2 天前
「大于 1000」把 1000 元那单也算进去了:我在 Vue3 订单页对了 10 句话
前端·人工智能·typescript·大模型·原力计划
GlobalAIagent2 天前
外贸站上线前的技术自查清单:漏一项询盘可能消失
google·typescript·ar·go语言·seo
半糖程序员2 天前
从零构建 Agent(11):压缩过长的上下文
typescript·agent
whi2 天前
一个小工具,解决了一个困扰 Vue 开发者多年的类型检查难题
vue.js·typescript