← 返回全部文章

发售周:把 CandyRush 送上 Steam

发布于:
candyrushci-cdevidenceperformance

CandyRush 是一款确定性的俯视角幸存者类 roguelite,于 2026 年 10 月 31 日在 Steam 上线。本文讲的是这个日期背后的 发布工程:构建如何到达 Steam、发售周末的部署是什么样子、我选择不做哪些事,以及 接下来做什么。

这里没有销量、愿望单或玩家数量;我没有可以报告的数字。

发布了什么

以下信息取自撰写当天的 Steam 商店 API:

发售日期2026 年 10 月 31 日
价格$7.99,首发 15% 折扣后为 $6.79
平台Windows、macOS、Linux
语言英语、简体中文
Steam 功能成就、统计、完全控制器支持、Steam Input、家庭共享

三个平台都出自同一条流水线,我在 之前的一篇文章里写过它。

发售周末的数字

自 8 月流水线重构以来的正式发布(main)部署:8 次全部成功。其中两次在发售 周末。下表时间均为 UTC,取自 GitHub Actions API。

运行通道开始时间耗时结果
qa #383dev 进入 qa(PR #1087,48 个提交)11 月 1 日 01:0126 分钟成功
main #384qa 进入 main(PR #1088,630 个提交)11 月 1 日 01:3135 分钟成功
qa #387地图修复(PR #1089)11 月 2 日 03:4832 分钟成功
main #388地图修复(PR #1090)11 月 2 日 03:5056 分钟成功

运行 #388 的 56 分钟里,有 31 分钟花在一个名为 "Wait for in-flight CD on the other trunk" 的 job 上。三条通道都在同一组三台 Unity 主机上构建,所以每次运行都会等待 其他分支上更早开始的运行。按运行编号排序可以避免死锁:更早的运行总是先走。这次 main 运行排在 qa #387 之后,随后它自己的构建和 Steam 上传用了约 24 分钟。

构建如何到达 Steam

流水线有三条通道,每个长期存在的 git 分支对应一条:

git dev  -> Steam "development"       automatic
git qa   -> Steam "qualityassurance"  automatic; the merge into qa is the gate
git main -> uploads to Steam          setting the build live is a human click

推送的提交可以自动到达测试人员手中。但没有人在 Steamworks 里晋级构建,任何东西都 到不了玩家那里。Steam 构建无法删除,所以最后一步刻意保持手动。

