A collaborative game cataloging every distinguishable closed shape. Draw a closed loop; the geometry pipeline classifies it, hashes the canonical form, and tells you whether it's a fresh discovery or a duplicate.
- React 19 + Vite + TypeScript on the client
- Cloudflare Worker for the API, with a single SQLite Durable Object holding the global registry
- KV for cached counts
- Vitest for the geometry pipeline
- npm workspaces (
client/shared/worker)
A stroke flows through the pipeline in client/src/pipeline/:
- Simplify — Douglas–Peucker via
simplify-jsremoves spikes from dense freehand strokes. Auto-tolerance is ~1% of the bounding-box diagonal. - Smooth + resample — moving-average smoothing, then 128 evenly spaced points around the loop.
- Fit — corner detection, interior angles, edge ratios, and per-segment curvature (
fitShape.ts). - Quantize — angles snap to 30° steps; edge ratios snap to 20% steps. Freehand wobble collapses into clean equivalence classes.
- Canonicalize — descriptors are reduced under the dihedral symmetry group, so a triangle is the same shape regardless of rotation or reflection.
- Hash — the canonical descriptor becomes the lookup key the worker checks.
The shape space is finite by construction.
| Method | Path | |
|---|---|---|
GET |
/api/check?hash=… |
Has this shape been discovered? |
POST |
/api/discover |
Submit a discovery. |
GET |
/api/stats |
Total discovered count. |
Everything else falls through to the static client assets bound to the worker.
npm install
npm run --workspace=shape-research-client dev # client only
npm run --workspace=shape-research-worker dev # worker + bound client assets
npm run --workspace=shape-research-client test # vitestWorker dev requires a Cloudflare account configured for wrangler. The Durable Object and KV bindings are declared in worker/wrangler.toml.
client/ React app — drawing canvas, pipeline, UI
shared/ Types shared between client and worker
worker/ Cloudflare Worker — routes + ShapeRegistry Durable Object