From 5ec22fe37e66271a5de18639067c42fae2d1473f Mon Sep 17 00:00:00 2001 From: Ayo Date: Sat, 4 Jul 2026 09:19:22 +0200 Subject: [PATCH] chore: add contribution guidance --- docs/decisions/0001-package-boundary.md | 30 ++++++++++++++++ docs/decisions/0002-game-adapter-pattern.md | 34 +++++++++++++++++++ .../decisions/0003-stored-boards-not-seeds.md | 23 +++++++++++++ docs/decisions/README.md | 14 ++++++++ .../docs/headless-core-and-client-design.md | 7 ++-- 5 files changed, 105 insertions(+), 3 deletions(-) create mode 100644 docs/decisions/0001-package-boundary.md create mode 100644 docs/decisions/0002-game-adapter-pattern.md create mode 100644 docs/decisions/0003-stored-boards-not-seeds.md create mode 100644 docs/decisions/README.md diff --git a/docs/decisions/0001-package-boundary.md b/docs/decisions/0001-package-boundary.md new file mode 100644 index 0000000..ebb069c --- /dev/null +++ b/docs/decisions/0001-package-boundary.md @@ -0,0 +1,30 @@ +# 0001. Backend-agnostic package boundary + +**Status:** Accepted · **Repo:** cozy-games · **Date:** 2026-07 + +## Context +cozy-games is a collection of reusable game modules (`@ayo-run/mnswpr`, the +leaderboard package, future extractions). Applications built on these packages +differ in how they store data, authenticate users, and enforce rules. + +## Decision +Packages in this repo are **backend-agnostic and permission-agnostic**: they +expose capability, never storage or authorization. Any backend — database, auth, +server-side logic — is supplied by the consuming application through injected +adapters and hooks (see 0002). Package code contains no storage-, deployment-, or +authorization-specific logic. + +## Rationale (technical) +- Permission-agnostic packages are correct library design: authorization and + storage belong to each deployment (e.g. via security rules and server-side + contexts), not to library code. +- Backend-agnostic packages are more adoptable, testable, and contributable; any + app or backend can consume them. +- Standalone packages are more reusable and testable than a monolithic app. + +## Consequences +- Public API changes in these packages are semver events for downstream consumers. +- Contributions must not introduce coupling to any specific backend or deployment. +- These decision records cover package and architecture decisions only; storage, + deployment, and operations choices belong to each consuming application, not to + this repo. diff --git a/docs/decisions/0002-game-adapter-pattern.md b/docs/decisions/0002-game-adapter-pattern.md new file mode 100644 index 0000000..fa4ec34 --- /dev/null +++ b/docs/decisions/0002-game-adapter-pattern.md @@ -0,0 +1,34 @@ +# 0002. Game-agnostic cores behind a game-adapter interface + +**Status:** Accepted · **Repo:** cozy-games · **Date:** 2026-07 + +## Context +Multiple games (Minesweeper today, Sudoku planned) share generic infrastructure: +move logging, replay, timing, and leaderboards. Building this per-game duplicates +logic; abstracting prematurely risks wrong boundaries. + +## Decision +Core modules are written **game-agnostic from day one**; each game supplies an +**adapter**. Package extraction and npm publishing are deferred until a second +game consumes the seam ("create seams, not packages"). + +### The adapter contract (v0, will be frozen after the second game ships) +A game adapter supplies: + +1. **Event vocabulary** — a typed set of move events (Minesweeper: `reveal | flag | unflag | chord`). Core code handles only the generic envelope `MoveEvent`: `{ seq, clientTs, type: T, payload }` wrapped in a log carrying `schema_version`. +2. **Progress reducer** — `progress(events) → percent` for progress display. +3. **State reducer** — `apply(state, event) → state` for full replay and validation. +4. **Terminal predicate** — `isTerminal(state) → win | loss | null`; generic timing code measures first event → terminal event. +5. **Board payload type** — serialized layout (e.g. grid + mine positions) stored as typed JSON under a `game_type` discriminator. +6. **Headless core** — pure functions (`generateBoard`, reducers, predicates) runnable in Node with no DOM dependency; presentation is a separate layer. +7. **Board injection & resume** — constructors accepting an externally supplied board and a mid-game state snapshot. + +### Rules +- Core/engine modules import no game-specific types. +- The event vocabulary and log schema_version live in the game's package; recorded logs are replayable forever (schema changes are versioned, never breaking). +- Generic storage columns (id, player, outcome, timestamps, game_type) never require migration to add a game; new games add a payload type and adapter only. + +## Consequences +- Adding a game = writing one adapter; infrastructure is untouched. +- The replay engine and the log envelope become extractable packages once validated by the second adapter. +- The adapter interface is frozen and versioned after the second game ships; breaking changes thereafter require a new decision record. diff --git a/docs/decisions/0003-stored-boards-not-seeds.md b/docs/decisions/0003-stored-boards-not-seeds.md new file mode 100644 index 0000000..e8bd76c --- /dev/null +++ b/docs/decisions/0003-stored-boards-not-seeds.md @@ -0,0 +1,23 @@ +# 0003. Stored board layouts, not reproducible seeds + +**Status:** Accepted · **Repo:** cozy-games · **Date:** 2026-07 + +## Context +Replay and shared-board features require two plays of the same board. Two options: +(a) seeded PRNG generation, where a seed reproduces the board; (b) serialize and +store the full board layout at game start, referenced by an id. + +## Decision +Store the full layout (b). Boards are serialized as the game's payload (per +0002 §5) and referenced by an unguessable `game_id`. + +## Rationale +- **No generator lock-in:** seeded reproduction breaks if the generation algorithm ever changes (bugfix, difficulty tuning, library swap). Stored layouts are immune to generator-version drift — old games replay forever. +- **No refactor required:** existing generators keep working; serialization is additive. +- **Simpler single-use semantics:** "a board is played once per player" is a fact about a stored entity, not a rule about seed distribution. +- Layout size is trivial (a Minesweeper expert board < 1KB). + +## Consequences +- Games recorded before layout storage existed cannot be replayed (archived instead). +- `game_id` values may appear in URLs → must be unguessable (no sequential IDs). +- Stored layout data is provided to clients as game rules require. diff --git a/docs/decisions/README.md b/docs/decisions/README.md new file mode 100644 index 0000000..0440ef9 --- /dev/null +++ b/docs/decisions/README.md @@ -0,0 +1,14 @@ +# Decision records + +Short documents that capture a significant technical decision — its context, the +choice made, the reasoning, and the consequences. One file per decision, numbered +and append-only. (This format is commonly called an **Architecture Decision +Record**, or ADR.) + +Records are immutable once accepted. A later decision that changes an earlier one +is added as a new record that supersedes it, rather than editing the old one — so +the history of *why* stays intact. + +- [0001 — Backend-agnostic package boundary](0001-package-boundary.md) +- [0002 — Game-agnostic cores behind a game-adapter interface](0002-game-adapter-pattern.md) +- [0003 — Stored board layouts, not reproducible seeds](0003-stored-boards-not-seeds.md) diff --git a/packages/mnswpr/docs/headless-core-and-client-design.md b/packages/mnswpr/docs/headless-core-and-client-design.md index 7b92763..139b7ff 100644 --- a/packages/mnswpr/docs/headless-core-and-client-design.md +++ b/packages/mnswpr/docs/headless-core-and-client-design.md @@ -12,7 +12,8 @@ Goals, in priority order: 1. **Headless core** — game state is a plain data model, no DOM, no wall clock. 2. **Server-authoritative capable** — the same core can run on a server that owns the RNG, the clock, and the move sequence, so leaderboard times are witnessed, - not claimed. (Closes the score-spoofing gap from the security review.) + not claimed — closing the gap where a client can otherwise report any finish + time it likes. 3. **Backwards compatible** — the existing DOM UI, CSS, and jsdom tests keep working; today's offline play is just "the core with a local transport." 4. **Extraction-ready** — a clean seam between generic (grid/session) and @@ -183,8 +184,8 @@ Two decisions that unlock everything: replay(rules, { seed, config, log }) // → { status, time, valid, reason? } ``` -This is the lighter "verifiable replay" anti-cheat path from the prior discussion: -no live per-move server needed — just call `replay()` in a Cloud Function at +This is the lighter "verifiable replay" anti-cheat path: no live per-move server +needed — just call `replay()` in a Cloud Function at submit time. It requires exactly this headless core and nothing else. > **Determinism is a hard rule for Layers 1–2:** no `Date.now()`, no