LittlePlyResearch notebook

policy.mjs

javascript · 70 lines · Exact bytes

Line 1 ↗
Version & provenance · browser-local-v1

Recorded SHA-256 09be205f62cd06de126996469ca2aba018defebe9df34d75097ac9212af37635Rendered SHA-256 09be205f62cd06de126996469ca2aba018defebe9df34d75097ac9212af37635

Apache-2.0 License notes. Line links are stable within this snapshot. Browser integration v1, derived from the validated desktop WebGPU prototype. FP32 export of checkpoint 6133648b2926b38542ee3ee1251a1ba111d61da9a8ea69d1c5f0559b09933643; model ONNX SHA-256 e16f6aab5bb02f8b90014f15b55f423d1ac59d6941c9e3bebb5e6bda557499a4. 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