LittlePlyResearch notebook

app.mjs

javascript · 38 lines · Exact bytes

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

Recorded SHA-256 eb028afca563c9097d6494d25513eb4ed3e9eb704060604da57492de62f0a404Rendered SHA-256 eb028afca563c9097d6494d25513eb4ed3e9eb704060604da57492de62f0a404

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.

1import {Chess} from 'chess.js';
2import {uci,outcome,exportPgn} from './policy.mjs';
3const $=id=>document.getElementById(id), game=new Chess();let worker,manifest,ready=false,busy=false,selected=null,epoch=0;
4const glyphs={w:{k:'♔',q:'♕',r:'♖',b:'♗',n:'♘',p:'♙'},b:{k:'♚',q:'♛',r:'♜',b:'♝',n:'♞',p:'♟'}};
5function status(text){$('status').textContent=text;}
6function render(){
7  $('board').replaceChildren();const reverse=$('color').value==='b';
8  const targets=selected?game.moves({square:selected,verbose:true}):[];
9  for(let row=0;row<8;row++)for(let col=0;col<8;col++){
10    const f=reverse?7-col:col,r=reverse?row:7-row,s=String.fromCharCode(97+f)+(r+1),p=game.get(s),button=document.createElement('button');
11    button.className=`square ${(f+r)%2===0?'dark':''} ${selected===s?'selected':''} ${targets.some(m=>m.to===s)?'target':''} ${p?.color==='w'?'white-piece':'black-piece'}`;
12    button.setAttribute('aria-label',`${s}${p?' '+(p.color==='w'?'white':'black')+' '+({k:'king',q:'queen',r:'rook',b:'bishop',n:'knight',p:'pawn'})[p.type]:''}`);button.dataset.square=s;
13    button.textContent=p?glyphs[p.color][p.type]:'';const label=document.createElement('small');label.textContent=s;button.append(label);
14    button.onclick=()=>click(s);$('board').append(button);
15  }
16  game.header('Event','LittlePly local browser game','ModelVersion',manifest?.version||'cm-5-c0a3f67ac4b3-fp32-v1','CheckpointSHA256',manifest?.checkpointSha256||'c0a3f67ac4b364276028950b2010c5644e03efb3ca0f1228b32c36b8b5340238');$('moves').textContent=game.pgn();
17}
18function click(s){
19  if(!ready||busy||game.turn()!==$('color').value||outcome(game))return;
20  if(selected){const moves=game.moves({square:selected,verbose:true}).filter(m=>m.to===s);if(moves.length){const move=moves.find(m=>m.promotion===$('promotion').value)||moves[0];game.move(move);selected=null;render();void turn();return;}}
21  selected=game.get(s)?.color===game.turn()?s:null;render();
22}
23async function turn(){const end=outcome(game);if(end){game.header('Result',end==='checkmate'?(game.turn()==='w'?'0-1':'1-0'):'1/2-1/2');render();status(end==='checkmate'?'Checkmate. Game over.':'Draw. Game over.');return;}if(game.turn()===$('color').value){status('Your move.');return;}busy=true;status('LittlePly is thinking…');worker.postMessage({id:epoch,type:'move',moves:game.history({verbose:true}).map(uci)});}
24function makeWorker(){worker=new Worker(new URL('./worker.js',import.meta.url),{type:'module'});worker.onmessage=({data})=>{
25  if(data.id!==epoch)return;
26  if(data.type==='progress')status(`Downloading model: ${Math.round(data.progress*100)}%`);
27  if(data.type==='ready'){ready=true;busy=false;status('Model ready.');$('load').textContent='Model loaded';void turn();}
28  if(data.type==='result'){busy=false;try{game.move({from:data.move.slice(0,2),to:data.move.slice(2,4),promotion:data.move[4]});if(data.tactical?.status==='proven'){game.setComment('Mate-helper override; model not queried');$('decision').textContent='Last bot move: bounded mate-helper override (model not queried).';}else{$('decision').textContent='Last bot move: legal candidate ranked by the model.';}render();void turn();}catch(e){status(`Invalid model move: ${e.message}`);}}
29  if(data.type==='error'){busy=false;status(`Unable to continue: ${data.message}`);if(!ready){$('load').disabled=false;$('load').textContent='Retry model download';}}
30};worker.onerror=e=>{busy=false;ready=false;status(`Worker failed: ${e.message}`);$('load').disabled=false;};}
31$('load').onclick=()=>{if(busy)return;epoch++;worker?.terminate();makeWorker();busy=true;ready=false;$('load').disabled=true;status('Preparing model download…');worker.postMessage({id:epoch,type:'load',manifest});};
32$('reset').onclick=()=>{epoch++;if(busy){worker?.terminate();ready=false;busy=false;$('load').disabled=!manifest;$('load').textContent='Download and load model';}game.reset();$('decision').textContent='';selected=null;render();if(ready)void turn();else status('Game reset. Load the model to play.');};
33$('color').onchange=()=>$('reset').click();
34$('export').onclick=()=>{const url=URL.createObjectURL(new Blob([exportPgn(game)],{type:'application/x-chess-pgn'}));const a=document.createElement('a');a.href=url;a.download='littleply.pgn';a.click();setTimeout(()=>URL.revokeObjectURL(url),1000);};
35render();
36try{if(!navigator.gpu)throw Error('This browser does not support WebGPU. Use a supported desktop browser with hardware acceleration.');const url=new URL('./models/manifest.json',import.meta.url);const response=await fetch(url);if(!response.ok)throw Error('Model manifest is unavailable');manifest=await response.json();manifest.url=url.href;$('details').textContent=`${(manifest.bytes/1e6).toFixed(1)} MB model + ${(manifest.runtime.bytes/1e6).toFixed(1)} MB runtime download · FP32 · Desktop WebGPU · No server inference`;$('load').textContent=`Download ${((manifest.bytes+manifest.runtime.bytes)/1e6).toFixed(1)} MB and play`;$('load').disabled=false;status('Ready to download. Model files may be cached by your browser.');}catch(e){status(e.message);}
37
38