The Schelling Game is a wallet-authenticated multiplayer coordination game built on Cloudflare Workers. Players commit answers privately, reveal them later, and try to match the answer they expect the most other players to pick. The public product now uses a literature-rooted Schelling prompt pool with both select and controlled open_text prompts. This repository contains the production Worker, the singleton Durable Object that runs the lobby and matches, D1-backed persistence, and the static frontend served at schelling.games.
- Live app: schelling.games
- Canonical game rules: docs/game-design.md
- Architecture decisions: docs/adr/README.md
docs/game-design.md is the authoritative rules document. Keep gameplay rule changes there instead of re-documenting them in this README.
- Browser-based multiplayer coordination game with commit/reveal gameplay and exact-match plurality settlement.
- Wallet login uses signed EIP-191 challenge messages. Sessions, balances, and match state live in the application backend rather than onchain.
- Progression uses an internal token balance plus a moderated public leaderboard.
- Optional Workers AI backfill can help fill public queues for testing and availability. Bot-assisted matches are off the record and do not affect balances, streaks, or leaderboard standing.
public/contains the static landing page, app shell, and shared frontend assets served by Workers Assets.src/worker.tsis the Worker entrypoint and defines the singletonGameRoomDurable Object that manages queueing, match formation, reconnects, and match settlement orchestration.src/worker/httpHandler.tshandles HTTP routes for auth, profile updates, leaderboard reads, exports, admin actions, and game config. WebSocket gameplay connects through/wsand is delegated intoGameRoom.src/domain/contains runtime-agnostic game logic such as commit/reveal validation, prompt selection, and settlement.d1-migrations/contains the D1 schema and schema changes for accounts, player stats, auth challenges, vote logs, example votes, and related data.test/domain/covers pure domain logic under Node/Vitest.test/worker/covers Worker, Durable Object, D1, and HTTP behavior with Cloudflare's Vitest worker pool.
.
├── public/ # Static frontend and landing pages
├── d1-migrations/ # D1 schema and migrations
├── docs/
│ ├── game-design.md # Canonical gameplay rules
│ └── adr/ # Architecture decision records
├── src/
│ ├── domain/ # Runtime-agnostic game logic
│ ├── types/ # Shared TypeScript types
│ ├── worker/ # HTTP/session/persistence helpers
│ └── worker.ts # Worker entrypoint + GameRoom Durable Object
├── test/
│ ├── domain/ # Domain tests
│ └── worker/ # Worker/DO/D1 route tests
├── package.json
└── wrangler.toml
- Node.js
24 npm- Cloudflare Wrangler access for D1 migrations and deploys
- An Ethereum-compatible browser wallet if you want to exercise the live UI manually
Install dependencies with:
npm ciApply local D1 migrations, then start the Worker runtime:
npx wrangler d1 migrations apply DB --local
npm run devnpm run dev starts wrangler dev, which serves the static frontend from public/, the HTTP API, and the WebSocket game endpoint from a local Workers runtime.
If you change D1 schema, re-apply the local migrations before restarting or re-testing flows that depend on the new schema.
| Command | Purpose |
|---|---|
npm test |
Run domain tests in test/domain/. |
npm run test:worker |
Run Worker, Durable Object, D1, and HTTP tests in test/worker/. |
npm run typecheck |
Type-check the Node-side domain/test code with tsconfig.json. |
npm run typecheck:worker |
Type-check Worker code with tsconfig.worker.json. |
npm run lint |
Run Biome checks across the repo. |
npm run smoke:staging |
Run the deployed staging smoke test. Requires STAGING_BASE_URL. |
npm run deploy |
Stamp build metadata, deploy the Worker, and restore checked-in HTML files. |
CI runs:
- Biome linting
- both TypeScript configs
- domain tests with coverage
- Worker tests
- a staging deploy plus smoke validation for same-repo pull requests
Wrangler-managed bindings and default variables live in wrangler.toml. The repo defines both default and staging environments.
| Name | Required | Source | Purpose |
|---|---|---|---|
DB |
Yes | wrangler.toml |
D1 database binding for accounts, stats, auth challenges, vote/export data, and other persistent state. |
GAME_ROOM |
Yes | wrangler.toml |
Durable Object namespace for the singleton GameRoom lobby/match coordinator. |
AI |
Optional | wrangler.toml |
Workers AI binding used for open-text answer normalization. |
ADMIN_KEY |
Optional | Worker secret/var | Protects admin-only HTTP routes such as leaderboard eligibility and CSV export. |
AI_BOT_ENABLED |
Optional | wrangler.toml var |
Enables limited AI queue backfill for undersized public queues. Bot-assisted matches stay off the record. |
AI_BOT_MODELS |
Optional | wrangler.toml var |
Comma-separated Workers AI model list for backfill bot selection. Models are deduplicated, each AI-assisted match may use each model at most once, and backfill is skipped if there are not enough distinct models to reach the current target size. These models must support Workers AI structured JSON output because bot commits use a schema-constrained response. |
AI_BOT_TIMEOUT_MS |
Optional | wrangler.toml var |
Timeout budget for Workers AI bot decisions. |
OPEN_TEXT_PROMPTS_ENABLED |
Required for public play | wrangler.toml var |
Enables the canonical mixed prompt catalog. If disabled, public matches will not start. |
OPEN_TEXT_NORMALIZER_MODEL |
Optional | wrangler.toml var |
Workers AI model used for authoritative open-text answer normalization. It must support structured JSON output. |
OPEN_TEXT_NORMALIZER_TIMEOUT_MS |
Optional | wrangler.toml var |
Timeout budget for each open-text normalization attempt before the retry/backoff loop advances. |
TURNSTILE_SITE_KEY |
Required for interactive landing-page demo voting | Worker var / local .dev.vars |
Public site key exposed through /api/game-config so the landing page can run Turnstile before posting demo votes. |
TURNSTILE_SECRET_KEY |
Required for interactive landing-page demo voting | Worker secret / local .dev.vars |
Secret used by the Worker to validate Turnstile tokens server-side before inserting demo votes. |
CLOUDFLARE_API_TOKEN |
Required for remote migrations and deploys | Shell environment / CI secret | Authenticates Wrangler for staging and production operations. |
STAGING_BASE_URL |
Required only for npm run smoke:staging |
Shell environment / CI | Base URL of the deployed staging Worker that the smoke script targets. |
For local manual testing of the landing-page demo vote flow, Cloudflare provides dummy Turnstile keys that work on localhost. Put them in .dev.vars instead of source control:
TURNSTILE_SITE_KEY=1x00000000000000000000AA
TURNSTILE_SECRET_KEY=1x0000000000000000000000000000000AAFor staging/production, set TURNSTILE_SITE_KEY as an environment variable and provision TURNSTILE_SECRET_KEY with Wrangler secrets:
npx wrangler secret put TURNSTILE_SECRET_KEY
npx wrangler secret put TURNSTILE_SECRET_KEY --env stagingBefore deploying to any remote environment, apply D1 migrations for that environment:
# Staging
npx wrangler d1 migrations apply DB --env staging --remote
# Default/production environment
npx wrangler d1 migrations apply DB --remoteStaging and production environment bindings are declared in wrangler.toml. Production deploys use:
CLOUDFLARE_API_TOKEN=... npm run deployGitHub Actions workflows currently do the following:
- pull requests to
main: run lint, both typechecks, domain tests, and Worker tests - eligible pull requests from the same repository: deploy to staging and run the smoke script
- pushes to
main: apply production D1 migrations and deploy the Worker
- Schelling Coordination in LLMs: A Review
- Tacit Coordination of Large Language Models
- Secret Collusion among AI Agents: Multi-Agent Deception via Steganography
- Subversion via Focal Points: Investigating Collusion in LLM Monitoring
Use the underlying papers rather than only secondary summaries when making concrete product or threat-model decisions.
This repo's optional Workers AI backfill bot is a queue-fill and availability aid, not canonical evidence about human focal points. Keep bot-influenced matches separate from prompt-pool calibration and any claims about human coordination quality.
The prompt pool should be described as a playable, literature-rooted adaptation of focal-point tasks, not as a direct replication of any single academic experiment.
MIT. See LICENSE.