feat(chess): add the engine adapter behind a single boundary - #199
Open
Utzig26 wants to merge 1 commit into
Open
feat(chess): add the engine adapter behind a single boundary#199Utzig26 wants to merge 1 commit into
Utzig26 wants to merge 1 commit into
Conversation
Implements RFC-001. The official chess.js replaces the personal fork the old code depended on, and ChessEngineService becomes the only place in the project allowed to import it. Position is exchanged as PGN. The service is stateless: every method takes the PGN and returns a described position, which is what lets threefold repetition and the fifty move rule come from the engine instead of being derived by hand. Those two rules depend on the history of positions, not on the current one, so replaying the PGN is the whole point of storing it. A test asserts exactly that: the same position reached by replay reports threefold repetition, and the same position loaded from its own FEN reports nothing. This refines the proposal, which had loadGame returning an opaque handle that the other calls would take. Passing the PGN removes the risk of leaking a Chess instance disguised as an opaque type, makes each method pure, and costs two reparses per move flow, which is microseconds against a Mongo round trip. The RFC records the change and the reason. An illegal move returns null rather than throwing. chess.js 1.x throws from move(), and converting that at the boundary keeps the business rules in RFC-003 free of try/catch. The boundary is enforced, not documented: a no-restricted-imports rule fails the lint if any file outside src/chess imports chess.js. Verified by feeding it a file that does. Arbitrary positions reach the tests through a PGN carrying a SetUp and FEN header, which chess.js honours, so mate, stalemate, insufficient material and the fifty move counter are all exercised without adding a FEN entry point to the public contract. RFC-005 will use the same trick. 26 unit tests, no database and no application.
This was referenced Jul 31, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements RFC-001. First piece of the chess domain.
What lands
The official
chess.jsreplaces the personal forkgithub:Utzig26/chess.js#devthat the old code depended on, andChessEngineServicebecomes the only place in the project allowed to import it.Why the PGN is the exchange format
Threefold repetition and the fifty move rule depend on the history of positions, not on the current one. A FEN cannot know that a position already occurred twice. Replaying the PGN lets the engine reconstruct that itself, instead of us deriving it by hand from the move list.
There is a test that asserts exactly this, and it is the one that justifies the storage decision:
Refinement against the RFC
The proposal had
loadGame(pgn)returning an opaqueLoadedGamethat the other calls would take. The service ended up stateless, with the PGN as the only parameter.Passing the PGN removes the risk of leaking a
Chessinstance disguised as an opaque type, makes every method pure, and costs two reparses per move flow — microseconds against a Mongo round trip. The RFC is updated with the change and the reason rather than left describing something the code does not do.The boundary is enforced, not documented
A
no-restricted-importsrule fails the lint if any file outsidesrc/chessimportschess.js. Verified by feeding it a file that does:Other decisions
An illegal move returns
nullrather than throwing.chess.js1.x throws frommove(), and converting that at the boundary keeps the business rules in RFC-003 free oftry/catch.The initial FEN is now complete (
... w KQkq - 0 1). The old schema stored only the piece placement, so any client trusting it before the first move received an invalid string.Arbitrary positions reach the tests through a PGN carrying
[SetUp "1"]and[FEN "..."]headers, whichchess.jshonours. Mate, stalemate, insufficient material and the fifty move counter are all exercised without adding a FEN entry point to the public contract. RFC-005 will use the same trick.Verification
26 unit tests for this module, no database and no application. Full suite green: 82 unit, 58 end-to-end, clean
stricttypecheck and lint.Covered: the twenty opening moves, illegal and unparseable notation, every SAN form the old regex tried to validate (pawn, piece, capture, check, both castles, promotion, en passant), all five terminal outcomes, mate winning over any draw condition, threefold repetition reconstructed by replay, and the derived FEN always matching what the PGN replays to.