This assumes that you update the hash key incrementally for the e.p. key, which in generally does not make any sense for a transient contribution, because it is both less efficient and more bug prone. When you put down a piece somewhere it makes sense to make that change to the incremental key, because the alternative would be to XOR that piece into the key for many future positions, as the piece is likely to stay there for a long time. So a one-time XOR into the incremental key saves a lot of work. But XORing the e.p. rights into the incremental key only causes that you have to XOR it out again on the next move. So you have actually doubled the work, compared to just XORing it into the actual (as opposed to the incremental) key...Sven Schüle wrote:3) If the previous position had an e.p. square set then XOR this (resp. the old e.p. file) to the hashkey
4) In addition to 3), if the new position has an e.p. square set then XOR this (resp. the new e.p. file) to the hashkey.
Think of the special case where both sides make a pawn double step and create an e.p. target square with their move => e.p. square is not *cleared* but *changed* with the second move. For this reason I do steps 3) and 4) independently.
so in my engines I calculate the key as
actualKey = incrementalKey ^ stmKey[stm] ^ castlingKey[castlingRights] ^ epKey[epRights];
where the final term is done in a conditional code section only executed when the move is an e.p. capture (which was needed anyway to displace the capture-square from the to-square).
I also do not treat the castling rights incrementally, despite the fact that they are quite persistent and not volatile like e.p. rights. But there are too many conditions that could change them to make incremental update competative, also because a single move can change rights for both sides (e.g. Ra1xa8). By having the castlingRights flags in the low-order 4 bits of an int, I can simply use its value as an index in the castlingKey table of 16 keys, one for each possible combination of castling rights. MakeMove then only has to worry about updating the rights (by clearing bits from a 'spoiler' table: castlingRights &= spoiler[piece] & spoiler[victim]).
For the side to move it really does not matter if you do it incrementally or when you need it, as in changes on every move, so in both cases you always have to do it once in each node. But in fact I realize now you can get rid of this XOR completely (but none of my enginesis that smart):
The incremental update (for the non-special part of the move) does
incrementalKey ^= zobrist[piece][fromSqr] ^ zobrist[piece][toSqr] ^ zobrist[victim][captSqr]
where usually captSqr =toSqr, and for non-captures victim = 0 (or whatever code you use for EMPTY squares). To have a branch-less implementation I actually do have a board full of Zobrist keys for empty squares, all initialized to 0, so that in non-captures this term is effectively absent. But I could have initialized it to the stm key! In that case the non-captures would automatically handle the stm key. To also have captures handle the stm key, in theory we would have to XOR all Zobrist keys for all pieces also with the stmkey. This contribution would cancel in the part that moves the pieces, because both the from- and to-part would both contain it. In practice, of course, you don't even have to do that, as XORing a table of random values with a constant value does not make it any less random.