LittlePlyResearch notebook

policy.mjs

javascript · 71 lines · Exact bytes

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

Recorded SHA-256 ac175f49a08b01b2c2c74415db78a975dfe67da38856b97e26b83fce14625660Rendered SHA-256 ac175f49a08b01b2c2c74415db78a975dfe67da38856b97e26b83fce14625660

Apache-2.0 License notes. Line links are stable within this snapshot. CM-7 schema-2 state-input browser integration; FP32 selected update2232 checkpoint 29e68a8e85336cbb1f56efdad36bd76217728b6a376d1e5f74e89887d21383da. ONNX Runtime Web 1.24.3, chess.js 1.4.0. Exact integrated source; standard-game UI, fixed masked120-slot prompt,512 raw output classes. Three-ply browser helper is separate from live seven-ply search. Original UI and terminal PGN export preserved; prototype scorer/codec/worker/runner unchanged except adding PGN export utility.

1import {prompt} from './codec.mjs';
2export const uci = m => m.from + m.to + (m.promotion || '');
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 normalize(values) {
6  if(values.length!==512 || !Array.from(values).every(Number.isFinite)) throw Error('Expected 512 finite logits');
7  const max=Math.max(...values), z=Math.log(Array.from(values).reduce((s,v)=>s+Math.exp(v-max),0));
8  return Array.from(values,v=>v-max-z);
9}
10export const legalMoves = b => b.moves({verbose:true}).sort((a,b)=>uci(a)<uci(b)?-1:uci(a)>uci(b)?1:0);
11// Match python-chess immediate claims, including a draw available by declaring a move.
12export function outcome(b) {
13  if(b.isCheckmate()) return 'checkmate';
14  if(b.isStalemate() || b.isInsufficientMaterial() || b.isThreefoldRepetition() || b.isDrawByFiftyMoves()) return 'draw';
15  for(const m of b.moves({verbose:true})) {
16    b.move(m); const draw=b.isThreefoldRepetition() || (b.isDrawByFiftyMoves() && !b.isCheckmate()); b.undo();
17    if(draw) return 'draw';
18  }
19  return null;
20}
21export function forcedMate(b, {maxNodes=50000, seconds=1}={}) {
22  const attacker=b.turn(), start=performance.now(); let nodes=0;
23  const guard=()=>{if(performance.now()-start>=seconds*1000)throw Error('budget');};
24  function visit(depth) {
25    guard();if(nodes>=maxNodes) throw Error('budget');nodes++;
26    const end=outcome(b);
27    guard();if(end) return end==='checkmate' && b.turn()!==attacker?{}:null;
28    if(!depth) return null;
29    const attack=b.turn()===attacker;
30    const moves=legalMoves(b);guard();
31    for(const m of moves) {
32      guard();
33      b.move(m); let ok; try {ok=visit(depth-1);} finally {b.undo();}
34      guard();if(attack && ok) return {move:uci(m)};
35      if(!attack && !ok) return null;
36    }
37    return !attack&&moves.length?{}:null;
38  }
39  try {
40    for(let depth=1;depth<=3;depth++) {
41      const proof=visit(depth);guard();
42      if(proof) return {status:'proven',move:proof.move,nodes};
43    }
44    return {status:'no_forced_mate',nodes};
45  } catch(e) {if(e.message!=='budget') throw e; return {status:'unknown',nodes};}
46}
47export async function score(board, runner, historyPlies=16) {
48  const hist=prompt(board,historyPlies);
49  const legal=legalMoves(board); if(!legal.length) throw Error('Terminal board');
50  const base=await runner.prefill(hist); const lp=new Map(); let calls=1;
51  const argmax = row => row.indexOf(Math.max(...row));
52  async function tails(rows) {
53    const unique=[...new Map(rows.map(r=>[r.join(','),r])).values()].sort((a,b)=>a[0]-b[0] || (a[1]??0)-(b[1]??0));
54    for(let i=0;i<unique.length;i+=8) {
55      const part=unique.slice(i,i+8), logits=await runner.branch(part,base);
56      part.forEach((r,j)=>lp.set(r.join(','),normalize(logits[j]))); calls++;
57    }
58  }
59  try {
60    lp.set('',normalize(base.logits));
61    const triples=legal.map(triple), raw=[argmax(lp.get(''))];
62    await tails([...triples.map(t=>t.slice(0,1)),raw.slice()]); raw.push(argmax(lp.get(raw.join(','))));
63    await tails([...triples.map(t=>t.slice(0,2)),raw.slice()]); raw.push(argmax(lp.get(raw.join(','))));
64    const scores=triples.map(([a,b,c])=>lp.get('')[a]+lp.get(`${a}`)[b]+lp.get(`${a},${b}`)[c]);
65    const best=argmax(scores);
66    return {move:uci(legal[best]),initialLogits:base.logits,scores,legal:legal.map(uci),raw,forwardCalls:calls};
67  } finally {base.dispose();}
68}
69
70export 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();}
71