policy.mjs
javascript · 70 lines · Exact bytes
Version & provenance · browser-local-cm5-v1
Recorded SHA-256 09be205f62cd06de126996469ca2aba018defebe9df34d75097ac9212af37635Rendered SHA-256 09be205f62cd06de126996469ca2aba018defebe9df34d75097ac9212af37635
Apache-2.0 License notes. Line links are stable within this snapshot. CM-5 browser integration v1; browser helper remains three plies, not the live seven-ply helper, derived from the validated desktop WebGPU prototype. FP32 export of checkpoint c0a3f67ac4b364276028950b2010c5644e03efb3ca0f1228b32c36b8b5340238; model ONNX SHA-256 f453044633683745fb519bd28d7f5174990f05c3329e072c17906938d827e13d. These are the exact integrated original frontend/policy/worker/runner files; third-party dependencies are separately licensed. ONNX Runtime Web 1.24.3 and chess.js 1.4.0. Website bundling changes paths and presentation but preserves model-scoring code. This source subset omits the exporter, which remains part of the recorded prototype validation, and is not a standalone build bundle. Device-dependent mate-helper budget may change overrides. No GPU memory or universal performance guarantee.
1export const uci = m => m.from + m.to + (m.promotion || '');
2export function exportPgn(board){const end=outcome(board);board.header('Result',end==='checkmate'?(board.turn()==='w'?'0-1':'1-0'):end?'1/2-1/2':'*');return board.pgn();}
3export const square = s => s.charCodeAt(0)-97 + 8*(Number(s[1])-1);
4export const triple = m => [square(m.from), square(m.to), ({n:65,b:66,r:67,q:68})[m.promotion] ?? 64];
5export function history(board) { return [69, ...board.history({verbose:true}).flatMap(triple)]; }
6export function normalize(values) {
7 if(values.length!==128 || !Array.from(values).every(Number.isFinite)) throw Error('Expected 128 finite logits');
8 const max=Math.max(...values), z=Math.log(Array.from(values).reduce((s,v)=>s+Math.exp(v-max),0));
9 return Array.from(values,v=>v-max-z);
10}
11export const legalMoves = b => b.moves({verbose:true}).sort((a,b)=>uci(a)<uci(b)?-1:uci(a)>uci(b)?1:0);
12// Match python-chess immediate claims, including a draw available by declaring a move.
13export function outcome(b) {
14 if(b.isCheckmate()) return 'checkmate';
15 if(b.isStalemate() || b.isInsufficientMaterial() || b.isThreefoldRepetition() || b.isDrawByFiftyMoves()) return 'draw';
16 for(const m of b.moves({verbose:true})) {
17 b.move(m); const draw=b.isThreefoldRepetition() || (b.isDrawByFiftyMoves() && !b.isCheckmate()); b.undo();
18 if(draw) return 'draw';
19 }
20 return null;
21}
22export function forcedMate(b, {maxNodes=50000, seconds=1}={}) {
23 const attacker=b.turn(), start=performance.now(); let nodes=0;
24 const guard=()=>{if(performance.now()-start>=seconds*1000)throw Error('budget');};
25 function visit(depth) {
26 guard();if(nodes>=maxNodes) throw Error('budget');nodes++;
27 const end=outcome(b);
28 guard();if(end) return end==='checkmate' && b.turn()!==attacker?{}:null;
29 if(!depth) return null;
30 const attack=b.turn()===attacker;
31 const moves=legalMoves(b);guard();
32 for(const m of moves) {
33 guard();
34 b.move(m); let ok; try {ok=visit(depth-1);} finally {b.undo();}
35 guard();if(attack && ok) return {move:uci(m)};
36 if(!attack && !ok) return null;
37 }
38 return !attack&&moves.length?{}:null;
39 }
40 try {
41 for(let depth=1;depth<=3;depth++) {
42 const proof=visit(depth);guard();
43 if(proof) return {status:'proven',move:proof.move,nodes};
44 }
45 return {status:'no_forced_mate',nodes};
46 } catch(e) {if(e.message!=='budget') throw e; return {status:'unknown',nodes};}
47}
48export async function score(board, runner) {
49 const hist=history(board); if(hist.length+2>1024) throw Error('Full game history exceeds the model context. Export this game and reset.');
50 const legal=legalMoves(board); if(!legal.length) throw Error('Terminal board');
51 const base=await runner.prefill(hist); const lp=new Map(); let calls=1;
52 const argmax = row => row.indexOf(Math.max(...row));
53 async function tails(rows) {
54 const unique=[...new Map(rows.map(r=>[r.join(','),r])).values()].sort((a,b)=>a[0]-b[0] || (a[1]??0)-(b[1]??0));
55 for(let i=0;i<unique.length;i+=8) {
56 const part=unique.slice(i,i+8), logits=await runner.branch(part,base);
57 part.forEach((r,j)=>lp.set(r.join(','),normalize(logits[j]))); calls++;
58 }
59 }
60 try {
61 lp.set('',normalize(base.logits));
62 const triples=legal.map(triple), raw=[argmax(lp.get(''))];
63 await tails([...triples.map(t=>t.slice(0,1)),raw.slice()]); raw.push(argmax(lp.get(raw.join(','))));
64 await tails([...triples.map(t=>t.slice(0,2)),raw.slice()]); raw.push(argmax(lp.get(raw.join(','))));
65 const scores=triples.map(([a,b,c])=>lp.get('')[a]+lp.get(`${a}`)[b]+lp.get(`${a},${b}`)[c]);
66 const best=argmax(scores);
67 return {move:uci(legal[best]),scores,legal:legal.map(uci),raw,forwardCalls:calls};
68 } finally {base.dispose();}
69}
70