Standout Builder Studio · planning Index / Architecture review Updated 12 Jun 2026

Architecture: the platform under the studio

This page covers the three load-bearing decisions that underpin Builder Studio: how games run at the edge on Cloudflare Workers for Platforms, how background agents do the engineering work on rails that already exist, and how the GameSpec data model grows to carry a complete build from idea to certification package. The audience includes external Game Builders — terms are glossed on first use.


Edge delivery on Workers for Platforms (decided)

Every game in Builder Studio runs as a Cloudflare Worker — a lightweight JavaScript/WebAssembly process that executes on Cloudflare's global network, a few milliseconds from any player. The routing layer is Workers for Platforms: a Cloudflare product that lets a platform host many independent customer Workers under a single dispatch Worker (the gatekeeper that routes incoming requests). Each collection of customer Workers is a dispatch namespace — think of it as a named bucket in which many isolated Workers live, each addressed by a custom route. Builder Studio uses three namespaces, one per environment:

Namespace Purpose Who can reach it
sandbox Active development sessions; game Workers deployed per build version. Playground for tuning and debugging. Builder (authenticated) via signed URL
demo Operator demo deployments; stable versions promoted from sandbox. Shown to prospective operators. Builder + invited operator contacts
prod Live player traffic. Populated from the certified-game pipeline, not from Builder Studio directly. All players via operator integrations

Immutable game Workers and zero-downtime rollback

Every time the Scaffolder agent generates a new build version, the Deploy agent uploads it to the target namespace as an immutable user Worker — the code never changes in place. A new version is a new Worker deployment. Rollback is a route flip: the dispatch Worker is updated to point to the previous version's Worker, with no redeployment of game code required. Side-by-side comparison of two versions is possible because both Workers are live simultaneously; the dispatch Worker routes to each by URL parameter. This is the real-world version of the version selector already present in SandboxLaunchLab.tsx. Every version deploy also carries a performance budget: bundle size and cold-start/latency checks run in the Deploy agent's smoke test before a version is made routable.

Durable Objects for shared-world sessions

Durable Objects are Cloudflare's consistency primitive: a single JavaScript object with a guaranteed single instance anywhere in the world, serving WebSocket connections with consistent state. Crash-style shared-world sessions (the "crash room" in the current templates — real-time, multi-player) use one Durable Object per room. All players in the same room connect to the same Object instance, ensuring every participant sees the same multiplier curve tick by tick. Single-player turn-based games (Trunk Raider, Silk Road) run stateless per request — no Durable Object, no persistent session state between rounds.

R2 object storage

Cloudflare R2 holds all large binary assets: game bundles (the compiled HTML5 output), art files, replay bundles captured after sandbox sessions, and uploaded documents (PDF info sheets, jurisdiction matrices). R2 is bucket-scoped per namespace so sandbox assets are isolated from demo and prod.

Stub-wallet Worker and fault injection

Every game integration requires a wallet service — an API the game calls to authorise bets, debit stakes, and credit winnings. In the sandbox environment a stub-wallet Worker implements this interface with realistic latency and configurable behaviour. The stub exposes the same five fault codes that the Launch Lab mock in SandboxLaunchLab.tsx already enumerates for testing:

Fault injection is implemented as middleware configured per session at launch time. A request to the session composer with faults=WALLET_TIMEOUT,SESSION_STALE in the launch parameters will cause the stub wallet to respond with those faults at the configured trigger points. This is the proposed real implementation behind the fault toggles that are already mocked in the Launch Lab.

HMAC-signed launch URLs

Launch URLs carry a server-computed HMAC (Hash-based Message Authentication Code — a cryptographic signature that proves the URL was issued by the platform and has not been tampered with). The URL shape is:

https://play.sandbox.standout.games/launch?session=<session-id>&sig=<hmac-hex>

The dispatch Worker verifies the HMAC on every request before routing to the game Worker. An expired or tampered URL returns SESSION_STALE. The current SandboxLaunchLab.tsx generates a fake FNV hash for the URL preview — the real HMAC signing is explicitly marked "not wired" in that component and is what this architecture specifies.

Where games run today GROUNDED · matches main
Production today is GCP Cloud Run — the game-engine container, the nginx game-clients, and the games-service launcher, backed by Cloud SQL, Memorystore and a GCS asset bucket. Workers for Platforms is the decided target, and the engine repo already holds an edge beachhead (games/trunk-raider-edge, apps/edge-do, apps/edge-engine, packages/edge-game-sdk) that makes incremental migration credible. The full today-pipeline is traced in Variant → Live; the comparison lives at today vs target.

