Skip to content

0xferit/schelling-games

Repository files navigation

Schelling Games

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 Links And Docs

docs/game-design.md is the authoritative rules document. Keep gameplay rule changes there instead of re-documenting them in this README.

Product Summary

  • 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.

Architecture Overview

  • public/ contains the static landing page, app shell, and shared frontend assets served by Workers Assets.
  • src/worker.ts is the Worker entrypoint and defines the singleton GameRoom Durable Object that manages queueing, match formation, reconnects, and match settlement orchestration.
  • src/worker/httpHandler.ts handles HTTP routes for auth, profile updates, leaderboard reads, exports, admin actions, and game config. WebSocket gameplay connects through /ws and is delegated into GameRoom.
  • 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.

Repo Layout

.
├── 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

Prerequisites

  • 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 ci

Local Development

Apply local D1 migrations, then start the Worker runtime:

npx wrangler d1 migrations apply DB --local
npm run dev

npm 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.

Test And Quality Commands

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.games deploy plus smoke validation on pushes to main

Configuration And Secrets

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=1x0000000000000000000000000000000AA

To rebuild the AI backfill pool from the latest supported Workers AI catalog entries, run:

npm run sync:ai-bot-pool

The 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 next

Deployment And CI

Before 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 --remote

Production 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 deploy

GitHub 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, deploy schelling-games-next, and smoke-test https://next.schelling.games
  • manual Deploy production to Cloudflare Workers runs: apply production D1 migrations and deploy schelling.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_KEY
  • SCHELLING_GAMES_TURNSTILE_SECRET_KEY

Background

Use the underlying papers rather than only secondary summaries when making concrete product or threat-model decisions.

LLM Usage Note

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.

License

MIT. See LICENSE.

About

They can't talk. They still agree. Playable proof of Schelling point coordination, collecting data for decentralized curation research.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Contributors