houserules/packages/server
Joey Yakimowich-Payne f4e030b8e5
feat(chess): piece-hp mechanic + extensible preset-hook infrastructure
Implements the piece-hp (Hit Points) preset end-to-end and, more
importantly, sets up the infrastructure for rules that need state,
capture interception, or custom UI. Adding a new "guns" rule, a
"poison-cloud" visual, or similar now requires zero changes to
engine.ts, Board.tsx, or protocol.ts.

Engine / preset registry
~~~~~~~~~~~~~~~~~~~~~~~~
PresetDef gains three new optional hooks:

  - onActivate(engine)       \u2014 fires once on active-set transition
                               (inactive \u2192 active). Idempotent by
                               convention so sync paths that re-apply
                               from server state don\`t stomp values.
  - onDeactivate(engine)     \u2014 symmetric cleanup. Also fires when a
                               turn-limited preset expires via
                               tickAfterMove.
  - onBeforeCapture(engine, attacker, target)
      Fires immediately before the engine\`s default capture path.
      Returns `{ consume: true }` to short-circuit: engine skips
      target retraction AND attacker move. Used by piece-hp for
      non-lethal damage; future rules can override however they like.

New ChessEngine.setActivePresets(requests) is the single entry point
that diffs old vs new and fires lifecycle hooks in deterministic
order (deactivate-then-activate). replaceAll stays public for tests
that want to bypass hooks.

ActivePresetSet.tickAfterMove now returns the list of expired ids
so the engine can fire onDeactivate on them.

piece-hp preset
~~~~~~~~~~~~~~~
- onActivate seeds Hp=2 on every piece.
- onDeactivate retracts Hp from all pieces.
- onBeforeCapture decrements Hp; if > 0 consumes the capture (attacker
  stays, target survives, turn advances). At 0, returns without
  consuming so the engine\`s default retract-and-move fires normally.

All capture sites intercepted: regular captures (engine.ts:200) AND
en-passant captures (engine.ts:194). The check-simulation path in
check.ts does NOT fire the hook \u2014 it uses an isolated snapshot
session, so lifecycle side effects don\`t leak into legal-move
filtering.

UI overlay registry
~~~~~~~~~~~~~~~~~~~
New packages/chess/src/ui/preset-overlays.tsx: a module-level registry
mapping preset id \u2192 React component that renders above each piece.
Board.tsx loads the registry via side-effect import and renders any
registered overlays for every active preset.

New packages/chess/src/presets/piece-hp.ui.tsx registers HealthBarPips:
2 pip dots above each piece, filled = remaining HP, empty = lost HP.
Pip color contrasts piece color for readability on either square.

Adding a new visual rule now costs 3 files and zero engine changes:
  presets/foo.ts        (mechanic + lifecycle hooks)
  presets/foo.ui.tsx    (overlay component + registry call)
  presets/ui-overlays-index.ts  (single-line import)

Testing
~~~~~~~
New piece-hp.test.ts: 8 integration tests covering lifecycle (seed,
retract, idempotent re-apply, no-fire on scope-only change) and
capture resolution (non-lethal decrement, lethal retract at HP=0,
HP drain over multiple captures, deactivate mid-game leaves damage
but retracts Hp). Total 861 tests pass (+8), 3/3 E2E green.
2026-04-17 15:54:33 -06:00
..
src feat(chess): piece-hp mechanic + extensible preset-hook infrastructure 2026-04-17 15:54:33 -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