Launch Week: Shipping CandyRush to Steam
CandyRush, a deterministic top-down survivor roguelite, went live on Steam on October 31, 2026. This post covers the release engineering behind that date: how the build reached Steam, what the launch-weekend deploys looked like, what I chose to leave out, and what comes next.
There are no sales, wishlist or player numbers here; I have none to report.
What shipped
From the Steam store API on the day of writing:
| Release date | Oct 31, 2026 |
| Price | $7.99, with a 15% launch discount to $6.79 |
| Platforms | Windows, macOS, Linux |
| Languages | English, Simplified Chinese |
| Steam features | Achievements, Stats, Full controller support, Steam Input, Family Sharing |
All three platforms come out of one pipeline, which I covered in an earlier post.
Launch weekend in numbers
Release (main) deploys since the August pipeline rework: 8 of 8
succeeded. Two of them were launch weekend. All times below are UTC, taken
from the GitHub Actions API.
| Run | Lane | Started | Duration | Result |
|---|---|---|---|---|
| qa #383 | dev into qa (PR #1087, 48 commits) | Nov 1, 01:01 | 26 min | success |
| main #384 | qa into main (PR #1088, 630 commits) | Nov 1, 01:31 | 35 min | success |
| qa #387 | map fix (PR #1089) | Nov 2, 03:48 | 32 min | success |
| main #388 | map fix (PR #1090) | Nov 2, 03:50 | 56 min | success |
Run #388 spent 31 of its 56 minutes in a job called "Wait for in-flight CD on the other trunk". All three lanes build on the same pool of three Unity hosts, so each run waits for any earlier run on another branch. Ordering by run number avoids a deadlock: the earlier run always goes first. The main run queued behind qa #387, then its builds and Steam upload took about 24 minutes.
How a build reaches Steam
The pipeline has three lanes, one for each long-lived git branch:
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
A pushed commit can reach a tester automatically. Nothing reaches players without a person promoting the build in Steamworks. Steam builds cannot be deleted, so the last step stays manual on purpose.
That rule shaped the whole release. In September I promoted qa to main for
Valve's build review (PR #924, Sept 18). Review reads from Steam's default
branch, and I wanted that build to come from main, not from a side lane. It
was the first trunk promotion since October 2025. After the last
September promotion (PR #945, Sept 22), main did not move for almost six
weeks. Day-to-day work went through dev and qa.
On launch night, PR #1088 brought main up to date in one merge: 630
commits (508 excluding merge commits) covering everything since September 22.
That sounds risky. It wasn't, because the merged commit had already gone green
through the whole qa lane: Windows, Linux and macOS builds and the upload to
Steam's qualityassurance branch. The main run rebuilt that same commit and
uploaded it. Nothing in the promotion was new to qa.
October: what had to be true before launch
Most of October went into making the build worth promoting. AI coding agents wrote much of the code; I set the direction, wrote the issues, and reviewed and gated each change. Four pieces of that work mattered most.
Performance at the worst case
A week before launch, the densest fight in the third arc was the slowest scene in the game. The fix (PR #1079, merged Oct 26) was measured before and after with a new real-time capture test of that segment at its 500-enemy cap:
| Before | After | |
|---|---|---|
| Arc 3 Horde, depth 5, Burst on | ~27 FPS | ~43 FPS |
| Plus Box2D and animator savings (engine A/B) | ~60 FPS | |
| Area-prop scan at 500 enemies | 1.80 ms | 0.57 ms |
These frame rates were captured in the Unity editor. In my experience, deployed player builds usually run 2-3x faster than the editor, but I haven't measured that for this scene.
The hard part was keeping the work deterministic. Enemy separation became a Burst job that reads every neighbour from a snapshot taken at the start of the tick, never a position some other enemy just changed. The snapshot is built before any simulated object runs, so update order can't change the result. State digests were verified identical with 1 and 23 job workers and with Burst on and off, and then the replay baselines were re-blessed. Enemies also stopped being simulated by Box2D after an audit showed that nothing in the live game read them through physics.
A release gate that level design can't break
In mid-October the dev deploy lane kept going red for a reason unrelated to bugs: some tests asserted authored content, such as prop layouts and spawn chances, that changes with level design almost daily. Ignoring those tests across the board would have been easy and wrong. Instead, the test rooms moved onto frozen copies of the content (Oct 11), so editing a shipped arc no longer moves the determinism baselines. A test fixture that had three tests ignored as a stopgap the same day now runs in full and passes 4 of 4 on the frozen rooms.
A week later, PR #1073 moved 34 PlayMode tests, each of which booted a scene to check that a component existed, into 32 EditMode tests. Thirty of them run in about 0.4 seconds. It also deleted 5 tests that could not fail.
A crash-report server that stays up
In mid-October the self-hosted crash-report server ran out of disk. Every dev build uploaded about 2 GB of native debug symbols, and dev builds ship often. The fix had two parts: an operational cleanup on the server, and a code change (Oct 13). Symbol upload is now a switch for each channel (dev, qa, prod), off for dev and on for the builds that players and testers run. A channel that is switched off shows up in CI as a notice, not a failure, so the setting is visible without being noisy.
Shipping when a machine is missing
macOS builds depend on a Mac being online. Before October 18, if no Mac answered, the qa lane skipped the whole Steam deploy. PR #1073 changed that: when the Mac probe finds no machine, qa ships Windows and Linux and leaves the macOS depot on its previous build. A failed Mac build still ships nothing. The announcement now names which depots actually went out.
The launch-week fix
The first post-launch change was a map bug, found and fixed on launch weekend. The run composer generates a branching map for each run, and the arc rules say two encounters of the same type may not come back to back. The composer's map edges ignored that rule. A later presentation step quietly swapped the placed encounter, so the minimap showed one thing while the gate showed another.
The fix runs a deterministic repair pass after the edges are built. It reorders the next row with the fewest moves, falls back to the current row, and as a last resort drops an edge that strands nothing. The test sweeps seeds 1 to 1000 over every shipped arc:
before the fix: 34-59% of maps on Arc0001-0005 had a barred repeat edge
after the fix: none
It went from dev to qa to main on November 2. From there it reaches
players when the build is set live in Steamworks.
What I deliberately left out
Launching meant choosing what not to finish. The choices worth naming:
- Test gates on the qa and main lanes are switched off. The variables
SKIP_TEST_GATES_QA(set Oct 27) andSKIP_TEST_GATES_MAIN(set Oct 31) are bothtrue, andSKIP_TEST_GATES_DEVisfalse. The full EditMode and PlayMode suites still run on the dev lane, and each promotion PR names the green run it promotes. The skip avoids re-running the full suites on a commit that has already passed them. It is a deliberate launch-week trade-off, stated in each PR body, and it can be undone by flipping a variable. - The dev lane is noisy. From October 1 to November 2 it completed 14 runs green, 14 red and 5 cancelled. Cancellations are by design (a newer push replaces an older run). The red runs are the cost of keeping the lane where breakage is cheap, but there are still too many.
- Steam Cloud saves are not on. The Auto-Cloud configuration and its live check are an open issue waiting on my review. Saves stay local until that is verified end to end.
- No new content systems. Reusable, rotatable prop layouts for endless fields are filed as an issue, but they touch the deterministic replay path, so they were left for after launch.
After launch: leaderboards and performance
Two public pages cover the game after launch:
- Weekly leaderboards. A submitted score is not trusted. The verification service replays the run's recorded inputs against the shipped production build in a headless browser, compares state digests, and only then writes the score it derived itself. The details are in The Verified Score.
- Performance. Frame rates measured from anonymous play sessions, published separately for development, release candidate and released builds.
What comes next
- Turn the qa and main test gates back on once the dev lane is reliably green.
- Bring the dev lane's failure rate down.
- Verify Steam Cloud saves end to end, then enable them.
- Ship content updates through the same three lanes the launch went through.
What generalizes
Keep the irreversible step manual. Everything up to the Steam upload is automated. Making a build live is a click, because a Steam build can't be deleted.
A large promotion is safe when nothing in it is new. The 630-commit merge
was uneventful because the exact commit had already been built and uploaded on
qa.
Write down what you didn't do. The skipped gates, the noisy lane and the missing cloud saves live in variables, PR bodies and open issues, where they can't be forgotten.