feat(engine): filterLegalMoves hook

Phase A.2 of the rule-variants epic — adds the post-aggregation
legal-move filter pipeline.

- Adds FilterLegalMovesContext + filterLegalMoves hook on PresetDef.
  Hook runs AFTER the self-check filter; iterated in activePresets
  list order, each preset sees the prior's output. Subset-only
  contract (drop, don't synthesize); documented + pinned via test.
- Wires the iteration into engine.getAllLegalMoves — immediately
  after the royal-aware self-check filter lands.
- Adds filter-legal-moves.test.ts (6 tests): baseline unchanged,
  no-a-file drop filter, compulsory-capture prototype (proves the
  suicide-chess rule surface before D.1 lands), two-preset
  composition, and subset-contract doc test.

Tests: 1434 passing (was 1428, +6 new). Blocks: unblocks D.1
(suicide-chess).
This commit is contained in:
Joey Yakimowich-Payne 2026-04-20 20:08:54 -06:00
commit db8145fb97
No known key found for this signature in database
4 changed files with 311 additions and 1 deletions

View file

@ -124,3 +124,32 @@ section).
- `packages/chess/src/presets/royal-pieces.test.ts` (new, 300+ lines, 11 tests)
**Blocks**: A.1 now clears B.1, C.1, C.2, C.3, D.1, D.2. Phase A remaining: A.2 (filterLegalMoves), A.3 (shouldAdvanceTurn + HalfMovesThisTurn), A.4 (overridePieceMoves), A.5 (gate + PRESET-API.md docs).
## [2026-04-20 20:08] Task: A.2 — filterLegalMoves hook
**Shipped**:
- `FilterLegalMovesContext` type + `filterLegalMoves` hook on
`PresetDef` (`packages/chess/src/presets/registry.ts`). Context
carries `engine`, `color`, `moves`. Hook signature returns
`readonly LegalMove[]` (subset of input).
- `engine.getAllLegalMoves`: after the self-check filter + royal
resolution, iterate every active preset's `filterLegalMoves` in
`activePresets.list()` order. Each preset sees the prior's output.
- New test file: `packages/chess/src/presets/filter-legal-moves.test.ts`
(6 tests). Covers: baseline unchanged (FIDE opening = 20 moves),
prototype "no-a-file" drop filter, compulsory-capture prototype
(suicide-chess surface — proven before D.1 lands), two-preset
composition (a-file + h-file = 14 surviving), and the subset-
contract documentation test.
**Verification**: `bun run check` green. 1434 tests (was 1428, +6).
**Gotchas**:
- `EntityId` is branded — tests creating synthetic `LegalMove`
literals need `999 as EntityId`. Vitest was happy; `tsc -b` caught
it on the full check.
- Hook contract is subset-only; the engine does NOT enforce it at
runtime (documented + pinned via a test). Future enforcement
would be a visible diff. Keeping enforcement out for now
because the 1 call site per legal-move request is hot-path-
adjacent and a `Set`-based prune would show up.