Skip to content

Commit 920ff4a

Browse files
Merge pull request #248 from CSSLab/codex/revert-maia-winrate
Revert "Merge pull request #247 from CSSLab/codex/maia-winrate-play"
2 parents 90fd2c4 + 8fb2ef5 commit 920ff4a

5 files changed

Lines changed: 19 additions & 285 deletions

File tree

README.md

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -187,24 +187,6 @@ The project uses automated code formatting and linting to maintain consistency:
187187

188188
This ensures consistent interpretation where positive values always represent an advantage for the player whose turn it is. The conversion logic is documented in `src/hooks/useStockfishEngine/engine.ts:138-155`.
189189

190-
#### Maia Value-Head Debug Logging
191-
192-
When testing the value-head play mode (`/play/maia` with `maiaMoveSelectionMode=value_head`), you can enable an opt-in console table that prints each legal move with Maia's evaluated win probability.
193-
194-
Enable in browser DevTools:
195-
196-
```js
197-
localStorage.setItem('maia.valueHeadDebug', 'true')
198-
```
199-
200-
Disable:
201-
202-
```js
203-
localStorage.removeItem('maia.valueHeadDebug')
204-
```
205-
206-
With the flag enabled, each Maia move prints a compact table (`san`, `move`, `maiaWinProb`) in the browser console. This is local-only and does not change behavior unless the flag is set in localStorage.
207-
208190
#### State Management Architecture
209191

210192
The platform uses a Context + Custom Hooks pattern:

src/components/Common/PlaySetupModal.tsx

Lines changed: 0 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import { useCallback, useContext, useState } from 'react'
77

