Standout Builder Studio · planning Index / Variant → Live review Updated 12 Jun 2026

Variant → Live: the complete component walkthrough

One question, answered end to end: what exactly happens — component by component, file by file — between the moment a builder saves a spec in the dashboard and the moment a player launches the game in an operator's lobby? This page traces that path through the two real repositories as they stand today (standout-platform, this repo; and vhslab/standout-games, the engine), names every artefact and the tool that produces it, marks honestly what is automated versus done by hand, and ends with the changes that would close the gap. Almost everything here is GROUNDED · matches main; the proposals at the end are CONCEPT · proposed. The audience includes external Game Builders — terms are glossed on first use.


The component map

"Going live" is not one system — it is a baton passed across two repositories and a cloud runtime. The dashboard (this repo, standout-platform) is where a builder authors a GameSpec and saves it. The CLI bridge (standout scaffold-game, which lives in standout-platform's apps/cli) turns that spec into source code inside the engine repo (vhslab/standout-games) — a Bun + Turborepo monorepo holding 15 games today. CI (three GitHub Actions workflows) builds container images from the engine repo and ships them to GCP, where three Cloud Run services run the engine, the game clients, and the launcher API. An operator then mints a signed launch URL and the player connects. The diagram below shows the artefact flow across those lanes; the table beneath names every component with its exact path.

Diagram 1 · Component mapGROUNDED · matches main
Dashboard · standout-platform CLI bridge → Engine repo · standout-games CI → GCP runtime → operator → player Builder UI apps/web · React POST /api/specs apps/mcp · audited D1 · game_specs spec JSON blob export spec.json standout scaffold-game apps/cli · 12 steps games/<slug>/ server · client · shared + variant.meta.json registry.ts PLUGIN_MAP entry apps/game-engine sync-games.ts ⚠ manual Dockerfiles · nginx.conf CI GAMES arg ⚠ also manual PR merged GitHub Actions ×3 _deploy-engine / -games / -games-service Artifact Registry docker images Cloud Run ×3 engine · clients · games-svc Cloud SQL Postgres Memorystore Redis GCS bucket VITE_ASSET_BUCKET_URL Operator HMAC launch URL + wallet callbacks Player any device /supplier/launch
artefact / request flow spec export, scaffold output & the manual hand-offs

Read it as a relay: blue lane authors and stores the spec; the flame edges are where a human carries the baton (export the spec, run the scaffold, run sync-games.ts, set the CI GAMES arg, merge the PR); the green lane is fully automated once the PR lands. The two flame boxes inside the engine lane — sync-games.ts and the CI GAMES argument — are the friction this whole page exists to make legible.

Component Exact path Role
Builder UIapps/web (this repo)React app where a builder authors a GameSpec — template + config overrides + theme.
Spec API + auditapps/mcp/src/api.tsServes /api/specs; every write attributed to the authenticated actor and appended to audit_log (create_spec / update_spec).
Spec storeD1 table game_specsPersists each spec as a JSON blob (specRow() in packages/core/src/repo.ts).
Template registrypackages/shared/src/templates.tsThree hard-coded GameTemplates (crash, trunk-raider, silk-road) declaring config fields + baseGame. Three of the engine's 15 games.
Scaffold CLIapps/cli/src/scaffold.tsstandout scaffold-game --spec <spec.json> — the bridge that turns a spec into engine source. 12 steps; see Stage B.
Base game (source)games/<baseGame>/The folder the scaffold copies: server/ (plugin), client/ (React 19 + Vite app), shared/ (types/physics), optional provably-fair/.
Server plugin + manifestgames/<slug>/server/src/plugin.tsExports the plugin; the embedded manifest (id, name, version, type, sessionMode, defaultRtp, useBonus, onServerFailure, rateLimits) is the contract the engine loads.
Config + typesgames/<slug>/server/src/types.tsHolds DEFAULT_CONFIG — the object literal the scaffold edits to apply spec overrides.
Client appgames/<slug>/client/Full Vite app using @standout/game-sdk/react (RoyaleProvider, GameSessionProvider, GameLoader); Socket.IO 4.8 + Zustand. client/src/assets.ts hard-codes 110+ CDN asset paths.
Plugin registryapps/game-engine/src/plugins/registry.tsHard-coded PLUGIN_MAP (manifest.id → { pkg, exportName }) + GAME_ID_ALIASES; GAME_ID/GAMES env vars select subsets; dynamic import(config.pkg).
Game engineapps/game-engine (Cloud Run :8080)Fastify + Socket.IO host. SessionManager + SocketManager; verifies the launch JWT on Socket.IO auth; serves GET /games (manifest catalogue source).
Game clients servicegames/ built via games/Dockerfile → nginxPer-game Vite client bundles served statically behind nginx; routes generated by scripts/sync-games.ts into games/nginx.conf.
Launcher APIapps/games-service (Cloud Run)Operator-facing supplier API. POST /supplier/launch mints the session JWT; reads the engine catalogue (60s cache of GET /games).
Sync scriptscripts/sync-games.tsRegenerates the auto-generated COPY blocks in both Dockerfiles and the redirect/location/fallback blocks in games/nginx.conf. Manual step after adding a game.
CI workflows.github/workflows/_deploy-{engine,games,games-service}.ymlBuild images with --build-arg GAMES=… → Artifact Registry → Cloud Run. The engine GAMES arg is a manually-supplied workflow input.
RNG packagepackages/game-rngSHA-256 hash-counter CSPRNG (GLI-19 §3.3.1); CERTIFICATION.md locks src/provably-fair.ts; BUILD_HASH verified at deploy.
Wallet adapterpackages/wallet-adapter-serverExternalAdapter calls operator REST /wallet/{balance,bet,win,rollback}, HMAC-signed both directions; bet state machine in Postgres.
Backing servicesCloud SQL · Memorystore · GCSPostgres (bet/session state), Redis (Memorystore), and the GCS asset bucket parameterised by VITE_ASSET_BUCKET_URL.
A note on the edge experiments
An edge beachhead already exists in the engine repo — games/trunk-raider-edge/, apps/edge-do/, apps/edge-engine/, and packages/edge-game-sdk/ (Rust/WASM Cloudflare Workers, with wrangler.jsonc and Durable-Object hosts). These are real, but they are experiments: production today is Cloud Run. The Workers-for-Platforms architecture described in architecture.html · Edge delivery is the decided target, not today's runtime. See Today vs target.

What a game physically is

A "game" in production is not a single file — it is a set of artefacts that must all exist and agree for one manifest.id to be launchable. The table below is the definitive checklist. For each artefact it records who creates it today: ✅ scaffold means scaffold-game writes it; ⚠ manual means a human does it by hand after scaffolding; 🤖 automatic means the runtime derives it with no per-game action. The manual rows are the gap story.

Artefact Path What it declares / does Created by
Copied game folder games/<slug>/ The whole tree copied from the base game, excluding node_modules/dist/.turbo. ✅ scaffold
Server plugin + manifest …/server/src/plugin.ts Plugin export + embedded manifest. Scaffold re-IDs id and name; everything else inherits from the base. ✅ scaffold
Types / DEFAULT_CONFIG …/server/src/types.ts The config object the engine instantiates the game with. Scaffold splices spec overrides into the literal (per-key regex). ✅ scaffold
Package identities every package.json + import refs @standout/<base>-server@standout/<slug>-server across server/client/shared and workspace deps + scripts. ✅ scaffold
Client app …/client/ The playable front end. Copied verbatim — the slug is renamed in package identity, but the theme is not applied (see next row). ✅ scaffold ⚠ theme by hand
Theme applied to client …/client/src/assets.ts, Tailwind, loader Palette/asset swap so the variant looks different. Scaffold writes the theme only to variant.meta.json; nothing reads it. ⚠ manual
Shared package …/shared/ Types/physics shared between server and client; renamed in identity, copied otherwise. ✅ scaffold
variant.meta.json games/<slug>/variant.meta.json Provenance record: id, name, templateId, baseGame, theme, createdAt. Written by scaffold; not consumed by any build step. ✅ scaffold
Registry entry apps/game-engine/src/plugins/registry.ts PLUGIN_MAP["<slug>"] = { pkg, exportName } inserted at the top of PLUGIN_MAP (exportName copied from the base entry). Not type-checked by the scaffold. ✅ scaffold
nginx route games/nginx.conf Redirect + location ^~ /<slug>/ + asset fallbacks so the client is reachable. Generated only when sync-games.ts is run. ⚠ manual (sync-games)
Dockerfile COPY lines games/Dockerfile, apps/game-engine/Dockerfile The auto-generated COPY block that pulls the new game's package.json files into the image. Regenerated by sync-games.ts. ⚠ manual (sync-games)
CI GAMES build-arg deploy-engine-*.yml input → --build-arg GAMES The engine image only loads the games named in GAMES. A new slug must be added to the workflow input for the engine to load it. ⚠ manual
games-service catalogue row derived from engine GET /games Once the engine boots with the game loaded, its manifest appears in the catalogue (60s cache) and /supplier/launch accepts the gameId. No per-game file. 🤖 automatic
Operator config operator integration (off-platform) The operator must add the gameId to their lobby and point wallet callbacks at the adapter. Outside both repos. ⚠ manual (operator)

Count the colours: of the fourteen artefacts, the scaffold produces eight outright, the catalogue row is automatic, and five are manual — theme application, the two sync-games outputs, the CI GAMES arg, and operator config. A variant is therefore roughly "scaffold gets you to source-complete; a human gets you to deployable." Section 5 proposes collapsing the manual rows.


The walkthrough, today

Let us trace one concrete build all the way to a player. The worked example is Neon Heist — the suite's reskin of Crash (see examples · Reskin — Neon Heist): same certified shared-world mechanic and RTP 97.0%, a cyan/violet palette (c1 #38e8ff, c2 #7a5cff) replacing Crash's flame. Five stages, A through E.

Stage A

Spec — in the dashboard

The builder opens the Builder UI, picks the Crash template, sets the theme colours and the variant name "Neon Heist", and saves. The save is a POST /api/specs handled in apps/mcp/src/api.ts; the spec is written to the D1 game_specs table as a JSON blob and the action is appended to audit_log as create_spec (attributed to the authenticated actor). The shape persisted is the GameSpec:

{
  "name": "Neon Heist",
  "templateId": "crash",
  "config": { "minCrashPoint": 1.0, "maxCrashPoint": 100.0 },
  "theme": { "c1": "#38e8ff", "c2": "#7a5cff", "name": "Neon Heist" }
}

To feed the next stage, the builder exports this record to a spec.json file. That hand-off — dashboard to CLI — is the first flame edge in diagram 1: there is no automated trigger from the dashboard into the scaffold today.

Stage B

Scaffold — the CLI bridge

The engineer runs standout scaffold-game --spec spec.json --games-repo ../standout-games. The CLI executes twelve steps (verified against apps/cli/src/scaffold.ts), grouped into four bands — Copy, Rewrite, Wire, Ship:

  1. Resolve + validate spec. Read spec.json; require name and templateId. fail-fast if missing
  2. Look up the GameTemplate. getTemplate(templateId) → resolves baseGame ("crash") and base manifest id. packages/shared/src/templates.ts
  3. Resolve the games repo. --games-repo$STANDOUT_GAMES_REPO → sibling ../standout-games. existence-checked
  4. Slug + collision guard. slugify("Neon Heist")neon-heist; refuse if games/neon-heist exists. games/<slug> reserved
  5. Copy the base game. cpSync the whole games/crash/ tree, excluding node_modules/dist/.turbo. → games/neon-heist/  [Copy]
  6. Rename package identities. Rewrite every package.json name + workspace dep + script: @standout/crash-*@standout/neon-heist-*. server/client/shared + root  [Rewrite]
  7. Re-ID the plugin manifest. In server/src/plugin.ts, set manifest id: "neon-heist" and name: "Neon Heist" (anchored regex). other manifest fields inherited
  8. Config surgery into DEFAULT_CONFIG. Balanced-brace extract the literal; per-key regex replace scalars and {min,max} ranges; warn on keys not found. …/server/src/types.ts
  9. Write variant.meta.json. id, name, templateId, baseGame, theme, createdAt. theme lands here — and only here  [Wire]
  10. Insert the registry entry. Inserts PLUGIN_MAP["neon-heist"] = { pkg: "@standout/neon-heist-server", exportName } at the top of PLUGIN_MAP (exportName copied from the base entry). apps/game-engine/src/plugins/registry.ts
  11. Branch + commit. git checkout -b builder/neon-heist; stage the new folder + registry; commit. never on main  [Ship]
  12. Optional PR. With --pr and gh available, open the PR; otherwise print the command.
Diagram 2 · Scaffold steps, bandedGROUNDED · matches main
COPY resolve, slug, copy the tree REWRITE identities, manifest, config surgery WIRE meta.json, registry entry SHIP branch, commit, optional PR 1 · resolve + validate spec · 2 · look up template · 3 · resolve repo 4 · slug + collision guard → games/neon-heist reserved 5 · cpSync base tree (excl. node_modules/dist/.turbo) 6 · rename package identities + deps + scripts 7 · re-ID plugin manifest (id + name) 8 · config surgery into DEFAULT_CONFIG (warn on miss) 9 · write variant.meta.json (theme lands here only) 10 · insert PLUGIN_MAP entry at top of registry.ts 11 · branch builder/neon-heist + commit · 12 · optional PR NOT done by scaffold sync-games.ts CI GAMES arg · theme typecheck · audit log

Output of Stage B: a committed branch builder/neon-heist in standout-games with a source-complete game folder and a registry entry — and a flame-coloured reminder that the scaffold's job ends there. What it deliberately does not do becomes Stage C.

Stage C

The manual gap — today

This stage is real friction, and it is all by hand GROUNDED · matches main
Between "scaffold committed a branch" and "CI can build a working image", a human must do four things the scaffold does not:
  1. Run bun scripts/sync-games.ts in the engine repo. This regenerates the COPY blocks in games/Dockerfile and apps/game-engine/Dockerfile and the redirect/location/fallback blocks in games/nginx.conf. Without it the client image has no route to /neon-heist/ and the Dockerfiles never copy the package. The scaffold does not run it — the Dockerfiles and nginx are stale the moment the branch is created.
  2. Add the slug to the CI GAMES build arg. The engine loads all PLUGIN_MAP keys when GAMES is empty, but when a deploy explicitly limits the list (as staging and production deploys typically do), any slug absent from GAMES in deploy-engine-staging.yml (passed through to _deploy-engine.yml as --build-arg GAMES=…) will be silently omitted — the deployed engine never registers the plugin and /supplier/launch will 404 the gameId.
  3. Apply the theme by hand. The spec's theme (c1 #38e8ff, c2 #7a5cff) sits unused in variant.meta.json — nothing reads it. To make Neon Heist actually look like Neon Heist, an engineer edits the copied client's client/src/assets.ts (which hard-codes ${ASSET_BUCKET_URL}/game-assets/trunk-raider-style paths — the per-game segment is literal, only the bucket is parameterised), the Tailwind palette, and the loader colours, then produces and uploads the new art. This is the bulk of the human effort in a reskin.
  4. An engineer reviews and merges the PR. This is the one structurally-required engineering gate (it matches the Scaffolder agent's "engineer merges the PR" gate in architecture.html · agent workforce). The reviewer also catches that the inserted registry entry compiles — the scaffold does not type-check it.
Stage D

Deploy — CI → GCP

With the PR merged, the staging deploy workflows run. _deploy-engine.yml builds apps/game-engine/Dockerfile with --build-arg GAMES=… (now including neon-heist), tags the image, pushes it to GCP Artifact Registry, and runs gcloud run services replace to roll out the royale-game-engine Cloud Run service. _deploy-games.yml builds games/Dockerfile (every client's Vite bundle + nginx) and deploys the clients service. _deploy-games-service.yml ships the launcher API. Backing services are fixed: Postgres on Cloud SQL, Redis on Memorystore, assets on the GCS bucket. Once the engine boots with the game loaded, its manifest is served at GET /games; the games-service catalogue picks it up within its 60-second cache window — no per-game launcher change.

Diagram 3 · Deploy pipelineGROUNDED · matches main
PR merged main · standout-games GitHub Actions ×3 deploy workflows Artifact Registry docker images + tags game-engine :8080 GAMES build-arg game clients nginx · synced routes games-service launcher API GET /games 60s cache
Stage E

Live — the operator launch

Now a player can play. The operator calls POST /supplier/launch on the games-service with an HMAC-signed request (verified by verifyHmac); the body carries operatorId, playerId, gameId: "neon-heist", currency, optional betLimits, callbackUrl, and a correlation sessionId. The service confirms the gameId exists in the catalogue, then mintSessionToken(...) issues a session JWT (subject operatorId:playerId) and returns a launch URL: {playBaseUrl}/neon-heist?token=<jwt>. The player's browser loads that URL — nginx serves the neon-heist client bundle — and the client opens a Socket.IO connection to the engine, which verifies the launch JWT (GAME_SESSION_JWT_SECRET) on auth. From there, every bet/win flows through the ExternalAdapter to the operator's wallet REST endpoints (/wallet/{balance,bet,win,rollback}), HMAC-signed both directions, with the bet state machine persisted in Postgres.

Diagram 4 · Runtime launch sequenceGROUNDED · matches main
Player browser Operator lobby + wallet HMAC launch games-service mint session JWT launch URL + token open /neon-heist?token nginx client neon-heist bundle game-engine verify JWT · Socket.IO verify on auth ExternalAdapter HMAC both ways operator wallet /wallet/balance,bet, win,rollback

Definition of LIVE — every box from §anatomy is green


The gaps

Each row is one gap: where it bites (with a path), what it costs today, the fix, and which suite proposal already covers it. Amber "NOT YET COVERED" marks a gap no current proposal owns.

Gap Where it bites Cost today Proposed fix Covered by
Theme captured, not applied variant.meta.json vs client/src/assets.ts Every reskin is hand-edited across assets + Tailwind + loader; the spec's colours are dead data. Shared client shell that reads theme tokens at runtime. build-changes #1
Client duplicated per variant games/trunk-raider vs games/trunk-raider-v2 A full client app is copied for every variant — no shared shell or theme tokens; bug fixes must be re-applied per copy. Data-not-code reskins: one shell, per-variant token files. build-changes #1
Scaffold doesn't run sync-games scripts/sync-games.ts, Dockerfiles, nginx.conf Dockerfiles + nginx stale the instant the branch exists; forgetting it means no route and no COPY. scaffold-game v2 runs sync-games.ts as a step. build-changes #3
CI GAMES list is manual deploy-engine-*.yml--build-arg GAMES Engine silently omits the plugin if the slug isn't added; launch 404s with no obvious cause. Note: the engine loads all PLUGIN_MAP keys when GAMES is empty — the risk bites only when a deploy explicitly limits the list. scaffold-game v2 patches the workflow input. build-changes #3
Registry insert unvalidated apps/game-engine/src/plugins/registry.ts A bad exportName or pkg name only surfaces at engine boot / deploy, not at scaffold time. Type-check the variant + validate the registry entry in the CLI. build-changes #3
Templates: 3 of 15; risk of drift packages/shared/src/templates.ts Only crash/trunk-raider/silk-road are buildable; the field lists are hand-maintained copies of engine DEFAULT_CONFIGs, so they can silently diverge as the engine evolves. Auto-extract templates from engine DEFAULT_CONFIGs at build time. build-changes #2
Scaffold is CLI-only apps/cli/src/scaffold.ts Not exposed via MCP or the dashboard; an engineer must run it locally, and the action is not written to audit_log. Expose scaffold via MCP so the dashboard/agents trigger it, audited. build-changes #3, arch · agents
No spec ↔ game linkage D1 game_specsgames/<slug> Nothing in the DB records that a spec produced a given game folder/PR; provenance lives only in variant.meta.json in the other repo. versions[] / baseGameRef on the grown spec record the PR + Worker. arch · data model
No RTP simulation tooling engine repo — none exists Config changes ship without a proof that RTP/volatility still hold; reviewers eyeball numbers. Build an RTP simulation harness (the Math-Sim agent's tool). build-changes #5, arch · Math-Sim
GCP today vs WfP target Cloud Run vs architecture.html#edge The decided edge target is unbuilt for production; migration ownership/sequencing is undefined. Grow the existing edge beachhead incrementally per game. build-changes #6
No per-variant preview deploys CI — no preview stage A variant can't be seen running until it is merged to main and deployed to staging. Add a preview-deploy CI stage per variant branch. NOT YET COVERED · #4
A drift claim we checked and corrected
It is sometimes asserted that the dashboard's trunk-raider thresholds differ from the engine's. They do not today: both packages/shared/src/templates.ts (lines 64–66) and the engine's games/trunk-raider/server/src/types.ts (lines 103–105) use silentPick 15 / informant 32 / cheatCode 65. The real risk is not a current mismatch but the mechanism: the template field lists are hand-maintained copies, so nothing prevents them from drifting the next time the engine's DEFAULT_CONFIG changes. That is exactly what build-change #2 (auto-extraction) removes.

What we should change in how we build

Six changes, in priority order. Effort is a rough guess (S / M / L). All are CONCEPT · proposed.

1Shared client shell + theme-token systemEffort L

Change: Extract one client shell that all variants share, driven by a per-variant theme-token file (palette, asset manifest, copy). A reskin becomes a data file, not a forked client.

Why: This is the single highest-leverage change. Today games/trunk-raider and games/trunk-raider-v2 are near-duplicate client apps, and a reskin is hours of hand-editing assets.ts + Tailwind. The theme captured in variant.meta.json would finally be read.

Unlocks: data-not-code reskins; shared bug fixes; the theme field stops being dead data; per-variant previews become cheap.

2Template auto-extraction from engine DEFAULT_CONFIGsEffort M

Change: A build-time script reads each game's server/src/types.ts DEFAULT_CONFIG and generates the GameTemplate field list, replacing the three hand-maintained templates.

Why: Kills drift at the source (see the corrected drift note in §gaps) and opens all 15 engine games to the Builder, not just three.

Unlocks: every game buildable; template fields can never silently diverge from the engine; new engine games appear in the Builder automatically.

3scaffold-game v2Effort M

Change: Extend the CLI to (a) run sync-games.ts, (b) patch the CI GAMES input, (c) write theme tokens (paired with #1), (d) tsc --noEmit the variant, (e) validate the new registry entry resolves, (f) write the action to audit_log, and (g) expose itself over MCP so the dashboard and agents can trigger it.

Why: Collapses four of the five manual rows in §anatomy into the tool, and makes the Scaffolder agent in architecture.html buildable on a real CLI rather than a hypothetical one.

Unlocks: "save spec → PR" with no hand steps; audited, agent-triggerable scaffolding; fewer broken deploys.

4Per-variant preview deploysEffort M

Change: A CI stage that deploys each variant branch to a throwaway preview (its own engine + client) before merge.

Why: Today a variant can't be seen running until it's merged and on staging. This is the one gap no current proposal fully owns.

Unlocks: review-before-merge on real running games; the sandbox-launch experience from Launch Lab on actual deploys.

5RTP simulation harnessEffort L

Change: Build the simulation tool the Math-Sim agent needs — replay the (certified, locked) RNG against a config to produce an RTP/volatility/max-win report. No such tooling exists in the engine repo today.

Why: Config changes currently ship without a proof artefact. Note the open compliance question in architecture.html · open questions about whether simulating against the certified RNG carries recertification implications — that must be answered first.

Unlocks: the Math-Sim agent; RTP-sim validation gate; reviewers approve a number, not a guess.

6Incremental edge migration pathEffort L

Change: Grow the existing beachhead — games/trunk-raider-edge, packages/edge-game-sdk, apps/edge-do/edge-engine — into the Workers-for-Platforms target one game at a time, rather than a big-bang cutover.

Why: The edge target is decided (architecture.html#edge) and the experiments already prove the runtime; the missing piece is owned, sequenced migration.

Unlocks: the WfP architecture (immutable Workers, route-flip rollback, Durable-Object rooms) without abandoning Cloud Run mid-flight.


Today vs target

The same game, two runtimes. The left column is what ships today (GROUNDED · matches main); the right is the decided edge target (CONCEPT · proposed, fully specified in architecture.html · Edge delivery).

Today — GCP / Cloud Run GROUNDED
Target — Workers for Platforms CONCEPT
Where games run
Three central Cloud Run services (engine :8080, clients/nginx, games-service) in one GCP region.
Per-game Workers in a dispatch namespace, executing at the edge near each player.
Session state
Postgres (Cloud SQL) for bet/session state; Redis (Memorystore) for hot state; shared-world rounds in the engine process.
Durable Objects for shared-world rooms (one per crash room); single-player turn-based stays stateless per request.
Assets
GCS bucket, parameterised by VITE_ASSET_BUCKET_URL; per-game path segment hard-coded in assets.ts.
R2 object storage, bucket-scoped per namespace (sandbox / demo / prod).
Deploy unit
Docker images per service; engine image selects games via the GAMES build-arg.
An immutable user Worker per build version; a new version is a new Worker.
Rollback
Re-deploy a prior image tag via gcloud run services replace.
A route flip on the dispatch Worker — no game-code redeploy; versions run side by side.
Sandboxing
Process/container isolation on Cloud Run; all games in one engine process per deploy.
Per-customer Worker isolation in the namespace; one tenant's Worker can't reach another's.

The point of the two columns is not that today is wrong — Cloud Run ships real games to real operators right now. The point is that the migration is credible and incremental: the edge experiments (trunk-raider-edge, edge-game-sdk, edge-do) already run the target runtime, so the path from here to there is per-game, not a rewrite. See build-change #6.