docs(preset-api): rule variants gallery + RULES.md cross-refs

Phase F.5 (docs portion) of the rule-variants epic.

- PRESET-API.md gains a 'Rule Variants Gallery' section summarizing
  the 14 new presets grouped by category (King Variants, Objectives,
  Multi-move, Movement). Flags which are scope-aware. Documents
  the reusable scope-flip pattern for future asymmetric presets.
  New section on 'ongoing' two-phase protocol for
  onCheckGameResult composition.

- RULES.md gains a 'Rule Variants Gallery (v2, 2026)' section
  cross-referencing each of the 14 presets with greenchess.net
  where applicable. Overview table bumps from 15 rules to 29.
  Also documents the new 'dual-classic' starting layout.

Tests: 1651 passing (docs-only; no test delta).

(Full Playwright regression + final commit still to come as part
of F.5 once the F.4 e2e spec lands.)
This commit is contained in:
Joey Yakimowich-Payne 2026-04-21 09:23:10 -06:00
commit ed2f0cc660
No known key found for this signature in database
2 changed files with 174 additions and 0 deletions

View file

@ -541,6 +541,55 @@ For preset tests that exercise hooks, see:
---
## Rule Variants Gallery (2026 epic)
The following 14 presets shipped in the rule-variants epic, mirroring variants from greenchess.net/variants.php?cat=4. All compose with the existing presets via the hook surface documented above — no engine changes beyond the 4 new hooks shipped in Phase A.
**King Variants** — redefine what counts as "royal":
- `knightmate-rules` — Every knight of `color` is royal; kings are ordinary pieces.
- `coregal` — King AND queen are royal; mate on either ends the game.
- `dual-king` — Multiple kings per side; mate on any one ends the game ("strong").
- `weak-dual-king` — Multiple kings per side; mating one is survivable; opts out of self-check filter so "sacrifice" moves on one king are legal.
**Objectives** — redefine the terminal-state condition:
- `first-promotion-wins` — First pawn to promote ends the game. Pairs with `pawns-only` layout.
- `suicide-chess` — Lose all pieces to win. Captures compulsory (via `filterLegalMoves`). Empty royal set. Stalemate-wins.
- `capture-all` — Inverse of suicide-chess: capture every enemy to win. Captures not compulsory.
- `extinction-chess` — Wipe out every enemy of a configurable TARGET TYPE. Target set via `engine.presetState<{ targetType: PieceType }>("extinction-chess")`; default `"pawn"`. King remains royal; default mate rules still apply until extinction triggers.
**Multi-move** — redefine the turn flip:
- `double-move` — Both sides play 2 half-moves per turn.
- `monster-rules` — Asymmetric; **scope-aware**. `scope: "white"` → white plays 2, black plays 1 (canonical Monster pairing). `scope: "black"` flips which side gets the double. `scope: "both"` ≡ `double-move`.
**Movement** — redefine piece-specific move generation:
- `berolina-pawns` — **scope-aware**; pawns push diagonally, capture orthogonally. `scope: "white"|"black"|"both"` picks which side(s) use the rule.
- `berolina-pawns-2` — Berolina plus SIDEWAYS captures.
- `bouncing-pieces` — Bishop/queen diagonals reflect off file edges (a-file, h-file) once.
- `bouncing-pieces-2` — Reflects off ALL four edges with a 2-bounce cap.
### Scope-flip pattern (reusable)
Two asymmetric presets (`monster-rules`, `berolina-pawns`, `berolina-pawns-2`) implement **scope-flippable** semantics by inspecting their own `scope` live at hook time:
```ts
let scope: "white" | "black" | "both" = "both";
for (const entry of engine.activePresets.list()) {
if (entry.id === MY_ID) {
scope = entry.scope;
break;
}
}
// Then branch on `scope === "both" || scope === relevantColor`.
```
This makes the activation's `scope` field a first-class rule-authoring knob, not just a duration/lifecycle filter. Any future asymmetric rule that has a "which side does this apply to" question should follow the same pattern.
### `onCheckGameResult` composition with `"ongoing"`
The engine implements a two-phase protocol (fixed in commit `7171dfd`): a TERMINAL GameResult from any preset immediately wins; `"ongoing"` sets a soft "suppress defaults" flag and CONTINUES polling; `undefined` means no opinion. This is what lets `piece-hp` (returns `"ongoing"` to suppress FIDE checkmate when HP kings can survive attacks) compose with `first-promotion-wins` / `capture-to-win` / `last-piece-standing` / `extinction-chess` without any preset short-circuiting the others. See `engine.checkGameResult` for the implementation.
---
## Post-landing backlog
Documented but NOT yet available:
@ -549,5 +598,7 @@ Documented but NOT yet available:
- Per-preset UI panels (not just overlays) — extension slot for sidebar widgets.
- Server-side piece-type manifest echo — when custom types are authored outside the shared `packages/chess`.
- Save-state migration — versioning for saves that predate attribute additions.
- Berolina en-passant — deferred for v1.
- Extinction-chess UI cycling — setting target type via a drawer chip.
File issues or propose extensions via pull request.