Illegal king captures generated during qsearch despite legal move filtering

Discussion of anything and everything relating to chess playing software and machines.

Moderator: Ras

abhinet
Posts: 7
Joined: Thu Feb 19, 2026 8:36 am
Full name: Abhineet Kulkarni

Illegal king captures generated during qsearch despite legal move filtering

Post by abhinet »

Hi all,

I'm debugging a Rust chess engine and have run into a strange issue.

Engine: AbhinEngine 1.0.2 (Rust)

### Symptoms

Perft is completely correct:

Code: Select all

perft 1 = 20
perft 2 = 400
perft 3 = 8902
perft 4 = 197281
perft 5 = 4895892
perft 6 = 120593537
Move generation appears correct from a perft perspective.

However, during search I get:

Code: Select all

position startpos moves e2e4 d7d5 a2a4
go depth 8
Output:

Code: Select all

NNUE eval: -5
info depth 1 score cp 37 nodes 59 time 54 pv d5e4

BLACK KING LOST AFTER make_move h5e8
The panic occurs inside `make_move()` after a move that captures the black king.

### What I found

The panic occurs inside the legality filtering stage:

Backtrace contains:

Code: Select all

Board::make_move
Vec::retain
SearchEngine::qsearch
so the move is being generated and then tested inside the legality filter.

The move being tested is:

Code: Select all

h5e8
which captures the black king.

### Move legality filtering

Current move filtering:

Code: Select all

moves.retain(|&mv| {
    let mut b = board.clone();

    b.make_move(mv);

    let ksq = b.find_king(opposite(b.side));

    ksq.map(|sq|
        !b.is_attacked(sq, opposite(board.side))
    ).unwrap_or(false)
});
This is used in both `generate_moves()` and `generate_captures()`.

### Relevant move generation

Sliders:

Code: Select all

match board.squares[to as usize] {
    Some(cp) if cp.color == color => break,

    Some(cp) => {
        moves.push(Move {
            from,
            to,
            captured: Some(cp.piece),
            promotion: None,
            is_ep: false,
            is_castle: false,
        });
        break;
    }

    None => {
        if !captures_only {
            moves.push(...);
        }
    }
}
Leapers use the same pattern.

Therefore king captures are currently generated if a king is found on the destination square.

### Questions

1. Is it standard practice to explicitly exclude king captures during move generation?
2. Does anything look wrong with the legality filter above?
3. Since perft is correct, would you suspect:
  • * an illegal position entering qsearch,
    * a move generation legality bug,
    * or a make/unmake corruption bug?
Any ideas on where you'd investigate next would be appreciated.

Thanks.
abhinet
Aleks Peshkov
Posts: 1005
Joined: Sun Nov 19, 2006 9:16 pm
Location: Russia
Full name: Aleks Peshkov

Re: Illegal king captures generated during qsearch despite legal move filtering

Post by Aleks Peshkov »

startpos perft 5 = 4865609, perft 6 = 119060324
abhinet
Posts: 7
Joined: Thu Feb 19, 2026 8:36 am
Full name: Abhineet Kulkarni

Re: Illegal king captures generated during qsearch despite legal move filtering

Post by abhinet »

Thanks Aleks. You're right, my perft numbers are not actually correct. I get:

197728 / 4895892 / 120593537

I'll run divide on depth 4 and compare against the reference values before investigating search further.