Launch request flow

Player any device Dispatch Worker verify HMAC route to namespace Game Worker vN immutable deploy sandbox namespace Durable Object crash room session R2 Storage bundles · art · replays Stub Wallet fault injection signed URL

The dispatch Worker is the sole point that verifies the HMAC and enforces session expiry (SESSION_STALE). Game Workers are isolated per version — each is an independent Worker deployment in the namespace. R2, the Durable Object, and the stub wallet are bound services available to the game Worker via its bindings.

Provisioning not yet implemented
Workers for Platforms dispatch namespaces (sandbox / demo / prod) are not yet provisioned. The stub-wallet Worker, R2 bucket bindings, and real HMAC signing are all specified here but not wired — SandboxLaunchLab.tsx contains explicit warnings to this effect. This architecture page specifies the target; implementation is a future engineering task.

The agent workforce

Agents are pipeline workers running on rails that already exist in the platform. Each agent authenticates as a named MCP bearer-token principal (a row in the api_tokens table in D1 (Cloudflare's managed SQLite database), verified in apps/mcp/src/worker.ts before the request body is read). Every write an agent makes is attributed to that principal and committed in the same atomic batch as an append-only row in the audit_log table — the repo.as(actor) pattern in packages/core/src/repo.ts enforces this. Human gates use comments as the agent–human channel: an agent posts a comment (via repo.addComment) requesting review; a human approves or rejects via a comment reply; the Validation agent monitors comment threads to update gate status. No custom notification system is required — the existing comment model carries the whole handoff.

What exists on main today
The MCP rails (bearer-token principals, append-only audit_log, comments, stage transitions) are implemented and production-ready at apps/mcp/src/worker.ts + apps/mcp/src/api.ts + packages/core/src/repo.ts. The agents described below are the proposed consumers of those rails — none of the agents themselves are implemented yet.

Agent roster

Agent Consumes Produces Human gate
Scaffolder Approved GameSpec (template + config overrides) Variant PR into vhslab/standout-games — new game folder with DEFAULT_CONFIG written from spec overrides Engineer merges the PR — the only step in the pipeline where engineering review is structurally required
Math-Sim Game config from GameSpec.config RTP / volatility simulation report (proof artefact) — stored as a validation run and posted as a comment for review Math owner approves the proof before the RTP-sim validation gate can pass
Asset Uploaded art files (lobby tile, art pack, sound pack) Resized and derived assets, lobby tile crops, safe-area check results — stored in R2 with a comment summarising the output Art owner approves derived outputs before the asset-completeness gate can pass
Copy / Localisation Spec fields + paytable from Math-Sim report Help text, operator-facing copy, locale variants — one document per locale, posted as a comment for review Reviewer approves each locale variant before the localisation gate can pass
Validation Current build state (spec, validation runs, assets, comments) Gate run results with pass / warn / fail per gate, plus fix-next recommendations — read-only reporter, posts a summary comment None — Validation is read-only; it surfaces results but dispatches to specialist agents for fixes
Deploy Merged game build (compiled bundle from standout-games) Workers for Platforms deployment into the target namespace + smoke-test result; rolls back automatically on failure Auto-deploys to sandbox with no gate; human gate on promote to demo or prod only
Cert-Pack Fully validated build (all gates passed, all approvals recorded in audit_log) Certification evidence bundle export — math proof, replay bundles, jurisdiction matrix, operator copy — assembled into a deliverable archive Compliance owner approves the bundle before it is finalised and the build can join the pipeline cert stage
Info-Sheet Generation direction: spec + Math-Sim report + approved assets
Extraction direction: uploaded PDF info sheet (or screenshot set)
Generation: formatted game info sheet document
Extraction: extracted manifest fields, math targets, paytable — prefilling the GameSpec for human correction
Human corrects and approves extracted fields before they are committed to the spec — extraction is assistive, never silently authoritative

Agent activity feed — one build

Agent activity · Trunk Raider: Overdrive v0.2.1 CONCEPT · proposed
14:02:11 Math-Sim Claimed work item rtp-proof #341 — running simulation against config v0.2.1
14:03:48 Math-Sim Sim complete: RTP 95.97% (target 96.00 ±0.25) · volatility High · max win 800× — needs-review
14:05:02 geoff@vhslab.com Approved rtp-proof #341 — RTP within tolerance, volatility class confirmed
14:05:04 Deploy Sandbox deploy v0.2.1 queued — compiling bundle from merged scaffold PR
14:06:31 Deploy Worker deployed to sandbox namespace — smoke test passed (3/3 rounds ok)
14:06:33 Validation Gate run complete: 5 pass, 0 warn, 0 fail — build is sandbox-ready
14:07:15 geoff@vhslab.com Launched sandbox session · player Maya Stone · EUR · mobile-web · UK

Work-item lifecycle

Queued work item created Claimed agent picks up Running agent working Needs review comment posted Approved human accepts Rejected human declines re-queued with feedback

All state transitions are written to the append-only audit_log via repo.as(agentPrincipal). Comments are the structured handoff: an agent posts a comment when entering needs-review; a human responds; the agent (or Validation) reads the thread to determine the transition to approved or rejected.


Data model evolution

The GameSpec interface in packages/shared/src/types.ts today is intentionally minimal: it captures just enough for the Builder to generate a variant scaffold via the Scaffolder CLI. The table below shows what each field holds today versus what the field needs to carry as Builder Studio grows to support the full pipeline. New fields are highlighted; existing fields are listed truthfully as they exist in the code.

The most architecturally significant proposed addition is ownerPrincipal — the tenancy seam. It costs nothing to add now (it is a nullable string stored in the JSON blob alongside the spec), and it is the single field that makes an external Game Builder portal possible later without redesigning the data model. See Two audiences, one surface below.

Field Today (packages/shared/src/types.ts) Proposed (grown spec)
id Optional string, generated server-side on create (spec + Date.now().toString(36)) Unchanged — stable identifier for the spec record
name String — the variant name set by the builder Unchanged — the human-readable build name
templateId String — references a GameTemplate in packages/shared/src/templates.ts (crash, trunk-raider, silk-road) Unchanged for now; becomes baseGameRef below when the mechanic library matures
config Record of override values keyed by ConfigField.key (dotted paths into the game config object) Unchanged — stores only the overrides, not the full merged config
theme Optional { c1, c2, name } — primary/secondary colour pair and theme name Unchanged; may grow to include typography and logo tokens
meta Optional Record<string, unknown> — free-form metadata bucket Unchanged; serves as the escape hatch for fields not yet promoted to typed schema
createdBy Optional string — actor who created the spec (email or principal) Unchanged
createdAt Optional ISO string — set on server create Unchanged
buildPath New — one of new | variant | reskin | prototype | import; determines which pipeline steps are required and which agents activate
baseGameRef New — slug of the base game in standout-games this build branches from; used by the Scaffolder to copy the correct folder and by the Math-Sim agent to compute config diffs
mechanicRefs[] New — array of mechanic record IDs from the mechanics library (see Mechanics library); links the spec to its underlying mechanics for the Math-Sim agent and the validation gate
assets[] New — list of attached asset records (R2 key, type, status, approval state); the Asset agent writes here on upload processing; the validation gate reads here for the asset-completeness check
infoSheet New — document record for the Info-Sheet agent; holds either the R2 key of the generated PDF or the R2 key of the uploaded PDF and the extraction result; one field, two directions (generate or extract)
validationRuns[] New — append-only list of gate run results, each timestamped and versioned; the Validation agent appends here on every run; the per-gate history is what drives the version-comparison view in step 7
sandboxSessions[] New — list of sandbox session records (session ID, version, operator, player, fault config, R2 replay key, status); the Deploy agent writes the session record when a launch URL is issued
versions[] New — append-only version history; each entry holds a snapshot of the config overrides and a reference to the corresponding Scaffolder PR and deployed Worker name, so any version can be re-launched or compared
stage New — current pipeline stage for this build: prototype | internal | review-candidate | operator-demo | cert-package; audited on every transition via audit_log; the join point with the main pipeline (see vision.html · How a build becomes a pipeline game)
ownerPrincipal New — the tenancy seam. Nullable string matching an api_tokens.principal value. Null for internal builds; set to the external builder's principal for external builds. Costs nothing now; enables the external portal later without a data migration. See Two audiences, one surface

Today, GameSpec rows in D1 are stored as a JSON blob in game_specs.data (the specRow() function in packages/core/src/repo.ts). Adding new fields to the JSON blob requires no schema migration — the blob grows incrementally. The only schema change needed is if a new field requires a D1 column for efficient querying (e.g. if ownerPrincipal needs an index for the external portal's "list my builds" query).

How specs join the pipeline

Builder Studio is the zoomed-in view of the build stage of the existing Standout pipeline. When a build is promoted to the cert-package stage, the spec joins the main pipeline as a Game row in the games table, with its stage set to certification. The GameSpec remains the source of truth for config, assets, and math proof — the pipeline Game row adds the operator and jurisdiction deployment records that Builder Studio does not manage. For the narrative version of this join, see vision.html · How a build becomes a pipeline game.

Entity relationships

GameSpec id · name · config stage · ownerPrincipal Versions[ ] config snapshot · PR ref ValidationRuns[ ] gate results · timestamp SandboxSessions[ ] session · replay · faults MechanicRecord id · traits · config schema mechanicRefs[ ] Pipeline games cert → live stages on promote to cert-package

Two audiences, one surface

Builder Studio serves two audiences from the same component surface:

Audience 1
Internal control room
  • The Standout team: pipeline view, revenue surfaces, all operators, all games, all builds.
  • Full promotion authority — can promote above internal review without a second approver.
  • Auth today: Cloudflare Access (Google SSO) fronts the entire dashboard; the authenticated email is the audited actor via Cf-Access-Authenticated-User-Email (set in apps/mcp/src/api.ts).
Audience 2
External Game Builder portal
  • External builders using Builder Studio as a product: see only their own games (filtered by ownerPrincipal); no revenue or pipeline surfaces exposed.
  • Promotion authority limited — cannot self-promote above internal review.
  • Auth: not yet implemented. Two options documented below.

The tenancy seam is ownerPrincipal on GameSpec. Null means internal; a principal string means external. A single list query with a WHERE owner_principal = ? filter (requiring a D1 column index) isolates each builder's data. The same components render both views — the only differences are the filter applied at data fetch time and which navigation items and surfaces are shown.

Authentication options — documented as open

The choice between these two options is not made here. Both are viable; trade-offs are presented neutrally.

Option A
Cloudflare Access

How internal auth works today. Access fronts the hostname, performs SSO, and injects the authenticated email as a header. External builders would be added to the Access policy.

  • Pro: No auth code to write — the platform handles login, MFA, and session management.
  • Pro: Already deployed and battle-tested on the internal dashboard.
  • Con: Requires provisioning each external builder via the Cloudflare Access admin UI (or via the Access API) — not self-service.
  • Con: Access policies are organisation-wide; fine-grained per-builder data scoping still requires application-layer filtering on ownerPrincipal.
Option B
Builder accounts on the D1 token system

Use the existing api_tokens / principals system already in D1 to issue builder-scoped bearer tokens. The api_tokens table and repo.createToken() already support this.

  • Pro: Self-service — a builder can be provisioned programmatically without Cloudflare admin access.
  • Pro: Principal identity is already the audited actor on every write; ownerPrincipal maps directly to api_tokens.principal.
  • Con: Requires building a login / token-issuance UI (email confirmation, token rotation, recovery) — auth surface is application responsibility.
  • Con: Bearer token auth is less ergonomic for browser sessions than cookie-based SSO; token rotation and logout need explicit implementation.
No winner selected
The auth choice is documented as an open question (see Open questions below). Adding ownerPrincipal to GameSpec now costs nothing and keeps both options viable without any data-model rework.

Open questions

These are the three open questions for this architecture page. They are not blockers — the decisions can be made independently; the rest of the architecture is not gated on them.

External-builder authentication
Cloudflare Access (Option A) vs builder accounts on the existing D1 token system (Option B) — see Two audiences, one surface for the full trade-off analysis. The choice affects how builders are provisioned, how sessions are managed, and how much auth code the platform team must own. A product decision and a feasibility check against Access provisioning APIs are needed before implementation begins.
Math-Sim proof runs and RNG recertification
The Math-Sim agent runs simulations against the game config to produce RTP and volatility proof artefacts. The RNG in vhslab/standout-games is certified and locked. Whether running Math-Sim proof simulations against the certified RNG — without touching the engine code itself — carries recertification implications is a compliance question that needs expert input before the Math-Sim agent is built. Flagged for the compliance owner.
Marketplace direction
The mind map includes a branch for mechanics as products — a marketplace where mechanic records can be licensed, revenue-shared, and reused across builders. This is a significant product direction with commercial, legal, and technical implications (mechanic versioning, revenue attribution, certification of third-party mechanics). It is documented here as an open direction only, with no commitment. See the mind map's Marketplace branch for the full option space.