fix(engine): refuse FEN positions the engine cannot play #545

Merged
root merged 1 commit from fix/433-refuse-unplayable-fen into main 2026-10-15 23:37:04 +00:00
root commented 2026-10-15 22:39:59 +00:00 (Migrated from gitlab.core-project.space)

Summary

parseFen (and so Game.fromFen) now refuses three positions the engine cannot play, each with its own FenError:

 parseFen(input)
   split fields, parse board / turn / castling / ep / clocks
-  return ok(position)
+  validatePlayable(position)
+    a held castling right whose king or rook is off its home square -> castlingPieceAway { right }
+    an ep square not behind a pawn the other side just pushed two    -> impossibleEp
+    the side not to move is in check                                 -> opponentInCheck
+    else ok(position)

The castling check lives with the castle table in rules/castling.ts (rightWithPieceAway); the ep and check rules sit in notation/fen.ts.

One SAN fixture (k7/8/8/8/4Q2Q/8/8/K6Q w) had Black in check with White to move. Its king moves to a5, which keeps both the three-queen disambiguation and the + of Qh4e1+.

Evidence

  • Before: Game.fromFen("4k3/8/8/8/8/8/8/3K3R w K - 0 1") returns ok, and .legalMoves() throws "no piece at from square". The 12 new cases in fen-parse.test.ts and game.test.ts fail.
    After: it returns { ok: false, error: { type: "castlingPieceAway", right: "whiteKingside" } }. All 754 engine tests pass, and the five commands are green locally.
