From 041dcdf78418f81be7830caeefe1193466232351 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 13 Nov 2026 05:53:42 +0000 Subject: [PATCH] feat(search): Add capture history to the capture ordering MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Captures were ordered by SEE and then MVV-LVA alone, so nothing learned which captures actually cut. A capture history table, keyed by the side, the piece taking, the to square and the piece taken, is taught at the quiet history's cutoff sites: a capture that cuts gains d², and every capture tried before a cutoff loses d², whether a capture or a quiet cut. A capture that cuts still teaches neither quiet memory. It is read where the quiet memories are (the main search's nodes, not the root or quiescence), on both the keyed and the spilled list paths. An entry adds 16 a point to the capture's key, so a full entry (131,072) is short of one pawn of SEE (200,000) by more than a quiet's widest history and the largest tiebreak. The swap's sign still picks the band, a losing capture stays behind every quiet, and the table can reorder captures a pawn apart but never two. `the_bands_do_not_overlap` checks this, and the table is cleared with the other memories at each go. The bench moves 7.1% (9,312,660 to 9,972,665). The reference search does not read the memories, so its counts hold. The tactical suite goes from 231 to 232 and the strategic total from 104,989 to 104,632, inside the 447 the total moves by under one centipawn nudges. The floor test on WAC.021 is moved to the new depth nine floors. Co-Authored-By: Andrew Wright Bench: 9972665 Elo: not measured --- README.md | 4 +- arche-core/src/bench.rs | 34 ++--- arche-core/src/engine.rs | 2 +- arche-core/src/ordering.rs | 247 +++++++++++++++++++++++++++++-------- arche-core/src/strategy.rs | 2 +- arche-core/src/tactics.rs | 2 +- tests/uci_session.rs | 6 +- 7 files changed, 221 insertions(+), 76 deletions(-) diff --git a/README.md b/README.md index 114190ad..e42540a2 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,8 @@ search, principal variation search, aspiration windows at the root, reverse futi null move pass, late move reductions and pruning off a margin under beta, quiet futility pruning and a late move count at the shallowest three depths, and the check and singular extensions. Captures are ordered by what a -static exchange evaluation says the swap wins, with -MVV-LVA breaking the ties between the ones it prices alike, and the quiet moves by the ones that +static exchange evaluation says the swap wins, then by which captures have +cut off before, with MVV-LVA breaking the ties, and the quiet moves by the ones that have cut off before. Evaluation is material, piece square tables, piece mobility, the pawns standing in front of each king, pawn structure, and the squares around each king the enemy pieces attack, and a tempo for the side to move, tapered between a middlegame and an diff --git a/arche-core/src/bench.rs b/arche-core/src/bench.rs index e838f2e1..b500611f 100644 --- a/arche-core/src/bench.rs +++ b/arche-core/src/bench.rs @@ -616,8 +616,8 @@ mod tests { ], [ [182_505, 7_085, 22_788, 535, 0, 287, 0], - [10_070, 755, 2_598, 57, 28, 0, 0], - [12_991, 977, 3_364, 3, 0, 0, 47], + [10_197, 757, 2_633, 59, 29, 0, 0], + [13_152, 968, 3_392, 4, 0, 0, 41], ], "nodes, cutoffs, stores, tainted, tcuts, refused, skipped" ); @@ -833,22 +833,22 @@ mod tests { assert_eq!( counted, vec![ - ("start", 810_901), - ("italian", 925_606), - ("ruy lopez", 568_063), - ("kiwipete", 1_717_616), - ("perft 4", 2_017_487), - ("promotions", 315_447), - ("middlegame", 519_737), - ("sharp middlegame", 683_004), - ("bratko kopec 1", 11_568), + ("start", 766_029), + ("italian", 685_915), + ("ruy lopez", 457_708), + ("kiwipete", 2_252_811), + ("perft 4", 1_346_541), + ("promotions", 1_800_032), + ("middlegame", 351_532), + ("sharp middlegame", 629_743), + ("bratko kopec 1", 11_454), ("wac 4", 1_516), - ("rook and pawns", 314_171), - ("tarrasch", 186_731), - ("lucena", 442_470), - ("philidor", 190_861), - ("minor endgame", 185_295), - ("queen endgame", 360_712), + ("rook and pawns", 246_068), + ("tarrasch", 186_494), + ("lucena", 441_662), + ("philidor", 190_796), + ("minor endgame", 185_292), + ("queen endgame", 357_597), ("king and pawn", 34_240), ("trebuchet", 27_235), ] diff --git a/arche-core/src/engine.rs b/arche-core/src/engine.rs index 3195dcb5..d945947a 100644 --- a/arche-core/src/engine.rs +++ b/arche-core/src/engine.rs @@ -2178,7 +2178,7 @@ impl AlphaBeta { } } } - // no memories here: they say nothing about captures or evasions + // no memories here: the main search alone reads them let Ordered { front, .. } = self.ordering .order_split(&self.board, &mut moves, captures, pv_play, None, 0); diff --git a/arche-core/src/ordering.rs b/arche-core/src/ordering.rs index 94a833a5..56af37d5 100644 --- a/arche-core/src/ordering.rs +++ b/arche-core/src/ordering.rs @@ -26,7 +26,11 @@ //! squares alone, and the continuation table keys it by its piece and to //! square under the move that reached the node, so a reply is scored //! against what it replies to. A quiet's history is the two entries -//! summed. The search fills the memories, `SearchConfig::move_memory` says +//! summed. A third table scores the captures: which have cut, by the +//! piece taking, the square and the piece taken, a weight that orders +//! captures the swap prices alike or a pawn apart and leaves the bands +//! where the swap's sign put them. The search fills the memories, +//! `SearchConfig::move_memory` says //! whether a node consults them, and `SearchConfig::continuation_history` //! whether the search tells the continuation table what it played. //! @@ -96,6 +100,19 @@ const _: () = assert!(MOVE_LIST_INLINE <= u64::BITS as usize); /// ordering behind one the search knows nothing about. type History = [[[i32; 64]; 64]; 2]; +/// Cutoffs against tries for each capture, by the side that made it, the +/// piece taking, the to square and the piece taken, under the quiet +/// history's update and bound. +type CaptureHistory = [[[[i32; 6]; 64]; 6]; 2]; +/// One side's capture history. +type SideCaptures = [[[i32; 6]; 64]; 6]; +/// What one point of a capture's entry is worth in its key. A full entry +/// is short of one pawn of SEE (`SEE_UNIT * 100`), so two entries at +/// opposite bounds can overturn one pawn of swap and never two, and short +/// by more than a quiet's widest history, so a losing capture still keys +/// below every quiet. `the_bands_do_not_overlap` checks both. +const CAPTURE_HISTORY_WEIGHT: i64 = 16; + /// The previous moves the continuation table keys by: each piece and to /// square. const CONTINUATION_PREVIOUS: usize = 6 * 64; @@ -147,6 +164,7 @@ pub(crate) struct MoveOrdering { /// node's list. killers: [[Option; 2]; MAX_PLY as usize], history: History, + capture_history: CaptureHistory, /// The continuation table, cutoffs against tries as `history` keeps /// them, indexed by the side to move, the previous move's piece and to /// square, then this move's piece and to square. Sixteen bits, since @@ -172,6 +190,7 @@ impl MoveOrdering { sorted: [Play::NOWHERE; MOVE_LIST_INLINE], killers: [[None; 2]; MAX_PLY as usize], history: [[[0; 64]; 64]; 2], + capture_history: [[[[0; 6]; 64]; 6]; 2], continuation: vec![0; CONTINUATION_LEN].into_boxed_slice(), line: [NO_PREVIOUS; MAX_PLY as usize], quiet_keys: Box::new([[0; QUIET_KEYS]; MAX_PLY as usize]), @@ -179,7 +198,7 @@ impl MoveOrdering { } } - /// Forget both memories, and the line the continuation table is read + /// Forget the memories, and the line the continuation table is read /// under. Each `go` starts with this: the memories describe the tree /// being searched now, and the iterations of one deepening share them. pub(crate) fn forget(&mut self) { @@ -189,6 +208,7 @@ impl MoveOrdering { from.fill(0); } } + self.capture_history = [[[[0; 6]; 64]; 6]; 2]; self.continuation.fill(0); self.line.fill(NO_PREVIOUS); } @@ -236,17 +256,17 @@ impl MoveOrdering { } /// A move that cut a node off, `ply` from the root with `depth` left, - /// and the moves the node made and searched before it. It becomes this - /// ply's first killer. Its history entries gain the square of the depth - /// and every quiet in `tried` loses the same: the maluses are the - /// denominator a count of cutoffs lacks, without which an entry - /// rewards a move for being ordered early. The continuation entries - /// are taught only under a previous move the line holds, and `board`, - /// the node's position, says which piece each move is. + /// and the moves the node made and searched before it. A quiet becomes + /// this ply's first killer. Its history entries gain the square of the + /// depth and every move of its kind in `tried` loses the same: the + /// maluses are the denominator a count of cutoffs lacks, without which + /// an entry rewards a move for being ordered early. The continuation + /// entries are taught only under a previous move the line holds, and + /// `board`, the node's position, says which piece each move is. /// - /// A capture is dropped, cutting or tried, since the swap orders the - /// captures already. The caller passes the moves it has rather than - /// sorting the quiets out first. + /// Every capture in `tried` is marked down in the capture history, + /// whatever cut. A capture that cuts teaches neither quiet memory. The + /// caller passes the moves it has rather than sorting them out first. pub(crate) fn cutoff<'a>( &mut self, board: &Position, @@ -257,7 +277,15 @@ impl MoveOrdering { depth: u8, ) { debug_assert!(ply < MAX_PLY as usize, "no killers past the rail"); + // d², held to the bound: a step wider than the bound would carry + // an entry past it. Only the rail plus a chain of check extensions + // reaches such a depth, but both bands rest on the bound holding + let bonus = (i32::from(depth) * i32::from(depth)).min(HISTORY_MAX); if m.capture.is_some() { + for t in tried { + self.capture_from(board, color, t, -bonus); + } + self.capture_from(board, color, m, bonus); return; } let killers = &mut self.killers[ply]; @@ -267,19 +295,17 @@ impl MoveOrdering { killers[1] = killers[0]; killers[0] = Some(*m); } - // d², held to the bound: a step wider than the bound would carry - // an entry past it. Only the rail plus a chain of check extensions - // reaches such a depth, but both bands rest on the bound holding - let bonus = (i32::from(depth) * i32::from(depth)).min(HISTORY_MAX); let base = self.continuation_base(color, ply, depth); for t in tried { - if t.capture.is_none() { - gravitate( - &mut self.history[color as usize][t.from as usize][t.to as usize], - -bonus, - ); - self.continue_from(base, board, t, -bonus); + if t.capture.is_some() { + self.capture_from(board, color, t, -bonus); + continue; } + gravitate( + &mut self.history[color as usize][t.from as usize][t.to as usize], + -bonus, + ); + self.continue_from(base, board, t, -bonus); } gravitate( &mut self.history[color as usize][m.from as usize][m.to as usize], @@ -288,6 +314,21 @@ impl MoveOrdering { self.continue_from(base, board, m, bonus); } + /// One capture history entry's update. A quiet, or a move whose from + /// square is empty, which only a test's made up move has, is passed + /// over. + #[inline(always)] + fn capture_from(&mut self, board: &Position, color: Color, m: &Play, bonus: i32) { + let (Some(victim), Some(piece)) = (m.capture, board.get_piece_index(m.from)) else { + return; + }; + gravitate( + &mut self.capture_history[color as usize][piece as usize][m.to as usize] + [victim as usize], + bonus, + ); + } + /// One continuation entry's update, when the node has a block and the /// board a piece on the move's from square. #[inline(always)] @@ -471,6 +512,7 @@ impl MoveOrdering { return self.order_by_sort(board, moves, table_move, ply, depth); } let len = moves.len(); + let captures_read = ply.map(|_| &self.capture_history[board.active_color as usize]); let keys = &mut self.keys; let mut front = 0; let mut table_at = None; @@ -480,7 +522,7 @@ impl MoveOrdering { if is_table_move { table_at = Some(0); } - let key = keyed(board, m, is_table_move, None); + let key = keyed(board, m, is_table_move, None, captures_read); front += usize::from(key < 0); keys[i] = pack(key, i); } @@ -567,6 +609,7 @@ impl MoveOrdering { ply: Option, depth: u8, ) -> Ordered { + let captures_read = ply.map(|_| &self.capture_history[board.active_color as usize]); let keys = &mut self.keys; let sorted = &mut self.sorted; // sort_by_cached_key allocates on every call, which costs more than @@ -581,7 +624,15 @@ impl MoveOrdering { .map(|base| &self.continuation[base..base + CONTINUATION_BLOCK]), board, }); - moves.sort_by_cached_key(|m| keyed(board, m, table_move == Some(*m), quiet.as_ref())); + moves.sort_by_cached_key(|m| { + keyed( + board, + m, + table_move == Some(*m), + quiet.as_ref(), + captures_read, + ) + }); // the same key orders this path, so the table's move is at // the head here too. It is rare enough to read the place off // the sorted list rather than carry it out of the closure @@ -605,9 +656,9 @@ impl MoveOrdering { let key = if is_table_move { // nothing else reaches its bonus, so it sorts to the head table_at = Some(0); - keyed(board, m, true, None) + keyed(board, m, true, None, captures_read) } else if m.capture.is_some() { - keyed(board, m, false, None) + keyed(board, m, false, None, captures_read) } else { 0 }; @@ -754,8 +805,7 @@ impl MoveOrdering { } else if killers[1] == Some(*m) { QUIET_KILLER[1] } else { - history[(m.from & 63) as usize][(m.to & 63) as usize] - + continued(block, board, m) + history[(m.from & 63) as usize][m.to as usize] + continued(block, board, m) }; keys[i] = pack_quiet(bonus, i); } @@ -909,7 +959,7 @@ impl Quiet<'_> { if self.killers[1] == Some(*m) { return KILLER_BONUS[1]; } - let butterfly = self.history[(m.from & 63) as usize][(m.to & 63) as usize]; + let butterfly = self.history[(m.from & 63) as usize][m.to as usize]; let continued = self .continuation .map_or(0, |block| continued(block, self.board, m)); @@ -1262,13 +1312,21 @@ fn gravitate(entry: &mut i32, bonus: i32) { *entry += bonus - *entry * bonus.abs() / HISTORY_MAX; } -/// What a move sorts by, smallest first: a capture by the swap, a quiet -/// move by the memories, and the table's move ahead of everything. The -/// caller says whether the move is the table's, having compared already. +/// What a move sorts by, smallest first: a capture by the swap and its +/// history, a quiet move by the memories, and the table's move ahead of +/// everything. The caller says whether the move is the table's, having +/// compared already. `captures` is the side's capture history, or none +/// where the memories are not consulted. #[inline(always)] -fn keyed(board: &Position, m: &Play, is_table_move: bool, quiet: Option<&Quiet<'_>>) -> i64 { +fn keyed( + board: &Position, + m: &Play, + is_table_move: bool, + quiet: Option<&Quiet<'_>>, + captures: Option<&SideCaptures>, +) -> i64 { let mut score = match m.capture { - Some(victim) => capture_score(board, m, victim), + Some(victim) => capture_score(board, m, victim, captures), None => match quiet { Some(quiet) => quiet.bonus(m), None => 0, @@ -1281,12 +1339,23 @@ fn keyed(board: &Position, m: &Play, is_table_move: bool, quiet: Option<&Quiet<' } /// Where a capture sorts: the swap's sign picks the band, the SEE value -/// orders within it, and MVV-LVA breaks ties between captures the swap -/// prices alike. +/// and the capture history order within it, and MVV-LVA breaks ties. #[inline] -fn capture_score(board: &Position, m: &Play, victim: Piece) -> i64 { +fn capture_score( + board: &Position, + m: &Play, + victim: Piece, + captures: Option<&SideCaptures>, +) -> i64 { let see = i64::from(board.see(m)); - let score = see * SEE_UNIT + mvv_lva(board, m, victim); + let learned = match (captures, board.get_piece_index(m.from)) { + (Some(captures), Some(piece)) => { + i64::from(captures[piece as usize][m.to as usize][victim as usize]) + * CAPTURE_HISTORY_WEIGHT + } + _ => 0, + }; + let score = see * SEE_UNIT + learned + mvv_lva(board, m, victim); if see >= 0 { WINNING_CAPTURE_BASE + score } else { @@ -1464,6 +1533,30 @@ mod order { assert_eq!(position_of(&moves, "h5d5"), 1); } + // the same two captures, with the queen's taking taught as a cutoff and + // the pawn's as tried before it: the history outranks the tiebreak + // within the band, on both paths, and is not read without a ply + #[test] + fn capture_history_reorders_captures_the_swap_prices_alike() { + let board = Board::from_fen(CAPTURES).unwrap(); + let generated = board.generate_moves(); + let (pawn, queen) = (named(&generated, "e4d5"), named(&generated, "h5d5")); + let mut ordering = MoveOrdering::new(); + ordering.cutoff(&board, Color::White, &queen, &[pawn], 1, 4); + + let moves = ordered_by(CAPTURES, None, &mut ordering); + assert_eq!(position_of(&moves, "h5d5"), 0); + assert_eq!(position_of(&moves, "e4d5"), 1); + + let mut sorted = board.generate_moves(); + ordering.order_by_sort(&board, &mut sorted, None, Some(0), u8::MAX); + assert_eq!(position_of(&sorted, "h5d5"), 0); + + let mut unread = board.generate_moves(); + ordering.order(&board, &mut unread, None, None, u8::MAX); + assert_eq!(position_of(&unread, "e4d5"), 0); + } + // the queen takes a pawn its king defends, which the swap prices at a // pawn for a queen: behind every quiet move, history or none #[test] @@ -1765,6 +1858,7 @@ mod order { #[cfg(test)] mod memory { + use super::CAPTURE_HISTORY_WEIGHT; use super::QUIET_HISTORY_MAX; use super::SEE_UNIT; use super::{ATTACKER_SCORES, HISTORY_MAX, KILLER_BONUS, MoveOrdering, PLACE_BITS}; @@ -1805,15 +1899,17 @@ mod memory { // and never loses more than the king's price let best_swap = i64::from(SEE_VALUES[Piece::Queen as usize]); let worst_swap = -i64::from(SEE_VALUES[Piece::King as usize]); - let best_capture = WINNING_CAPTURE_BASE + best_swap * SEE_UNIT + biggest; - let worst_capture = worst_swap * SEE_UNIT + smallest; + // and the capture history moves a key by up to its full entry + let learned = i64::from(HISTORY_MAX) * CAPTURE_HISTORY_WEIGHT; + let best_capture = WINNING_CAPTURE_BASE + best_swap * SEE_UNIT + learned + biggest; + let worst_capture = worst_swap * SEE_UNIT - learned + smallest; // the table's move ahead of the best capture even when it is itself // the worst, which the root's aborted answer swap rests on assert!(TABLE_MOVE_BONUS + worst_capture > best_capture); // the winning and even captures above the killers, the killers in // order above the history - assert!(WINNING_CAPTURE_BASE + smallest > KILLER_BONUS[0]); + assert!(WINNING_CAPTURE_BASE - learned + smallest > KILLER_BONUS[0]); assert!(KILLER_BONUS[0] > KILLER_BONUS[1]); assert!(KILLER_BONUS[1] > i64::from(QUIET_HISTORY_MAX)); @@ -1821,8 +1917,12 @@ mod memory { // pawn at least, so the best it can key is that swap with the // largest tiebreak on top, and the worst a quiet can key is both // tables' bound. The quiet has to stay ahead on the spilled list path - let least_losing = -i64::from(SEE_VALUES[Piece::Pawn as usize]) * SEE_UNIT + biggest; + let pawn = i64::from(SEE_VALUES[Piece::Pawn as usize]) * SEE_UNIT; + let least_losing = -pawn + learned + biggest; assert!(-i64::from(QUIET_HISTORY_MAX) > least_losing); + // and two entries at opposite bounds overturn less than two pawns + // of swap between captures in one band + assert!(2 * learned < 2 * pawn); // the widest key there could be has to survive the place shift; the // place half of `pack` has a compile time assert of its own @@ -1854,16 +1954,44 @@ mod memory { assert_eq!(ordering.killers[4], [None, None]); } + // a2 takes on b3 (the pawn on a2 the piece taking), as from the + // start position: the pieces are read off the board, the victim off + // the move #[test] - fn a_capture_cutoff_touches_neither_memory() { + fn a_capture_cutoff_teaches_the_capture_history_and_neither_quiet_memory() { let mut ordering = MoveOrdering::new(); - let take = Play::new(8, 16, Some(Piece::Pawn), None, false, false); - let tried = quiet(9, 17); - ordering.cutoff(&Board::new(), Color::White, &take, &[tried], 0, 4); + let take = Play::new(8, 17, Some(Piece::Knight), None, false, false); + let tried_take = Play::new(1, 18, Some(Piece::Pawn), None, false, false); + let tried = quiet(9, 25); + ordering.cutoff( + &Board::new(), + Color::White, + &take, + &[tried_take, tried], + 0, + 4, + ); assert_eq!(ordering.killers[0], [None, None]); - assert_eq!(ordering.history[Color::White as usize][8][16], 0); - // the moves tried before it are not marked down either - assert_eq!(ordering.history[Color::White as usize][9][17], 0); + assert_eq!(ordering.history[Color::White as usize][8][17], 0); + // the quiet tried before it is not marked down + assert_eq!(ordering.history[Color::White as usize][9][25], 0); + let captures = &ordering.capture_history[Color::White as usize]; + assert_eq!( + captures[Piece::Pawn as usize][17][Piece::Knight as usize], + 16 + ); + // the capture tried before it is, under the knight on b1 that made it + assert_eq!( + captures[Piece::Knight as usize][18][Piece::Pawn as usize], + -16 + ); + assert!( + ordering.capture_history[Color::Black as usize] + .iter() + .flatten() + .flatten() + .all(|&e| e == 0) + ); } #[test] @@ -1878,8 +2006,14 @@ mod memory { // zero, so a first update is the step itself assert_eq!(history[8][16], 25); assert_eq!(history[9][17], -25); - // a capture among the moves tried is passed over + // a capture among the moves tried is passed over here, and marked + // down in the capture history (a pawn on c2 taking on c3) assert_eq!(history[10][18], 0); + assert_eq!( + ordering.capture_history[Color::White as usize][Piece::Pawn as usize][18] + [Piece::Pawn as usize], + -25 + ); // the side that played it is part of the index assert_eq!(ordering.history[Color::Black as usize][8][16], 0); } @@ -1925,13 +2059,24 @@ mod memory { } #[test] - fn forgetting_empties_both_memories() { + fn forgetting_empties_the_memories() { let mut ordering = MoveOrdering::new(); let m = quiet(8, 16); let tried = quiet(9, 17); + let take = Play::new(10, 17, Some(Piece::Pawn), None, false, false); ordering.cutoff(&Board::new(), Color::White, &m, &[tried], 2, 4); + ordering.cutoff(&Board::new(), Color::White, &take, &[], 3, 4); assert!(ordering.history[Color::White as usize][9][17] < 0); ordering.forget(); + assert!( + ordering + .capture_history + .iter() + .flatten() + .flatten() + .flatten() + .all(|&e| e == 0) + ); assert_eq!(ordering.killers[2], [None, None]); assert_eq!(ordering.history[Color::White as usize][8][16], 0); // a marked down entry goes with the rest of them diff --git a/arche-core/src/strategy.rs b/arche-core/src/strategy.rs index a6eb36d2..d4b2f909 100644 --- a/arche-core/src/strategy.rs +++ b/arche-core/src/strategy.rs @@ -32,7 +32,7 @@ pub const TABLE_BYTES: usize = 16 * 1024 * 1024; /// How many of the suite's points the search takes at that depth with that /// table. Exact, not a floor: a change that moves it updates this number in /// the same commit. -pub const EXPECTED_POINTS: u32 = 104989; +pub const EXPECTED_POINTS: u32 = 104632; const SUITE: &str = include_str!("../strategy.epd"); diff --git a/arche-core/src/tactics.rs b/arche-core/src/tactics.rs index 9fb2e55f..6157addd 100644 --- a/arche-core/src/tactics.rs +++ b/arche-core/src/tactics.rs @@ -33,7 +33,7 @@ pub const TABLE_BYTES: usize = 16 * 1024 * 1024; /// is the gate. A change that moves it in either direction updates this /// number in the same commit, and a change that lowers it says what the /// positions were spent on. -pub const EXPECTED_PASSES: usize = 231; +pub const EXPECTED_PASSES: usize = 232; /// The count the suite may not go under, whatever a commit says it meant to /// spend. diff --git a/tests/uci_session.rs b/tests/uci_session.rs index 1e8d78f2..08d96d19 100644 --- a/tests/uci_session.rs +++ b/tests/uci_session.rs @@ -546,15 +546,15 @@ fn a_root_move_that_reaches_beta_is_reported_as_a_floor_and_then_answered_with() fn a_floor_answers_until_the_wider_search_replaces_it() { // what the engine plays when the wider search never finishes. WAC.021 is // answered with d2c3 at depth eight. At depth nine d2h6 reaches beta and - // is reported as a floor at 100,380 nodes, and again at 102,809 and - // 105,374 as the window widens, and the search finishes at 151,728. A budget + // is reported as a floor at 140,219 nodes, and again at 144,700 as the + // window widens, and the search finishes at 220,301. A budget // after d2h6's last floor and inside that is interrupted before anything // beats alpha, so the floor is what is left to answer with; with the // floor not held it answers d2c3, the move the search has just shown // worse let mut s = Session::start(&[]); s.say(&format!("position fen {}", WAC_021)); - s.say("go nodes 120000"); + s.say("go nodes 180000"); let answer = s.wait_for(|l| l.starts_with("bestmove")); let best = move_of(&answer);