geometric packing game (tetris-ish) on an icosahedron

README.md

Rad Cohesion #

A browser puzzle about packing triangular pieces onto a polyhedron. Fill an original face to clear it; pieces can cross face boundaries and wrap around corners. Play against the clock or build layouts in Free Play, then share scenarios and submit scores.

Run locally #

Use Node.js 24.16 or later in the Node 24 series and a browser with WebGL.

npm ci
npm run dev

Open http://127.0.0.1:5173/game/ and press Start. The website and APIs run together; local play needs no account or external service. Use ?seed=1234 for a reproducible piece sequence. Use 127.0.0.1 rather than localhost if signing in, so the game and OAuth callback share browser storage.

Play #

In Timed mode, place the current piece before its countdown expires. A legal piece locks automatically at timeout; an invalid timeout costs a strike. Three strikes end the run. A blocked manual placement leaves the piece and timer active. A jammed next piece ends the run only if no saved reward piece can rescue it.

Full faces clear simultaneously. Points are 100 × faces² × level, using the level before the clear. Every five cumulative faces cleared advances a level. Subsequent piece timers shorten from 10 seconds by one second per level, down to four seconds. New game lets you choose a starting level from 1 to 99. Restart repeats the seed and retains the starting layout and level.

Input Action
Hover Aim the piece; mint means legal, red means blocked.
Click / Space Place the piece.
R / Shift+R Rotate clockwise / counterclockwise by 60°.
F Reflect the piece.
Drag Orbit in 3D or pan in 2D.
Scroll / pinch Zoom.
3D / 2D Change the display without changing the game.
Recenter / C in 2D Center the current aim and reset zoom.
Reset view Center the current aim at default zoom.
P / Esc / Pause Pause or resume; pausing hides the board.

Touch uses center aim: drag the board beneath the preview, then tap to place. On-screen rotate, flip, and Place buttons replace keyboard controls. Desktop can enable Center aim too. Dragging never places or erases a triangle. On compact screens, scroll vertically to reach controls when needed.

Clearing multiple faces banks one saved piece: two faces earn a three-tile Triamond, three earn a two-tile Diamond, and four earn a one-tile Moniamond. Activating one suspends the normal piece and its timer while keeping the queue. Afterward, the normal piece resumes. Saved pieces stack; only one can be active at a time.

Boards and Free Play #

New game offers an icosahedron or octahedron and an edge subdivision k from 1 to 10. Each original face contains k² cells: 20 × k² on an icosahedron, 8 × k² on an octahedron. The default is an icosahedron at k=4 (320 cells). URLs can select these too: ?polyhedron=octahedron&k=4.

At k=1, each four-tile piece fills and clears four faces. At larger sizes, Hook can wrap around an octahedron vertex to clear four faces; Ribbon and Crown can touch at most three there. The size ceiling limits placement-index memory.

Free Play has no countdown, random queue, strikes, or game over. Select any piece or the eraser; full faces still clear. Clear board removes all cells. Changing the board shape or size creates an empty board, so save a scenario first if you want to keep the layout.

Audio settings has separate Music (Off, Track 1, Track 2) and Sound effects choices. Both start off and remember their settings. Audio starts after user interaction; music pauses with the game, hidden tab, or game over.

Save and share #

The header links to Play, Leaderboards, Scenarios, and Library.

  • Save game names a resumable checkpoint. Library manages saved games, local scenarios, and finished runs. These stay in this browser profile and site origin; signing in does not upload or synchronize them.
  • Leaving play preserves temporary recovery for the tab. Play and reload restore it paused. An explicit new game or scenario launch replaces it.
  • Save scenario in Free Play stores a reusable layout. Sign in to publish a separate public copy. Public Scenarios supports search, previews, and play.
  • Leaderboards lists layouts with submitted results and their populated starting levels. Exact standings offer Play this challenge.
  • Submit score on Game over or in Library publishes to the signed-in account, or offers a Guest submission with an optional name and sign-in link. All current scores are unverified summaries.

Multiplayer score races #

Choose Multiplayer in the game toolbar or open /multiplayer/. Create a room for 2–4 players, choose a board or local scenario, starting level, and a 1, 2, 3, or 5 minute duration. Share the invite link or choose Add bot to fill open seats without another browser tab. No account is needed. Human players mark Ready; bots ready automatically after preparation. The host starts the shared countdown and seeded race, and can remove bots before it starts.

Your board runs locally. Opponent boards show their live layouts and placement previews; rotate them independently or choose Inspect board to enlarge one. The race clock continues during inspection, tab switching, and connection loss. There is no individual pause or restart. An early game over leaves you watching with your score retained. Highest reported score wins; equal scores share rank. The race ends early when everyone has finished, or when the sole active player has a strict lead over every finished opponent. Ties and trailing survivors continue until the outcome changes or time expires.

The server retains disconnected seats for 30 seconds. Rejoining the same tab can resume its local checkpoint; losing that checkpoint forfeits the seat but still allows watching. Rooms are temporary and end when the server restarts. Race results are client-reported and unverified, and are separate from the single-player leaderboards.

Install and offline play #

The production app can be installed through the browser's install menu, or Safari's Share → Add to Home Screen. After the first online load finishes caching, the game, local libraries, and audio work offline. Public discovery, sign-in, and submissions need connectivity. Updates activate after existing app windows close.

For local production testing, run npm run build then npm start and open http://127.0.0.1:3000/. Installation requires HTTPS or localhost; development mode does not register the production service worker.

Development #

npm test                 # Core, persistence, rendering, and UI unit tests
npm run lint             # Oxlint and vendored anti-slop rules
npm run typecheck        # Svelte and browser/server TypeScript checks
npm run build            # Typecheck and build the production Node app
npm run test:server      # AppView, migrations, and HTTP API tests
npm run test:browser     # Playwright interaction and navigation tests
npm run test:pwa         # Production subpath, installability, and offline tests
npm run test:shutdown    # Production server shutdown test; build first

Install Chromium once with npx playwright install chromium. Browser fixtures expose test state; the production game does not. npm run test:all runs unit tests, build, server tests, and shutdown checks; browser and PWA checks are separate.

Documentation #

  • Architecture: code boundaries, data flow, and lifecycle.
  • Deployment: configuration, OAuth, ingestion, and backups.
  • Roadmap: current capabilities and deferred features.

For implementation contracts, see sessions and snapshots and records and APIs. Start with Architecture before those references.