parseFen FenError: castlingPieceAway  (king off e1, rook off a1, king off e8, white rook on h8)
parseFen FenError: impossibleEp       (off ranks 3/6, wrong side's rank, no pawn in front, own pawn in front)
parseFen FenError: opponentInCheck    (black in check with white to move, and the reverse)
Game.fromFen refuses a castling right whose king is off its home square

Merge Danger

Door: two-way

Blast Radius: front

The front calls Game.fromFen on a stored game's start FEN (local/stored-game.ts), in game/positions.ts and in game/lib/replay.ts. A stored start FEN that hits one of the new refusals now fails to load instead of crashing later. Start FENs come from the engine, so none should.

Closes #433

## Summary `parseFen` (and so `Game.fromFen`) now refuses three positions the engine cannot play, each with its own `FenError`: ```diff parseFen(input) split fields, parse board / turn / castling / ep / clocks - return ok(position) + validatePlayable(position) + a held castling right whose king or rook is off its home square -> castlingPieceAway { right } + an ep square not behind a pawn the other side just pushed two -> impossibleEp + the side not to move is in check -> opponentInCheck + else ok(position) ``` The castling check lives with the castle table in `rules/castling.ts` (`rightWithPieceAway`); the ep and check rules sit in `notation/fen.ts`. One SAN fixture (`k7/8/8/8/4Q2Q/8/8/K6Q w`) had Black in check with White to move. Its king moves to a5, which keeps both the three-queen disambiguation and the `+` of `Qh4e1+`. ## Evidence - **Before:** `Game.fromFen("4k3/8/8/8/8/8/8/3K3R w K - 0 1")` returns `ok`, and `.legalMoves()` throws "no piece at from square". The 12 new cases in `fen-parse.test.ts` and `game.test.ts` fail. **After:** it returns `{ ok: false, error: { type: "castlingPieceAway", right: "whiteKingside" } }`. All 754 engine tests pass, and the five commands are green locally. ```text parseFen FenError: castlingPieceAway (king off e1, rook off a1, king off e8, white rook on h8) parseFen FenError: impossibleEp (off ranks 3/6, wrong side's rank, no pawn in front, own pawn in front) parseFen FenError: opponentInCheck (black in check with white to move, and the reverse) Game.fromFen refuses a castling right whose king is off its home square ``` ## Merge Danger **Door:** two-way **Blast Radius:** front The front calls `Game.fromFen` on a stored game's start FEN (`local/stored-game.ts`), in `game/positions.ts` and in `game/lib/replay.ts`. A stored start FEN that hits one of the new refusals now fails to load instead of crashing later. Start FENs come from the engine, so none should. Closes #433
project_1_bot_a8dd9262857d850eef8dca5206e61de5 commented 2026-10-15 22:54:50 +00:00 (Migrated from gitlab.core-project.space)

Screenshots

Before is production, after is this merge request's review app at bcc826d.

Desktop

Page Before After
Home
Local game
Room

Mobile

Page Before After
Home
Local game
Room
<!-- chess-screenshots --> ### Screenshots Before is production, after is this merge request's review app at bcc826d. #### Desktop | Page | Before | After | | --- | --- | --- | | Home | <img src="https://gitlab.core-project.space/amaurycoudr/chess-project/-/jobs/1731/artifacts/raw/review-screenshots/before/home-desktop.png" width="400"> | <img src="https://gitlab.core-project.space/amaurycoudr/chess-project/-/jobs/1731/artifacts/raw/review-screenshots/after/home-desktop.png" width="400"> | | Local game | <img src="https://gitlab.core-project.space/amaurycoudr/chess-project/-/jobs/1731/artifacts/raw/review-screenshots/before/local-desktop.png" width="400"> | <img src="https://gitlab.core-project.space/amaurycoudr/chess-project/-/jobs/1731/artifacts/raw/review-screenshots/after/local-desktop.png" width="400"> | | Room | <img src="https://gitlab.core-project.space/amaurycoudr/chess-project/-/jobs/1731/artifacts/raw/review-screenshots/before/room-desktop.png" width="400"> | <img src="https://gitlab.core-project.space/amaurycoudr/chess-project/-/jobs/1731/artifacts/raw/review-screenshots/after/room-desktop.png" width="400"> | #### Mobile | Page | Before | After | | --- | --- | --- | | Home | <img src="https://gitlab.core-project.space/amaurycoudr/chess-project/-/jobs/1731/artifacts/raw/review-screenshots/before/home-mobile.png" width="200"> | <img src="https://gitlab.core-project.space/amaurycoudr/chess-project/-/jobs/1731/artifacts/raw/review-screenshots/after/home-mobile.png" width="200"> | | Local game | <img src="https://gitlab.core-project.space/amaurycoudr/chess-project/-/jobs/1731/artifacts/raw/review-screenshots/before/local-mobile.png" width="200"> | <img src="https://gitlab.core-project.space/amaurycoudr/chess-project/-/jobs/1731/artifacts/raw/review-screenshots/after/local-mobile.png" width="200"> | | Room | <img src="https://gitlab.core-project.space/amaurycoudr/chess-project/-/jobs/1731/artifacts/raw/review-screenshots/before/room-mobile.png" width="200"> | <img src="https://gitlab.core-project.space/amaurycoudr/chess-project/-/jobs/1731/artifacts/raw/review-screenshots/after/room-mobile.png" width="200"> |
root commented 2026-10-15 23:26:05 +00:00 (Migrated from gitlab.core-project.space)

added 19 commits

  • 22c74fe3...40b59e31 - 18 commits from branch main
  • bcc826d3 - fix(engine): refuse FEN positions the engine cannot play

Compare with previous version

added 19 commits <ul><li>22c74fe3...40b59e31 - 18 commits from branch <code>main</code></li><li>bcc826d3 - fix(engine): refuse FEN positions the engine cannot play</li></ul> [Compare with previous version](/amaurycoudr/chess-project/-/merge_requests/45/diffs?diff_id=295&start_sha=22c74fe34a4e02515b3255543a83db78c68f7b10)
root (Migrated from gitlab.core-project.space) merged commit into main 2026-10-15 23:37:04 +00:00
Sign in to join this conversation.
No description provided.