1- import { GameTree , GameNode } from 'src/types'
1+ import { Chess } from 'chess.ts'
2+ import { GameTree } from 'src/types'
23import { EngineAnalysisPosition } from 'src/api/analysis/analysis'
4+ import { cpToWinrate } from 'src/lib/stockfish'
35
46/**
57 * Collects analysis data from a game tree to send to the backend
@@ -63,18 +65,11 @@ export const applyEngineAnalysisData = (
6365
6466 // Apply Stockfish analysis
6567 if ( stockfish ) {
66- // Create a StockfishEvaluation object with minimal required fields
67- const stockfishEval = {
68- sent : true ,
69- depth : stockfish . depth ,
70- model_move : Object . keys ( stockfish . cp_vec ) [ 0 ] || '' ,
71- model_optimal_cp : Math . max (
72- ...Object . values ( stockfish . cp_vec ) . map ( Number ) ,
73- 0 ,
74- ) ,
75- cp_vec : stockfish . cp_vec ,
76- cp_relative_vec : calculateRelativeCp ( stockfish . cp_vec ) ,
77- }
68+ const stockfishEval = reconstructStockfishEvaluation (
69+ stockfish . cp_vec ,
70+ stockfish . depth ,
71+ node . fen ,
72+ )
7873
7974 // Only apply if we don't have deeper analysis already
8075 if (
@@ -90,19 +85,87 @@ export const applyEngineAnalysisData = (
9085}
9186
9287/**
93- * Helper function to calculate relative centipawn values
88+ * Reconstruct a complete StockfishEvaluation from stored cp_vec using the same logic as the engine
9489 */
95- const calculateRelativeCp = ( cpVec : {
96- [ move : string ] : number
97- } ) : { [ move : string ] : number } => {
98- const maxCp = Math . max ( ...Object . values ( cpVec ) )
99- const relativeCp : { [ move : string ] : number } = { }
100-
101- Object . entries ( cpVec ) . forEach ( ( [ move , cp ] ) => {
102- relativeCp [ move ] = cp - maxCp
103- } )
104-
105- return relativeCp
90+ const reconstructStockfishEvaluation = (
91+ cpVec : { [ move : string ] : number } ,
92+ depth : number ,
93+ fen : string ,
94+ ) => {
95+ const board = new Chess ( fen )
96+ const isBlackTurn = board . turn ( ) === 'b'
97+
98+ // Find the best move and cp (model_move and model_optimal_cp)
99+ let bestCp = isBlackTurn ? Infinity : - Infinity
100+ let bestMove = ''
101+
102+ for ( const move in cpVec ) {
103+ const cp = cpVec [ move ]
104+ if ( isBlackTurn ) {
105+ if ( cp < bestCp ) {
106+ bestCp = cp
107+ bestMove = move
108+ }
109+ } else {
110+ if ( cp > bestCp ) {
111+ bestCp = cp
112+ bestMove = move
113+ }
114+ }
115+ }
116+
117+ // Calculate cp_relative_vec using exact same logic as engine.ts:215-217
118+ const cp_relative_vec : { [ move : string ] : number } = { }
119+ for ( const move in cpVec ) {
120+ const cp = cpVec [ move ]
121+ cp_relative_vec [ move ] = isBlackTurn
122+ ? bestCp - cp // Black turn: model_optimal_cp - cp
123+ : cp - bestCp // White turn: cp - model_optimal_cp
124+ }
125+
126+ // Calculate winrate_vec using exact same logic as engine.ts:219 and 233
127+ const winrate_vec : { [ move : string ] : number } = { }
128+ for ( const move in cpVec ) {
129+ const cp = cpVec [ move ]
130+ // Use exact same logic as engine: cp * (isBlackTurn ? -1 : 1)
131+ const winrate = cpToWinrate ( cp * ( isBlackTurn ? - 1 : 1 ) , false )
132+ winrate_vec [ move ] = winrate
133+ }
134+
135+ // Calculate winrate_loss_vec using the same logic as the engine (lines 248-264)
136+ let bestWinrate = - Infinity
137+ for ( const move in winrate_vec ) {
138+ const wr = winrate_vec [ move ]
139+ if ( wr > bestWinrate ) {
140+ bestWinrate = wr
141+ }
142+ }
143+
144+ const winrate_loss_vec : { [ move : string ] : number } = { }
145+ for ( const move in winrate_vec ) {
146+ winrate_loss_vec [ move ] = winrate_vec [ move ] - bestWinrate
147+ }
148+
149+ // Sort all vectors by winrate (descending) as done in engine.ts:267-281
150+ const sortedEntries = Object . entries ( winrate_vec ) . sort (
151+ ( [ , a ] , [ , b ] ) => b - a ,
152+ )
153+
154+ const sortedWinrateVec = Object . fromEntries ( sortedEntries )
155+ const sortedWinrateLossVec = Object . fromEntries (
156+ sortedEntries . map ( ( [ move ] ) => [ move , winrate_loss_vec [ move ] ] ) ,
157+ )
158+
159+ return {
160+ sent : true ,
161+ depth,
162+ model_move : bestMove ,
163+ model_optimal_cp : bestCp ,
164+ cp_vec : cpVec ,
165+ cp_relative_vec,
166+ winrate_vec : sortedWinrateVec ,
167+ winrate_loss_vec : sortedWinrateLossVec ,
168+ }
106169}
107170
108171/**
0 commit comments