feat(official-bots): make HybridBot veto actionable and use it for expert
Build & Test (NowChessSystems) TeamCity build finished
Build & Test (NowChessSystems) TeamCity build finished
When classical and NNUE evals diverge above the veto threshold, HybridBot now re-searches excluding the suspect move and switches to NNUE's preferred alternative instead of merely logging. BotController maps the expert bot to HybridBot so tournament auto-join uses it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,14 +1,17 @@
|
||||
package de.nowchess.bot
|
||||
|
||||
import de.nowchess.bot.bots.ClassicalBot
|
||||
import de.nowchess.bot.bots.{ClassicalBot, HybridBot}
|
||||
import jakarta.enterprise.context.ApplicationScoped
|
||||
import org.jboss.logging.Logger
|
||||
|
||||
object BotController:
|
||||
private val log = Logger.getLogger(classOf[BotController])
|
||||
|
||||
private val bots: Map[String, Bot] = Map(
|
||||
"easy" -> ClassicalBot(BotDifficulty.Easy),
|
||||
"medium" -> ClassicalBot(BotDifficulty.Medium),
|
||||
"hard" -> ClassicalBot(BotDifficulty.Hard),
|
||||
"expert" -> ClassicalBot(BotDifficulty.Expert),
|
||||
"expert" -> HybridBot(BotDifficulty.Expert, vetoReporter = log.debug(_)),
|
||||
)
|
||||
|
||||
def getBot(name: String): Option[Bot] = bots.get(name.toLowerCase)
|
||||
|
||||
@@ -24,16 +24,24 @@ object HybridBot:
|
||||
val search = AlphaBetaSearch(rules, TranspositionTable(), classicalEvaluation)
|
||||
context =>
|
||||
val blockedMoves = BotMoveRepetition.blockedMoves(context)
|
||||
|
||||
def nnueScore(move: Move): Int = nnueEvaluation.evaluate(rules.applyMove(context)(move))
|
||||
def classicalScore(move: Move): Int = classicalEvaluation.evaluate(rules.applyMove(context)(move))
|
||||
|
||||
def refine(move: Move): Move =
|
||||
val moveNnue = nnueScore(move)
|
||||
if (classicalScore(move) - moveNnue).abs <= Config.VETO_THRESHOLD then move
|
||||
else
|
||||
search
|
||||
.bestMoveWithTime(context, Config.TIME_LIMIT_MS, blockedMoves + move)
|
||||
.filterNot(blockedMoves.contains)
|
||||
.filter(alt => nnueScore(alt) < moveNnue)
|
||||
.map { alt =>
|
||||
vetoReporter(f"[Veto] ${move.from}->${move.to} replaced by ${alt.from}->${alt.to} — NNUE prefers it")
|
||||
alt
|
||||
}
|
||||
.getOrElse(move)
|
||||
|
||||
book.flatMap(_.probe(context)).filterNot(blockedMoves.contains).orElse {
|
||||
search.bestMoveWithTime(context, Config.TIME_LIMIT_MS, blockedMoves).map { move =>
|
||||
val next = rules.applyMove(context)(move)
|
||||
val staticNnue = nnueEvaluation.evaluate(next)
|
||||
val classical = classicalEvaluation.evaluate(next)
|
||||
val diff = (classical - staticNnue).abs
|
||||
if diff > Config.VETO_THRESHOLD then
|
||||
vetoReporter(
|
||||
f"[Veto] ${move.from}->${move.to}: nnue=$staticNnue classical=$classical diff=$diff — flagged but trusted (deep search)",
|
||||
)
|
||||
move
|
||||
}
|
||||
search.bestMoveWithTime(context, Config.TIME_LIMIT_MS, blockedMoves).map(refine)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user