Atomic-GDN2 / Observatory /source /app-source.js
Akseleu's picture
Publish English Observatory and updated Colab with discrete neuron cores
68d82b8 verified
Raw History Blame Contribute Delete
26 kB
'use strict';
(()=>{
const $=id=>document.getElementById(id), canvas=$('scene'), gl=canvas.getContext('webgl',{alpha:true,antialias:true,preserveDrawingBuffer:true});
if(!gl){$('message').textContent='WebGL is unavailable. Enable hardware acceleration in your browser.';return;}
let expanded=true,projectionCount=0;
let trace,token=0,mode='network',selected=0,head=0,edges=[],nodes=[],labels=[],total=0,kept=0,clock=0,last=0,play=false,signals=true,orbit=false,nextToken=0,frames=0,fpsStart=0,frameTimes=[];
const F=Filaments;let geometryTier=0;let moving=true,selectionId=-1,selectionFade=0,selectionGoal=0,transition=1,transitionStart=0,visualTime=0,cameraGoal=null;
let nodeIndex=new Map(),nodeByPosition=new Map(),adjacency=new Map(),normCache=new Map(),previousNodeColors=new Map(),previousEdgeColors=new Map(),lastNodeColors=new Map(),lastEdgeColors=new Map(),screenNodes=[],pickDirty=true,hoverX=-1,hoverY=-1,pickStamp=0;
let camera={yaw:-.14,pitch:.13,distance:62,target:[0,0,0]},matrix,drag=null,hover=null,focused=null;
const vs=`
attribute vec3 a;attribute vec3 b;attribute vec4 c;attribute vec4 d;
attribute vec4 meta;attribute vec4 prior;attribute vec4 lut;
uniform mat4 vp;uniform float time,particle,scale,selectedId,focusMix,transition,sway,blockLight;uniform mediump float point;
varying vec4 color;
float parameter(float q){
if(q<lut.x)return .25*q/max(lut.x,.00001);
if(q<lut.y)return .25+.25*(q-lut.x)/max(lut.y-lut.x,.00001);
if(q<lut.z)return .5+.25*(q-lut.y)/max(lut.z-lut.y,.00001);
return .75+.25*(q-lut.z)/max(1.-lut.z,.00001);
}
void main(){
float q=fract(time*d.y+d.x);float t=particle>.5?parameter(q):d.x;
float envelope=sin(t*3.14159265),wave=sin(time*.63+meta.z+t*4.2)*meta.w*sway;
vec3 p=mix(a,b,t);p.z+=envelope*(d.z+wave);p.y+=envelope*(d.w+wave*.7);
gl_Position=vp*vec4(p,1.);
color=mix(prior,c,transition);
// A solid, magnitude-shaded neuron body remains readable without bloom.
if(point>1.5){color.rgb*=.58+.42*sqrt(color.a);color.a=.62+.38*color.a;}
float hit=selectedId>=0.&&(abs(meta.x-selectedId)<.25||abs(meta.y-selectedId)<.25)?1.:0.;
float amount=focusMix*hit;
color.rgb=mix(color.rgb,vec3(1.,.98,.92),amount);
color.a*=mix(1.,.055,focusMix*(1.-hit));
color.a=mix(color.a,max(color.a,.66),amount);
if(lut.w>.5)color.a*=mix(blockLight,1.,amount);
if(particle>.5)color.a*=smoothstep(0.,.06,q)*(1.-smoothstep(.9,1.,q));
gl_PointSize=scale*(particle>.5?2.1:mix(1.9,3.4,sqrt(c.a)))*clamp(100./gl_Position.w,.65,4.5)*(1.+amount*.45);
if(point>1.5)gl_PointSize=clamp(gl_PointSize,2.2,6.5)*(1.+amount*.25);
}`;
const fs=`precision mediump float;varying vec4 color;uniform float point,exposure;
void main(){
float alpha=1.;vec3 rgb=color.rgb;
if(point>.5){
vec2 uv=(gl_PointCoord-.5)*2.;float r=length(uv);if(r>1.)discard;
if(point>1.5){
alpha=1.-smoothstep(.78,1.,r);
float dome=sqrt(max(0.,1.-r*r));
float highlight=pow(max(0.,1.-length(uv-vec2(-.25,-.28))),5.);
rgb=rgb*(.48+.52*dome)+vec3(.2)*highlight;
}else alpha=exp(-r*r*5.5);
}
gl_FragColor=vec4(rgb,color.a*alpha*exposure);
}`;
function shader(type,s){const sh=gl.createShader(type);gl.shaderSource(sh,s);gl.compileShader(sh);if(!gl.getShaderParameter(sh,gl.COMPILE_STATUS))throw Error(gl.getShaderInfoLog(sh));return sh;}
const program=gl.createProgram();gl.attachShader(program,shader(gl.VERTEX_SHADER,vs));gl.attachShader(program,shader(gl.FRAGMENT_SHADER,fs));gl.linkProgram(program);if(!gl.getProgramParameter(program,gl.LINK_STATUS))throw Error(gl.getProgramInfoLog(program));gl.useProgram(program);
const loc={};for(const k of ['a','b','c','d','meta','prior','lut'])loc[k]=gl.getAttribLocation(program,k);for(const k of ['vp','time','particle','scale','point','exposure','selectedId','focusMix','transition','sway','blockLight'])loc[k]=gl.getUniformLocation(program,k);
const buffers={line:gl.createBuffer(),node:gl.createBuffer(),pulse:gl.createBuffer()};let counts={};
function multiply(a,b){let o=new Float32Array(16);for(let c=0;c<4;c++)for(let r=0;r<4;r++)for(let k=0;k<4;k++)o[c*4+r]+=a[k*4+r]*b[c*4+k];return o;}
const sub=(a,b)=>a.map((v,i)=>v-b[i]),dot=(a,b)=>a.reduce((s,v,i)=>s+v*b[i],0),cross=(a,b)=>[a[1]*b[2]-a[2]*b[1],a[2]*b[0]-a[0]*b[2],a[0]*b[1]-a[1]*b[0]],norm=a=>a.map(v=>v/Math.hypot(...a));
function view(){const t=camera.target,eye=[t[0]+Math.sin(camera.yaw)*Math.cos(camera.pitch)*camera.distance,t[1]+Math.sin(camera.pitch)*camera.distance,t[2]+Math.cos(camera.yaw)*Math.cos(camera.pitch)*camera.distance],z=norm(sub(eye,t)),x=[Math.cos(camera.yaw),0,-Math.sin(camera.yaw)],y=cross(z,x);const v=new Float32Array([x[0],y[0],z[0],0,x[1],y[1],z[1],0,x[2],y[2],z[2],0,-dot(x,eye),-dot(y,eye),-dot(z,eye),1]),f=1/Math.tan(.58/2),asp=canvas.width/canvas.height,n=.05,far=5000;return multiply(new Float32Array([f/asp,0,0,0,0,f,0,0,0,0,(far+n)/(n-far),-1,0,0,2*far*n/(n-far),0]),v);}
function project(p){const q=[...p,1],o=[0,0,0,0];for(let r=0;r<4;r++)for(let c=0;c<4;c++)o[r]+=matrix[c*4+r]*q[c];return [(o[0]/o[3]*.5+.5)*innerWidth,(-o[1]/o[3]*.5+.5)*innerHeight,o[3]];}
const color=(v,a=1)=>v>=0?[1,.055,.035,a]:[.025,1,.12,a];
function bank(values,x,height,z,title){let out=[];const max=Math.max(.000001,...values.map(Math.abs));for(let i=0;i<values.length;i++){const p=[x,(.5-i/Math.max(1,values.length-1))*height,z];const n={p,v:values[i],title:title+' ['+i+']',c:color(values[i],.3+.7*Math.sqrt(Math.abs(values[i])/max))};nodes.push(n);out.push(n);}return out;}
function label(p,title,sub=''){labels.push({p,title,sub});}
function edge(a,b,value,info,arc=0,kind='dense'){edges.push({a:a.p||a,b:b.p||b,v:value,info,arc,kind});}
function denseGraph(d,x,z=0,spacing=2.2,h=8){const input=d.input[token],out=d.output[token],a=bank(input,x,h,z,d.id+' input'),b=bank(out,x+spacing,h*Math.sqrt(out.length/input.length),z,d.id+' output');let all=[];for(let i=0;i<input.length;i++)for(let j=0;j<out.length;j++)all.push({i,j,v:input[i]*d.weight[i][j]});total+=all.length;all.sort((a,b)=>Math.abs(b.v)-Math.abs(a.v));let count=Math.max(1,Math.round(all.length*Number($('density').value)/100));for(const e of all.slice(0,count)){edge(a[e.i],b[e.j],e.v,`${d.id}\nx[${e.i}] × W[${e.i},${e.j}] = ${e.v.toPrecision(5)}\nx=${input[e.i].toPrecision(4)} · W=${d.weight[e.i][e.j].toPrecision(4)}`,0,'dense');}label([x+spacing/2,h/2+1.2,z],d.id.split('/').pop(),`${input.length} → ${out.length}`);return {a,b};}
function frame(x,y,z,w,h){const p=[[x-w/2,y-h/2,z],[x+w/2,y-h/2,z],[x+w/2,y+h/2,z],[x-w/2,y+h/2,z]];for(let i=0;i<4;i++)edge(p[i],p[(i+1)%4],0,'Module boundary',0,'frame');}
function moduleDenses(m){return trace.dense.filter(d=>d.id.startsWith(m.id+'/'));}
function network(){const g=AtomicGraph(trace,token,Number($('density').value));nodes=g.nodes;edges=g.edges;labels=g.labels;total=g.total;camera.distance=g.width/(2*Math.tan(.29)*(innerWidth/innerHeight))*1.12;camera.target=[3,-3,0];$('explain').textContent='One residual stream. Violet links show preceding layer deltas in the current block; gold links show deltas from earlier blocks. All '+g.routes.length+' H-DAR routes × '+trace.channels+' channels are retained. Blue junctions represent tensor operations; their links are structural dependencies. Hover a neuron to trace its links; click to pin.';}
function productionScene(){
if(mode==='dar'){dar();return;}
if(mode==='memory'){const m=trace.modules[selected],g=trace.memories.find(g=>g.id===m.id);if(!g){$('explain').textContent='Select a GDN-2 module.';return;}const h=Math.min(head,g.heads-1),a=g.states[h],mx=Math.max(1e-8,...a.flat().map(Math.abs));for(let i=0;i<a.length;i++)for(let j=0;j<a[i].length;j++)nodes.push({p:[(j/(a[i].length-1)-.5)*20,(.5-i/(a.length-1))*20,0],v:a[i][j],title:`H head ${h} [${i},${j}] · token ${g.token} · pooled region mean`,c:color(a[i][j],.2+.8*Math.sqrt(Math.abs(a[i][j])/mx))});heatmap(a,`FP32 serial reference · head ${h+1} · token ${g.token}`);label([0,12,0],'GDN STATE · '+m.label,'published checkpoint · '+g.dim+' × '+g.dim+(g.original_dim?' pooled from '+g.original_dim+' × '+g.original_dim:'' ));camera.distance=65;camera.target=[0,0,0];$('explain').textContent='FP32 reference memory from the published weights. Each compact cell is the mean of an 8×8 region in the original matrix. Only the final captured token is stored. TPU bf16/fused parity has not been established.';return;}
if(mode==='attention'){const m=trace.modules[selected],a=trace.attention.find(g=>g.id===m.id);if(!a){$('explain').textContent='Select an MLA module.';return;}const h=Math.min(head,a.heads-1),row=a.attention[h][token],src=bank(row,-10,16,0,'Attention probability'),out=bank([row.reduce((a,b)=>a+b,0)],10,0,0,'Σ attention');src.forEach((n,i)=>{if(i<=token)edge(n,out[0],n.v,`A[${token},${i}]=${n.v}`,0,'local');});heatmap(a.attention[h],'Captured FP32 MLA causal attention · head '+(h+1));label([0,10,0],'MLA · '+m.label,'query token '+token);camera.distance=60;camera.target=[0,0,0];$('explain').textContent='Causal attention across the captured GPT-2 tokens. Probabilities are computed using the published checkpoint weights.';return;}
const g=ProductionGraph(trace,token,mode==='projection',selected,Number($('groups').value),expanded);projectionCount=g.projectionCount||0;nodes=g.nodes;edges=g.edges;labels=g.labels;total=g.total;camera.distance=Math.max(mode==='projection'?95:0,g.width/(2*Math.tan(.29)*(innerWidth/innerHeight))*1.12);camera.target=[0,-3,0];
$('explain').textContent=mode==='projection'?`Individual channels of the selected module. Exact xᵢWᵢⱼ products: the 24 largest-magnitude outputs and 8 largest-magnitude inputs for each are saved. Detail is captured for token ${trace.detail_token+1}; other links are not exported.`:`All ${trace.metadata.config.num_blocks} blocks, ${trace.channels} channels. Overview nodes are means across ${Number($('groups').value)} groups. All ${trace.routes.reduce((n,r)=>n+r.sources.length,0)} H-DAR sources are retained. ${expanded?'40k+ mode includes measured contributions from '+projectionCount+' input Dense projections; 40,000+ links at full density.':'Standard view.'} Individual Dense contributions remain fixed to token ${trace.detail_token+1}. Select Module projections to inspect individual neurons.`;
}
function projection(){const m=trace.modules[selected],ds=moduleDenses(m),width=ds.length*5;ds.forEach((d,i)=>denseGraph(d,-width/2+i*5,(i%2?1:-1)*1.8,3.5,12));label([0,11,0],m.label,'Measured xᵢWᵢⱼ · bias is included in output, not edges');camera.distance=Math.max(85,width*1.9);camera.target=[-1,0,0];$('explain').textContent='Each link is xᵢ × Wᵢⱼ at the selected token. Color encodes sign; brightness encodes magnitude. Output nodes contain the sum of contributions plus bias. Q/K normalization, RoPE and SwiGLU are not drawn as Dense links.';}
function dar(){const m=trace.modules[selected];const r=trace.routes.find(r=>r.target===m.stage);if(!r){$('explain').textContent='The first layer has no preceding DAR sources.';camera.distance=40;return;}const target=bank(r.retrieved[token],9,15,0,'DAR retrieved'),res=bank(r.residual[token],14,15,0,'Residual'),sum=bank(r.residual[token].map((v,i)=>v+r.retrieved[token][i]),20,15,0,'Residual + retrieved');r.sources.forEach((id,s)=>{let vals=trace.stages.find(v=>v.id===id).values[token],z=(s-(r.sources.length-1)/2)*4.5;const b=bank(vals,-12,15,z,id);for(let c=0;c<b.length;c++){edge(b[c],target[c],vals[c]*r.weights[token][s],`DAR ${id}\n${trace.publication?'group mean':'channel'} ${c} · α=${r.weights[token][s].toPrecision(6)}\ncontribution=${(vals[c]*r.weights[token][s]).toPrecision(6)}`,0,'dar');}label([-12,9,z],id,'α = '+r.weights[token][s].toFixed(4));});for(let c=0;c<sum.length;c++){edge(target[c],sum[c],r.retrieved[token][c],'retrieved contribution',0,'retrieved');edge(res[c],sum[c],r.residual[token][c],'residual contribution',0,'residual');}label([9,9,0],'Retrieved Δ');label([14,9,0],'Residual');label([20,9,0],'Routed input');camera.distance=70;camera.target=[3,0,0];$('explain').textContent='Paper-DAR: learned query · RMSNorm keys → depth softmax. Links connect matching channels only: αₛΔₛ. The retrieved vector is added to the residual stream before sublayer normalization.';}
function memory(){const m=trace.modules[selected],g=trace.gdn.find(g=>g.id===m.id);if(!g){$('explain').textContent='Select a GDN-2 module to inspect its memory.';return;}const h=Math.min(head,g.heads-1),state=g.states[token][h],dim=state.length;const q=bank(g.q[token][h],-13,14,0,'q'),k=bank(g.k[token][h],-9,14,-3,'k'),v=bank(g.write_value[token][h],-9,14,3,'w·v − erase'),read=bank(g.measured[token][h],13,14,0,'Measured kernel output');let max=Math.max(1e-6,...state.flat().map(Math.abs));for(let i=0;i<dim;i++)for(let j=0;j<state[i].length;j++){const p=[(j/(state[i].length-1)-.5)*8,(.5-i/(dim-1))*14,0],n={p,v:state[i][j],title:`H[${i},${j}] reference`,c:color(state[i][j],.3+.7*Math.abs(state[i][j])/max)};nodes.push(n);const contribution=g.q[token][h][i]/Math.sqrt(dim)*state[i][j];edge(q[i],n,contribution,`Read q[${i}]H[${i},${j}]/√d = ${contribution}`,0,'dense');edge(n,read[j],contribution,'Reference read contribution',0,'dense');total+=2;}
label([0,9,0],'RECURRENT STATE','Hₜ = decay(Hₜ₋₁) + k ⊗ (w·v − erase)');label([-13,9,0],'Query');label([-9,9,-3],'Key');label([-9,9,3],'Write value');label([13,9,0],'Kernel output');camera.distance=65;camera.target=[0,0,0];heatmap(state,'GDN Hₜ · serial reference · max output error '+g.max_error.toExponential(2));$('explain').textContent='Memory is reconstructed using the serial recurrence, not captured from fused-kernel internals. The measured kernel output is on the right. Comparison error is retained. Key and write value have separate banks.';}
function attention(){const m=trace.modules[selected],a=trace.mla.find(g=>g.id===m.id);if(!a){$('explain').textContent='Select an MLA module to inspect attention.';return;}const h=Math.min(head,a.heads-1),weights=a.attention[h][token],dim=a.v[0][h].length,context=Array(dim).fill(0);for(let t=0;t<=token;t++)for(let d=0;d<dim;d++)context[d]+=weights[t]*a.v[t][h][d];const out=bank(context,12,14,0,'Attention context Σ A·V');for(let t=0;t<=token;t++){const z=(t-token/2)*1.8,vals=a.v[t][h],v=bank(vals,-10,14,z,'V token '+t);for(let d=0;d<dim;d++){edge(v[d],out[d],weights[t]*vals[d],`A[${token},${t}]V[${t},${d}] = ${weights[t]*vals[d]}`,0,'dense');total++;}label([-10,9,z],'Byte '+t,'α = '+weights[t].toFixed(4));}label([12,9,0],'ATTENTION CONTEXT','Σ Aₜₛ Vₛ');camera.distance=Math.max(65,token*2);camera.target=[0,0,0];heatmap(a.attention[h],'MLA causal attention · query '+token+' · max output error '+a.max_error.toExponential(2));$('explain').textContent='Each link connects a previous token V channel to the matching context channel: AₜₛVₛ. A causal mask excludes future tokens. Q/K after RoPE determine A; output is checked against the model.';}
function heatmap(data,caption){const c=$('heat'),cx=c.getContext('2d');c.style.display='block';let max=Math.max(1e-8,...data.flat().map(Math.abs));cx.clearRect(0,0,256,256);data.forEach((row,i)=>row.forEach((v,j)=>{const col=color(v),s=Math.sqrt(Math.abs(v)/max);cx.fillStyle=`rgb(${col.slice(0,3).map(v=>Math.round(12+v*s*235)).join(',')})`;cx.fillRect(j*256/row.length,i*256/data.length,Math.ceil(256/row.length),Math.ceil(256/data.length));}));$('heatCaption').textContent=caption;}
/* @renderer */
function build(reset=true){if(!trace)return;const old=JSON.parse(JSON.stringify(camera)),selectedKey=focused?F.key(focused):null;previousNodeColors=lastNodeColors;previousEdgeColors=lastEdgeColors;focused=null;hover=null;frameTimes=[];edges=[];nodes=[];labels=[];total=0;document.body.classList.toggle('detail-view',mode!=='network');$('heat').style.display='none';$('heatCaption').textContent='';if(trace.version===4)productionScene();else ({network,projection,dar,memory,attention}[mode])();if(!reset)camera=old;if(trace.version===4){const dense=edges.filter(e=>e.kind==='dense').sort((a,b)=>Math.abs(b.v)-Math.abs(a.v)),keep=new Set(dense.slice(0,Math.ceil(dense.length*Number($('density').value)/100)));edges=edges.filter(e=>e.kind!=='dense'||keep.has(e));}focused=selectedKey?nodes.find(n=>F.key(n)===selectedKey)||null:null;uploadGeometry();setSelection(focused);transition=previousNodeColors.size?0:1;transitionStart=visualTime;pickDirty=true;$('labels').replaceChildren();for(const l of labels){const el=document.createElement('div');el.className='scene-label';const title=document.createElement('strong');title.textContent=l.title;const sub=document.createElement('span');sub.className='sub';sub.textContent=l.sub;el.append(title,sub);$('labels').append(el);l.el=el;}$('stats').textContent=`${trace.version===4?'HF · FP32 · ':''}${nodes.length.toLocaleString()} nodes · ${edges.filter(e=>e.kind!=='frame').length.toLocaleString()} links${total?' · Dense: '+kept.toLocaleString()+' / '+total.toLocaleString():''} · ${trace.channels} channels`;$('tokenLabel').textContent=`${trace.version===4?'TOKEN':'BYTE'} ${token+1} / ${trace.tokens.length} · ${trace.tokens[token].label}`;$('token').value=token;[...$('tokens').children].forEach((b,i)=>b.classList.toggle('active',i===token));$('predictions').replaceChildren();const p=document.createElement('p');p.className='eyebrow';p.textContent=trace.version===4?'NEXT GPT-2 TOKEN / TOP 5':'NEXT BYTE / TOP 5';$('predictions').append(p);for(const t of trace.top[token]){const d=document.createElement('div');const a=document.createElement('span'),b=document.createElement('span');a.textContent=t.label.replaceAll('\n','⏎').replaceAll('\t','⇥').replaceAll(' ','␠');b.textContent=(t.prob*100).toFixed(2)+'%';d.append(a,b);$('predictions').append(d);}if(focused)inspectNode(focused);}
function load(data){if(![3,4].includes(data.version)||!data.modules?.length||(data.version===3&&!data.dense?.length))throw Error('A complete version 3 or 4 trace is required');trace=data;normCache.clear();previousNodeColors.clear();previousEdgeColors.clear();lastNodeColors.clear();lastEdgeColors.clear();focused=null;if(document.body.dataset.replayOnly==='true'){$('text').readOnly=true;$('prompt').querySelector('button').textContent='New prompt in Colab';}$('detail40').disabled=data.version!==4;if(data.version!==4)expanded=false;$('detail40').classList.toggle('active',expanded);$('detail40').setAttribute('aria-pressed',String(expanded));token=data.version===4?data.detail_token:0;selected=0;head=0;$('text').value=data.text;$('module').replaceChildren();data.modules.forEach((m,i)=>{const o=document.createElement('option');o.value=i;o.textContent=m.label;$('module').append(o);});$('token').max=data.tokens.length-1;$('tokens').replaceChildren();data.tokens.forEach((t,i)=>{const b=document.createElement('button');b.textContent=t.label;b.title=(data.version===4?'Token ':'Byte ')+i;b.onclick=()=>{token=i;build(false);};$('tokens').append(b);});$('badge').textContent=`${data.version===4?'HF FP32':'TINY'} · ${data.channels}D · ${data.modules.length} MODULES`;if(data.version===4){$('head').replaceChildren();for(let h=0;h<data.metadata.config.n_heads;h++){const o=document.createElement('option');o.value=h;o.textContent='Head '+(h+1);$('head').append(o);}}$('provenance').textContent=`Tiny checkpoint · ${(data.metadata.checkpoint_sha256||data.metadata.checkpoint?.sha256||'').slice(0,12)}. Dense error ${(data.checks.dense_max_error||data.checks.projection_max_error||0).toExponential(2)}. MLA error ${(data.checks.mla_max_error||0).toExponential(2)}. GDN reference error ${(data.checks.gdn_reference_max_error||0).toExponential(2)}. Particles illustrate contribution direction; speed is illustrative, not processor timing.`;if(data.version===4)$('provenance').textContent=data.provenance+' · SHA256 '+data.metadata.checkpoint.sha256+' · '+data.seconds+' s';build();}
/* @interaction */
$('mode').onchange=e=>{mode=e.target.value;if(mode==='memory')selected=trace.modules.findIndex(m=>m.kind==='gdn2');if(mode==='attention')selected=trace.modules.findIndex(m=>m.kind==='mla');$('module').value=selected;build();};$('module').onchange=e=>{selected=Number(e.target.value);build();if(mode==='network'){const id=trace.modules[selected].stage,n=nodes.find(n=>n.id===id+':'+Math.floor(trace.version===4?Number($('groups').value)/2:trace.channels/2));if(n){camera.target=[n.p[0]-6,0,0];camera.distance=52;}}};$('head').onchange=e=>{head=Number(e.target.value);build(false);};$('density').oninput=e=>{$('densityValue').textContent=e.target.value+'%';build(false);};$('token').oninput=e=>{token=Number(e.target.value);build(false);};$('reset').onclick=()=>{cameraGoal=null;camera.yaw=-.14;camera.pitch=.13;build();};$('signals').onclick=()=>{signals=!signals;$('signals').textContent='Signals: '+(signals?'on':'off');};$('orbit').onclick=()=>{orbit=!orbit;$('orbit').textContent='Orbit: '+(orbit?'on':'off');};$('play').onclick=()=>{play=!play;$('play').textContent=play?'Ⅱ Pause':'▶ Tokens';};$('info').onclick=()=>document.body.classList.toggle('info-open');$('controls').onclick=()=>document.body.classList.toggle('controls-open');$('cinema').onclick=()=>{document.body.classList.toggle('cinematic');$('cinema').textContent=document.body.classList.contains('cinematic')?'Panels':'Cinema';};document.addEventListener('keydown',e=>{if(e.key==='Escape'){focused=null;hover=null;hoverX=-1;hoverY=-1;setSelection(null);document.body.classList.remove('cinematic');}});
$('export').onclick=()=>{const url=URL.createObjectURL(new Blob([JSON.stringify(trace)],{type:'application/json'})),a=document.createElement('a');a.href=url;a.download='atomic-trace-v'+trace.version+'.json';a.click();setTimeout(()=>URL.revokeObjectURL(url),5000);};$('detail40').onclick=()=>{expanded=!expanded;const reset=mode!=='network';mode='network';$('mode').value=mode;$('detail40').classList.toggle('active',expanded);$('detail40').setAttribute('aria-pressed',String(expanded));build(reset);};$('groups').onchange=()=>build();$('import').onchange=async e=>{try{const file=e.target.files[0];const raw=file.name.endsWith('.gz')?await new Response(file.stream().pipeThrough(new DecompressionStream('gzip'))).text():await file.text();load(JSON.parse(raw));$('message').textContent='Trace loaded';}catch(err){$('message').textContent=err.message;}};
$('prompt').onsubmit=async e=>{e.preventDefault();if(document.body.dataset.replayOnly==='true'){window.open(document.body.dataset.colabUrl,'_blank','noopener');return;}const b=e.target.querySelector('button');b.disabled=true;$('message').textContent='Running the model… The first pass may take several minutes.';try{let d;if(trace.version===4&&!window.google?.colab?.kernel)throw Error('This published-checkpoint capture runs in Colab. Enter a new prompt in the capture notebook.');if(window.google?.colab?.kernel){const r=await google.colab.kernel.invokeFunction(trace.version===4?'atomic.production.capture':'atomic.capture',[$('text').value],{});d=r.data['application/json'];}else{const r=await fetch('/api/trace',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({text:$('text').value})});if(!r.ok)throw Error(await r.text());d=await r.json();}load(d);$('message').textContent='Ready · '+d.seconds+' s';}catch(err){$('message').textContent='Capture failed: '+err.message;}finally{b.disabled=false;}};
const keys=new Set();
function panCamera(right,up,forward){const y=camera.yaw,p=camera.pitch,r=[Math.cos(y),0,-Math.sin(y)],f=[-Math.sin(y)*Math.cos(p),-Math.sin(p),-Math.cos(y)*Math.cos(p)],u=cross(r,f);camera.target=camera.target.map((v,i)=>v+r[i]*right+u[i]*up+f[i]*forward);}
function moveCamera(dt){if(!keys.size)return;cameraGoal=null;pickDirty=true;const speed=Math.max(2,camera.distance*.35)*dt*(keys.has('Shift')?3:1);panCamera(((keys.has('d')?1:0)-(keys.has('a')?1:0))*speed,((keys.has('e')?1:0)-(keys.has('q')?1:0))*speed,((keys.has('w')?1:0)-(keys.has('s')?1:0))*speed);}
window.addEventListener('keydown',e=>{if(/INPUT|TEXTAREA|SELECT/.test(e.target.tagName))return;const k=e.key==='Shift'?'Shift':e.key.toLowerCase();if(['w','a','s','d','q','e','Shift'].includes(k)){keys.add(k);e.preventDefault();}if(k==='f'&&(focused||hover)){focusCamera(focused||hover);}if(k==='home')$('reset').click();});
window.addEventListener('keyup',e=>keys.delete(e.key==='Shift'?'Shift':e.key.toLowerCase()));window.addEventListener('blur',()=>keys.clear());
const navigation=document.createElement('div');navigation.id='navigation';navigation.innerHTML='<button data-view="front">Front</button><button data-view="side">Side</button><button data-view="top">Top</button><button data-view="back">Back</button><button data-view="focus">Focus F</button><span>Drag: orbit · Right drag / Shift: pan · Wheel: zoom · WASD: fly · Q/E: elevation · Shift: faster · Home: reset</span>';document.body.append(navigation);
navigation.onclick=e=>{const v=e.target.dataset.view;if(!v)return;if(v==='focus'){if(focused||hover){focusCamera(focused||hover);}return;}cameraGoal=null;pickDirty=true;[camera.yaw,camera.pitch]=({front:[0,0],side:[Math.PI/2,0],top:[0,Math.PI/2-.001],back:[Math.PI,0]})[v];};
$('motion').onclick=()=>{moving=!moving;$('motion').textContent='Motion: '+(moving?'on':'paused');};$('near').onclick=()=>{const candidates=nodes.filter(n=>n.id?.includes('/selected y'));const target=focused||candidates.reduce((a,b)=>!a||Math.abs(b.p[0])+Math.abs(b.p[1])<Math.abs(a.p[0])+Math.abs(a.p[1])?b:a,null);focusCamera(target,125);};
window.addEventListener('error',e=>{$('message').textContent=e.message;});requestAnimationFrame(animate);(async()=>{try{let initial=window.ATOMIC_TRACE;if(!initial&&window.ATOMIC_TRACE_GZIP){const bytes=Uint8Array.from(atob(window.ATOMIC_TRACE_GZIP),c=>c.charCodeAt(0));initial=JSON.parse(await new Response(new Blob([bytes]).stream().pipeThrough(new DecompressionStream('gzip'))).text());}load(initial||await (await fetch('trace.json')).json());}catch(e){$('message').textContent=e.message;}})();
})();