Yarncrawler Demos

const ctx = canvas.getContext('2d'); function drawGrid(grid, mode){ const {w,h,B,C,N,S} = grid; const img = ctx.createImageData(w,h); for(let i=0;i green, C -> orange, N -> blue, S -> white overlay const bB = Math.min(1,B[i]); const bC = Math.min(1,C[i]); const bN = Math.min(1,N[i]); r = 200*bC + 40*bB; g = 220*bB + 60*bC; b = 220*bN + 30*bB; const s = Math.min(1,S[i]); r = lerp(r,255,s*0.2); g = lerp(g,255,s*0.2); b = lerp(b,255,s*0.2); }else{ // RSVP: visualize Φ in magma palette const v = Math.min(1, grid.B[i]); // simple palette const t=v; r= 255*Math.pow(t,0.6); g= 200*Math.pow(t,0.9); b= 80*t; } const ii=i*4; img.data[ii]=r; img.data[ii+1]=g; img.data[ii+2]=b; img.data[ii+3]=255; } // Scale image to canvas const off = new OffscreenCanvas(grid.w, grid.h); const octx = off.getContext('2d'); octx.putImageData(img,0,0); ctx.imageSmoothingEnabled = false; ctx.clearRect(0,0,canvas.width,canvas.height); ctx.drawImage(off, 0,0, canvas.width, canvas.height); } // Painting helper function attachPainter(grid, state){ let painting=false; function xy(evt){ const rect=canvas.getBoundingClientRect(); const cx=evt.clientX-rect.left, cy=evt.clientY-rect.top; const x = Math.floor(cx/canvas.width*grid.w); const y = Math.floor(cy/canvas.height*grid.h); return {x:clamp(x,0,grid.w-1), y:clamp(y,0,grid.h-1)}; } function dab(x0,y0,rad,field,val,op){ for(let y=y0-rad;y<=y0+rad;y++){ if(y<0||y>=grid.h) continue; for(let x=x0-rad;x<=x0+rad;x++){ if(x<0||x>=grid.w) continue; const d=(x-x0)**2+(y-y0)**2; if(d>rad*rad) continue; const i=grid.index(x,y); let v = field[i]; if(op==='add') v += val; else if(op==='set') v = val; field[i]=clamp(v,0,1e3); } } } canvas.addEventListener('pointerdown',e=>{painting=true; const {x,y}=xy(e); if(state.paintTarget==='S') dab(x,y,state.brush,grid.S, state.paintValue, 'add'); if(state.paintTarget==='Phi') dab(x,y,state.brush,grid.B, state.paintValue, 'add'); }); canvas.addEventListener('pointermove',e=>{ if(!painting) return; const {x,y}=xy(e); if(state.paintTarget==='S') dab(x,y,state.brush,grid.S, state.paintValue, 'add'); if(state.paintTarget==='Phi') dab(x,y,state.brush,grid.B, state.paintValue, 'add'); }); window.addEventListener('pointerup',()=>painting=false); } // ------------------------------- // Simulation A: Stigmergic Berms // ------------------------------- function makeBermSim(){ const W=180, H=120; const grid = new Grid(W,H); // Initialize affinity S as a band across the center (imitate boundary) grid.forEach((x,y,i)=>{ const d=Math.abs(y-H/2); const tau=10; const base=Math.exp(-d/tau); grid.S[i]=base*0.7; // gentle resource mask R -> modulate S horizontally const rx = 0.4+0.6*Math.exp(-((x-W*0.75)**2)/(2*(W*0.2)**2)); grid.S[i]*=rx; }); // Facilitation constants const params={ dt:0.2, DB:0.8, DC:0.2, DN:0.25, alphaB:0.09, alphaC:0.06, alphaN:0.05, betaB:0.6, deltaB:0.02, deltaC:0.006, deltaN:0.008, C0:0.5, N0:0.5, K:1.2, adv:0.0, // simple advection strength running:true, paintTarget:'S', paintValue:0.3, brush:6 }; // Simple slope for advection (optional) grid.forEach((x,y,i)=>{ const ux=0, uy=0.0; grid.u[i*2]=ux; grid.u[i*2+1]=uy; }); function lap(A,i,x,y){ const xm=x>0?i-1:i, xp=x0?i-grid.w:i, yp=y{ let dB = p.DB*lap(B,i,x,y); if(p.adv!==0){ const ux=u[i*2], uy=u[i*2+1]; const xm=Math.max(0,x-1), xp=Math.min(w-1,x+1), ym=Math.max(0,y-1), yp=Math.min(h-1,y+1); const Bx=B[grid.index(xp,y)]-B[grid.index(xm,y)]; const By=B[grid.index(x,y p)]-B[grid.index(x,ym)]; // prevent typo } }); // We'll do simpler: pure diffusion; advection term omitted for stability in this minimal demo // Reaction terms & update grid.forEach((x,y,i)=>{ const b=B[i], c=C[i], n=N[i], s=S[i]; const M = (c/(c+p.C0))*(n/(n+p.N0)); const growth = p.alphaB*s*(1 + p.betaB*M*b)*(1 - b/p.K); const dB = p.DB*lap(B,i,x,y) + (growth - p.deltaB*b); const dC = p.DC*lap(C,i,x,y) + (p.alphaC*s - p.deltaC*c); const dN = p.DN*lap(N,i,x,y) + (p.alphaN*s - p.deltaN*n - 0.02*b*n); tmp[i]= b + dt*dB; C[i] = c + dt*dC; N[i] = n + dt*dN; }); grid.B.set(grid.tmp); } const state={grid, params, mode:'berm'}; attachPainter(grid, params); function ui(){ controlsBody.innerHTML = ''+ `
`; legend.innerHTML = 'B (berm) = green, C (char) = orange, N (nutrients) = blue; S (affinity) lightens.'; const $=(id)=>document.getElementById(id); $('play').onclick=()=>{params.running=!params.running; ui();}; $('step').onclick=()=>{step(); drawGrid(grid,'berm');}; $('reset').onclick=()=>{grid.clear(); grid.forEach((x,y,i)=>{ const d=Math.abs(y-grid.h/2); grid.S[i]=0.7*Math.exp(-d/10);});}; $('paintTarget').onchange=(e)=>params.paintTarget=e.target.value; $('brush').oninput=(e)=>params.brush=+e.target.value; $('pval').oninput=(e)=>params.paintValue=+e.target.value; ['dt','DB','alphaB','betaB','deltaB'].forEach(k=>{ $(k).oninput=(e)=>{params[k]=+e.target.value;}; }); } ui(); function render(){ drawGrid(grid,'berm'); hud.textContent=`Terra Preta — t=${(t*params.dt).toFixed(1)} | ⌀S=${avg(grid.S).toFixed(2)} | ⌀B=${avg(grid.B).toFixed(2)}`; } function avg(A){ let s=0; for(let i=0;i{ const cx=W/2, cy=H/2; const dx=(x-cx)/W, dy=(y-cy)/H; // tangential vector field (vortex) const ux = -dy, uy = dx; const s=0.6; grid.u[i*2]=s*ux; grid.u[i*2+1]=s*uy; grid.B[i]=0; grid.S[i]=0; // S unused here; we paint Φ directly }); const params={ dt:0.15, D:0.9, adv:0.8, decay:0.01, running:true, paintTarget:'Phi', paintValue:1.0, brush:6 }; function lap(A,i,x,y){ const xm=x>0?i-1:i, xp=x0?i-grid.w:i, yp=y{ const lx = D*lap(B,i,x,y); // Upwind-ish advection (simple centered difference) const xm=Math.max(0,x-1), xp=Math.min(w-1,x+1), ym=Math.max(0,y-1), yp=Math.min(h-1,y+1); const dBx=(B[grid.index(xp,y)]-B[grid.index(xm,y)])*0.5; const dBy=(B[grid.index(x,y p)]-B[grid.index(x,ym)])*0.5; // guard below }); // For numerical stability in a compact demo, use diffusion+decay only, then add gentle swirl in render grid.forEach((x,y,i)=>{ const b=grid.B[i]; const d = params.D*lap(grid.B,i,x,y) - params.decay*b; grid.tmp[i] = b + params.dt*d; }); grid.B.set(grid.tmp); } attachPainter(grid, params); function ui(){ controlsBody.innerHTML = ''+ `
`; legend.innerHTML = 'Φ (scalar) heatmap; paint to inject mass. (Vector flow shown as swirl overlay.)'; const $=(id)=>document.getElementById(id); $('play').onclick=()=>{params.running=!params.running; ui();}; $('step').onclick=()=>{step(); draw();}; $('reset').onclick=()=>{grid.B.fill(0)}; ['pval','brush','dt','D','decay'].forEach(k=>{$(k).oninput=(e)=>{params[k]=+e.target.value;};}); } function drawVectors(){ // draw a subtle swirl field for intuition ctx.save(); ctx.globalAlpha=0.25; ctx.strokeStyle='#64d7ff'; const skip=14; const sx=canvas.width/grid.w, sy=canvas.height/grid.h; for(let y=skip;y{ const t=e.target.closest('.tab'); if(!t) return; [...tabs.children].forEach(x=>x.classList.remove('active')); t.classList.add('active'); load(t.dataset.tab); }); // boot load('berm'); })();