Schelling Games 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 check:max-lines |
Fail if the current branch causes any changed text file to cross above 1000 lines compared against the merge-base of MAX_LINES_BASE_REF and HEAD (by default, typically origin/main). Override the base with MAX_LINES_BASE_REF and the limit with MAX_LINES_LIMIT. |
npm run smoke:remote |
Run the deployed remote smoke test. Requires SMOKE_BASE_URL. |
npm run deploy |
Stamp build metadata, deploy the Worker, and restore checked-in HTML files. |
CI runs:
- Biome linting
- a changed-file line-count gate that blocks new crossings above 1000 lines while grandfathering already-oversized files
- both TypeScript configs
- domain tests with coverage
- Worker tests
- a PR-scoped preview deploy plus smoke validation for same-repo pull requests
- a
next.schelling.gamesdeploy plus smoke validation on pushes tomain
Wrangler-managed bindings and most variables live in wrangler.toml. Some optional variables (timeouts) default in code and can be overridden via env vars. The repo defines default and next environments. PR previews use a temporary Wrangler config generated by CI.
| 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. Models may use guided_json, response_format JSON mode, or a prompt-only fallback, but they must still return short, parseable JSON/text answers for both select and open_text bot decisions. |
AI_BOT_MODEL_OUTPUT_MODES |
Optional | wrangler.toml var |
Comma-separated model=mode mapping for AI backfill output handling. Supported modes are guided_json, response_format, and prompt_only. This overrides the Worker's built-in fallback allowlists when present. |
AI_BOT_TIMEOUT_MS |
Optional | Code default / env override | Timeout budget for Workers AI bot decisions (default: 30 000 ms). |
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 | Code default / env override | Timeout budget for each open-text normalization attempt before the retry/backoff loop advances (default: 30 000 ms). |
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 PR preview, next, and production operations. |
SMOKE_BASE_URL |
Required only for npm run smoke:remote |
Shell environment / CI | Base URL of the deployed Worker that the remote 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=1x0000000000000000000000000000000AATo rebuild the AI backfill pool from the latest supported Workers AI catalog entries, run:
npm run sync:ai-bot-poolThe sync script uses the current Cloudflare catalog, filters to the task buckets Text Generation, Text Classification, and Translation, sorts by newest created_at, and then writes the latest bot-compatible subset into wrangler.toml. It does not admit every catalog model automatically: only models that this repo already knows how to drive safely are eligible for the live backfill pool.
npm run deploy runs this sync automatically before stamping and deploying the Worker.
For next and 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 nextBefore deploying to any remote environment, apply D1 migrations for that environment:
# Next
npx wrangler d1 migrations apply DB --env next --remote
# Default/production environment
npx wrangler d1 migrations apply DB --remoteProduction and next environment bindings are declared in wrangler.toml. The next Worker is attached to next.schelling.games in Cloudflare, and manual production deploys use:
CLOUDFLARE_API_TOKEN=... npm run deployGitHub Actions workflows currently do the following:
- pull requests to
main: run lint, the changed-file line-count gate, both typechecks, domain tests, and Worker tests - eligible pull requests from the same repository: deploy a PR-scoped preview Worker plus D1 database, then run the smoke script against that preview URL
- pushes to
main: apply next D1 migrations, deployschelling-games-next, and smoke-testhttps://next.schelling.games - manual
Deploy production to Cloudflare Workersruns: apply production D1 migrations and deployschelling.games
PR previews use the GitHub pull request merge ref, so they show the merged result rather than only the branch head. Each same-repo PR gets a stable workers.dev preview URL backed by a dedicated Worker service and D1 database, and both are deleted automatically when the PR closes.
To support PR previews in GitHub Actions, add these repository secrets alongside CLOUDFLARE_API_TOKEN:
SCHELLING_GAMES_ADMIN_KEYSCHELLING_GAMES_TURNSTILE_SECRET_KEY
- 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.