88
import {
99
Color,
10-
MaiaMoveSelectionMode,
1110
PlayType,
1211
TimeControl,
1312
TimeControlOptionNames,
@@ -81,7 +80,6 @@ interface Props {
8180
maiaVersion?: string
8281
isBrain?: boolean
8382
sampleMoves?: boolean
84-
maiaMoveSelectionMode?: MaiaMoveSelectionMode
8583
simulateMaiaTime?: boolean
8684
startFen?: string
8785
}
@@ -107,10 +105,6 @@ export const PlaySetupModal: React.FC<Props> = (props: Props) => {
107105
const [sampleMoves, setSampleMoves] = useState<boolean>(
108106
props.sampleMoves || true,
109107
)
110-
const [maiaMoveSelectionMode, setMaiaMoveSelectionMode] =
111-
useState<MaiaMoveSelectionMode>(
112-
props.maiaMoveSelectionMode || 'move_matching',
113-
)
114108
const [simulateMaiaTime, setSimulateMaiaTime] = useState<boolean>(
115109
props.simulateMaiaTime !== undefined ? props.simulateMaiaTime : true,
116110
)
@@ -180,7 +174,6 @@ export const PlaySetupModal: React.FC<Props> = (props: Props) => {
180174
maiaVersion: maiaVersion,
181175
timeControl: timeControl,
182176
sampleMoves: sampleMoves,
183-
maiaMoveSelectionMode: maiaMoveSelectionMode,
184177
simulateMaiaTime: simulateMaiaTime,
185178
startFen: fen,
186179
},
@@ -195,7 +188,6 @@ export const PlaySetupModal: React.FC<Props> = (props: Props) => {
195188
timeControl: timeControl,
196189
isBrain: isBrain,
197190
sampleMoves: sampleMoves,
198-
maiaMoveSelectionMode: maiaMoveSelectionMode,
199191
simulateMaiaTime: simulateMaiaTime,
200192
startFen: fen,
201193
},
@@ -210,7 +202,6 @@ export const PlaySetupModal: React.FC<Props> = (props: Props) => {
210202
maiaVersion,
211203
timeControl,
212204
sampleMoves,
213-
maiaMoveSelectionMode,
214205
simulateMaiaTime,
215206
fen,
216207
isBrain,
@@ -464,35 +455,6 @@ export const PlaySetupModal: React.FC<Props> = (props: Props) => {
464455
</div>
465456
</div>
466457

467-
{props.playType == 'againstMaia' ? (
468-
<div>
469-
<label
470-
htmlFor="maia-play-style-select"
471-
className="mb-1 block text-sm font-medium text-primary"
472-
>
473-
Maia play style:
474-
</label>
475-
<div id="maia-play-style-select">
476-
<OptionSelect
477-
options={['move_matching', 'value_head']}
478-
labels={['Human move-match', 'Best win rate']}
479-
selected={maiaMoveSelectionMode}
480-
onChange={(selected) =>
481-
setMaiaMoveSelectionMode(
482-
selected as MaiaMoveSelectionMode,
483-
)
484-
}
485-
selectedClassName="border-human-4 bg-human-4 text-white hover:bg-human-4/90"
486-
/>
487-
</div>
488-
<p className="mt-2 text-xs text-secondary">
489-
{maiaMoveSelectionMode === 'move_matching'
490-
? 'Classic Maia: chooses the move it expects a human to play.'
491-
: 'Local Maia 2 mode: evaluates every legal move and picks the one with the best perceived win rate.'}
492-
</p>
493-
</div>
494-
) : null}
495-
496458
<div className="flex items-center gap-2">
497459
<input
498460
type="checkbox"

src/hooks/usePlayController/useVsMaiaController.ts

Lines changed: 15 additions & 166 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useCallback, useContext, useEffect } from 'react'
1+
import { useEffect } from 'react'
22
import { Chess } from 'chess.ts'
33
import { PlayGameConfig } from 'src/types'
44
import { backOff } from 'exponential-backoff'
@@ -7,26 +7,6 @@ import { usePlayController } from 'src/hooks/usePlayController'
77
import { fetchGameMove, logGameMove, fetchPlayPlayerStats } from 'src/api'
88
import { useSound } from 'src/hooks/useSound'
99
import { safeUpdateRating } from 'src/lib/ratingUtils'
10-
import { MaiaEngineContext } from 'src/contexts'
11-
12-
const MAIA_VALUE_HEAD_DEBUG_KEY = 'maia.valueHeadDebug'
13-
14-
const isTruthy = (value: string | null | undefined): boolean => {
15-
if (!value) return false
16-
return ['1', 'true', 'yes', 'on'].includes(value.toLowerCase())
17-
}
18-
19-
const isMaiaValueHeadDebugEnabled = (): boolean => {
20-
if (typeof window === 'undefined') {
21-
return false
22-
}
23-
24-
try {
25-
return isTruthy(window.localStorage.getItem(MAIA_VALUE_HEAD_DEBUG_KEY))
26-
} catch {
27-
return false
28-
}
29-
}
3010

3111
const playStatsLoader = async () => {
3212
const stats = await fetchPlayPlayerStats()
@@ -45,126 +25,6 @@ export const useVsMaiaPlayController = (
4525
const controller = usePlayController(id, playGameConfig)
4626
const [stats, incrementStats, updateRating] = useStats(playStatsLoader)
4727
const { playMoveSound } = useSound()
48-
const maiaEngine = useContext(MaiaEngineContext)
49-
const valueHeadModel =
50-
playGameConfig.maiaMoveSelectionMode === 'value_head'
51-
? maiaEngine.maia
52-
: null
53-
const valueHeadStatus =
54-
playGameConfig.maiaMoveSelectionMode === 'value_head'
55-
? maiaEngine.status
56-
: null
57-
const playerRatingForValueHead = stats.rating ?? 1500
58-
const valueHeadDebugEnabled = isMaiaValueHeadDebugEnabled()
59-
60-
const selectValueHeadMove = useCallback(async () => {
61-
if (
62-
!controller.currentNode ||
63-
valueHeadStatus !== 'ready' ||
64-
!valueHeadModel
65-
) {
66-
if (valueHeadDebugEnabled) {
67-
console.log('[Maia value-head debug] selector unavailable', {
68-
hasCurrentNode: !!controller.currentNode,
69-
valueHeadStatus,
70-
hasModel: !!valueHeadModel,
71-
})
72-
}
73-
return null
74-
}
75-
76-
const currentFen = controller.currentNode.fen
77-
const chess = new Chess(currentFen)
78-
const legalMoves = chess.moves({ verbose: true }) as Array<{
79-
from: string
80-
to: string
81-
promotion?: string
82-
}>
83-
84-
if (legalMoves.length === 0) {
85-
return null
86-
}
87-
88-
const candidateMoves = legalMoves.map(
89-
(move) => `${move.from}${move.to}${move.promotion ?? ''}`,
90-
)
91-
const candidateBoards = candidateMoves.map((moveUci) => {
92-
const board = new Chess(currentFen)
93-
board.move(moveUci, { sloppy: true })
94-
return board.fen()
95-
})
96-
97-
const maiaRating = parseInt(
98-
playGameConfig.maiaVersion.replace('maia_kdd_', ''),
99-
10,
100-
)
101-
const modelElo = Number.isNaN(maiaRating) ? 1500 : maiaRating
102-
103-
// After Maia makes a candidate move, it is the human's turn.
104-
// Maia's value head conditions on the side to move as elo_self,
105-
// so the resulting boards must use the human as elo_self and Maia as elo_oppo.
106-
const { result } = await valueHeadModel.batchEvaluate(
107-
candidateBoards,
108-
Array(candidateBoards.length).fill(playerRatingForValueHead),
109-
Array(candidateBoards.length).fill(modelElo),
110-
)
111-
112-
const maiaIsWhite = controller.player === 'black'
113-
let bestMove = candidateMoves[0]
114-
let bestScore = maiaIsWhite ? result[0].value : 1 - result[0].value
115-
116-
for (let index = 1; index < candidateMoves.length; index++) {
117-
const whiteWinProb = result[index].value
118-
const maiaWinProb = maiaIsWhite ? whiteWinProb : 1 - whiteWinProb
119-
120-
if (maiaWinProb > bestScore) {
121-
bestMove = candidateMoves[index]
122-
bestScore = maiaWinProb
123-
}
124-
}
125-
126-
if (valueHeadDebugEnabled) {
127-
const candidateSummaries = candidateMoves
128-
.map((moveUci, index) => {
129-
const moveResult = result[index]
130-
const whiteWinProb = moveResult.value
131-
const maiaWinProb = maiaIsWhite ? whiteWinProb : 1 - whiteWinProb
132-
const board = new Chess(currentFen)
133-
const moveObj = board.move(moveUci, { sloppy: true })
134-
135-
return {
136-
move: moveUci,
137-
san: moveObj?.san ?? moveUci,
138-
maiaWinProb: Number(maiaWinProb.toFixed(4)),
139-
}
140-
})
141-
.sort((a, b) => b.maiaWinProb - a.maiaWinProb)
142-
143-
console.groupCollapsed(
144-
`[Maia value-head debug] ${playGameConfig.maiaVersion} selected ${bestMove}`,
145-
)
146-
console.table(candidateSummaries)
147-
console.groupEnd()
148-
}
149-
150-
const estimatedDelaySeconds = Math.min(
151-
3,
152-
0.35 + legalMoves.length * 0.04 + Math.random() * 0.25,
153-
)
154-
155-
return {
156-
top_move: bestMove,
157-
move_delay: estimatedDelaySeconds,
158-
}
159-
}, [
160-
controller.currentNode,
161-
controller.player,
162-
playGameConfig.maiaVersion,
163-
playerRatingForValueHead,
164-
valueHeadModel,
165-
valueHeadStatus,
166-
valueHeadDebugEnabled,
167-
])
16828

16929
const makePlayerMove = async (moveUci: string) => {
17030
const moveTime = controller.updateClock()
@@ -188,28 +48,20 @@ export const useVsMaiaPlayController = (
18848
? parseInt(controller.timeControl.split('+')[0]) * 60
18949
: 0
19050

191-
const maiaMoves =
192-
playGameConfig.maiaMoveSelectionMode === 'value_head'
193-
? await selectValueHeadMove()
194-
: await backOff(
195-
() =>
196-
fetchGameMove(
197-
controller.moveList,
198-
playGameConfig.maiaVersion,
199-
playGameConfig.startFen,
200-
null,
201-
simulateMaiaTime ? initialClock : 0,
202-
simulateMaiaTime ? maiaClock : 0,
203-
),
204-
{
205-
jitter: 'full',
206-
},
207-
)
208-
209-
if (!maiaMoves?.top_move) {
210-
return
211-
}
212-
51+
const maiaMoves = await backOff(
52+
() =>
53+
fetchGameMove(
54+
controller.moveList,
55+
playGameConfig.maiaVersion,
56+
playGameConfig.startFen,
57+
null,
58+
simulateMaiaTime ? initialClock : 0,
59+
simulateMaiaTime ? maiaClock : 0,
60+
),
61+
{
62+
jitter: 'full',
63+
},
64+
)
21365
const nextMove = maiaMoves['top_move']
21466
const moveDelay = maiaMoves['move_delay']
21567

@@ -255,11 +107,8 @@ export const useVsMaiaPlayController = (
255107
controller.game.termination,
256108
controller.moveList.length,
257109
playGameConfig.maiaVersion,
258-
playGameConfig.maiaMoveSelectionMode,
259110
playGameConfig.startFen,
260111
simulateMaiaTime,
261-
selectValueHeadMove,
262-
valueHeadStatus,
263112
])
264113

265114
useEffect(() => {

0 commit comments

Comments
 (0)