← Physics Playground results

Physics Playground

GPT 5.6 terra · full

Initial view of Physics Playground

Reviews & generation details

Reviews

No human review yet. Unreviewed does not mean unsuccessful.

Generation context

Imported historical artifact. Its exact original instructions and lineage are not known.

Generation prompt · exact text unavailable

See the current prompt for reference. It may differ from the instructions used to generate this artifact.

Inspect original source 16,799 bytes · SHA-256 7c944d3174f0
<!doctype html>
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Soft Body Playground</title>
<style>
:root{--panel:#141a27e8;--ink:#dce7ff;--muted:#8290aa;--line:#2b3650;--cyan:#3edcff;--pink:#ff5da2;--lime:#a9f36b;--gold:#ffc65c}*{box-sizing:border-box}body{margin:0;overflow:hidden;background:#080c14;color:var(--ink);font:12px ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;user-select:none}canvas{display:block;width:100vw;height:100vh;cursor:crosshair}.bar{position:fixed;top:16px;left:16px;display:flex;align-items:center;gap:7px;padding:8px 10px;background:var(--panel);border:1px solid #29344d;border-radius:12px;backdrop-filter:blur(12px);box-shadow:0 12px 35px #0008;z-index:2}.brand{font-weight:800;letter-spacing:1.2px;color:#fff;margin-right:7px}.brand b{color:var(--cyan)}button,.select{font:inherit;color:#cbd8ee;background:#1b2435;border:1px solid #36445e;border-radius:7px;padding:7px 9px;cursor:pointer;transition:.15s}button:hover,.select:hover{border-color:#6f89b6;background:#243149}button.active{color:#071019;background:var(--cyan);border-color:var(--cyan);font-weight:bold}.divider{height:23px;width:1px;background:#33415a;margin:0 2px}.hint{position:fixed;bottom:16px;left:16px;max-width:580px;padding:8px 11px;border-radius:8px;background:#101725c9;border:1px solid #25304a;color:#96a6c4;line-height:1.55;pointer-events:none}.hint b{color:#dce7ff}.stats{position:fixed;right:16px;top:16px;text-align:right;line-height:1.55;color:#9baaca;text-shadow:0 1px #000}.stats b{color:#fff}.controls{position:fixed;right:16px;bottom:16px;width:220px;background:var(--panel);border:1px solid #29344d;border-radius:12px;padding:11px;line-height:1.8}.controls label{display:flex;justify-content:space-between;align-items:center;color:#aebbd2}.controls input[type=range]{width:118px;accent-color:var(--cyan)}.controls input[type=checkbox]{accent-color:var(--cyan)}.mode{position:fixed;top:69px;left:16px;background:#111827d9;padding:6px 10px;border-radius:8px;color:#9baaca;border:1px solid #26324b}.mode b{color:var(--cyan)}@media(max-width:760px){.bar{gap:3px;left:7px;top:7px}.brand{display:none}.hint{display:none}.controls{right:7px;bottom:7px}.mode{top:55px;left:7px}}
</style></head><body>
<canvas id="canvas"></canvas>
<div class="bar"><span class="brand">SOFT<b>/</b>LAB</span><button data-tool="grab" class="active">Grab</button><button data-tool="rope">Rope</button><button data-tool="cloth">Cloth</button><button data-tool="soft">Blob</button><button data-tool="ball">Ball</button><span class="divider"></span><button id="pause">❚❚</button><button id="step">Step</button><button id="reset">Reset</button></div>
<div class="mode">MODE <b id="modeLabel">GRAB</b></div><div class="stats" id="stats"></div>
<div class="controls"><label>Gravity <span><input id="gravity" type="range" min="0" max="1800" value="980"><b id="gravityV">980</b></span></label><label>Relaxation <span><input id="iterations" type="range" min="3" max="10" value="7"><b id="iterationsV">7</b></span></label><label>Wind <input id="wind" type="checkbox"></label><label>Stress colors <input id="stress" type="checkbox"></label><label>Cloth pins <select id="pins" class="select"><option value="corners">corners</option><option value="edge">full edge</option></select></label></div>
<div class="hint"><b>Drag</b> to place selected material · <b>Grab</b> and throw points · <b>Shift + click</b> pin/unpin · <b>Right-drag</b> cut constraints · <b>Space</b> pause · <b>→</b> one frame</div>
<script>
'use strict';
/* ============================ TUNABLE CONSTANTS ============================ */
const CFG={dt:1/60,maxFrame:.08,damping:.992,gravity:980,iterations:7,tearRadius:12,wallFriction:.78,pointFriction:.92,worldPad:18,colors:{rope:'#ffc65c',cloth:'#3edcff',soft:'#ff5da2',ball:'#a9f36b'}};
/* ============================ CANVAS & MODEL =============================== */
const canvas=document.querySelector('#canvas'),ctx=canvas.getContext('2d');let W=innerWidth,H=innerHeight,dpr=1;
function resize(){dpr=Math.min(devicePixelRatio||1,2);W=innerWidth;H=innerHeight;canvas.width=W*dpr;canvas.height=H*dpr;ctx.setTransform(dpr,0,0,dpr,0,0)}addEventListener('resize',resize);resize();
const sim=window.sim={points:[],constraints:[],shapes:[],paused:false,gravity:CFG.gravity,iterations:CFG.iterations,wind:false,stress:false,time:0};
let nextId=0;
function point(x,y,opt={}){const p={id:nextId++,x,y,px:x-(opt.vx||0)*CFG.dt,py:y-(opt.vy||0)*CFG.dt,r:opt.r||4,pinned:!!opt.pinned,kind:opt.kind||'ball',color:opt.color||CFG.colors.ball,group:opt.group||null};sim.points.push(p);return p}
function link(a,b,opt={}){let dx=b.x-a.x,dy=b.y-a.y;const c={a,b,len:opt.len??Math.hypot(dx,dy),stiff:opt.stiff??1,kind:opt.kind||'rope',color:opt.color||CFG.colors.rope,dead:false};if(c.len>0.001)sim.constraints.push(c);return c}
function group(type,color){return {type,color,points:[],constraints:[]}}
function addLink(g,a,b,o){const c=link(a,b,{...o,kind:g.type,color:g.color});g.constraints.push(c);return c}
/* ============================ CONSTRUCTORS ================================= */
function spawnRope(x1,y1,x2,y2){const g=group('rope',CFG.colors.rope),n=Math.max(5,Math.min(34,Math.round(Math.hypot(x2-x1,y2-y1)/16)));let last;for(let i=0;i<n;i++){let t=i/(n-1),p=point(x1+(x2-x1)*t,y1+(y2-y1)*t,{r:3.7,pinned:i===0,kind:'rope',color:g.color,group:g});g.points.push(p);if(last)addLink(g,last,p,{stiff:.98});last=p}return g}
function spawnCloth(x1,y1,x2,y2){let w=Math.max(70,Math.abs(x2-x1)),h=Math.max(55,Math.abs(y2-y1)),cols=Math.max(5,Math.min(22,Math.round(w/16)+1)),rows=Math.max(4,Math.min(18,Math.round(h/16)+1));let sx=(x2>=x1?1:-1),sy=(y2>=y1?1:-1);const g=group('cloth',CFG.colors.cloth),a=[];for(let j=0;j<rows;j++){a[j]=[];for(let i=0;i<cols;i++){let pin=j===0&&(document.querySelector('#pins').value==='edge'||i===0||i===cols-1);let p=point(x1+sx*w*i/(cols-1),y1+sy*h*j/(rows-1),{r:2.4,pinned:pin,kind:'cloth',color:g.color,group:g});a[j][i]=p;g.points.push(p)}}for(let j=0;j<rows;j++)for(let i=0;i<cols;i++){if(i) addLink(g,a[j][i-1],a[j][i],{stiff:.91});if(j) addLink(g,a[j-1][i],a[j][i],{stiff:.91});if(i&&j)addLink(g,a[j-1][i-1],a[j][i],{stiff:.48});if(i<cols-1&&j)addLink(g,a[j-1][i+1],a[j][i],{stiff:.48})}g.grid={a,cols,rows};return g}
function spawnSoft(x,y,r){const g=group('soft',CFG.colors.soft),n=12,center=point(x,y,{r:4,kind:'soft',color:g.color,group:g});g.points.push(center);let ring=[];for(let i=0;i<n;i++){let q=i/n*Math.PI*2,p=point(x+Math.cos(q)*r,y+Math.sin(q)*r,{r:4,kind:'soft',color:g.color,group:g});ring.push(p);g.points.push(p);addLink(g,center,p,{stiff:.72});if(i)addLink(g,ring[i-1],p,{stiff:.85})}addLink(g,ring[n-1],ring[0],{stiff:.85});for(let i=0;i<n;i++)addLink(g,ring[i],ring[(i+6)%n],{stiff:.32});g.ring=ring;return g}
function spawnBall(x,y,r=13){const g=group('ball',CFG.colors.ball),p=point(x,y,{r,kind:'ball',color:g.color,group:g});g.points.push(p);return g}
/* ============================ STATIC GEOMETRY ============================== */
function initShapes(){sim.shapes=[{type:'circle',x:W*.51,y:H*.66,r:62},{type:'box',x:W*.76,y:H*.73,w:138,h:20,angle:-.16},{type:'box',x:W*.22,y:H*.79,w:130,h:18,angle:.12}]}
/* ============================ PHYSICS ====================================== */
function integrate(dt){const gy=sim.gravity, wind=sim.wind?Math.sin(sim.time*1.3)*110:0;for(const p of sim.points){if(p.pinned||p===grab.point)continue;let vx=(p.x-p.px)*CFG.damping,vy=(p.y-p.py)*CFG.damping;p.px=p.x;p.py=p.y;p.x+=vx+wind*dt*dt;p.y+=vy+gy*dt*dt}}
function solveLinks(){for(const c of sim.constraints){if(c.dead)continue;let dx=c.b.x-c.a.x,dy=c.b.y-c.a.y,d2=dx*dx+dy*dy;if(d2<1e-10)continue;let d=Math.sqrt(d2),diff=(d-c.len)/d*c.stiff;let am=c.a.pinned||c.a===grab.point?0:.5,bm=c.b.pinned||c.b===grab.point?0:.5,tot=am+bm;if(!tot)continue;c.a.x+=dx*diff*(am/tot);c.a.y+=dy*diff*(am/tot);c.b.x-=dx*diff*(bm/tot);c.b.y-=dy*diff*(bm/tot)}}
function collidePoint(p){if(p.pinned||p===grab.point)return;const pad=CFG.worldPad,r=p.r;if(p.x<pad+r){p.x=pad+r;p.px=p.x+(p.px-p.x)*CFG.wallFriction}if(p.x>W-pad-r){p.x=W-pad-r;p.px=p.x+(p.px-p.x)*CFG.wallFriction}if(p.y<pad+r){p.y=pad+r;p.py=p.y+(p.py-p.y)*CFG.wallFriction}if(p.y>H-pad-r){p.y=H-pad-r;p.py=p.y+(p.py-p.y)*CFG.wallFriction}
for(const s of sim.shapes){if(s.type==='circle'){let dx=p.x-s.x,dy=p.y-s.y,d=Math.hypot(dx,dy),min=s.r+r;if(d<min){if(d<.001){dx=1;dy=0;d=1}let nx=dx/d,ny=dy/d;p.x=s.x+nx*min;p.y=s.y+ny*min;let vx=p.x-p.px,vy=p.y-p.py,normal=vx*nx+vy*ny;p.px=p.x-(vx-normal*nx)*CFG.pointFriction;p.py=p.y-(vy-normal*ny)*CFG.pointFriction}}else{ // oriented box via local-space closest point
let co=Math.cos(-s.angle),si=Math.sin(-s.angle),dx=p.x-s.x,dy=p.y-s.y,lx=dx*co-dy*si,ly=dx*si+dy*co,hx=s.w/2,hy=s.h/2,cx=Math.max(-hx,Math.min(hx,lx)),cy=Math.max(-hy,Math.min(hy,ly)),ex=lx-cx,ey=ly-cy,d=Math.hypot(ex,ey);if(d<r){let nx,ny;if(d>.001){nx=ex/d;ny=ey/d}else if(Math.abs(lx/hx)>Math.abs(ly/hy)){nx=Math.sign(lx)||1;ny=0}else{nx=0;ny=Math.sign(ly)||1}lx=cx+nx*r;ly=cy+ny*r;let cc=Math.cos(s.angle),ss=Math.sin(s.angle);p.x=s.x+lx*cc-ly*ss;p.y=s.y+lx*ss+ly*cc;p.px=p.x+(p.px-p.x)*CFG.pointFriction;p.py=p.y+(p.py-p.y)*CFG.pointFriction}}}}
function physicsStep(){if(sim.paused)return;sim.time+=CFG.dt;integrate(CFG.dt);for(let k=0;k<sim.iterations;k++){solveLinks();for(const p of sim.points)collidePoint(p);if(grab.point){grab.point.x=grab.x;grab.point.y=grab.y}}sim.constraints=sim.constraints.filter(c=>!c.dead)}sim.step=physicsStep;
/* ============================ INTERACTION ================================== */
const grab={point:null,x:0,y:0},mouse={x:0,y:0,down:false,button:0,startX:0,startY:0,lastX:0,lastY:0};let tool='grab',cutTrail=[];
function pointer(e){let r=canvas.getBoundingClientRect();return{x:e.clientX-r.left,y:e.clientY-r.top}}
function nearest(x,y,r=24){let best=null,bd=r*r;for(const p of sim.points){let d=(p.x-x)**2+(p.y-y)**2;if(d<bd){bd=d;best=p}}return best}
function cut(x1,y1,x2,y2){for(const c of sim.constraints){let ax=c.a.x,ay=c.a.y,bx=c.b.x,by=c.b.y;let vx=bx-ax,vy=by-ay,wx=x2-x1,wy=y2-y1,den=vx*wy-vy*wx;if(Math.abs(den)<.00001)continue;let t=((x1-ax)*wy-(y1-ay)*wx)/den,u=((x1-ax)*vy-(y1-ay)*vx)/den;if(t>=0&&t<=1&&u>=0&&u<=1)c.dead=true}cutTrail.push({x:x2,y:y2,life:1})}
canvas.addEventListener('contextmenu',e=>e.preventDefault());canvas.addEventListener('pointerdown',e=>{canvas.setPointerCapture(e.pointerId);let q=pointer(e);Object.assign(mouse,{...q,down:true,button:e.button,startX:q.x,startY:q.y,lastX:q.x,lastY:q.y});if(e.button===2)return;if(e.shiftKey){let p=nearest(q.x,q.y);if(p){p.pinned=!p.pinned;p.px=p.x;p.py=p.y}return}if(tool==='grab'){let p=nearest(q.x,q.y);if(p){grab.point=p;grab.x=q.x;grab.y=q.y}}});
canvas.addEventListener('pointermove',e=>{let q=pointer(e);if(mouse.down){if(mouse.button===2)cut(mouse.lastX,mouse.lastY,q.x,q.y);if(grab.point){grab.x=q.x;grab.y=q.y}}Object.assign(mouse,{...q,lastX:q.x,lastY:q.y})});
canvas.addEventListener('pointerup',e=>{if(mouse.down&&mouse.button!==2&&!grab.point&&tool!=='grab'){let dx=mouse.x-mouse.startX,dy=mouse.y-mouse.startY;if(tool==='rope')spawnRope(mouse.startX,mouse.startY,mouse.x,mouse.y);if(tool==='cloth')spawnCloth(mouse.startX,mouse.startY,mouse.x,mouse.y);if(tool==='soft')spawnSoft(mouse.startX,mouse.startY,Math.max(18,Math.min(65,Math.hypot(dx,dy))));if(tool==='ball')spawnBall(mouse.startX,mouse.startY,Math.max(7,Math.min(28,Math.hypot(dx,dy))));}if(grab.point){let p=grab.point;p.px=p.x-(mouse.x-mouse.lastX)*.8;p.py=p.y-(mouse.y-mouse.lastY)*.8;grab.point=null}mouse.down=false});
/* ============================ DRAWING ====================================== */
function line(x1,y1,x2,y2,col,w=1){ctx.strokeStyle=col;ctx.lineWidth=w;ctx.beginPath();ctx.moveTo(x1,y1);ctx.lineTo(x2,y2);ctx.stroke()}
function drawShape(s){ctx.save();ctx.translate(s.x,s.y);if(s.type==='circle'){let g=ctx.createRadialGradient(-20,-25,2,0,0,s.r);g.addColorStop(0,'#52627c');g.addColorStop(1,'#222c40');ctx.fillStyle=g;ctx.beginPath();ctx.arc(0,0,s.r,0,7);ctx.fill();ctx.strokeStyle='#667794';ctx.stroke()}else{ctx.rotate(s.angle);ctx.fillStyle='#273247';ctx.fillRect(-s.w/2,-s.h/2,s.w,s.h);ctx.strokeStyle='#61718e';ctx.strokeRect(-s.w/2,-s.h/2,s.w,s.h)}ctx.restore()}
function draw(){ctx.clearRect(0,0,W,H);let bg=ctx.createLinearGradient(0,0,0,H);bg.addColorStop(0,'#0c1220');bg.addColorStop(1,'#060a10');ctx.fillStyle=bg;ctx.fillRect(0,0,W,H);ctx.strokeStyle='#25314a';ctx.lineWidth=2;ctx.strokeRect(CFG.worldPad,CFG.worldPad,W-CFG.worldPad*2,H-CFG.worldPad*2);for(const s of sim.shapes)drawShape(s);
// cloth translucent panels first
let seen=new Set;for(const p of sim.points){let g=p.group;if(!g||g.type!=='cloth'||seen.has(g))continue;seen.add(g);ctx.fillStyle='rgba(62,220,255,.075)';for(let j=0;j<g.grid.rows-1;j++)for(let i=0;i<g.grid.cols-1;i++){let a=g.grid.a[j][i],b=g.grid.a[j][i+1],c=g.grid.a[j+1][i+1],d=g.grid.a[j+1][i];ctx.beginPath();ctx.moveTo(a.x,a.y);ctx.lineTo(b.x,b.y);ctx.lineTo(c.x,c.y);ctx.lineTo(d.x,d.y);ctx.closePath();ctx.fill()}}
for(const c of sim.constraints){let col=c.color;if(sim.stress){let ratio=Math.abs(Math.hypot(c.b.x-c.a.x,c.b.y-c.a.y)/c.len-1),t=Math.min(1,ratio*8);col=`hsl(${120*(1-t)},90%,${55+t*8}%)`}line(c.a.x,c.a.y,c.b.x,c.b.y,col,c.kind==='cloth'?0.7:2)}
// soft polygons
seen.clear();for(const p of sim.points){let g=p.group;if(g?.type==='soft'&&!seen.has(g)){seen.add(g);ctx.fillStyle='rgba(255,93,162,.28)';ctx.beginPath();g.ring.forEach((q,i)=>i?ctx.lineTo(q.x,q.y):ctx.moveTo(q.x,q.y));ctx.closePath();ctx.fill()}}
for(const p of sim.points){if(p.kind==='cloth')continue;ctx.fillStyle=p.pinned?'#fff':p.color;ctx.beginPath();ctx.arc(p.x,p.y,p.r,pinnedArc(p)?0:0,Math.PI*2);ctx.fill();if(p.pinned){ctx.strokeStyle=p.color;ctx.lineWidth=2;ctx.stroke()}}function pinnedArc(){return 0}
if(grab.point){ctx.strokeStyle='#fff';ctx.lineWidth=2;ctx.beginPath();ctx.arc(grab.x,grab.y,13,0,7);ctx.stroke();line(grab.x-18,grab.y,grab.x+18,grab.y,'#fff8');line(grab.x,grab.y-18,grab.x,grab.y+18,'#fff8')}cutTrail=cutTrail.filter(t=>(t.life-=.06)>0);for(const t of cutTrail){ctx.fillStyle=`rgba(255,100,130,${t.life})`;ctx.beginPath();ctx.arc(t.x,t.y,4*(1-t.life)+2,0,7);ctx.fill()}
if(mouse.down&&mouse.button!==2&&!grab.point&&tool!=='grab'){ctx.setLineDash([5,5]);line(mouse.startX,mouse.startY,mouse.x,mouse.y,'#dce7ff',1);ctx.setLineDash([])}}
/* ============================ APP LOOP & UI ================================ */
let last=performance.now(),acc=0,fps=60,frames=0,stamp=last,stepMs=0;function frame(now){let elapsed=Math.min(CFG.maxFrame,(now-last)/1000);last=now;acc+=elapsed;let t0=performance.now();while(acc>=CFG.dt){physicsStep();acc-=CFG.dt}stepMs=performance.now()-t0;frames++;if(now-stamp>500){fps=frames*1000/(now-stamp);frames=0;stamp=now}draw();document.querySelector('#stats').innerHTML=`<b>${fps.toFixed(0)} FPS</b> &nbsp; ${stepMs.toFixed(2)} ms<br>${sim.points.length} points · ${sim.constraints.length} links`;requestAnimationFrame(frame)}requestAnimationFrame(frame);
function reset(){sim.points.length=0;sim.constraints.length=0;nextId=0;initShapes();spawnRope(W*.16,95,W*.29,290);spawnCloth(W*.38,80,W*.62,250);spawnSoft(W*.77,140,32);spawnBall(W*.25,110,14)}
document.querySelectorAll('[data-tool]').forEach(b=>b.onclick=()=>{tool=b.dataset.tool;document.querySelectorAll('[data-tool]').forEach(x=>x.classList.toggle('active',x===b));document.querySelector('#modeLabel').textContent=tool.toUpperCase()});
document.querySelector('#pause').onclick=()=>{sim.paused=!sim.paused;document.querySelector('#pause').textContent=sim.paused?'▶':'❚❚'};document.querySelector('#step').onclick=()=>{if(sim.paused){sim.paused=false;physicsStep();sim.paused=true}};document.querySelector('#reset').onclick=reset;document.querySelector('#gravity').oninput=e=>{sim.gravity=+e.target.value;document.querySelector('#gravityV').textContent=e.target.value};document.querySelector('#iterations').oninput=e=>{sim.iterations=+e.target.value;document.querySelector('#iterationsV').textContent=e.target.value};document.querySelector('#wind').onchange=e=>sim.wind=e.target.checked;document.querySelector('#stress').onchange=e=>sim.stress=e.target.checked;addEventListener('keydown',e=>{if(e.code==='Space'){e.preventDefault();document.querySelector('#pause').click()}if(e.code==='ArrowRight'&&sim.paused){e.preventDefault();document.querySelector('#step').click()}});initShapes();reset();
</script></body></html>
<!-- agent-meta {"model":"openai/gpt-5.6-terra","provider":"openrouter","persona":"full","sessionId":"be8f0990-1c9d-4717-8ff7-b036caf5b952","tokensIn":76181,"tokensOut":6857,"tokensTotal":83038,"cost":0.0615118,"turns":7,"toolCalls":6,"failedToolCalls":1,"timestamp":"2026-07-31T19:31:31.843Z"} -->