Skip to content

ci: parallelize game builds with a per-game matrix#95

Merged
BCook98 merged 1 commit into
mainfrom
parallel-game-builds
Jul 4, 2026
Merged

ci: parallelize game builds with a per-game matrix#95
BCook98 merged 1 commit into
mainfrom
parallel-game-builds

Conversation

@BCook98

@BCook98 BCook98 commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Mass-update pushes (kit autobumps, art waves like #94) currently build serially — today's 14-game validate ran 15m43s, 91% of it the per-game build+conformance loop, with a single slow game (floorfall, 5.5min TinyGo build) blocking the other 13.

Change: both validate.yml and publish.yml fan out to a per-game matrix build job (fail-fast: false — one bad game can't cancel the rest; publish skips failed legs so good games still ship). Wall-clock becomes O(slowest game), not O(sum): ~2× today's outlier-bound case, ~3.4× a typical mass update (~2.7min vs ~9min), degrading gracefully as the catalog grows.

Security posture preserved and documented inline:

  • The trusted changed job still computes the work list, maintainer gate, and static validation before any game code runs; it emits the matrix.
  • Build legs: no secrets, persist-credentials: false, read-only, matrix dir passed via env (shell data, never spliced).
  • No caching in untrusted legs — GOCACHE also feeds the trusted conformance CLI, so a poisoned entry could subvert the very checker gating the wasm; same-repo branches share writable caches, so cache: false everywhere. Toolchains stay pinned + checksum-verified per leg.
  • Release job unchanged trust boundary (secrets, zero game code); validate gains a no-secrets smoke-merge job stitching per-leg previews so smoke-preview.yml is untouched.

Trade-off: ~14× the setup runner-minutes (parallel and cheap). Add max-parallel: N under strategy if billing needs a bound.

Worth watching the first real matrix run after merge (per-leg artifact naming + merge-multiple is the newest moving part).

🤖 Generated with Claude Code

The publish and validate workflows built every changed game serially in a
single job's `while read` loop, so a mass update (a 14-game art release, or a
kit autobump touching every game) paid the sum of all per-game build+conformance
times. On a recent 14-game validate run that was ~15m43s of wall-clock, ~14m20s
of it in the serial loop — and one heavy game (floorfall, a ~5.5min TinyGo
build) alone blocked the other 13 behind it.

Split each workflow into three jobs:

- changed: computes the changed game dirs BEFORE any game code runs (git diff +
  the maintainer-permission gate + the static dir validator) and emits a compact
  JSON array for the matrix. This preserves the property that the work list — and
  publish's release DIRS — is decided by trusted code only.
- build: one matrix leg PER GAME (fail-fast: false), each compiling untrusted
  community code on its OWN read-only, secret-less runner. Wall-clock becomes
  O(slowest single game) instead of O(sum). A malicious leg can no longer even
  observe a sibling game's build.
- release / smoke-merge: unchanged trust boundary. publish's release job still
  holds the secrets and runs zero game code, now reading dirs from the changed
  job and downloading every leg's artifact (release-wasm-*, merge-multiple).
  validate's new smoke-merge job (no secrets, no game code) stitches the per-leg
  preview parts back into the single smoke-previews artifact + merged
  manifest.json, so smoke-preview.yml is untouched.

Cache safety: the build legs set setup-go cache:false and add no actions/cache.
They compile untrusted code, and the Go build cache also feeds the TRUSTED
conformance CLI — a poisoned cache entry could subvert the checker gating the
wasm and hand a malicious game a false pass. The matrix's parallelism delivers
the win without caching poisonable build output; each leg pays a cold ~30-40s
kit-CLI compile, which is dwarfed by running all games concurrently.

fail-fast: false means one bad game no longer blocks the others' publishes: the
release job skips any game whose build leg failed (that leg is the red signal)
and ships the rest.

Also silenced a pre-existing SC2016 false positive (literal backticks in a
printf format) so actionlint is fully clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@BCook98 BCook98 merged commit f53b5af into main Jul 4, 2026
5 checks passed
@BCook98 BCook98 deleted the parallel-game-builds branch July 4, 2026 12:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant