houserules/packages/server
Joey Yakimowich-Payne de059fe707
feat(chess): per-color preset scope, turn-limited duration, server-authoritative sync
Presets previously lived on a process-global singleton with only a
binary on/off toggle. Two bugs followed:

1. Multiplayer illegal-move errors — the client-side toggle didn`t
   reach the server, so optimistic moves legal under client rules got
   rejected by the server`s unmodified ChessEngine.
2. No way to apply a rule to just white or just black, or to time-box
   it for N turns.

Replaces the shared `PRESET_REGISTRY.active: Set<string>` with
instance-owned `ChessEngine.activePresets: ActivePresetSet`. Each
activation carries:

  - scope: `both` | `white` | `black`
  - turnsRemaining: positive int or null (permanent)

Engine reads `getForColor(color)` per piece, so scope=white never
contributes moves during black`s turn. `applyMove` calls
`tickAfterMove(moverColor)` which implements player-local counting:
white-only durations tick only when white moves.

Compatibility is the LOOSE rule — `incompatibleWith` blocks only when
the two activations have overlapping scopes. `scope=white` + `scope=black`
pair of otherwise-incompatible presets is allowed because the engine
never evaluates both for the same side.

Server changes: GameSession owns an ActivePresetSet. New protocol
messages:

  - client → server: `room.setPresets` with full activation list
  - server → client: `game.presets` broadcast on every set change
    (post-setPresets + post-move-with-expiry)

`game.state` snapshots now include `activations` so reconnects pick
up the current rule set without extra round-trips.

Client changes: PredictionManager applies `game.presets` to the base
engine`s ActivePresetSet and re-renders via onStateChange; cloneEngine
carries activations onto the predicted clone. New hook surface:

  - activations: readonly PresetActivation[]
  - setPresets(next): replace the active set

useMultiplayerGame dispatches setPresets through the socket
(server-authoritative); useChessEngine mutates in-place (local mode).

UI: RulesDrawer + RulesView render scope radios (Both/White/Black)
and a duration input per active preset. Empty duration means
permanent, positive integers last N player-local turns.

Tests:

  - 15 new ActivePresetSet unit tests (scope, tick, loose compat,
    atomicity, clone)
  - 4 new engine-presets integration tests (per-color, duration,
    white-only vs black-only)
  - Migrated older preset tests from `PRESET_REGISTRY.activate` to
    the instance API
  - New E2E regression test: enable knights-leap-twice scope=white in
    multiplayer; verify the double-leap is accepted by the server,
    verify black`s knight cannot use it
2026-04-17 14:23:37 -06:00
..
src feat(chess): per-color preset scope, turn-limited duration, server-authoritative sync 2026-04-17 14:23:37 -06:00
package.json feat(server): add authoritative game session per room (P4.5) 2026-04-16 17:17:42 -06:00
PROTOCOL.md chore(root): scaffold monorepo — Phase 0 complete 2026-04-16 13:32:21 -06:00
README.md chore(root): scaffold monorepo — Phase 0 complete 2026-04-16 13:32:21 -06:00
tsconfig.json feat(server): add authoritative game session per room (P4.5) 2026-04-16 17:17:42 -06:00
vitest.config.ts chore(root): scaffold monorepo — Phase 0 complete 2026-04-16 13:32:21 -06:00

@paratype/chess-server — authoritative WebSocket server