KataGo Review
A KataGo review turns every move in a finished game into a winrate / score delta, a classification (good / inaccuracy / mistake / blunder), and a list of suggested continuations. The unified flow uses one KataGo query per game instead of one per turn, which collapsed the typical N+2 round-trips into a single replay.
This page describes the flow end-to-end: where it is triggered, what
runFullReview does, where the results land, and how the UI consumes them.
Trigger
Section titled “Trigger”When a bot game ends:
BotGameService.endGamemarks the gameendedinbotGames.- A BullMQ job is enqueued for
BotToAnalysisProcessor(apps/weiqi-server/src/modules/bot/infrastructure/jobs/bot-to-analysis.processor.ts:37). - The processor resolves the game’s
boardNodes(the canonical board tree) and callsAnalysisService.runFullReview.
The legacy single-turn BotGameReviewProcessor still exists for back-compat
but is no longer wired into the hot path.
runFullReview
Section titled “runFullReview”AnalysisService.runFullReview (modules/analysis/application/analysis.service.ts:745):
- Walks the main line of
boardNodesto collect every position. - Builds one
analyzeOptionswithanalyzeTurns: [0, 1, 2, …, N]. - Calls
KatagoService.getGameReview(analyzeOptions). - Persists results as one
BoardAiReviewrow inboard_ai_reviewsplus oneBoardAiSnapshotper (record, node, engine, hash) inboard_ai_snapshots. - Emits an
AnalysisGraphPointper turn with{ turnNumber, winrate, scoreLead, bestMove, classification }.
The analyzeTurns optimisation
Section titled “The analyzeTurns optimisation”KataGo accepts analyzeTurns: number[] to replay multiple positions in one
process invocation. Internally it walks each turn, computes its root
position, and returns one response per turn in a single
KatagoAnalysisResponse.
Before this consolidation, each turn was a separate query (plus a pre-game
probe and a post-game probe). For a 200-move game that was 202 round-trips
through katago-service; now it is 1.
The downside is that very long games keep KataGo busy for longer per call —
the queue’s concurrency cap (KATAGO_MAX_CONCURRENT) limits how many games
can be in flight at once.
Response shape
Section titled “Response shape”KatagoAnalysisResponse (modules/game/infrastructure/katago/types.ts:71):
{ jobId: string; status: "ok" | "error"; error?: string; turns?: Record<number, KatagoTurnResponse>; // 0-based ply keys // legacy single-turn fields — mirror turns[0] when set moveInfos?: KatagoMoveInfo[]; rootInfo?: KatagoRootInfo;}
type KatagoTurnResponse = { turnNumber: number; moveInfos: KatagoMoveInfo[]; rootInfo: { winrate: number; // 0..1, side-to-move perspective scoreMean: number; // lead in points, side-to-move perspective visits: number; ownership?: number[]; // row-major, length boardSize², -1..1 };};
type KatagoMoveInfo = { move: string; // GTP coord, e.g. "Q16" or "pass" visits: number; winrate: number; scoreMean: number; scoreStdev: number; pv: string[]; // principal variation};Classification
Section titled “Classification”MoveClassification is derived from rootInfo.winrate and the played move’s
winrate:
- good — played move is within 1 % winrate of the engine’s top choice.
- inaccuracy — 1–5 % gap.
- mistake — 5–15 % gap.
- blunder — > 15 % gap.
These thresholds are defined alongside the analysis service and surfaced to
the client via MoveClass in apps/game-web/src/entities/bot-game/types/bot-game.ts.
Persistence
Section titled “Persistence”Two tables, both keyed off the underlying board record:
board_ai_reviews— one row per review run. Storesengine,engineConfigHash,createdAt, and a JSON blob of the full response (so we can re-project without re-querying KataGo).board_ai_snapshots— one row per (record, node, engine, hash). Used for “compare two snapshots” flows and for diffing engine versions.
The (recordId, nodeId, engine, engineConfigHash) unique key prevents
duplicate snapshots when the same review is re-run.
Read paths
Section titled “Read paths”GET /api/game/bot/games/:id/review(bot.controller.ts:299) returns the latestboard_ai_reviewsrow, projected throughprojectAiReviewToBotReviewfor the bot-review UI. Falls back to legacybot_game_reviewsif no new review is present yet.GET /api/analysis/games/:id/graphreturns the time-series subset (turnNumber,winrate,scoreLead) for the chart component.
UI consumption
Section titled “UI consumption”apps/game-web/src/pages/play/bots/review/bot-review.page.tsx renders:
- A
WinrateGraphfor the per-turn winrate series. - A
MoveListwidget (apps/game-web/src/widgets/MoveList/ui/move-list.tsx:1) with classification badges per ply. - A
GameOverModalwidget (apps/game-web/src/widgets/GameOverModal/game-over-modal.tsx:1) on entry, withonNewGame,onReview,onRematchactions.
Real-time progress
Section titled “Real-time progress”AnalysisGateway emits a Socket.IO event per turn as runFullReview
progresses, so the chart can draw point-by-point instead of waiting for the
whole review. This is wired only on the analysis page; the bot-review page
waits for the full result before rendering (less noise, simpler state).