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.
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 |
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 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.
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.
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:
WALLET_INSUFFICIENT_FUNDS — debit rejected; player balance too low.WALLET_TIMEOUT — wallet API did not respond within the SLA window.WALLET_DUPLICATE_TXN — idempotency key collision; transaction already recorded.WALLET_ROLLBACK_FAILED — credit could not be issued after a debit; requires manual reconciliation.SESSION_STALE — the signed launch URL has passed its expiry window.
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.
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.
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.
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.
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.
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.
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 | 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 |
rtp-proof #341 — running simulation against config v0.2.1
rtp-proof #341 — RTP within tolerance, volatility class confirmed
sandbox namespace — smoke test passed (3/3 rounds ok)
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.
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).
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.
Builder Studio serves two audiences from the same component surface:
Cf-Access-Authenticated-User-Email (set in apps/mcp/src/api.ts).ownerPrincipal); no revenue or pipeline surfaces exposed.
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.
The choice between these two options is not made here. Both are viable; trade-offs are presented neutrally.
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.
ownerPrincipal.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.
ownerPrincipal maps directly to api_tokens.principal.ownerPrincipal to GameSpec now costs nothing and keeps both options viable without any data-model rework.
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.
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.