private static void CompareBestMove(string filePath, int timeBudgetMs)
        {
            var         file       = File.OpenText(filePath);
            double      freq       = Stopwatch.Frequency;
            long        totalTime  = 0;
            long        totalNodes = 0;
            int         count      = 0;
            int         foundBest  = 0;
            List <Move> bestMoves  = new List <Move>();

            while (!file.EndOfStream)
            {
                ParseEpd(file.ReadLine(), out Board board, bestMoves);
                Transpositions.Clear();
                IterativeSearch search = new IterativeSearch(board);
                Move            pvMove = default;
                long            t0     = Stopwatch.GetTimestamp();
                long            tStop  = t0 + (timeBudgetMs * Stopwatch.Frequency) / 1000;
                //search until running out of time
                while (true)
                {
                    search.SearchDeeper(() => Stopwatch.GetTimestamp() > tStop);
                    if (search.Aborted)
                    {
                        break;
                    }
                    pvMove = search.PrincipalVariation[0];
                }
                long t1 = Stopwatch.GetTimestamp();
                long dt = t1 - t0;
                totalTime  += dt;
                totalNodes += search.NodesVisited;
                count++;
                string pvString      = string.Join(' ', search.PrincipalVariation);
                bool   foundBestMove = bestMoves.Contains(pvMove);
                if (foundBestMove)
                {
                    foundBest++;
                }
                Console.WriteLine($"{count,4}. {(foundBestMove ? "[X]" : "[ ]")} {pvString} = {search.Score:+0.00;-0.00}, {search.NodesVisited / 1000}K nodes, { 1000 * dt / freq}ms");
                Console.WriteLine($"{totalNodes,14} nodes, { (int)(totalTime / freq)} seconds, {foundBest} solved.");
            }
            Console.WriteLine();
            Console.WriteLine($"Searched {count} positions for {timeBudgetMs}ms each. {totalNodes/1000}K nodes visited. Took {totalTime/freq:0.###} seconds!");
            Console.WriteLine($"Best move found in {foundBest} / {count} positions!");
        }
        private void Search()
        {
            while (CanSearchDeeper())
            {
                _time.StartInterval();
                _search.SearchDeeper(_time.CheckTimeBudget);

                //aborted?
                if (_search.Aborted)
                {
                    break;
                }

                //collect PV
                Collect();
            }
            //Done searching!
            Uci.BestMove(_best);
            _search = null;
        }
        //*****************
        //*** INTERNALS ***
        //*****************

        private void StartSearch(int maxDepth, long maxNodes)
        {
            //do the first iteration. it's cheap, no time check, no thread
            Uci.Log($"Search scheduled to take {_time.TimePerMoveWithMargin}ms!");

            //add all history positions with a score of 0 (Draw through 3-fold repetition) and freeze them by setting a depth that is never going to be overwritten
            foreach (var position in _history)
            {
                Transpositions.Store(position.ZobristHash, Transpositions.HISTORY, 0, SearchWindow.Infinite, 0, default);
            }

            _search = new IterativeSearch(_board, maxNodes);
            _time.StartInterval();
            _search.SearchDeeper();
            Collect();

            //start the search thread
            _maxSearchDepth = maxDepth;
            _searching      = new Thread(Search)
            {
                Priority = ThreadPriority.Highest
            };
            _searching.Start();
        }