I added history heuristics and killer move tracking to my bot, and now it performs worse against a previous version at 0.5s per move. I decay by 1/2 every search, I only use "quiet" moves, and they're ordered after MVV.
Is this normal in bullet games or should I post code to help debug.
History/Killer heuristics made bot weaker
Moderator: Ras
-
- Posts: 5
- Joined: Thu Sep 18, 2025 3:55 am
- Full name: Joshua Bahr
-
- Posts: 935
- Joined: Sun Nov 19, 2006 9:16 pm
- Location: Russia
- Full name: Aleks Peshkov
Re: History/Killer heuristics made bot weaker
0.5s per move is not bullet for modern engines. It is long time control in fact. 1s + 0.01s is bullet. 10s + 0.1s is average for tests.
-
- Posts: 5
- Joined: Thu Sep 18, 2025 3:55 am
- Full name: Joshua Bahr
Re: History/Killer heuristics made bot weaker
Is it common for history heuristics to make the engine worse in low ply searches (3-4)
-
- Posts: 479
- Joined: Fri Dec 16, 2016 11:04 am
- Location: France
- Full name: Richard Delorme
Re: History/Killer heuristics made bot weaker
With my engine Dumb, which is pretty simple, I have got a +22 Elo increase with killer moves & a +94 Elo increase with history heuristics, measured at 10+0.1s per game (I do not use fixed time per move) in self-play games. I suppose your implementation is not correct somewhere.Joeger-Bahar wrote: ↑Thu Sep 25, 2025 10:52 pm I added history heuristics and killer move tracking to my bot, and now it performs worse against a previous version at 0.5s per move. I decay by 1/2 every search, I only use "quiet" moves, and they're ordered after MVV.
Is this normal in bullet games or should I post code to help debug.
Richard Delorme
-
- Posts: 3
- Joined: Tue Jul 20, 2021 9:20 am
- Full name: Rick Powell
Re: History/Killer heuristics made bot weaker
How were you sorting non-MVV moves before you added history/killers? Were they just in movegen order? If that's the case, then I can't imagine history/killers making things worse. And both history and killers should be very fast to track and use, so the overhead shouldn't be a big factor.
I suspect you have a bug somewhere.
I suspect you have a bug somewhere.
-
- Posts: 441
- Joined: Thu Apr 26, 2012 1:51 am
- Location: Oak Park, IL, USA
- Full name: Erik Madsen
Re: History/Killer heuristics made bot weaker
Killer moves should improve playing strength by 60 Elo or so. But it varies based on what features already are present in your engine.Joeger-Bahar wrote: ↑Thu Sep 25, 2025 10:52 pm I added history heuristics and killer move tracking to my bot, and now it performs worse against a previous version at 0.5s per move. I decay by 1/2 every search, I only use "quiet" moves, and they're ordered after MVV.
Decaying history by 1/2 every search throws away too much information, I believe. My engines reduces the history heuristic by 248 / 256 each search iteration (97%). Perhaps you do mean each search and we're saying the same thing? So after a depth 22 search my statement == your statement? Because (248 / 256) Pow 22 == 1/2.
In my experience, history heuristic isn't worth much at all without LMR. The point of the history heuristic is to order moves for aggressive late move reductions.
Erik Madsen | My C# chess engine: https://www.madchess.net
-
- Posts: 10902
- Joined: Thu Mar 09, 2006 12:37 am
- Location: Tel-Aviv Israel
Re: History/Killer heuristics made bot weaker
Aleks Peshkov wrote: ↑Fri Sep 26, 2025 3:12 am 0.5s per move is not bullet for modern engines. It is long time control in fact. 1s + 0.01s is bullet. 10s + 0.1s is average for tests.
Bullet is not defined by the type of the engine but only by time control.
0.5 second per move is certainly bullet regardless of the software or hardware.
It will continue to be bullet even if hardware becomes 100 times faster.
-
- Posts: 982
- Joined: Fri Mar 10, 2006 4:29 pm
- Location: Germany
- Full name: Jörg Oster
Re: History/Killer heuristics made bot weaker
On this I tend to agree with you.Uri Blass wrote: ↑Mon Sep 29, 2025 12:49 pmAleks Peshkov wrote: ↑Fri Sep 26, 2025 3:12 am 0.5s per move is not bullet for modern engines. It is long time control in fact. 1s + 0.01s is bullet. 10s + 0.1s is average for tests.
Bullet is not defined by the type of the engine but only by time control.
0.5 second per move is certainly bullet regardless of the software or hardware.
It will continue to be bullet even if hardware becomes 100 times faster.
Jörg Oster
-
- Posts: 5
- Joined: Thu Sep 18, 2025 3:55 am
- Full name: Joshua Bahr
Re: History/Killer heuristics made bot weaker
Here is the relevant code. I use negamax, NUM_PIECES is 6, and depth goes from greatest at the top node to least at the leaf nodes.
I use iterative deepening, and call OrderMoves after each move generation (pseudo-legal)
Assigning heuristics and killer moves
Ordering moves
I use iterative deepening, and call OrderMoves after each move generation (pseudo-legal)
Code: Select all
// [color][pieces][start square][end square]
int historyHeuristic[2][NUM_PIECES][64][64] = { 0 };
Move killerMoves[MAX_PLY][2];
Code: Select all
alpha = std::max(alpha, eval);
if (alpha >= beta) // Beta cutoff
{
if (!move.IsCapture(engine->GetBoard()) && (Pieces)move.promotion == Pieces::NONE && !opponentInCheck) // Beta cutoff = good
{
if (killerMoves[ply][0] != move)
{
killerMoves[ply][1] = killerMoves[ply][0];
killerMoves[ply][0] = move;
}
int from = move.startSquare;
int to = move.endSquare;
Piece movingPiece = engine->GetBoard()[from].GetPiece();
int pieceType = (int)movingPiece.GetType();
historyHeuristic[(int)movingPiece.GetColor()][pieceType][from][to] += depth * depth;
}
break;
}
Code: Select all
// Move ordering scores
const int captureBonus = 1000000;
const int killerBonus = 500000;
int Bot::ScoreMove(const Move move, int ply, bool onlyMVVLVA)
{
Piece moved = engine->GetBoard()[move.startSquare].GetPiece();
Piece captured = engine->GetBoard()[move.endSquare].GetPiece();
// MVV-LVA: Most Valuable Victim - Least Valuable Attacker
if (captured.GetType() != Pieces::NONE)
return captureBonus + PieceValue(captured) * 16 - PieceValue(moved);
if (onlyMVVLVA) return 0;
if (move == killerMoves[ply][0]) return killerBonus;
else if (move == killerMoves[ply][1]) return killerBonus - 100;
return historyHeuristic[(int)moved.GetColor()][(int)moved.GetType()][move.startSquare][move.endSquare];
}
// Orders moves in-place, best first
void Bot::OrderMoves(std::vector<Move>& moves, int ply, bool onlyMVVLVA, Move firstMove)
{
std::sort(moves.begin(), moves.end(),
[&](const Move& a, const Move& b) {
return ScoreMove(a, ply, onlyMVVLVA) > ScoreMove(b, ply, onlyMVVLVA);
});
if (!firstMove.IsNull())
{
// Insert previous iter best move at front
moves.insert(moves.begin(), firstMove);
// Remove move if it was already in
auto it = std::find(moves.begin() + 1, moves.end(), firstMove);
if (it != moves.end()) {
moves.erase(it);
}
}
}