Skip to content

[DO NOT MERGE] Synthetic castling bug — CI mutation test#96

Closed
ber4444 wants to merge 1 commit into
mainfrom
ci/castling-mutation-test
Closed

[DO NOT MERGE] Synthetic castling bug — CI mutation test#96
ber4444 wants to merge 1 commit into
mainfrom
ci/castling-mutation-test

Conversation

@ber4444

@ber4444 ber4444 commented Jul 15, 2026

Copy link
Copy Markdown
Owner

⚠️ DO NOT MERGE — synthetic mutation / CI coverage probe

This PR intentionally breaks a chess rule to test whether CI catches the regression. It will be reverted immediately after the CI verdict is captured.

The bug injected

In chess-core/.../Move.kt, getCastlingMoves previously simulated the king's queenside transit through both c1 and d1 (resp. c8/d8) to verify neither square is attacked. This PR drops the d-square check:

-                // Simulate to check for check on d and c (b doesn't need to be unattacked)
-                val simD = allyPositions.toMutableList().apply { set(kingIndex, d) }
                 val simC = allyPositions.toMutableList().apply { set(kingIndex, c) }
-                if (!checkCheck(d, enemyPositions, enemyPieces, simD) &&
-                    !checkCheck(c, enemyPositions, enemyPieces, simC)) {
+                if (!checkCheck(c, enemyPositions, enemyPieces, simC)) {

That violates the FIDE castling rule: the king may not pass through a square that is attacked. Queenside castling is now legal even when d1/d8 is attacked.

Why this particular mutation

I deliberately picked a gap in the bespoke test suite:

  • CastlingTest.kt does NOT cover this case. It tests the kingside king-path rule (f-file attacker) and the b1 exception, but never places an attacker on the d-file. → These unit tests still pass with the bug.
  • The perft oracle (canonical node counts from chessprogramming.org) runs on castling-rich positions (Kiwipete, Position 4) and should diverge. → This is the safety net firing on a case the hand-written tests miss.

Local verification (before pushing)

Suite Result
CastlingTest (bespoke unit tests) ✅ PASS — confirms the gap
PerftTest > kiwipete_depth_2 ❌ FAIL — expected:<2039> but was:<2041> (+2 illegal castles)
PerftCanonicalGateTest > kiwipete_depth_3 ❌ FAIL
PerftVsStockfishTest > canonical_positions_match_stockfish ❌ FAIL (Stockfish cross-check)

What CI should do

  • check-kotlin (Linux):chess-core:check:chess-core:desktopTest runs the perft rig → RED
  • apple (macOS):chess-core:desktopTest --parallelRED

If both jobs go red on PerftTest/PerftCanonicalGateTest, CI's regression coverage is confirmed. If any goes green, that's a real gap in the gate.

Outcome

Once CI finishes, this branch will be deleted and Move.kt restored to main. Do not merge.

SYNTHETIC BUG — intentional mutation to test CI's regression coverage.

Drops the attack check on the king's queenside transit square (d1/d8) in
getCastlingMoves. This violates FIDE castling rule (king may not pass
through an attacked square), allowing two extra illegal castles in
Kiwipete depth-2 (2041 vs canonical 2039).

The bespoke CastlingTest does NOT cover the d-file case (gap), so it
still passes. The perft oracle (PerftTest/PerftCanonicalGateTest/
PerftVsStockfishTest) catches it on castling-rich positions.

Expect: CI red on :chess-core:desktopTest and :chess-core:check.
Do NOT merge — revert immediately after CI verdict is captured.
@ber4444 ber4444 closed this Jul 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant