Files
blind_chess/DECISIONS.md
T
2026-04-28 10:53:26 -04:00

57 lines
7.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# DECISIONS.md — blind_chess Decision Log
Project-specific decisions. For global/cross-cutting decisions, see `~/bin/DECISIONS.md`.
Format: `YYYY-MM-DD: <decision> — <why>`
## Architecture
- 2026-04-28: Node 22 + TypeScript stack — single-language top-to-bottom; `chess.js` is the de facto rules engine and lives natively here.
- 2026-04-28: pnpm workspace with three packages — `packages/server` (Fastify + ws), `packages/client` (Svelte + Vite), `packages/shared` (TS types). Shared types are the load-bearing decision: the WS protocol drift surface is high-risk and shared types catch it at compile time.
- 2026-04-28: Fastify > Express — better TypeScript ergonomics, faster, cleaner plugin model for `ws` integration.
- 2026-04-28: Svelte > React — smaller bundle, reactive stores fit the constantly-changing board state model. React is overkill for a 2-route app.
- 2026-04-28: `chess.js` for rules + custom `geometricMoves` helper — chess.js doesn't expose pseudo-legal moves; ~80 LoC pure function covers all six piece types. Lives in `packages/shared` so server and client use the same code.
- 2026-04-28: In-memory only; `Map<gameId, Game>` is the entire database — simplest possible. SQLite later if crash recovery becomes painful. Rejected: SQLite for MVP (premature given hobby-project scope).
- 2026-04-28: Single-port Node service — Fastify serves both static client and `/ws` upgrade on port 3000. No reverse proxy logic in our service; Caddy CT 600 handles TLS and routing.
- 2026-04-28: Deploy target: new LXC on node-241 — clean isolation, matches existing patterns. Behind Caddy CT 600 at `chess.sethpc.xyz`.
- 2026-04-28: No auth beyond the hashed game link — friction-minimal; appropriate for casual play. No Authentik gate. Rejected: gating with Authentik (overkill).
- 2026-04-28: 8-character `gameId` (32 bits, `^[a-z0-9]{8}$`), 24-character `PlayerToken` (144 bits) — game IDs short enough for hand-shareable links, tokens long enough to prevent guessing.
- 2026-04-28: WebSocket transport for in-game; REST POST `/api/games` for creation — keeps create flow simple (refresh-friendly, cacheable), keeps in-game traffic on a single channel.
## Implementation
- 2026-04-28: Both modes (vanilla + blind) shipped day one — single engine, mode = per-player view filter. Vanilla mode is "blind mode with full reveal."
- 2026-04-28: Moderator hierarchy refined to four tiers: (1) `no_such_piece`, (2) `no_legal_moves` = pseudo-legal ∅, (3) `wont_help` = pseudo-legal ≠∅ but legal ∅ (pin OR unresolved check), (4) silent = legal moves exist. Each tier is information-strictly-monotonic (more info revealed at later tiers).
- 2026-04-28: Touch-move FSM — tap arms (reversible, client-side only), drag-start or destination-click commits ("touches"). Server tracks `armed: { color, from }`. `no_legal_moves` and `wont_help` checks fire only on first commit with a piece; once committed, all subsequent failed attempts are `illegal_move` with the touch staying.
- 2026-04-28: Highlighting (blind+ON) is purely geometric — function of `(piece type, position, own-piece set)`, no opponent input. Rays extend through unseen opponent pieces. Stop at own pieces. Off-board excluded. Zero opponent info leak. (Vanilla+ON shows engine-truth: legal-empty as green dot, legal-capture as red ring.)
- 2026-04-28: Game creation: creator picks side at create time (default random); single-use link (first joiner takes the open slot, then locked); no spectators in MVP; link dies with the game.
- 2026-04-28: Reconnect via opaque `PlayerToken` in browser `localStorage`, 5-minute grace window — generous for phone hiccups, short enough that abandoned games end. Grace expiry → `endReason: 'abandoned'`, opponent wins. Both-sides simultaneous expiry → game ends with `winner: undefined`.
- 2026-04-28: Pawn promotion via modal (Q/R/B/N), client must include `promotion` field in the move; moderator announces the promotion (it's tactically significant — public info).
- 2026-04-28: All draws auto-detected (stalemate, insufficient material, threefold, 50-move) — casual-play friendly; no "claim" UI.
- 2026-04-28: `Announcement` is an enum (`ModeratorText`), not a free-form string. Display strings live client-side. Tests assert against enum values.
- 2026-04-28: `update` is the single, idempotent server-to-client message that includes a filtered `view` and any new `Announcement` entries. Replaying the latest `update` produces correct render.
- 2026-04-28: Moderator-vocabulary "errors" (no_such_piece, no_legal_moves, wont_help, illegal_move) come through as `Announcement` entries on `update`, NOT as `error` messages. Errors reserved for protocol failures.
- 2026-04-28: Janitor prunes finished games after 30 min idle; active games never expire (until restart).
- 2026-04-28: Rate limiting via per-token bucket on `commit`: 10/s, burst 20 — well above human pace, well below abuse.
- 2026-04-28: Mobile-first responsive design — IDEA.md's share-a-link flow strongly implies phone use.
- 2026-04-28: Logging via Pino (Fastify default) → journald. `/api/health` for Uptime Kuma probe. No Prometheus/OpenTelemetry in MVP.
- 2026-04-28: Resign + draw-offer/accept-decline flow — standard chess UX. Resignation ends without grace; disconnect applies grace.
- 2026-04-28: Game-over screen reveals full board for both sides — post-game review is part of the experience.
## Deferred / Rejected
<!-- Decisions NOT to do something are just as valuable -- prevents re-proposing rejected ideas -->
- 2026-04-28: **Tactical-advice interpretation of "won't help you"** — rejected. The phrase is a check-resolution announcement, not engine evaluation. Subjective "this move is bad" is anti-fun and out of scope.
- 2026-04-28: **Spectator mode** — deferred. Single-use links and no spectators in MVP. Revisit if there's demand.
- 2026-04-28: **Time controls (clocks)** — deferred. Untimed correspondence-style for MVP. Optional 5+0 / 10+0 / 15+10 in a follow-up if Seth wants.
- 2026-04-28: **SQLite persistence** — deferred. In-memory only for MVP. Add when crash recovery becomes painful (1-day implementation: serialize Map on `ExecStop`, deserialize on `ExecStart`).
- 2026-04-28: **End-to-end browser tests (Playwright)** — out of scope for MVP. Protocol-level integration tests cover the same drift surface for ~10× less maintenance. Manual phone+desktop testing suffices.
- 2026-04-28: **Vanilla-only or blind-only MVP** — rejected in favor of both-from-day-one. The shared engine + view-filter architecture means vanilla is essentially free.
- 2026-04-28: **Authentik gate on `chess.sethpc.xyz`** — rejected. The hashed link IS the auth; an additional gate would be friction with no security benefit (link guessing is already infeasible).
- 2026-04-28: **CI/CD automation** — deferred. Manual `pnpm -r build` + `rsync` + `systemctl restart` is fine for a hobby project. Add Gitea Actions later if deploy friction grows.
- 2026-04-28: **Move log / PGN export, post-game replay** — deferred. Announcements are persisted in-game (so the moderator-panel scrollback works); export and replay are post-MVP.
- 2026-04-28: **Public lobby / matchmaking / ratings** — out of scope. This is a private-link game, not a chess site.
- 2026-04-28: **Pre-deploy "server restarting" warning to active players** — stretch goal, not MVP. Mitigation for now: deploy during low-usage windows.
- 2026-04-28: **Client-side AI / hint generation** — explicitly out of scope. Human vs. human only.