Static Exchange Evaluation (SEE)

Static Exchange Evaluation (SEE) SEE simulates a capture sequence on a square to calculate the net material gain/loss. It asks: “If I make this move and we trade pieces on this square, will I gain at least v centipawns?” /// Position::see_ge (Static Exchange Evaluation Greater or Equal) tests if the /// SEE value of move is greater or equal to the given value. We'll use an /// algorithm similar to alpha-beta pruning with a null window. bool Position::see_ge(Move m, Value v) const { assert(is_ok(m)); // Castling moves are implemented as king capturing the rook so cannot be // handled correctly. Simply assume the SEE value is VALUE_ZERO that is always // correct unless in the rare case the rook ends up under attack. if (type_of(m) == CASTLING) return VALUE_ZERO >= v; Square from = from_sq(m), to = to_sq(m); PieceType nextVictim = type_of(piece_on(from)); Color stm = ~color_of(piece_on(from)); // First consider opponent's move Value balance; // Values of the pieces taken by us minus opponent's ones Bitboard occupied, stmAttackers; if (type_of(m) == ENPASSANT) { occupied = SquareBB[to - pawn_push(~stm)]; // Remove the captured pawn balance = PieceValue[MG][PAWN]; } else { balance = PieceValue[MG][piece_on(to)]; occupied = 0; } if (balance < v) return false; if (nextVictim == KING) return true; balance -= PieceValue[MG][nextVictim]; if (balance >= v) return true; bool relativeStm = true; // True if the opponent is to move occupied ^= pieces() ^ from ^ to; // Find all attackers to the destination square, with the moving piece removed, // but possibly an X-ray attacker added behind it. Bitboard attackers = attackers_to(to, occupied) & occupied; while (true) { stmAttackers = attackers & pieces(stm); // Don't allow pinned pieces to attack pieces except the king as long all // pinners are on their original square. if (!(st->pinnersForKing[stm] & ~occupied)) stmAttackers &= ~st->blockersForKing[stm]; if (!stmAttackers) return relativeStm; // Locate and remove the next least valuable attacker nextVictim = min_attacker<PAWN>(byTypeBB, to, stmAttackers, occupied, attackers); if (nextVictim == KING) return relativeStm == bool(attackers & pieces(~stm)); balance += relativeStm ? PieceValue[MG][nextVictim] : -PieceValue[MG][nextVictim]; relativeStm = !relativeStm; if (relativeStm == (balance >= v)) return relativeStm; stm = ~stm; } } The Algorithm 1. Setup Phase: assert(is_ok(m)); Debug check: Verify the move is legal/valid before proceeding. // Castling moves are implemented as king capturing the rook so cannot be // handled correctly. Simply assume the SEE value is VALUE_ZERO that is always // correct unless in the rare case the rook ends up under attack. if (type_of(m) == CASTLING) return VALUE_ZERO >= v; Castling special case: Internally, castling is coded as “king captures own rook,” which would confuse the SEE algorithm. Just assume SEE = 0 for castling moves (safe assumption since you’re not actually capturing anything). ...

January 31, 2026 · 9 min · Sanketh