Docs Decisions

Contents

ADR-0009: The web platform: one player, bundles, a separate play origin

  • Status: Proposed
  • Date: 2026-09-30

Context

The site should let people browse and play games, chat with the Director, manage projects and have accounts. Findings (research/web-platform.md):

  • A 4.7.2 web player is 38.8 MB raw and 9.57 MB as Vercel serves it (brotli, measured).
  • Threaded builds need cross-origin isolation of the whole frame chain, and credentialless COEP doesn't work in Safari or iOS Safari. Godot has defaulted to single-threaded web exports since 4.3.
  • Vercel Blob can't serve HTML pages or COOP/COEP headers, but it serves ACAO: *, so the engine can fetch from it. A same-origin rewrite keeps our URLs.
  • User Godot content can execute code: JavaScriptBridge.eval on the web.
  • The xaxis-projects Vercel team is on the Pro plan (checked through the API on 2026-09-30).

Decision

  1. Stack. A Next.js 16 App Router site in web/ (a pnpm workspace package), deployed as the Vercel project gamewright with Root Directory web. Later: AI SDK for chat streaming, Better Auth on Neon Postgres via Drizzle, Vercel Blob for bundles, Vercel Workflows for long runs, Vercel Sandbox with a Godot image for server-side verification.
  2. Play = shared player + bundle. One no-threads web player per engine release, served immutable under /engine/<version>-<hash>/. Bundles live under /g/<gameId>/<bundleHash>/. A game page never needs Godot on a server.
  3. A separate registrable domain for play. Games run in an iframe on a play origin that is not a subdomain of the site, with sandbox="allow-scripts allow-same-origin allow-pointer-lock", a strict CSP including frame-ancestors, no cookies, and origin-checked postMessage.
  4. No threads by default. A threaded build is opt-in, per game, opened top-level on the play origin under a path that sends COOP/COEP.
  5. Phases. Landing page → gallery of playable demos → Director preview (invite, bring your own key, hosted MCP) → accounts and projects → community (publish, remix, moderation).
  6. Deploys are proven. Every deploy script ends by fetching the live URL, and for play pages booting the game in headless Chromium.

Alternatives considered

  • Per-game Godot exports on the server. 45 MB per game, Godot on the hot path, and a format that carries code.
  • Games on the site origin or a subdomain. A subdomain is same-site: cookies and CSRF defences don't separate it.
  • A static HTML landing page (Reelwright's). It's smaller today, but it would be thrown away at the gallery phase. The Next skeleton proves the monorepo and Root Directory pipeline now.
  • Cloudflare R2 as primary storage. A second vendor, and a 25 MiB static-asset cap that the wasm exceeds. Kept as the bandwidth escape hatch.

Consequences

  • The site is cheap to run until the Director chat opens.
  • Two domains are needed. The site's is gamewright.diy (bought 2026-09-30, Namecheap DNS, attached to the Vercel project with www redirecting to the apex). The play origin's separate registrable domain is still the owner's decision, needed before user-made games are hosted.
  • The play origin, the player export and the web ladder rung have to be in place before any user-made game is hosted.

Notes

  • 2026-09-30: the play origin is live on its vercel.app name. Waiting for a play domain would have blocked M5, so the play origin is its own Vercel project, gamewright-play on xaxis-projects, served at its production domain https://gamewright-play.vercel.app and deployed and proven by tools/play-origin/deploy.sh. vercel.app is on the Public Suffix List (private section, "Vercel, Inc", list version 2026-09-24_13-26-36_UTC, fetched from publicsuffix.org the same day), so gamewright-play.vercel.app is itself a registrable domain: a different site from gamewright.diy, and from gamewright.vercel.app too. That is what decision 3 asks for. Moving to a custom play domain later is configuration, not code: add the domain to the gamewright-play project and run the deploy, which prefers a custom production domain over the vercel.app name when it writes the site's game list (web/data/games.json). The site's origins in play/vercel.json's frame-ancestors stay as they are.
  • 2026-09-30: bundles ship inside the play deployment, not in Blob yet. Vercel's limits (docs updated 2026-09-16) cap a Pro CLI deployment at 1 GB of source and 15,000 files, with no per-file cap on static files; the 10 MB cacheable-response cap applies to Function responses. Measured on the live origin: the 39,514,754-byte wasm is served as a static file, brotli, at 9,550,647 bytes on the wire, application/wasm, immutable, and x-vercel-cache: HIT on a repeat fetch. Blob needs a store and a token; it waits for user-made games.

From docs/adr/0009-web-platform.md in the Gamewright repository.