这条规则塑造了整个发布过程。9 月,我把 qa 晋级到 main,用于 Valve 的构建审核 (PR #924,9 月 18 日)。审核读取的是 Steam 的默认分支,而我希望那个构建来自 main,而不是某条旁路通道。这是自 2025 年 10 月以来第一次主干晋级。9 月最后一次 晋级(PR #945,9 月 22 日)之后,main 将近六周没有动过。日常工作都走 dev 和 qa。

发售当晚,PR #1088 用一次合并把 main 更新到最新:630 个提交(不含合并提交为 508 个),涵盖 9 月 22 日以来的全部工作。听起来有风险,其实没有,因为被合并的那个 提交已经完整地通过了 qa 通道:Windows、Linux 和 macOS 构建,以及上传到 Steam 的 qualityassurance 分支。main 运行重新构建了同一个提交并上传。晋级里没有任何对 qa 来说是新的东西。

10 月:发售前必须做到的事

10 月的大部分时间都花在让构建值得晋级上。很多代码由 AI 编程代理编写;我确定方向、 撰写 issue,并审查和把关每一处改动。其中最重要的是四项工作。

最坏情况下的性能

发售前一周,第三章最密集的战斗是游戏里最慢的场景。修复(PR #1079,10 月 26 日合并) 前后都用一个新的实时采集测试来测量,测的是该段落在 500 个敌人上限下的表现:

之前之后
Arc 3 Horde,深度 5,Burst 开启约 27 FPS约 43 FPS
再加上 Box2D 和 animator 的节省(引擎 A/B)约 60 FPS
500 个敌人时的区域道具扫描1.80 ms0.57 ms

这些帧率是在 Unity 编辑器中采集的。根据我的经验,部署后的玩家构建通常比编辑器 快 2-3 倍,但我没有针对这个场景测量过。

难点在于保持确定性。敌人分离变成了一个 Burst job,它从 tick 开始时拍下的快照里读取 每个邻居,而不是读取另一个敌人刚刚改过的位置。快照在任何模拟对象运行之前构建, 所以更新顺序不会改变结果。状态摘要在 1 个和 23 个 job worker、Burst 开启和关闭的 情况下都验证为一致,然后重新认可了回放基线。一次审计表明线上游戏中没有任何地方 通过物理读取敌人,于是敌人也不再由 Box2D 模拟。

关卡设计改不坏的发布闸

10 月中旬,dev 部署通道一直变红,原因与 bug 无关:一些测试断言了随关卡设计几乎每天 都在变的已编写内容,比如道具布局和生成概率。一刀切地忽略这些测试很容易,但是错的。 取而代之的是,测试房间改用内容的冻结副本(10 月 11 日),这样编辑已发布的章节不再 改变确定性基线。一个当天曾作为权宜之计忽略了三个测试的测试夹具,现在完整运行, 并在冻结房间上 4/4 通过。

一周后,PR #1073 把 34 个 PlayMode 测试改成了 32 个 EditMode 测试,原来的每个测试都要 启动一个场景只为检查某个组件是否存在。其中 30 个在约 0.4 秒内跑完。它还删除了 5 个 永远不会失败的测试。

不再宕机的崩溃报告服务器

10 月中旬,自托管的崩溃报告服务器磁盘满了。每个 dev 构建都会上传约 2 GB 的原生调试 符号,而 dev 构建发布得很频繁。修复分两部分:服务器上的一次运维清理,以及一次代码 改动(10 月 13 日)。符号上传现在是按通道(dev、qa、prod)设置的开关,dev 关闭, 玩家和测试人员运行的构建开启。被关闭的通道在 CI 中显示为提示而不是失败,所以这个 设置可见但不吵闹。

缺一台机器时照样发布

macOS 构建依赖一台在线的 Mac。10 月 18 日之前,如果没有 Mac 响应,qa 通道会跳过 整个 Steam 部署。PR #1073 改变了这一点:当 Mac 探测找不到机器时,qa 发布 Windows 和 Linux,macOS depot 保留之前的构建。Mac 构建失败时仍然什么都不发布。通知现在会写明 实际发出了哪些 depot。

发售周的修复

发售后的第一个改动是一个地图 bug,在发售周末发现并修复。run composer 为每局游戏生成 一张分支地图,章节规则规定同类遭遇不能连续出现。composer 的地图连线忽略了这条规则。 后续的一个展示步骤悄悄替换了放置的遭遇,于是小地图显示的是一回事,关卡门显示的是 另一回事。

修复在连线建好之后运行一个确定性的修复步骤。它以最少的移动重排下一行,必要时退回 到重排当前行,最后的手段是删掉一条不会让任何节点孤立的连线。测试在每个已发布章节上 遍历种子 1 到 1000:

before the fix: 34-59% of maps on Arc0001-0005 had a barred repeat edge
after the fix:  none

它在 11 月 2 日从 dev 到 qa 再到 main。之后,当构建在 Steamworks 中设为上线时, 它就会到达玩家手中。

我有意没做的事

发售意味着选择不完成什么。值得点名的选择有:

  • qa 和 main 通道上的测试闸已关闭。 变量 SKIP_TEST_GATES_QA(10 月 27 日设置) 和 SKIP_TEST_GATES_MAIN(10 月 31 日设置)都是 true,而 SKIP_TEST_GATES_DEV 是 false。完整的 EditMode 和 PlayMode 套件仍在 dev 通道上运行,每个晋级 PR 都写明了 它所晋级的那次成功运行。跳过避免了在一个已经通过完整套件的提交上重跑一遍。这是 发售周的一个有意取舍,写在每个 PR 的描述里,改一个变量就能撤销。
  • dev 通道很吵。 从 10 月 1 日到 11 月 2 日,它完成的运行中 14 次成功、14 次失败、 5 次取消。取消是有意设计的(更新的推送会替换较旧的运行)。失败是让这条通道成为 "坏了也便宜" 的地方的代价,但现在还是太多了。
  • Steam 云存档没有开启。 Auto-Cloud 配置及其线上验证是一个等待我审查的未关闭 issue。在端到端验证之前,存档保留在本地。
  • 没有新的内容系统。 用于无尽场地的可复用、可旋转道具布局已经立了 issue,但它们 涉及确定性回放路径,所以留到发售之后。

发售之后:排行榜与性能

两个公开页面在发售后覆盖这款游戏:

  • 每周排行榜。 提交的分数不被信任。验证服务 在无头浏览器中用已发布的正式构建回放这局游戏记录下的输入,比较状态摘要,然后才写入 它自己推导出的分数。细节见 The Verified Score。
  • 性能。 从匿名游戏会话中测得的帧率,按开发版、 候选发布版和正式版分别公布。

接下来做什么

  • 等 dev 通道稳定变绿后,重新开启 qa 和 main 的测试闸。
  • 降低 dev 通道的失败率。
  • 端到端验证 Steam 云存档,然后开启它。
  • 通过发售时用的同样三条通道发布内容更新。

可以推广的经验

让不可逆的一步保持手动。 直到 Steam 上传为止的一切都是自动化的。让构建上线是 一次点击,因为 Steam 构建无法删除。

当其中没有任何新东西时,大规模晋级是安全的。 那次 630 个提交的合并平平无奇, 因为完全相同的提交已经在 qa 上构建并上传过。

把没做的事写下来。 跳过的测试闸、吵闹的通道和缺失的云存档都记录在变量、PR 描述 和未关闭的 issue 里,不会被遗忘。