Bug
backend/modules/chess/src/pgn.rs — parse_moves() strips move numbers with the regex ^\d+\.+$, which only matches when the move number is its own whitespace-separated token (the spaced form 1. e4).
A compact but perfectly valid PGN where the move number is glued to the move — 1.e4 e5 2.Nf3 Nc6, the form produced by many exporters/engines — tokenizes to ["1.e4", "e5", "2.Nf3", "Nc6"]. The 1.e4 / 2.Nf3 tokens slip through the filter and are then handed to the SAN parser, which rejects them.
Impact
This is live via the JWT-authenticated POST /games/import endpoint (backend/modules/api/src/games.rs:396). Importing a compact-format PGN fails with a 422 "Illegal move" even though the game is completely legal.
Reproduce
[White "A"]
[Black "B"]
[Result "*"]
1.e4 e5 2.Nf3 Nc6 *
- Compact
1.e4 e5 2.Nf3 Nc6 → parsed ["1.e4","e5","2.Nf3","Nc6"] → validate_game = false ❌
- Spaced
1. e4 e5 2. Nf3 Nc6 → parsed ["e4","e5","Nf3","Nc6"] → validate_game = true ✅
Suggested fix
Strip a leading move-number prefix from each token (^\d+\.+, no trailing $ anchor) instead of dropping whole-number-only tokens. This handles 1.e4, 1. e4, 1...e5, and 10.Ba2 uniformly.
Related
While here, parse_moves only removes non-nested variations (\([^()]*\)), so nested variations like (1. d4 (2. c4) Nf6) leave the outer parentheses behind. Noting for a possible follow-up; not addressed here.
Bug
backend/modules/chess/src/pgn.rs—parse_moves()strips move numbers with the regex^\d+\.+$, which only matches when the move number is its own whitespace-separated token (the spaced form1. e4).A compact but perfectly valid PGN where the move number is glued to the move —
1.e4 e5 2.Nf3 Nc6, the form produced by many exporters/engines — tokenizes to["1.e4", "e5", "2.Nf3", "Nc6"]. The1.e4/2.Nf3tokens slip through the filter and are then handed to the SAN parser, which rejects them.Impact
This is live via the JWT-authenticated
POST /games/importendpoint (backend/modules/api/src/games.rs:396). Importing a compact-format PGN fails with a 422 "Illegal move" even though the game is completely legal.Reproduce
1.e4 e5 2.Nf3 Nc6→ parsed["1.e4","e5","2.Nf3","Nc6"]→validate_game= false ❌1. e4 e5 2. Nf3 Nc6→ parsed["e4","e5","Nf3","Nc6"]→validate_game= true ✅Suggested fix
Strip a leading move-number prefix from each token (
^\d+\.+, no trailing$anchor) instead of dropping whole-number-only tokens. This handles1.e4,1. e4,1...e5, and10.Ba2uniformly.Related
While here,
parse_movesonly removes non-nested variations (\([^()]*\)), so nested variations like(1. d4 (2. c4) Nf6)leave the outer parentheses behind. Noting for a possible follow-up; not addressed here.