The Game Analysis feature analyzes all moves in a chess game using a UCI engine, calculating evaluations, Centipawn Loss (CPL), move assessments, best moves, and material tracking for each position. The system processes moves sequentially, analyzing each position before and after the move to determine move quality. Analysis results can be stored in PGN tags for persistence.
The game analysis system follows a Controller-Service-Thread pattern with persistent engine communication:
GameAnalysisController (app/controllers/game_analysis_controller.py):
- Orchestrates game analysis operations
- Manages
GameAnalysisEngineServiceandMovesListModel - Extracts moves from active game
- Processes analysis results and updates move data
- Calculates CPL, assessments, and material tracking
- Handles book move detection
- Manages progress reporting and error handling
- Supports post-game brilliancy refinement
GameAnalysisEngineService (app/services/game_analysis_engine_service.py):
- Manages persistent engine thread for analyzing multiple positions
- Queues analysis requests for sequential processing
- Handles engine lifecycle (start, stop, cleanup)
- Provides thread interface for signal connections
GameAnalysisEngineThread (app/services/game_analysis_engine_service.py):
- Persistent QThread for UCI engine communication
- Processes queue of position analysis requests
- Parses UCI info lines for depth, score, PV, MultiPV
- Emits progress updates and completion signals
- Handles MultiPV (PV1, PV2, PV3) extraction
AnalysisDataStorageService (app/services/analysis_data_storage_service.py):
- Serializes analysis data to JSON
- Compresses and stores data in PGN tag
[CARAAnalysisData "..."] - Stores metadata in
[CARAAnalysisInfo "..."]and checksum in[CARAAnalysisChecksum "..."] - Loads and validates stored analysis data
- Handles corrupted data cleanup
BookMoveService (app/services/book_move_service.py):
- Detects if moves are in opening book
- Integrates with
OpeningServicefor book move detection - Used to skip best move storage for book moves
MoveAnalysisService (app/services/move_analysis_service.py):
- Calculates CPL (Centipawn Loss) for moves
- Assesses move quality (Brilliant, Best Move, Good Move, Inaccuracy, Mistake, Blunder)
- Normalizes move notation for comparison
- Detects if played move is in top 3 alternatives
Analysis Start Flow:
- User initiates game analysis
- Controller validates active game and engine assignment
- Controller extracts moves from game PGN
- Controller initializes
GameAnalysisEngineServicewith engine parameters - Service starts persistent engine thread
- Controller begins analyzing first move
Move Analysis Flow:
- Controller calls
_analyze_next_move()for current move - Controller updates board position to show move being analyzed
- Controller analyzes position BEFORE move (to get best alternative)
- Engine thread processes position and emits
analysis_completesignal - Controller stores best move (PV1, PV2, PV3) from position before
- Controller analyzes position AFTER move (to get evaluation)
- Engine thread processes position and emits
analysis_completesignal - Controller calculates CPL using
MoveAnalysisService - Controller assesses move quality using configurable thresholds
- Controller updates
MovesListModelwith evaluation, CPL, assessment, best moves - Controller tracks material balance and piece counts
- Controller moves to next move and repeats
Progress Reporting Flow:
- Engine thread emits
progress_updatesignals during analysis - Controller tracks progress depth, centipawns, elapsed time
- Controller uses
QTimerto emit periodic progress updates - Progress service displays move number, estimated remaining time
- Progress bar shows percentage based on moves completed
Analysis Completion Flow:
- Controller detects all moves analyzed
- If post-game brilliancy refinement enabled, performs secondary pass
- Controller sets
is_game_analyzedflag inGameModel - If storage enabled,
AnalysisDataStorageServicestores results in PGN tag - Controller marks database as unsaved if game was updated
- Controller emits
analysis_completedsignal
Error Handling Flow:
- Engine thread emits
error_occurredsignal on errors - Controller tracks consecutive errors
- Critical errors (process termination, initialization failure) stop analysis
- Non-critical errors skip to next move and continue
- Progress service displays error messages
The controller extracts moves from the active game's PGN:
- Parses PGN using
chess.pgn.read_game() - Iterates through mainline moves
- For each move, stores:
- Move number, side to move, SAN notation
- FEN before and after move
- Board positions (for material calculation)
Each move requires analyzing two positions:
-
Position BEFORE move:
- Purpose: Get best alternative move (PV1) and evaluation after playing best move
- Used for: CPL calculation, best move storage, top 3 detection
- MultiPV: Always uses MultiPV=3 to get PV1, PV2, PV3
-
Position AFTER move:
- Purpose: Get evaluation after the actual move
- Used for: CPL calculation, move assessment, material tracking
- MultiPV: Uses MultiPV=3 to get PV2 and PV3 scores for CPL calculation
CPL (Centipawn Loss) measures how much evaluation is lost by playing a move instead of the best move:
- Normal positions:
CPL = eval_after_best_move - eval_after_played_move - Mate positions: Uses special handling based on mate distance changes
- If checkmate achieved: CPL = 0 (perfect move)
- If creating checkmate: CPL reflects mate distance improvement
- If allowing checkmate: CPL reflects mate distance loss
- If both positions are mate: CPL reflects mate distance comparison
CPL calculation is handled by MoveAnalysisService.calculate_cpl() with proper handling for mate positions and side-to-move perspective.
Move quality is assessed using configurable thresholds:
- Book Move: Detected via
BookMoveService(no assessment) - Brilliant: Requires material sacrifice, eval swing, and other criteria
- Best Move: CPL ≤
good_move_max_cpl(default: 50) - Good Move: CPL ≤
good_move_max_cpl(default: 50) - Inaccuracy: CPL ≤
inaccuracy_max_cpl(default: 100) - Mistake: CPL ≤
mistake_max_cpl(default: 200) - Blunder: CPL >
mistake_max_cpl(default: 200)
All thresholds are configurable via MoveClassificationModel and can be customized in application settings or config.json.
Material balance is calculated after each move:
- Tracks material count for both sides (using piece values)
- Tracks piece counts (queens, rooks, bishops, knights, pawns)
- Updates
MovesListModelwith material data for each move - Used for material sacrifice detection in brilliancy assessment
For each move, the system stores:
- Best Move (PV1): Best alternative from position before move
- PV2: Second-best alternative from position before move
- PV3: Third-best alternative from position before move
- Top 3 Detection: Checks if played move matches any of top 3 alternatives
- Depth: Engine depth used for analysis
Book moves do not store best moves (empty strings).
Optional secondary pass that re-checks all moves for brilliancy using multi-ply look-ahead for material sacrifice detection. This can catch sacrifices that aren't immediately recaptured:
- Uses
material_sacrifice_lookahead_plies(default: 3) to look ahead multiple plies - Re-calculates material sacrifice for each move
- Re-assesses brilliancy with refined material sacrifice values
- Updates assessments if moves are now brilliant
Game analysis is configured via config.json:
{
"game_analysis": {
"max_depth": 18,
"time_limit_per_move_ms": 3000,
"max_threads": 6,
"progress_update_interval_ms": 500,
"assessment_thresholds": {
"good_move_max_cpl": 50,
"inaccuracy_max_cpl": 100,
"mistake_max_cpl": 200
},
"brilliant_criteria": {
"min_eval_swing": 50,
"min_material_sacrifice": 300,
"max_eval_before": 500,
"exclude_already_winning": true,
"material_sacrifice_lookahead_plies": 3
},
"store_analysis_results_in_pgn_tag": false
}
}Engine-specific parameters can be configured per-engine in engine_parameters.json:
depth: Maximum search depthmovetime: Time limit per move (milliseconds)threads: Number of CPU threads- Other engine-specific options
Analysis results can be stored in PGN tags for persistence:
- Tag:
[CARAAnalysisData "..."]- Compressed, base64-encoded JSON - Metadata:
[CARAAnalysisInfo "..."]- App version and creation datetime - Checksum:
[CARAAnalysisChecksum "..."]- SHA256 hash for data integrity
Storage format:
- JSON serialization of all
MoveDatafields - Gzip compression (level 9)
- Base64 encoding for PGN tag compatibility
- Checksum validation on load
If storage is enabled and analysis completes, the system automatically stores results and marks the database as unsaved.
- Controller:
app/controllers/game_analysis_controller.py - Service:
app/services/game_analysis_engine_service.py - Storage:
app/services/analysis_data_storage_service.py - Book Moves:
app/services/book_move_service.py - Move Analysis:
app/services/move_analysis_service.py - Model Updates:
app/models/moveslist_model.py(MoveData class) - Configuration:
app/config/config.json(game_analysis section)