chore: add contribution guidance
This commit is contained in:
parent
062b912549
commit
5ec22fe37e
5 changed files with 105 additions and 3 deletions
30
docs/decisions/0001-package-boundary.md
Normal file
30
docs/decisions/0001-package-boundary.md
Normal file
|
|
@ -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.
|
||||||
34
docs/decisions/0002-game-adapter-pattern.md
Normal file
34
docs/decisions/0002-game-adapter-pattern.md
Normal file
|
|
@ -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<T>`: `{ 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.
|
||||||
23
docs/decisions/0003-stored-boards-not-seeds.md
Normal file
23
docs/decisions/0003-stored-boards-not-seeds.md
Normal file
|
|
@ -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.
|
||||||
14
docs/decisions/README.md
Normal file
14
docs/decisions/README.md
Normal file
|
|
@ -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)
|
||||||
|
|
@ -12,7 +12,8 @@ Goals, in priority order:
|
||||||
1. **Headless core** — game state is a plain data model, no DOM, no wall clock.
|
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
|
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,
|
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
|
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."
|
working; today's offline play is just "the core with a local transport."
|
||||||
4. **Extraction-ready** — a clean seam between generic (grid/session) and
|
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? }
|
replay(rules, { seed, config, log }) // → { status, time, valid, reason? }
|
||||||
```
|
```
|
||||||
|
|
||||||
This is the lighter "verifiable replay" anti-cheat path from the prior discussion:
|
This is the lighter "verifiable replay" anti-cheat path: no live per-move server
|
||||||
no live per-move server needed — just call `replay()` in a Cloud Function at
|
needed — just call `replay()` in a Cloud Function at
|
||||||
submit time. It requires exactly this headless core and nothing else.
|
submit time. It requires exactly this headless core and nothing else.
|
||||||
|
|
||||||
> **Determinism is a hard rule for Layers 1–2:** no `Date.now()`, no
|
> **Determinism is a hard rule for Layers 1–2:** no `Date.now()`, no
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue