First release. A 3.0M-parameter relational chess engine: it tracks how every pair of squares relates to every other, and searches with MCTS at move time.
Browse files- README.md +32 -0
- browser/entry.js +1 -0
- browser/manifest.json +56 -0
- browser/rook6_trained.onnx +3 -0
README.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
library_name: onnx
|
| 3 |
+
tags:
|
| 4 |
+
- chess
|
| 5 |
+
- chess-engine
|
| 6 |
+
- onnx
|
| 7 |
+
pipeline_tag: other
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
# rook-chess-v1
|
| 11 |
+
|
| 12 |
+
A relational chess engine for the browser: it reads the 64 squares, keeps a table of how every pair of squares relates to every other, searches with MCTS, and plays one legal move.
|
| 13 |
+
|
| 14 |
+
About 3.0 million parameters, so roughly 40% the size of mini-chessformer-v1. The difference is structural rather than a matter of scale: instead of reasoning over squares alone, it carries a table of ordered square pairs through all six layers, seeded from the rules of movement (which squares attack which, and what blocks them). Same arena package contract as the other releases.
|
| 15 |
+
|
| 16 |
+
## What's in this repo
|
| 17 |
+
|
| 18 |
+
This is a browser chess engine package (`chess-gpt-package-v1`), not a Transformers checkpoint.
|
| 19 |
+
|
| 20 |
+
A compatible runner loads:
|
| 21 |
+
|
| 22 |
+
- `browser/manifest.json` — file list and hashes
|
| 23 |
+
- `browser/entry.js` — search and move picker
|
| 24 |
+
- the ONNX net named in that manifest
|
| 25 |
+
|
| 26 |
+
## Use
|
| 27 |
+
|
| 28 |
+
Point a chess-gpt-compatible runner at `burrowdweller/rook-chess-v1`. It calls `loadPackage` → `newGame` → `chooseMove({ history, legalMoves })` and must return one of those legal moves.
|
| 29 |
+
|
| 30 |
+
The net is small on purpose. Strength comes from searching at move time.
|
| 31 |
+
|
| 32 |
+
Source: [junisbuilding/chessdb](https://github.com/junisbuilding/chessdb)
|
browser/entry.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
const SEQ_LEN=69;const VOCAB_SIZE=53;const PIECE_TYPE={p:1,n:2,b:3,r:4,q:5,k:6};function fenToTokens(fen){const tokens=new Array(SEQ_LEN).fill(0);const[placement,activeColor,castling,epSquare,halfmoveStr]=fen.split(" ");const ranks=placement.split("/");if(ranks.length!==8){throw new Error(`fenToTokens: expected 8 ranks in placement, got ${ranks.length}`)}for(let ri=0;ri<8;ri++){const rank=7-ri;let file=0;for(const ch of ranks[ri]){if(ch>="1"&&ch<="8"){file+=Number(ch);continue}const lower=ch.toLowerCase();const pieceType=PIECE_TYPE[lower];if(pieceType===void 0){throw new Error(`fenToTokens: bad piece char '${ch}' in placement`)}const isWhite=ch===ch.toUpperCase();const sq=rank*8+file;tokens[sq]=isWhite?pieceType:pieceType+6;file+=1}if(file!==8){throw new Error(`fenToTokens: rank ${8-ri} did not sum to 8 files (got ${file})`)}}tokens[64]=activeColor==="w"?13:14;let castlingMask=0;if(castling.includes("K"))castlingMask|=1;if(castling.includes("Q"))castlingMask|=2;if(castling.includes("k"))castlingMask|=4;if(castling.includes("q"))castlingMask|=8;tokens[65]=15+castlingMask;if(epSquare&&epSquare!=="-"){const file=epSquare.charCodeAt(0)-"a".charCodeAt(0);tokens[66]=31+file}else{tokens[66]=39}const halfmove=Number(halfmoveStr??0);tokens[67]=40+Math.min(Math.floor(halfmove/5),9);tokens[68]=50+0;return tokens}const PROMO_PIECES=[2,3,4,5];const PROMO_LETTER_TO_INT={n:2,b:3,r:4,q:5};function squareRank(sq){return Math.floor(sq/8)}function squareFile(sq){return sq%8}function buildMoveIndex(){const moves=[];for(let frm=0;frm<64;frm++){for(let to=0;to<64;to++){moves.push([frm,to,null])}}for(let frm=0;frm<64;frm++){const fr=squareRank(frm);const ff=squareFile(frm);for(let to=0;to<64;to++){const tr=squareRank(to);const tf=squareFile(to);const whitePromo=fr===6&&tr===7;const blackPromo=fr===1&&tr===0;if((whitePromo||blackPromo)&&Math.abs(ff-tf)<=1){for(const p of PROMO_PIECES){moves.push([frm,to,p])}}}}const idx=new Map;moves.forEach((m,i)=>idx.set(m.join(","),i));return{moves,idx}}const{moves:MOVES_LIST,idx:MOVE_TO_IDX}=buildMoveIndex();const MOVE_SPACE=MOVES_LIST.length;function squareNameToIndex(name){const file=name.charCodeAt(0)-"a".charCodeAt(0);const rank=name.charCodeAt(1)-"1".charCodeAt(0);return rank*8+file}function moveToIndex(fromName,toName,promoLetter){const frm=squareNameToIndex(fromName);const to=squareNameToIndex(toName);const promo=promoLetter?PROMO_LETTER_TO_INT[promoLetter]:null;if(promoLetter&&promo===void 0){throw new Error(`moveToIndex: unrecognized promotion letter '${promoLetter}'`)}const key=[frm,to,promo].join(",");const i=MOVE_TO_IDX.get(key);if(i===void 0){throw new Error(`moveToIndex: no index for move ${fromName}${toName}${promoLetter||""}`)}return i}const PAWN=1,KNIGHT=2,BISHOP=3,ROOK=4,QUEEN=5,KING=6;const WHITE=0,BLACK=1;const PIECE_CHAR={[PAWN]:"p",[KNIGHT]:"n",[BISHOP]:"b",[ROOK]:"r",[QUEEN]:"q",[KING]:"k"};const CHAR_PIECE=Object.fromEntries(Object.entries(PIECE_CHAR).map(([k,v])=>[v,Number(k)]));const PROMO_LETTER={[KNIGHT]:"n",[BISHOP]:"b",[ROOK]:"r",[QUEEN]:"q"};const LETTER_PROMO={n:KNIGHT,b:BISHOP,r:ROOK,q:QUEEN};function sqIndex(name){const file=name.charCodeAt(0)-97;const rank=name.charCodeAt(1)-49;return rank*8+file}function sqName(idx){const file=idx&7;const rank=idx>>3;return String.fromCharCode(97+file)+String.fromCharCode(49+rank)}const fileOf=sq=>sq&7;const rankOf=sq=>sq>>3;const START_FEN="rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";const KNIGHT_OFFSETS=[[1,2],[2,1],[2,-1],[1,-2],[-1,-2],[-2,-1],[-2,1],[-1,2]];const KING_OFFSETS=[[1,0],[1,1],[0,1],[-1,1],[-1,0],[-1,-1],[0,-1],[1,-1]];const BISHOP_DIRS=[[1,1],[1,-1],[-1,1],[-1,-1]];const ROOK_DIRS=[[1,0],[-1,0],[0,1],[0,-1]];const QUEEN_DIRS=[...BISHOP_DIRS,...ROOK_DIRS];function inBounds(file,rank){return file>=0&&file<8&&rank>=0&&rank<8}function pieceTypeAt(board,sq){const p=board[sq];return p===0?0:p<=6?p:p-6}function colorAt(board,sq){const p=board[sq];return p===0?-1:p<=6?WHITE:BLACK}function isAttacked(board,sq,byColor){const sf=fileOf(sq),sr=rankOf(sq);for(const[df,dr]of KNIGHT_OFFSETS){const f=sf+df,r=sr+dr;if(!inBounds(f,r))continue;const t=r*8+f;if(colorAt(board,t)===byColor&&pieceTypeAt(board,t)===KNIGHT)return true}for(const[df,dr]of KING_OFFSETS){const f=sf+df,r=sr+dr;if(!inBounds(f,r))continue;const t=r*8+f;if(colorAt(board,t)===byColor&&pieceTypeAt(board,t)===KING)return true}{const dr=byColor===WHITE?-1:1;for(const df of[-1,1]){const f=sf+df,r=sr+dr;if(!inBounds(f,r))continue;const t=r*8+f;if(colorAt(board,t)===byColor&&pieceTypeAt(board,t)===PAWN)return true}}for(const[df,dr]of BISHOP_DIRS){let f=sf+df,r=sr+dr;while(inBounds(f,r)){const t=r*8+f;const c=colorAt(board,t);if(c!==-1){if(c===byColor){const pt=pieceTypeAt(board,t);if(pt===BISHOP||pt===QUEEN)return true}break}f+=df;r+=dr}}for(const[df,dr]of ROOK_DIRS){let f=sf+df,r=sr+dr;while(inBounds(f,r)){const t=r*8+f;const c=colorAt(board,t);if(c!==-1){if(c===byColor){const pt=pieceTypeAt(board,t);if(pt===ROOK||pt===QUEEN)return true}break}f+=df;r+=dr}}return false}function kingDangerSquares(board,kingSq,enemyColor){const scratch=board.slice();scratch[kingSq]=0;const kingColor=enemyColor===WHITE?BLACK:WHITE;const kf=fileOf(kingSq),kr=rankOf(kingSq);const candidates=new Set;for(const[df,dr]of KING_OFFSETS){const f=kf+df,r=kr+dr;if(inBounds(f,r))candidates.add(r*8+f)}for(const spec of CASTLING_SPECS){if(spec.color!==kingColor)continue;for(const sq of spec.kingPath)candidates.add(sq)}const danger=new Set;for(const sq of candidates){if(isAttacked(scratch,sq,enemyColor))danger.add(sq)}return danger}function computeCheckState(board,kingSq,kingColor){const enemyColor=kingColor===WHITE?BLACK:WHITE;const kf=fileOf(kingSq),kr=rankOf(kingSq);const checkers=[];for(const[df,dr]of KNIGHT_OFFSETS){const f=kf+df,r=kr+dr;if(!inBounds(f,r))continue;const t=r*8+f;if(colorAt(board,t)===enemyColor&&pieceTypeAt(board,t)===KNIGHT)checkers.push(t)}{const dr=kingColor===WHITE?1:-1;for(const df of[-1,1]){const f=kf+df,r=kr+dr;if(!inBounds(f,r))continue;const t=r*8+f;if(colorAt(board,t)===enemyColor&&pieceTypeAt(board,t)===PAWN)checkers.push(t)}}const pinned=new Map;for(const[df,dr]of QUEEN_DIRS){const isDiagonal=df!==0&&dr!==0;let f=kf+df,r=kr+dr;let ownBlocker=-1;while(inBounds(f,r)){const t=r*8+f;const c=colorAt(board,t);if(c===-1){f+=df;r+=dr;continue}if(c===kingColor){if(ownBlocker===-1){ownBlocker=t;f+=df;r+=dr;continue}break}const pt=pieceTypeAt(board,t);const attacksThisRay=isDiagonal?pt===BISHOP||pt===QUEEN:pt===ROOK||pt===QUEEN;if(ownBlocker===-1){if(attacksThisRay)checkers.push(t);break}if(attacksThisRay){const ray=new Set;let rf=kf+df,rr=kr+dr;while(true){const rsq=rr*8+rf;ray.add(rsq);if(rsq===t)break;rf+=df;rr+=dr}pinned.set(ownBlocker,ray)}break}}return{checkers,pinned}}function checkResponseMask(board,kingSq,checkers){if(checkers.length===0)return null;if(checkers.length>=2)return new Set;const checkerSq=checkers[0];const pt=pieceTypeAt(board,checkerSq);const mask=new Set([checkerSq]);if(pt===BISHOP||pt===ROOK||pt===QUEEN){const kf=fileOf(kingSq),kr=rankOf(kingSq);const cf=fileOf(checkerSq),cr=rankOf(checkerSq);const df=Math.sign(cf-kf),dr=Math.sign(cr-kr);let f=kf+df,r=kr+dr;while(!(f===cf&&r===cr)){mask.add(r*8+f);f+=df;r+=dr}}return mask}function inMask(mask,sq){return mask===null||mask.has(sq)}function _genSliderMoves(board,color,checkMask,pinned){const moves=[];for(let sq=0;sq<64;sq++){if(colorAt(board,sq)!==color)continue;const pt=pieceTypeAt(board,sq);if(pt!==BISHOP&&pt!==ROOK&&pt!==QUEEN)continue;const dirs=pt===BISHOP?BISHOP_DIRS:pt===ROOK?ROOK_DIRS:QUEEN_DIRS;const pinRay=pinned.get(sq)??null;const sf=fileOf(sq),sr=rankOf(sq);for(const[df,dr]of dirs){let f=sf+df,r=sr+dr;while(inBounds(f,r)){const t=r*8+f;const c=colorAt(board,t);if(c===color)break;if(inMask(checkMask,t)&&(pinRay===null||pinRay.has(t))){moves.push({from:sq,to:t,promotion:null})}if(c!==-1)break;f+=df;r+=dr}}}return moves}function _genKnightMoves(board,color,checkMask,pinned){const moves=[];for(let sq=0;sq<64;sq++){if(colorAt(board,sq)!==color||pieceTypeAt(board,sq)!==KNIGHT)continue;if(pinned.has(sq))continue;const sf=fileOf(sq),sr=rankOf(sq);for(const[df,dr]of KNIGHT_OFFSETS){const f=sf+df,r=sr+dr;if(!inBounds(f,r))continue;const t=r*8+f;if(colorAt(board,t)===color)continue;if(!inMask(checkMask,t))continue;moves.push({from:sq,to:t,promotion:null})}}return moves}function _genKingMoves(board,color,kingSq,kingDanger){const moves=[];const sf=fileOf(kingSq),sr=rankOf(kingSq);for(const[df,dr]of KING_OFFSETS){const f=sf+df,r=sr+dr;if(!inBounds(f,r))continue;const t=r*8+f;if(colorAt(board,t)===color)continue;if(kingDanger.has(t))continue;moves.push({from:kingSq,to:t,promotion:null})}return moves}function findKing(board,color){for(let sq=0;sq<64;sq++){if(colorAt(board,sq)===color&&pieceTypeAt(board,sq)===KING)return sq}throw new Error("no king on board for color "+color)}function legalMovesNoPawnsNoCastling(board,color){const kingSq=findKing(board,color);const enemyColor=color===WHITE?BLACK:WHITE;const{checkers,pinned}=computeCheckState(board,kingSq,color);const checkMask=checkResponseMask(board,kingSq,checkers);const kingDanger=kingDangerSquares(board,kingSq,enemyColor);const moves=_genKingMoves(board,color,kingSq,kingDanger);if(checkers.length>=2)return moves;return moves.concat(_genSliderMoves(board,color,checkMask,pinned),_genKnightMoves(board,color,checkMask,pinned))}const PROMOTION_PIECE_TYPES=[KNIGHT,BISHOP,ROOK,QUEEN];function epExposesDiscoveredCheck(board,fromSq,capturedPawnSq,color,kingSq){if(rankOf(kingSq)!==rankOf(fromSq))return false;const scratch=board.slice();scratch[fromSq]=0;scratch[capturedPawnSq]=0;const enemyColor=color===WHITE?BLACK:WHITE;return isAttacked(scratch,kingSq,enemyColor)}function _genPawnMoves(board,color,checkMask,pinned,epSquare,kingSq){const moves=[];const dir=color===WHITE?1:-1;const startRank=color===WHITE?1:6;const promoRank=color===WHITE?7:0;for(let sq=0;sq<64;sq++){if(colorAt(board,sq)!==color||pieceTypeAt(board,sq)!==PAWN)continue;const f=fileOf(sq),r=rankOf(sq);const pinRay=pinned.get(sq)??null;const addMove=(to,promo=null)=>{if(!inMask(checkMask,to))return;if(pinRay!==null&&!pinRay.has(to))return;if(r+dir===promoRank){for(const p of PROMOTION_PIECE_TYPES)moves.push({from:sq,to,promotion:p})}else{moves.push({from:sq,to,promotion:promo})}};const oneAheadRank=r+dir;if(inBounds(f,oneAheadRank)){const oneAhead=oneAheadRank*8+f;if(colorAt(board,oneAhead)===-1){addMove(oneAhead);if(r===startRank){const twoAheadRank=r+2*dir;const twoAhead=twoAheadRank*8+f;if(colorAt(board,twoAhead)===-1)addMove(twoAhead)}}}for(const df of[-1,1]){const cf=f+df,cr=r+dir;if(!inBounds(cf,cr))continue;const capSq=cr*8+cf;if(colorAt(board,capSq)===(color===WHITE?BLACK:WHITE)){addMove(capSq)}else if(capSq===epSquare){const capturedPawnSq=r*8+cf;if(epExposesDiscoveredCheck(board,sq,capturedPawnSq,color,kingSq))continue;if(checkMask!==null&&!checkMask.has(epSquare)&&!checkMask.has(capturedPawnSq))continue;if(pinRay!==null&&!pinRay.has(epSquare))continue;moves.push({from:sq,to:epSquare,promotion:null})}}}return moves}function pawnMovesForTest(board,color,epSquare){const kingSq=findKing(board,color);const{checkers,pinned}=computeCheckState(board,kingSq,color);const checkMask=checkResponseMask(board,kingSq,checkers);if(checkers.length>=2)return[];return _genPawnMoves(board,color,checkMask,pinned,epSquare,kingSq)}const CASTLING_SPECS=[{color:WHITE,side:"k",kingFrom:4,kingTo:6,rookFrom:7,clear:[5,6],kingPath:[4,5,6],rightsBit:1},{color:WHITE,side:"q",kingFrom:4,kingTo:2,rookFrom:0,clear:[1,2,3],kingPath:[4,3,2],rightsBit:2},{color:BLACK,side:"k",kingFrom:60,kingTo:62,rookFrom:63,clear:[61,62],kingPath:[60,61,62],rightsBit:4},{color:BLACK,side:"q",kingFrom:60,kingTo:58,rookFrom:56,clear:[57,58,59],kingPath:[60,59,58],rightsBit:8}];function _genCastling(board,color,castlingRights,checkersCount,kingDanger){if(checkersCount>0)return[];const moves=[];for(const spec of CASTLING_SPECS){if(spec.color!==color)continue;if((castlingRights&spec.rightsBit)===0)continue;if(spec.clear.some(sq=>colorAt(board,sq)!==-1))continue;if(spec.kingPath.some(sq=>kingDanger.has(sq)))continue;moves.push({from:spec.kingFrom,to:spec.kingTo,promotion:null})}return moves}function castlingMovesForTest(board,color,castlingRights){const kingSq=findKing(board,color);const enemyColor=color===WHITE?BLACK:WHITE;const{checkers}=computeCheckState(board,kingSq,color);const kingDanger=kingDangerSquares(board,kingSq,enemyColor);return _genCastling(board,color,castlingRights,checkers.length,kingDanger)}class Chess{constructor(fen){this._loadFen(fen??START_FEN)}_loadFen(fen){const[placement,active,castling,ep,halfmove,fullmove]=fen.split(" ");this._board=new Uint8Array(64);const ranks=placement.split("/");if(ranks.length!==8)throw new Error(`bad FEN placement: ${placement}`);for(let ri=0;ri<8;ri++){const rank=7-ri;let file=0;for(const ch of ranks[ri]){if(ch>="1"&&ch<="8"){file+=Number(ch);continue}const lower=ch.toLowerCase();const type=CHAR_PIECE[lower];if(type===void 0)throw new Error(`bad FEN piece char: ${ch}`);const isWhite=ch===ch.toUpperCase();this._board[rank*8+file]=type+(isWhite?0:6);file+=1}if(file!==8)throw new Error(`bad FEN rank (not 8 files): ${ranks[ri]}`)}this._turn=active==="w"?WHITE:BLACK;let mask=0;if(castling.includes("K"))mask|=1;if(castling.includes("Q"))mask|=2;if(castling.includes("k"))mask|=4;if(castling.includes("q"))mask|=8;this._castling=mask;this._epSquare=ep==="-"?-1:sqIndex(ep);this._halfmove=Number(halfmove??0);this._fullmove=Number(fullmove??1);this._history=[];this._repCounts=new Map;this._seedRepCount()}turn(){return this._turn===WHITE?"w":"b"}fen(){let placement="";for(let rank=7;rank>=0;rank--){let empty=0;for(let file=0;file<8;file++){const piece=this._board[rank*8+file];if(piece===0){empty++;continue}if(empty>0){placement+=empty;empty=0}const isWhite=piece<=6;const type=isWhite?piece:piece-6;placement+=isWhite?PIECE_CHAR[type].toUpperCase():PIECE_CHAR[type]}if(empty>0)placement+=empty;if(rank>0)placement+="/"}let castling="";if(this._castling&1)castling+="K";if(this._castling&2)castling+="Q";if(this._castling&4)castling+="k";if(this._castling&8)castling+="q";if(castling==="")castling="-";const ep=this._hasLegalEnPassant()?sqName(this._epSquare):"-";return`${placement} ${this._turn===WHITE?"w":"b"} ${castling} ${ep} ${this._halfmove} ${this._fullmove}`}_hasLegalEnPassant(){if(this._epSquare===-1)return false;return this._legalMoves().some(m=>m.to===this._epSquare&&pieceTypeAt(this._board,m.from)===PAWN&&fileOf(m.from)!==fileOf(m.to))}_positionKey(){const ep=this._hasLegalEnPassant()?this._epSquare:-1;return this._board.join(",")+"|"+this._turn+"|"+this._castling+"|"+ep}_seedRepCount(){const key=this._positionKey();this._repCounts.set(key,(this._repCounts.get(key)??0)+1)}_legalMoves(){const kingSq=findKing(this._board,this._turn);const enemyColor=this._turn===WHITE?BLACK:WHITE;const{checkers,pinned}=computeCheckState(this._board,kingSq,this._turn);const checkMask=checkResponseMask(this._board,kingSq,checkers);const kingDanger=kingDangerSquares(this._board,kingSq,enemyColor);const moves=_genKingMoves(this._board,this._turn,kingSq,kingDanger);moves.push(..._genCastling(this._board,this._turn,this._castling,checkers.length,kingDanger));if(checkers.length<2){moves.push(..._genSliderMoves(this._board,this._turn,checkMask,pinned),..._genKnightMoves(this._board,this._turn,checkMask,pinned),..._genPawnMoves(this._board,this._turn,checkMask,pinned,this._epSquare,kingSq))}return moves}isCheck(){const kingSq=findKing(this._board,this._turn);return isAttacked(this._board,kingSq,this._turn===WHITE?BLACK:WHITE)}isCheckmate(){return this.isCheck()&&this._legalMoves().length===0}_isStalemate(){return!this.isCheck()&&this._legalMoves().length===0}moves(opts){const raw=this._legalMoves();if(opts&&opts.verbose){const computeSan=opts.computeSan!==false;return raw.map(m=>{const verbose={from:sqName(m.from),to:sqName(m.to),promotion:m.promotion?PROMO_LETTER[m.promotion]:void 0};if(computeSan)verbose.san=this.moveToSan(m);return verbose})}return raw.map(m=>this.moveToSan(m))}_applyMoveForTest(from,to,promotion){return this._applyMove({from,to,promotion:promotion??null})}_applyMove(move){const{from,to,promotion}=move;const board=this._board;const movingPiece=board[from];const movingType=pieceTypeAt(board,from);const isWhite=this._turn===WHITE;const capturedPiece=board[to];const record={from,to,promotion,movingPiece,capturedPiece,castling:this._castling,epSquare:this._epSquare,halfmove:this._halfmove,isEnPassant:false,capturedSq:to,rookFrom:-1,rookTo:-1};if(movingType===PAWN&&to===this._epSquare&&board[to]===0&&fileOf(from)!==fileOf(to)){const capturedSq=rankOf(from)*8+fileOf(to);record.isEnPassant=true;record.capturedSq=capturedSq;record.capturedPiece=board[capturedSq];board[capturedSq]=0}if(movingType===KING&&Math.abs(fileOf(to)-fileOf(from))===2){const spec=CASTLING_SPECS.find(s=>s.color===this._turn&&s.kingTo===to);board[spec.rookFrom]=0;const rookTo=spec.side==="k"?spec.kingTo-1:spec.kingTo+1;board[rookTo]=ROOK+(isWhite?0:6);record.rookFrom=spec.rookFrom;record.rookTo=rookTo}board[from]=0;board[to]=promotion!==null?promotion+(isWhite?0:6):movingPiece;if(movingType===KING){this._castling&=isWhite?~3:~12}for(const spec of CASTLING_SPECS){if(from===spec.rookFrom||to===spec.rookFrom)this._castling&=~spec.rightsBit}this._epSquare=-1;if(movingType===PAWN&&Math.abs(rankOf(to)-rankOf(from))===2){this._epSquare=(rankOf(from)+rankOf(to))/2*8+fileOf(from)}this._halfmove=movingType===PAWN||record.capturedPiece!==0?0:this._halfmove+1;if(this._turn===BLACK)this._fullmove+=1;this._turn=isWhite?BLACK:WHITE;this._history.push(record);this._recordRepetition()}undo(){const record=this._history.pop();if(!record)return;this._forgetRepetition();const board=this._board;const isWhite=this._turn===BLACK;board[record.from]=record.movingPiece;if(record.isEnPassant){board[record.to]=0;board[record.capturedSq]=record.capturedPiece}else if(record.capturedPiece!==0){board[record.to]=record.capturedPiece}else{board[record.to]=0}if(record.rookFrom!==-1){board[record.rookTo]=0;board[record.rookFrom]=ROOK+(isWhite?0:6)}this._castling=record.castling;this._epSquare=record.epSquare;this._halfmove=record.halfmove;if(this._turn===WHITE)this._fullmove-=1;this._turn=isWhite?WHITE:BLACK}history(opts){if(opts&&opts.verbose){return this._history.map(r=>({from:sqName(r.from),to:sqName(r.to),promotion:r.promotion?PROMO_LETTER[r.promotion]:void 0}))}return this._history.map(r=>sqName(r.from)+sqName(r.to))}_recordRepetition(){const key=this._positionKey();this._repCounts.set(key,(this._repCounts.get(key)??0)+1)}_forgetRepetition(){const key=this._positionKey();const count=this._repCounts.get(key)??0;if(count<=1)this._repCounts.delete(key);else this._repCounts.set(key,count-1)}_isThreefoldRepetition(){return(this._repCounts.get(this._positionKey())??0)>=3}_isInsufficientMaterial(){const pieces=[];for(let sq=0;sq<64;sq++){const p=this._board[sq];if(p===0)continue;const type=pieceTypeAt(this._board,sq);if(type===KING)continue;pieces.push({type,color:colorAt(this._board,sq),sq})}if(pieces.length===0)return true;if(pieces.length===1&&(pieces[0].type===KNIGHT||pieces[0].type===BISHOP))return true;if(pieces.length===2&&pieces.every(p=>p.type===BISHOP)&&pieces[0].color!==pieces[1].color){const colorOfSquare=sq=>(fileOf(sq)+rankOf(sq))%2;if(colorOfSquare(pieces[0].sq)===colorOfSquare(pieces[1].sq))return true}return false}isGameOver(){if(this.isCheckmate())return true;if(this._isStalemate())return true;if(this._isInsufficientMaterial())return true;if(this._halfmove>=100)return true;if(this._isThreefoldRepetition())return true;return false}move(input){const legal=this._legalMoves();let chosen;if(typeof input==="string"){chosen=this._resolveSan(input,legal);if(!chosen)throw new Error(`move() called with unrecognized SAN: ${input}`)}else{const fromIdx=sqIndex(input.from),toIdx=sqIndex(input.to);const promo=input.promotion?LETTER_PROMO[input.promotion]:null;chosen=legal.find(m=>m.from===fromIdx&&m.to===toIdx&&m.promotion===promo);if(!chosen)throw new Error(`move() called with illegal move: ${JSON.stringify(input)}`)}const san=this.moveToSan(chosen);this._applyMove(chosen);return{from:sqName(chosen.from),to:sqName(chosen.to),promotion:chosen.promotion?PROMO_LETTER[chosen.promotion]:void 0,san}}_resolveSan(san,legal){const clean=san.replace(/[+#]$/,"");if(clean==="O-O"||clean==="0-0"){return legal.find(m2=>pieceTypeAt(this._board,m2.from)===KING&&m2.to-m2.from===2)}if(clean==="O-O-O"||clean==="0-0-0"){return legal.find(m2=>pieceTypeAt(this._board,m2.from)===KING&&m2.from-m2.to===2)}const m=clean.match(/^([NBRQK]?)([a-h]?)([1-8]?)(x?)([a-h][1-8])(?:=([NBRQ]))?$/);if(!m)return null;const[,pieceLetter,disFile,disRank,,destName,promoLetter]=m;const pieceType=pieceLetter?CHAR_PIECE[pieceLetter.toLowerCase()]:PAWN;const dest=sqIndex(destName);const promo=promoLetter?LETTER_PROMO[promoLetter.toLowerCase()]:null;const candidates=legal.filter(cand=>{if(cand.to!==dest)return false;if(cand.promotion!==promo)return false;if(pieceTypeAt(this._board,cand.from)!==pieceType)return false;if(disFile&&fileOf(cand.from)!==disFile.charCodeAt(0)-97)return false;if(disRank&&rankOf(cand.from)!==disRank.charCodeAt(0)-49)return false;return true});return candidates.length===1?candidates[0]:null}moveToSan(move){const from=typeof move.from==="string"?sqIndex(move.from):move.from;const to=typeof move.to==="string"?sqIndex(move.to):move.to;const promotion=move.promotion==null?null:typeof move.promotion==="string"?LETTER_PROMO[move.promotion]:move.promotion;move={from,to,promotion};const movingType=pieceTypeAt(this._board,from);if(movingType===KING&&Math.abs(fileOf(to)-fileOf(from))===2){const base=to>from?"O-O":"O-O-O";return base+this._checkSuffix(move)}const isCapture=this._board[to]!==0||movingType===PAWN&&to===this._epSquare;let san="";if(movingType===PAWN){if(isCapture)san+=sqName(from)[0]+"x";san+=sqName(to);if(promotion)san+="="+PROMO_LETTER[promotion].toUpperCase()}else{san+=PIECE_CHAR[movingType].toUpperCase();san+=this._disambiguator(move);if(isCapture)san+="x";san+=sqName(to)}return san+this._checkSuffix(move)}_disambiguator(move){const{from,to,promotion}=move;const movingType=pieceTypeAt(this._board,from);const sharing=this._legalMoves().filter(m=>m.to===to&&m.promotion===promotion&&m.from!==from&&pieceTypeAt(this._board,m.from)===movingType);if(sharing.length===0)return"";const sameFile=sharing.some(m=>fileOf(m.from)===fileOf(from));const sameRank=sharing.some(m=>rankOf(m.from)===rankOf(from));if(!sameFile)return sqName(from)[0];if(!sameRank)return sqName(from)[1];return sqName(from)}_checkSuffix(move){this._applyMove(move);try{if(this.isCheck())return this._legalMoves().length===0?"#":"+";return""}finally{this.undo()}}}const PROMO_PIECE_ORDER=[KNIGHT,BISHOP,ROOK,QUEEN];const PROMO_MOVE_TABLE=(()=>{const table=[];for(let frm=0;frm<64;frm++){const fr=frm>>3,ff=frm&7;for(let to=0;to<64;to++){const tr=to>>3,tf=to&7;if((fr===6&&tr===7||fr===1&&tr===0)&&Math.abs(ff-tf)<=1){for(const p of PROMO_PIECE_ORDER)table.push({from:frm,to,promotion:p})}}}return table})();function decodeMoveIndex(idx){if(idx<4096)return{from:idx>>6,to:idx&63,promotion:null};return PROMO_MOVE_TABLE[idx-4096]}const ADAPTIVE_BATCH_SAFETY=1.25;const ADAPTIVE_BATCH_GUARD_MS=2;const ADAPTIVE_BATCH_EMA_ALPHA=.25;function clockMs(){if(typeof performance!=="undefined"&&typeof performance.now==="function"){return performance.now()}return Date.now()}function adaptiveBatchSizeForBudget(maxBatchSize,remainingMs,referenceBatchMs,referenceBatchSize=maxBatchSize){if(!Number.isInteger(maxBatchSize)||maxBatchSize<=0)return 0;if(remainingMs===Infinity)return maxBatchSize;if(!Number.isFinite(remainingMs)||remainingMs<=0)return 0;if(!Number.isFinite(referenceBatchMs)||referenceBatchMs<=0||!Number.isInteger(referenceBatchSize)||referenceBatchSize<=0){return maxBatchSize}for(let candidate=maxBatchSize;candidate>=1;candidate=Math.floor(candidate/2)){const estimatedMs=referenceBatchMs*(candidate/referenceBatchSize);const requiredMs=estimatedMs*ADAPTIVE_BATCH_SAFETY+ADAPTIVE_BATCH_GUARD_MS;if(remainingMs>=requiredMs)return candidate;if(candidate===1)break}return 0}class MCTSConfig{constructor({nSimulations=800,timeBudgetMs=null,cPuct=1.5,batchSize=1,reuseTree=true,deferredEval=false,deferredEvalMinVisits=2,lazyExpansion=false,drawContempt=0,drawContemptMinQ=.55}={}){this.nSimulations=nSimulations;this.timeBudgetMs=timeBudgetMs;this.cPuct=cPuct;this.batchSize=batchSize;this.reuseTree=reuseTree;this.deferredEval=deferredEval;this.deferredEvalMinVisits=deferredEvalMinVisits;this.drawContempt=drawContempt;this.drawContemptMinQ=drawContemptMinQ;this.lazyExpansion=lazyExpansion}}class Node{constructor(prior){this.prior=prior;this.visitCount=0;this.valueSum=0;this.children=new Map;this.expanded=false;this.terminal=false;this.lazy=null;this.valueQ=null;this.terminalValue=0}}function softmax(arr){const m=Math.max(...arr);const exps=arr.map(x=>Math.exp(x-m));const total=exps.reduce((a,b)=>a+b,0);if(total<=0)return arr.map(()=>1/arr.length);return exps.map(e=>e/total)}function wdlToScore(wdlProbs){return wdlProbs[0]+.5*wdlProbs[1]}function isDraw(chess){return chess.isGameOver()&&!chess.isCheckmate()}function rootSnapshot(root,simCount,chess){return{simCount,candidates:Array.from(root.children.values(),edge=>({san:chess.moveToSan({from:edge.from,to:edge.to,promotion:edge.promotion}),from:sqName(edge.from),to:sqName(edge.to),promotion:edge.promotion?PROMO_LETTER[edge.promotion]:void 0,visits:edge.node.visitCount,prior:edge.node.prior}))}}class MCTS{constructor(evaluator,config){this.evaluator=evaluator;this.config=config||new MCTSConfig;this.lastRootQ=null;this.batchDurationMs=null;this.batchDurationSamples=0;this._root=null;this._rootHistory=null;this._rootFen=null}async search(chess,{timeBudgetMs,onRootUpdate}={}){const cfg=this.config;const effectiveTimeBudgetMs=timeBudgetMs!==void 0?timeBudgetMs:cfg.timeBudgetMs;const root=cfg.reuseTree&&this._acquireRoot(chess)||new Node(1);this.lastRootQ=root.valueQ;const notifyRoot=simCount=>{if(typeof onRootUpdate!=="function"||root.children.size===0)return;try{onRootUpdate(rootSnapshot(root,simCount,chess))}catch{}};const startTime=clockMs();let sim=0;if(cfg.batchSize<=1){while(sim<cfg.nSimulations){if(effectiveTimeBudgetMs!==null&&clockMs()-startTime>=effectiveTimeBudgetMs){break}await this._simulate(root,chess);sim++;if(sim===1||sim%16===0)notifyRoot(sim)}}else{if(sim<cfg.nSimulations&&(effectiveTimeBudgetMs===null||clockMs()-startTime<effectiveTimeBudgetMs)){const value=await this._expand(root,chess,true);this.lastRootQ=root.valueQ;this._backup([root],this._drawContemptValue(value,0));sim++;notifyRoot(sim)}while(sim<cfg.nSimulations){if(effectiveTimeBudgetMs!==null&&clockMs()-startTime>=effectiveTimeBudgetMs){break}const maxRoundBudget=Math.min(cfg.batchSize,cfg.nSimulations-sim);const remainingMs=effectiveTimeBudgetMs===null?Infinity:effectiveTimeBudgetMs-(clockMs()-startTime);const roundBudget=this._chooseAdaptiveBatchSize(remainingMs,maxRoundBudget);if(roundBudget===0)break;const completed=await this._runBatchedRound(root,chess,roundBudget,startTime,effectiveTimeBudgetMs);sim+=completed;if(completed>0)notifyRoot(sim);if(completed===0)break}}this.lastSimCount=sim;if(cfg.reuseTree){this._root=root;this._rootHistory=chess.history({verbose:true});this._rootFen=chess.fen()}if(root.children.size===0){const moves=chess.moves({verbose:true});if(moves.length===0){throw new Error("search() called on a position with no legal moves")}return chess.moveToSan({from:moves[0].from,to:moves[0].to,promotion:moves[0].promotion})}let bestEdge=null;let bestVisits=-1;for(const edge of root.children.values()){if(edge.node.visitCount>bestVisits){bestVisits=edge.node.visitCount;bestEdge=edge}}return chess.moveToSan({from:bestEdge.from,to:bestEdge.to,promotion:bestEdge.promotion})}_acquireRoot(chess){if(this._root===null||this._rootHistory===null||this._rootFen===null)return null;const oldHistory=this._rootHistory;const newHistory=chess.history({verbose:true});if(newHistory.length<oldHistory.length)return null;for(let i=0;i<oldHistory.length;i++){const oldMove=oldHistory[i];const newMove=newHistory[i];if(newMove.from!==oldMove.from||newMove.to!==oldMove.to||newMove.promotion!==oldMove.promotion){return null}}const elapsed=newHistory.slice(oldHistory.length);const replay=new Chess(this._rootFen);for(const move of elapsed){try{replay.move({from:move.from,to:move.to,promotion:move.promotion})}catch{return null}}if(replay.fen()!==chess.fen()){return null}let node=this._root;for(const move of elapsed){if(!node.expanded||node.terminal)return null;let moveIdx;try{moveIdx=moveToIndex(move.from,move.to,move.promotion)}catch{return null}const edge=node.children.get(moveIdx);if(edge===void 0)return null;node=edge.node}return node}_recordDefaultBatchTiming(elapsedMs){if(!Number.isFinite(elapsedMs)||elapsedMs<=0)return;if(this.batchDurationMs===null){this.batchDurationMs=elapsedMs}else{this.batchDurationMs=(1-ADAPTIVE_BATCH_EMA_ALPHA)*this.batchDurationMs+ADAPTIVE_BATCH_EMA_ALPHA*elapsedMs}this.batchDurationSamples++}_chooseAdaptiveBatchSize(remainingMs,maxBatchSize=this.config.batchSize){return adaptiveBatchSizeForBudget(maxBatchSize,remainingMs,this.batchDurationMs,this.config.batchSize)}_shouldDeferEval(node,path,currentVisitCounted){const cfg=this.config;if(!cfg.deferredEval)return false;if(path.length<=1)return false;const visits=currentVisitCounted?node.visitCount:node.visitCount+1;return visits<cfg.deferredEvalMinVisits}_drawContemptValue(value,depth){const cfg=this.config;if(value!==0||cfg.drawContempt===0||!(this.lastRootQ>=cfg.drawContemptMinQ)){return value}return value-cfg.drawContempt*(depth%2===0?1:-1)}async _simulate(root,chess){let node=root;const path=[node];let pushed=0;let depth=0;while(node.expanded&&!node.terminal){if(this.config.lazyExpansion)this._maybeExpandLazy(node,chess);const moveIdx=this._selectPuct(node);const edge=node.children.get(moveIdx);chess._applyMove({from:edge.from,to:edge.to,promotion:edge.promotion});pushed++;depth++;node=edge.node;path.push(node)}let value;if(node.terminal){value=node.terminalValue}else if(this._shouldDeferEval(node,path,false)&&!chess.isCheckmate()&&!isDraw(chess)){value=0}else{value=await this._expand(node,chess,node===root)}if(node===root&&node.valueQ!==null)this.lastRootQ=node.valueQ;value=this._drawContemptValue(value,depth);this._backup(path,value);for(let i=0;i<pushed;i++){chess.undo()}}async _runBatchedRound(root,chess,roundBudget,startTime,timeBudgetMs){const roundStart=clockMs();const pending=new Map;const order=[];let completed=0;for(let i=0;i<roundBudget;i++){if(timeBudgetMs!==null&&clockMs()-startTime>=timeBudgetMs){break}const result=this._selectLeafVL(root,chess);if(result.kind==="fresh"){let entry=pending.get(result.node);if(entry===void 0){entry={node:result.node,chess:result.chess,simulations:[]};pending.set(result.node,entry);order.push(result.node)}entry.simulations.push(result.path)}else{this._finishImmediate(result);completed++}}if(pending.size>0){const entries=order.map(node=>pending.get(node));const boards=entries.map(entry=>entry.chess);const{policyLogits,wdlLogits}=await this.evaluator.evaluateBatch(boards);for(let i=0;i<entries.length;i++){completed+=this._finishFreshEntry(entries[i],policyLogits[i],wdlLogits[i])}}if(order.length===this.config.batchSize){this._recordDefaultBatchTiming(clockMs()-roundStart)}return completed}_selectLeafVL(root,chess){let node=root;const path=[node];let pushed=0;while(true){if(node.terminal){for(let i=0;i<pushed;i++)chess.undo();return{kind:"terminal",path,value:node.terminalValue}}if(!node.expanded){if(chess.isCheckmate()){node.terminal=true;node.expanded=true;node.valueQ=0;node.terminalValue=-1;for(let i=0;i<pushed;i++)chess.undo();return{kind:"terminal",path,value:-1}}if(isDraw(chess)){node.terminal=true;node.expanded=true;node.valueQ=.5;node.terminalValue=0;for(let i=0;i<pushed;i++)chess.undo();return{kind:"terminal",path,value:0}}if(this._shouldDeferEval(node,path,true)){for(let i=0;i<pushed;i++)chess.undo();return{kind:"deferred",path,value:0}}const boardCopy=new Chess(chess.fen());for(let i=0;i<pushed;i++)chess.undo();return{kind:"fresh",path,node,chess:boardCopy}}if(this.config.lazyExpansion)this._maybeExpandLazy(node,chess);const moveIdx=this._selectPuct(node);const edge=node.children.get(moveIdx);chess._applyMove({from:edge.from,to:edge.to,promotion:edge.promotion});pushed++;edge.node.visitCount++;edge.node.valueSum+=1;node=edge.node;path.push(node)}}_finishImmediate(result){this._correctValue(result.path,this._drawContemptValue(result.value,result.path.length-1))}_finishFreshEntry(entry,policy,wdl){const value=this._buildChildren(entry.node,entry.chess,policy,wdl);if(entry.simulations.some(path=>path.length===1)){this.lastRootQ=entry.node.valueQ}for(const path of entry.simulations){this._correctValue(path,this._drawContemptValue(value,path.length-1))}return entry.simulations.length}_buildChildren(node,chess,policy,wdl,eager=false){if(!eager&&this.config.lazyExpansion===true){return this._buildChildrenLazy(node,chess,policy,wdl)}const legalMoves=chess._legalMoves();const moveIndices=legalMoves.map(m=>moveToIndex(sqName(m.from),sqName(m.to),m.promotion?PROMO_LETTER[m.promotion]:void 0));const legalLogits=moveIndices.map(idx=>policy[idx]);const priors=softmax(legalLogits);for(let i=0;i<legalMoves.length;i++){const m=legalMoves[i];node.children.set(moveIndices[i],{node:new Node(priors[i]),from:m.from,to:m.to,promotion:m.promotion})}node.expanded=true;const wdlProbs=softmax(Array.from(wdl));const score=wdlToScore(wdlProbs);node.valueQ=score;return 2*score-1}_buildChildrenLazy(node,chess,policy,wdl){node.expanded=true;const board=chess._board;const usWhite=chess._turn===0;const candIdx=[];for(let frm=0;frm<64;frm++){const p=board[frm];if(p===0||p<=6!==usWhite)continue;const base=frm<<6;for(let to=0;to<64;to++){const idx=base|to;if(!this._candidateShapeLegal(chess,frm,to,null))continue;candIdx.push(idx)}}for(let k=0;k<PROMO_MOVE_TABLE.length;k++){const m=PROMO_MOVE_TABLE[k];if(!this._candidateShapeLegal(chess,m.from,m.to,m.promotion))continue;candIdx.push(4096+k)}node.lazy={candIdx,logits:policy,maxL:null,sumExp:0};const wdlProbs=softmax(Array.from(wdl));const score=wdlToScore(wdlProbs);node.valueQ=score;return 2*score-1}_materializeNextLazyChild(node,chess){const lazy=node.lazy;const candIdx=lazy.candIdx;while(candIdx.length>0){let bi=0;let bestL=-Infinity;for(let i=0;i<candIdx.length;i++){const l=lazy.logits[candIdx[i]];if(l>bestL){bestL=l;bi=i}}const best=candIdx[bi];candIdx[bi]=candIdx[candIdx.length-1];candIdx.pop();const cand=decodeMoveIndex(best);if(!this._candidateLeavesKingSafe(chess,cand))continue;if(lazy.maxL===null){lazy.maxL=bestL}const weight=Math.exp(bestL-lazy.maxL);lazy.sumExp+=weight;node.children.set(best,{node:new Node(weight/lazy.sumExp),from:cand.from,to:cand.to,promotion:cand.promotion,weight});for(const edge of node.children.values()){edge.node.prior=edge.weight/lazy.sumExp}return true}return false}_maybeExpandLazy(node,chess){const lazy=node.lazy;if(lazy===null||lazy.candIdx.length===0)return;if(node.children.size>0){for(const edge of node.children.values()){if(edge.node.visitCount===0)return}}this._materializeNextLazyChild(node,chess)}_candidateLeavesKingSafe(chess,cand){const board=chess._board;const usWhite=chess._turn===0;const p=board[cand.from];const pt=usWhite?p:p-6;chess._applyMove(cand);let kingSq;if(pt===KING){kingSq=cand.to}else{kingSq=-1;for(let sq=0;sq<64;sq++){const b=board[sq];if(b!==0&&b<=6===usWhite&&(usWhite?b:b-6)===KING){kingSq=sq;break}}}const leavesKingInCheck=isAttacked(chess._board,kingSq,usWhite?1:0);chess.undo();return!leavesKingInCheck}_candidateShapeLegal(chess,from,to,promotion){const board=chess._board;const usWhite=chess._turn===0;const p=board[from];if(p===0||p<=6!==usWhite)return false;const pt=usWhite?p:p-6;if(pt!==PAWN&&promotion!==null)return false;const t=board[to];if(t!==0&&t<=6===usWhite)return false;const ff=from&7,fr=from>>3,tf=to&7,tr=to>>3;const df=tf-ff,dr=tr-fr;if(pt===PAWN){const dir=usWhite?1:-1;const startRank=usWhite?1:6;const lastRank=usWhite?7:0;if(df===0){if(t!==0)return false;if(dr===dir){}else if(dr===2*dir&&fr===startRank&&board[(fr+dir)*8+ff]===0){}else{return false}}else if(df===1||df===-1){if(dr!==dir)return false;const isEpCapture=t===0&&to===chess._epSquare;if(!isEpCapture&&t===0)return false}else{return false}if(tr===lastRank!==(promotion!==null))return false}else if(pt===KNIGHT){const adf=Math.abs(df),adr=Math.abs(dr);if(!(adf===1&&adr===2||adf===2&&adr===1))return false}else if(pt===BISHOP||pt===ROOK||pt===QUEEN){const diagonal=Math.abs(df)===Math.abs(dr);const straight=df===0||dr===0;if(pt===BISHOP&&!diagonal)return false;if(pt===ROOK&&!straight)return false;if(pt===QUEEN&&!(diagonal||straight))return false;const sf=Math.sign(df),sr=Math.sign(dr);let f=ff+sf,r=fr+sr;while(f!==tf||r!==tr){if(board[r*8+f]!==0)return false;f+=sf;r+=sr}}else if(pt===KING){if(Math.abs(df)<=1&&Math.abs(dr)<=1)return true;return this._castlingCandidateLegal(chess,from,to)}else{return false}return true}_castlingCandidateLegal(chess,from,to){const board=chess._board;const white=chess._turn===0;const rank=white?0:7;if(from!==rank*8+4)return false;const kingSide=to===rank*8+6;const queenSide=to===rank*8+2;if(!kingSide&&!queenSide)return false;const rightsBit=white?kingSide?1:2:kingSide?4:8;if((chess._castling&rightsBit)===0)return false;const rookSq=rank*8+(kingSide?7:0);const rook=board[rookSq];if(rook===0||rook<=6!==white||(white?rook:rook-6)!==ROOK)return false;if(kingSide){if(board[rank*8+5]!==0||board[rank*8+6]!==0)return false}else{for(let f=3;f>=1;f--){if(board[rank*8+f]!==0)return false}}const enemy=white?1:0;const danger=kingDangerSquares(board,from,enemy);const pathFiles=kingSide?[4,5,6]:[4,3,2];for(const f of pathFiles){if(danger.has(rank*8+f))return false}return true}async _expand(node,chess,eager=false){if(chess.isCheckmate()){node.terminal=true;node.expanded=true;node.valueQ=0;node.terminalValue=-1;return node.terminalValue}if(isDraw(chess)){node.terminal=true;node.expanded=true;node.valueQ=.5;node.terminalValue=0;return node.terminalValue}const{policyLogits,wdlLogits}=await this.evaluator.evaluateBatch([chess]);return this._buildChildren(node,chess,policyLogits[0],wdlLogits[0],eager)}_selectPuct(node){const cfg=this.config;const sqrtN=Math.sqrt(Math.max(node.visitCount,1));let bestScore=-Infinity;let bestIdx=null;for(const[idx,edge]of node.children){const child=edge.node;const q=child.visitCount===0?0:-(child.valueSum/child.visitCount);const u=cfg.cPuct*child.prior*sqrtN/(1+child.visitCount);const score=q+u;if(score>bestScore){bestScore=score;bestIdx=idx}}return bestIdx}_correctValue(path,value){let v=value;const root=path[0];for(let i=path.length-1;i>=0;i--){const node=path[i];if(node===root){node.visitCount++;node.valueSum+=v}else{node.valueSum+=v-1}v=-v}}_backup(path,value){let v=value;for(let i=path.length-1;i>=0;i--){const node=path[i];node.visitCount++;node.valueSum+=v;v=-v}}}class OrtEvaluator{constructor(session,ort,{evalCacheMaxEntries=0}={}){this.session=session;this.ort=ort;this.evalCacheMaxEntries=evalCacheMaxEntries;this.evalCache=new Map;this.evalCacheHits=0;this.evalCacheMisses=0}async evaluateBatch(chessInstances){const n=chessInstances.length;const allTokens=new BigInt64Array(n*SEQ_LEN);const keys=new Array(n);for(let i=0;i<n;i++){const tokens=fenToTokens(chessInstances[i].fen());let key="";for(let j=0;j<SEQ_LEN;j++){const t=tokens[j];allTokens[i*SEQ_LEN+j]=BigInt(t);key+=String.fromCharCode(t)}keys[i]=key}if(this.evalCacheMaxEntries<=0){const inputTensor=new this.ort.Tensor("int64",allTokens,[n,SEQ_LEN]);const results2=await this.session.run({tokens:inputTensor});const policyLogits=[];const wdlLogits=[];for(let i=0;i<n;i++){policyLogits.push(results2.policy.data.subarray(i*MOVE_SPACE,(i+1)*MOVE_SPACE));wdlLogits.push(results2.wdl.data.subarray(i*3,(i+1)*3))}return{policyLogits,wdlLogits}}const results={policyLogits:new Array(n),wdlLogits:new Array(n)};const missSlots=new Map;for(let i=0;i<n;i++){const cached=this.evalCache.get(keys[i]);if(cached!==void 0){this.evalCache.delete(keys[i]);this.evalCache.set(keys[i],cached);this.evalCacheHits++;results.policyLogits[i]=cached.policy;results.wdlLogits[i]=cached.wdl}else{this.evalCacheMisses++;let slot=missSlots.get(keys[i]);if(slot===void 0){slot={instIdx:i,slots:[i]};missSlots.set(keys[i],slot)}else{slot.slots.push(i)}}}const m=missSlots.size;if(m>0){const missTokens=new BigInt64Array(m*SEQ_LEN);const keyToOutIdx=new Map;let out=0;for(const[key,slot]of missSlots){missTokens.set(allTokens.subarray(slot.instIdx*SEQ_LEN,(slot.instIdx+1)*SEQ_LEN),out*SEQ_LEN);keyToOutIdx.set(key,out);out++}const inputTensor=new this.ort.Tensor("int64",missTokens,[m,SEQ_LEN]);const runRes=await this.session.run({tokens:inputTensor});for(const[key,slot]of missSlots){const oi=keyToOutIdx.get(key);const policyCopy=Float32Array.from(runRes.policy.data.subarray(oi*MOVE_SPACE,(oi+1)*MOVE_SPACE));const wdlCopy=Float32Array.from(runRes.wdl.data.subarray(oi*3,(oi+1)*3));this.evalCache.set(key,{policy:policyCopy,wdl:wdlCopy});while(this.evalCache.size>this.evalCacheMaxEntries){const oldest=this.evalCache.keys().next().value;this.evalCache.delete(oldest)}for(let s=0;s<slot.slots.length;s++){const i=slot.slots[s];results.policyLogits[i]=policyCopy;results.wdlLogits[i]=wdlCopy}}}return results}}const STM_STATE_DIM=8;const STM_MOVE_SPACE=4272;const STM_PIECE_TYPE={p:1,n:2,b:3,r:4,q:5,k:6};const STM_PROMO_PIECES=[2,3,4,5];function stmMirrorSquare(square){return(7-Math.floor(square/8))*8+square%8}function stmMoveIndex(from,to,promotion=null){if(promotion===null||promotion===void 0)return from*64+to;let index=64*64;for(let candidateFrom=0;candidateFrom<64;candidateFrom++){const fromRank=Math.floor(candidateFrom/8);const fromFile=candidateFrom%8;for(let candidateTo=0;candidateTo<64;candidateTo++){const toRank=Math.floor(candidateTo/8);const toFile=candidateTo%8;const isPromotion=fromRank===6&&toRank===7||fromRank===1&&toRank===0;if(!isPromotion||Math.abs(fromFile-toFile)>1)continue;for(const piece of STM_PROMO_PIECES){if(candidateFrom===from&&candidateTo===to&&piece===promotion)return index;index++}}}throw new Error(`stmMoveIndex: invalid promotion tuple ${from},${to},${promotion}`)}const STM_MIRROR_INDEX=(()=>{const table=new Uint32Array(STM_MOVE_SPACE);let index=0;for(let from=0;from<64;from++){for(let to=0;to<64;to++){table[index++]=stmMoveIndex(stmMirrorSquare(from),stmMirrorSquare(to))}}for(let from=0;from<64;from++){const fromRank=Math.floor(from/8);const fromFile=from%8;for(let to=0;to<64;to++){const toRank=Math.floor(to/8);const toFile=to%8;const isPromotion=fromRank===6&&toRank===7||fromRank===1&&toRank===0;if(!isPromotion||Math.abs(fromFile-toFile)>1)continue;for(const promotion of STM_PROMO_PIECES){table[index++]=stmMoveIndex(stmMirrorSquare(from),stmMirrorSquare(to),promotion)}}}if(index!==STM_MOVE_SPACE)throw new Error(`STM move table has ${index} entries`);return table})();function stmParsePlacement(placement){const absolute=new Uint8Array(64);const ranks=placement.split("/");if(ranks.length!==8)throw new Error("stm encoder: invalid board placement");for(let row=0;row<8;row++){const rank=7-row;let file=0;for(const symbol of ranks[row]){if(symbol>="1"&&symbol<="8"){file+=Number(symbol);continue}const pieceType=STM_PIECE_TYPE[symbol.toLowerCase()];if(pieceType===void 0)throw new Error(`stm encoder: invalid piece ${symbol}`);if(file>=8)throw new Error("stm encoder: board rank is wider than eight files");absolute[rank*8+file]=symbol===symbol.toUpperCase()?pieceType:pieceType+6;file++}if(file!==8)throw new Error("stm encoder: board rank is not eight files wide")}return absolute}function stmRepetitionCount(chess){if(typeof chess?._positionKey==="function"&&chess._repCounts instanceof Map){return Number(chess._repCounts.get(chess._positionKey())??1)}return typeof chess?._isThreefoldRepetition==="function"&&chess._isThreefoldRepetition()?3:1}function stmBoardInputs(chess){return stmEncodeFen(chess.fen(),{repetitionCount:stmRepetitionCount(chess)})}function stmEncodeFen(fen,{repetitionCount=1}={}){const[placement,activeColor,castling,epSquare,halfmoveStr]=fen.split(" ");const absolute=stmParsePlacement(placement);const mirror=activeColor==="b";const squareIds=new BigInt64Array(64);for(let square=0;square<64;square++){let piece=absolute[square];if(mirror&&piece!==0)piece=piece<=6?piece+6:piece-6;const target=mirror?stmMirrorSquare(square):square;squareIds[target]=BigInt(piece)}const state=new Float32Array(STM_STATE_DIM);state[0]=1;if(mirror){state[1]=castling.includes("k")?1:0;state[2]=castling.includes("q")?1:0;state[3]=castling.includes("K")?1:0;state[4]=castling.includes("Q")?1:0}else{state[1]=castling.includes("K")?1:0;state[2]=castling.includes("Q")?1:0;state[3]=castling.includes("k")?1:0;state[4]=castling.includes("q")?1:0}state[5]=epSquare&&epSquare!=="-"?(epSquare.charCodeAt(0)-"a".charCodeAt(0))/7:-1;const halfmove=Number(halfmoveStr??0);state[6]=Math.min(Math.floor(halfmove/5),9)/9;const count=Number.isFinite(Number(repetitionCount))?Number(repetitionCount):1;state[7]=Math.min(Math.max(0,count-1),2)/2;return{squareIds,state,mirror}}function stmMirrorPolicyIndex(index){if(!Number.isInteger(index)||index<0||index>=STM_MOVE_SPACE){throw new Error(`stmMirrorPolicyIndex: index out of range: ${index}`)}return STM_MIRROR_INDEX[index]}function stmMirrorPolicyLogits(logits){if(!logits||logits.length!==STM_MOVE_SPACE){throw new Error(`stmMirrorPolicyLogits: expected ${STM_MOVE_SPACE} logits`)}const output=new Float32Array(STM_MOVE_SPACE);for(let absoluteIndex=0;absoluteIndex<STM_MOVE_SPACE;absoluteIndex++){output[absoluteIndex]=logits[STM_MIRROR_INDEX[absoluteIndex]]}return output}const CF_STATE_DIM=8;const CF_MOVE_SPACE=4272;const CF_PIECE_TYPE={p:1,n:2,b:3,r:4,q:5,k:6};const CF_DEFAULT_TIME_BUDGET_MS=9200;const CF_MOVE_TIME_LIMIT_MARGIN=.92;const CF_MAX_SIMULATIONS=1e5;const CF_MCTS_BATCH_SIZE=16;function cfBoardInputs(chess,stmCanonical=false){if(stmCanonical)return stmBoardInputs(chess);const fen=chess.fen();const[placement,activeColor,castling,epSquare,halfmoveStr]=fen.split(" ");const squareIds=new BigInt64Array(64);const ranks=placement.split("/");if(ranks.length!==8)throw new Error("invalid board placement");for(let row=0;row<8;row++){const rank=7-row;let file=0;for(const symbol of ranks[row]){if(symbol>="1"&&symbol<="8"){file+=Number(symbol);continue}const pieceType=CF_PIECE_TYPE[symbol.toLowerCase()];if(pieceType===void 0)throw new Error(`invalid piece ${symbol}`);if(file>=8)throw new Error("invalid board rank width");squareIds[rank*8+file]=BigInt(symbol===symbol.toUpperCase()?pieceType:pieceType+6);file++}if(file!==8)throw new Error("invalid board rank width")}const state=new Float32Array(CF_STATE_DIM);state[0]=activeColor==="w"?1:0;state[1]=castling.includes("K")?1:0;state[2]=castling.includes("Q")?1:0;state[3]=castling.includes("k")?1:0;state[4]=castling.includes("q")?1:0;state[5]=epSquare&&epSquare!=="-"?(epSquare.charCodeAt(0)-"a".charCodeAt(0))/7:-1;const halfmove=Number(halfmoveStr??0);state[6]=Math.min(Math.floor(halfmove/5),9)/9;if(typeof chess._positionKey==="function"&&chess._repCounts instanceof Map){const occurrences=Number(chess._repCounts.get(chess._positionKey())??0);const priorRepetitions=Math.max(0,occurrences-1);state[7]=Math.min(priorRepetitions,2)/2}else{state[7]=typeof chess._isThreefoldRepetition==="function"&&chess._isThreefoldRepetition()?1:0}return{squareIds,state,mirror:false}}export class CFLiteOrtEvaluator{constructor(session,ort,contempt=0,{evalCacheMaxEntries=0,stmCanonical=false}={}){this.session=session;this.ort=ort;this.contempt=Number.isFinite(Number(contempt))?Number(contempt):0;this.stmCanonical=stmCanonical===true;this.evalCacheMaxEntries=evalCacheMaxEntries;this.evalCache=new Map;this.evalCacheHits=0;this.evalCacheMisses=0}async evaluateBatch(chessInstances){const n=chessInstances.length;if(n===0)return{policyLogits:[],wdlLogits:[]};const allSquareIds=new BigInt64Array(n*64);const allState=new Float32Array(n*CF_STATE_DIM);const allContempt=new Float32Array(n);const mirrors=new Array(n);const keys=new Array(n);for(let i=0;i<n;i++){const inputs=cfBoardInputs(chessInstances[i],this.stmCanonical);allSquareIds.set(inputs.squareIds,i*64);allState.set(inputs.state,i*CF_STATE_DIM);allContempt[i]=this.contempt;mirrors[i]=this.stmCanonical&&inputs.mirror===true;keys[i]=cfEvalCacheKey(inputs.squareIds,inputs.state)}if(this.evalCacheMaxEntries<=0){const results2=await this.session.run({square_ids:new this.ort.Tensor("int64",allSquareIds,[n,64]),state_features:new this.ort.Tensor("float32",allState,[n,CF_STATE_DIM]),contempt:new this.ort.Tensor("float32",allContempt,[n])});return cfScatter(results2,n,mirrors)}const results={policyLogits:new Array(n),wdlLogits:new Array(n)};const missSlots=new Map;for(let i=0;i<n;i++){const cached=this.evalCache.get(keys[i]);if(cached!==void 0){this.evalCache.delete(keys[i]);this.evalCache.set(keys[i],cached);this.evalCacheHits++;results.policyLogits[i]=cfPolicyForBoard(cached.policy,mirrors[i]);results.wdlLogits[i]=cached.wdl}else{this.evalCacheMisses++;let slot=missSlots.get(keys[i]);if(slot===void 0){slot={instIdx:i,slots:[i]};missSlots.set(keys[i],slot)}else{slot.slots.push(i)}}}const m=missSlots.size;if(m>0){const mSquareIds=new BigInt64Array(m*64);const mState=new Float32Array(m*CF_STATE_DIM);const mContempt=new Float32Array(m);let out=0;const keyToOutIdx=new Map;for(const[key,slot]of missSlots){const bi=slot.instIdx;mSquareIds.set(allSquareIds.subarray(bi*64,(bi+1)*64),out*64);mState.set(allState.subarray(bi*CF_STATE_DIM,(bi+1)*CF_STATE_DIM),out*CF_STATE_DIM);mContempt[out]=this.contempt;keyToOutIdx.set(key,out);out++}const runRes=await this.session.run({square_ids:new this.ort.Tensor("int64",mSquareIds,[m,64]),state_features:new this.ort.Tensor("float32",mState,[m,CF_STATE_DIM]),contempt:new this.ort.Tensor("float32",mContempt,[m])});for(const[key,slot]of missSlots){const oi=keyToOutIdx.get(key);const policyCopy=Float32Array.from(runRes.policy.data.subarray(oi*CF_MOVE_SPACE,(oi+1)*CF_MOVE_SPACE));const wdlCopy=Float32Array.from(runRes.wdl.data.subarray(oi*3,(oi+1)*3));this.evalCache.set(key,{policy:policyCopy,wdl:wdlCopy});while(this.evalCache.size>this.evalCacheMaxEntries){const oldest=this.evalCache.keys().next().value;this.evalCache.delete(oldest)}for(const i of slot.slots){results.policyLogits[i]=cfPolicyForBoard(policyCopy,mirrors[i]);results.wdlLogits[i]=wdlCopy}}}return results}}function cfEvalCacheKey(squareIds,state){const bytes=new Uint8Array(squareIds.buffer,squareIds.byteOffset,squareIds.byteLength);const sbytes=new Uint8Array(state.buffer,state.byteOffset,state.byteLength);let key="";const all=[bytes,sbytes];for(const arr of all){for(let off=0;off<arr.length;off+=4096){key+=String.fromCharCode.apply(null,arr.subarray(off,off+4096))}key+="|"}return key}function cfPolicyForBoard(policy,mirror){return mirror?stmMirrorPolicyLogits(policy):policy}function cfScatter(results,n,mirrors=null){const policyLogits=[];const wdlLogits=[];for(let i=0;i<n;i++){const policy=results.policy.data.subarray(i*CF_MOVE_SPACE,(i+1)*CF_MOVE_SPACE);policyLogits.push(cfPolicyForBoard(policy,mirrors?.[i]===true));wdlLogits.push(results.wdl.data.subarray(i*3,(i+1)*3))}return{policyLogits,wdlLogits}}function cfResolveModelBytes(artifacts){if(artifacts instanceof Map)return artifacts.get("model");if(artifacts&&typeof artifacts==="object")return artifacts.model;return void 0}function cfClockMs(){return typeof performance!=="undefined"?performance.now():Date.now()}function cfEmitEvent(onEvent,event){if(typeof onEvent!=="function")return;try{onEvent(event)}catch{}}export async function loadPackage({artifacts,ort,config}){const modelBytes=cfResolveModelBytes(artifacts);if(modelBytes===void 0)throw new Error('missing "model" artifact');const packageConfig=config&&typeof config==="object"?config:{};const onEvent=typeof packageConfig.onEvent==="function"?packageConfig.onEvent:null;const executionProviders=packageConfig.executionProviders||["webgpu","wasm"];const session=await ort.InferenceSession.create(modelBytes,{executionProviders});const contempt=Number.isFinite(Number(packageConfig.contempt))?Number(packageConfig.contempt):0;const stmCanonical=packageConfig.encoder==="stm-canonical";const evalCacheMaxEntries=Number.isFinite(Number(packageConfig.evalCacheMaxEntries))?Number(packageConfig.evalCacheMaxEntries):2048;const mctsBatchSize=Number.isFinite(Number(packageConfig.mctsBatchSize))?Number(packageConfig.mctsBatchSize):CF_MCTS_BATCH_SIZE;const warmupEvaluator=new CFLiteOrtEvaluator(session,ort,contempt,{stmCanonical});try{const warmupBoard=new Chess;await warmupEvaluator.evaluateBatch([warmupBoard]);await warmupEvaluator.evaluateBatch(new Array(mctsBatchSize).fill(warmupBoard))}catch{}const evaluator=new CFLiteOrtEvaluator(session,ort,contempt,{evalCacheMaxEntries,stmCanonical});const defaultTimeBudget=Number.isFinite(Number(packageConfig.timeBudgetMs))?Number(packageConfig.timeBudgetMs):CF_DEFAULT_TIME_BUDGET_MS;const nSimulations=Number.isFinite(Number(packageConfig.nSimulations))?Number(packageConfig.nSimulations):CF_MAX_SIMULATIONS;const rawMcts=packageConfig.mcts&&typeof packageConfig.mcts==="object"?packageConfig.mcts:{};const hasPackageCpuct=Object.prototype.hasOwnProperty.call(packageConfig,"cPuct");const configuredCpuct=Number(packageConfig.cPuct);if(hasPackageCpuct&&(!Number.isFinite(configuredCpuct)||configuredCpuct<=0)){throw new Error(`cPuct must be a finite positive number, got ${packageConfig.cPuct}`)}const cPuct=hasPackageCpuct?configuredCpuct:Number.isFinite(rawMcts.cPuct)&&rawMcts.cPuct>=.5&&rawMcts.cPuct<=3?rawMcts.cPuct:1.5;const drawContempt=Number.isFinite(rawMcts.drawContempt)&&rawMcts.drawContempt>=0&&rawMcts.drawContempt<=1?rawMcts.drawContempt:0;const drawContemptMinQ=Number.isFinite(rawMcts.drawContemptMinQ)&&rawMcts.drawContemptMinQ>=0&&rawMcts.drawContemptMinQ<=1?rawMcts.drawContemptMinQ:.55;return{async newGame(){const mcts=new MCTS(evaluator,new MCTSConfig({nSimulations,timeBudgetMs:defaultTimeBudget,cPuct,batchSize:mctsBatchSize,drawContempt,drawContemptMinQ}));return{async chooseMove({history,legalMoves,moveTimeLimitMs,nodesMode=false,nSimulations:requestedSimulations}){if(!Array.isArray(legalMoves)||legalMoves.length===0){throw new Error("chooseMove called without legalMoves")}const started=cfClockMs();try{const chess=new Chess;for(const san of history||[]){const moved=chess.move(san);if(!moved)throw new Error(`invalid SAN history: ${san}`)}let chosen;if(nodesMode===true){if(moveTimeLimitMs!==void 0){throw new Error("nodesMode cannot be combined with moveTimeLimitMs")}if(!Number.isInteger(requestedSimulations)||requestedSimulations<=0){throw new Error("nodesMode requires a positive integer nSimulations")}mcts.config.nSimulations=requestedSimulations;chosen=await mcts.search(chess,{timeBudgetMs:null})}else{const requested=Number(moveTimeLimitMs);const searchBudget=Number.isFinite(requested)&&requested>0?requested*CF_MOVE_TIME_LIMIT_MARGIN:defaultTimeBudget;chosen=await mcts.search(chess,{timeBudgetMs:searchBudget})}const elapsedMs=cfClockMs()-started;if(legalMoves.includes(chosen)){cfEmitEvent(onEvent,{type:"mcts_move",ply:(history||[]).length,move:chosen,elapsedMs});return chosen}const unrecognized=legalMoves[0];cfEmitEvent(onEvent,{type:"fallback",reason:"unrecognized_move",proposed:chosen,move:unrecognized,elapsedMs});return unrecognized}catch(error){if(nodesMode===true)throw error;const fallback=legalMoves[0];cfEmitEvent(onEvent,{type:"fallback",reason:"exception",error:String(error instanceof Error?error.message:error),move:fallback,elapsedMs:cfClockMs()-started});return fallback}finally{void started}},async dispose(){}}},async dispose(){if(typeof session.release==="function")await session.release()}}}
|
browser/manifest.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"schema": "chess-gpt-package-v1",
|
| 3 |
+
"name": "rook-chess-v1",
|
| 4 |
+
"entrypoint": {
|
| 5 |
+
"path": "entry.js",
|
| 6 |
+
"bytes": 55965,
|
| 7 |
+
"sha256": "ab95d3c25b9232468fcc9256b158682ac7f39ee9a76ec363d0d4420310a273e5"
|
| 8 |
+
},
|
| 9 |
+
"artifacts": {
|
| 10 |
+
"model": {
|
| 11 |
+
"path": "rook6_trained.onnx",
|
| 12 |
+
"bytes": 13431685,
|
| 13 |
+
"sha256": "f79e9a284f35e46f42279b5a60133edc93091154bc1140c9c2e9766bfc225b35"
|
| 14 |
+
}
|
| 15 |
+
},
|
| 16 |
+
"config": {
|
| 17 |
+
"release": {
|
| 18 |
+
"variant": "rook-6",
|
| 19 |
+
"modelSha256": "f79e9a284f35e46f42279b5a60133edc93091154bc1140c9c2e9766bfc225b35",
|
| 20 |
+
"modelBytes": 13431685,
|
| 21 |
+
"sourceCommit": "e725d8c1ee15ff39ada117d8b46e3dacbb3edc32",
|
| 22 |
+
"modelPath": "runs/rook_deliverable/rook6_trained.onnx",
|
| 23 |
+
"modelSource": "registry",
|
| 24 |
+
"modelContract": {
|
| 25 |
+
"validated": true,
|
| 26 |
+
"publishable": true,
|
| 27 |
+
"kind": "cflite",
|
| 28 |
+
"inputNames": [
|
| 29 |
+
"square_ids",
|
| 30 |
+
"state_features",
|
| 31 |
+
"contempt"
|
| 32 |
+
],
|
| 33 |
+
"inputTypes": {
|
| 34 |
+
"square_ids": "int64",
|
| 35 |
+
"state_features": "float32",
|
| 36 |
+
"contempt": "float32"
|
| 37 |
+
},
|
| 38 |
+
"warmupBatchSizes": [
|
| 39 |
+
1,
|
| 40 |
+
16
|
| 41 |
+
],
|
| 42 |
+
"outputShapes": {
|
| 43 |
+
"policy": [
|
| 44 |
+
1,
|
| 45 |
+
4272
|
| 46 |
+
],
|
| 47 |
+
"wdl": [
|
| 48 |
+
1,
|
| 49 |
+
3
|
| 50 |
+
]
|
| 51 |
+
}
|
| 52 |
+
}
|
| 53 |
+
},
|
| 54 |
+
"encoder": "stm-canonical"
|
| 55 |
+
}
|
| 56 |
+
}
|
browser/rook6_trained.onnx
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:f79e9a284f35e46f42279b5a60133edc93091154bc1140c9c2e9766bfc225b35
|
| 3 |
+
size 13431685
|