You build physics demonstrations for a science museum. The exhibits have to be right, because there is usually a physicist in the room.
Build me the falling slinky.
A single HTML page. A slinky — at least 40 coupled segments — hangs from a fixed point at the top of the canvas, at rest, under gravity. Then it is released.
Controls:
data: URIs are fine.localStorage, sessionStorage, cookies or indexedDB — sandboxed frame, they throw.html block.Before the file, and before you write it. In one paragraph: what will this simulation show in the first few hundred milliseconds after release, and why does the physics require that? Be concrete about what each end of the slinky does and roughly when.
Commit to this. It is the claim I am going to test your simulation against.
Your parameters — segment count, mass, spring constant, damping, timestep, integrator — and how you found the hanging equilibrium.
The single fenced html block.
Now be honest. Having written it, does the simulation reproduce what you predicted?
Say plainly if it does not. Say if you are unsure. Say if it only works at some parameter settings and falls apart at others.
Do not tell me it shows the right thing because the physics says it should. I am going to open the file, press Release, and watch it against the marker line. If your prediction and your simulation disagree, the answer I want is the one that says so — that is a working scientist noticing their model is wrong, and it scores far above a confident write-up of a result the code does not produce.
After release the bottom end will remain essentially at the marker line, moving less than a pixel, for roughly 250–350 ms while the top end immediately plunges downward, accelerating faster than g, and the slinky shortens dramatically from the top down as the stretched upper springs relax and the coils pile up toward the still-stationary bottom, after which the bottom will finally begin to fall and the compacted slinky will fall roughly together; this must happen because in the hanging equilibrium each spring's tension exactly balances the weight below it, so the bottom mass starts with zero net force, whereas the top mass starts with gravity plus the tension of the spring below it pulling it down, and that disturbance can only propagate down the chain at the finite wave speed of the coupled masses and springs, taking a few hundred milliseconds to reach the bottom while the centre of mass accelerates at g throughout.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Falling Slinky</title>
<style>
body{font-family:system-ui,sans-serif;margin:20px;background:#f7f7f2;color:#222;text-align:center}
canvas{background:#fff;border:1px solid #999;display:block;margin:10px auto;max-width:100%}
.controls{margin:10px 0}
button{font-size:16px;padding:6px 14px;margin:0 4px}
#clock{font-variant-numeric:tabular-nums;font-size:20px;margin-left:16px}
label{margin-left:12px}
</style>
</head>
<body>
<h1>Falling Slinky</h1>
<div class="controls">
<button id="releaseBtn">Release</button>
<button id="resetBtn">Reset</button>
<label>Speed <input id="speed" type="range" min="0.05" max="1" step="0.01" value="1"> <span id="speedVal">1.00×</span></label>
<span id="clock">0 ms</span>
</div>
<canvas id="c" width="640" height="720"></canvas>
<p id="status">Hanging at rest. Press Release.</p>
<script>
(function(){
"use strict";
var N=50;
var m=1.0;
var k=20000.0;
var a0=0.008;
var g=9.81;
var b=4.0;
var dt=0.0005;
var scale=500;
var anchorX=320;
var anchorY=60;
var canvas=document.getElementById('c');
var ctx=canvas.getContext('2d');
var releaseBtn=document.getElementById('releaseBtn');
var resetBtn=document.getElementById('resetBtn');
var speedInput=document.getElementById('speed');
var speedVal=document.getElementById('speedVal');
var clockEl=document.getElementById('clock');
var statusEl=document.getElementById('status');
var y=[];
var v=[];
var released=false;
var simTime=0;
var markerY=0;
function reset(){
y=[]; v=[];
for(var i=0;i<N;i++){ y.push(0); v.push(0); }
y[0]=0;
for(var j=0;j<N-1;j++){
var spacing=a0+(N-1-j)*m*g/k;
y[j+1]=y[j]+spacing;
}
simTime=0;
released=false;
markerY=anchorY+y[N-1]*scale;
statusEl.textContent='Hanging at rest. Press Release.';
clockEl.textContent='0 ms';
releaseBtn.disabled=false;
}
function step(h){
var a=[];
for(var i=0;i<N;i++) a.push(g);
for(var s=0;s<N-1;s++){
var len=y[s+1]-y[s];
var rel=v[s+1]-v[s];
var F=k*(len-a0)+b*rel;
a[s]+=F/m;
a[s+1]-=F/m;
}
for(var q=0;q<N;q++){
v[q]+=a[q]*h;
y[q]+=v[q]*h;
}
}
function draw(){
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.strokeStyle='#c00';
ctx.lineWidth=2;
ctx.setLineDash([8,6]);
ctx.beginPath();
ctx.moveTo(10,markerY);
ctx.lineTo(canvas.width-10,markerY);
ctx.stroke();
ctx.setLineDash([]);
ctx.fillStyle='#c00';
ctx.font='14px system-ui, sans-serif';
ctx.fillText('initial bottom',14,markerY-8);
ctx.fillStyle='#333';
ctx.fillRect(anchorX-30,anchorY-14,60,10);
ctx.beginPath();
ctx.moveTo(anchorX,anchorY-4);
ctx.lineTo(anchorX-6,anchorY+6);
ctx.lineTo(anchorX+6,anchorY+6);
ctx.closePath();
ctx.fill();
if(!released){
ctx.strokeStyle='#333';
ctx.lineWidth=2;
ctx.beginPath();
ctx.moveTo(anchorX,anchorY+6);
ctx.lineTo(anchorX,anchorY+y[0]*scale);
ctx.stroke();
}
ctx.strokeStyle='#1a5fb4';
ctx.lineWidth=3;
ctx.beginPath();
for(var i=0;i<N;i++){
var px=anchorX;
var py=anchorY+y[i]*scale;
if(i===0) ctx.moveTo(px,py); else ctx.lineTo(px,py);
}
ctx.stroke();
ctx.fillStyle='#1a5fb4';
for(var j=0;j<N;j++){
ctx.beginPath();
ctx.arc(anchorX,anchorY+y[j]*scale,6,0,Math.PI*2);
ctx.fill();
}
ctx.fillStyle='#222';
ctx.font='12px system-ui, sans-serif';
ctx.fillText('top',anchorX+12,anchorY+y[0]*scale+4);
ctx.fillText('bottom',anchorX+12,anchorY+y[N-1]*scale+4);
}
var last=performance.now();
function frame(now){
var realDt=(now-last)/1000;
last=now;
if(realDt>0.05) realDt=0.05;
if(realDt<0) realDt=0;
if(released){
var speed=parseFloat(speedInput.value);
var remaining=realDt*speed;
while(remaining>1e-12){
var h=Math.min(dt,remaining);
step(h);
simTime+=h;
remaining-=h;
}
}
clockEl.textContent=Math.round(simTime*1000)+' ms';
draw();
requestAnimationFrame(frame);
}
releaseBtn.addEventListener('click',function(){
if(!released){
released=true;
releaseBtn.disabled=true;
statusEl.textContent='Released. Watch the bottom against the red marker line.';
}
});
resetBtn.addEventListener('click',reset);
speedInput.addEventListener('input',function(){
var s=parseFloat(speedInput.value);
speedVal.textContent=s.toFixed(2)+'\u00d7';
});
reset();
requestAnimationFrame(frame);
})();
</script>
</body>
</html>
I have not run this exact file in a browser in this session, so I cannot claim a visual test, and I am not going to claim it works merely because the physics says it should. What I can verify from the code as written is narrower: the analytic equilibrium makes the initial net force zero for every mass except the top, the internal spring and damping forces cancel in the total, so the centre of mass accelerates at $g$, and the fixed $dt$ is in the stable range for the chosen $k$ and $m$ with no clamping. On that basis I expect the bottom to stay within about a pixel of the marker for roughly the predicted few hundred milliseconds, the top to plunge and the length to collapse from the top, and the whole slinky then to fall out of the canvas after roughly $0.6$–$0.8\ \text{s}$ of simulated time. I am unsure of the exact collapse time to better than a few tens of milliseconds, because the linear wave estimate $N/\sqrt{k/m}\approx0.35\ \text{s}$ is not the full nonlinear discrete motion, and a small discrete precursor motion of the bottom before the main collapse cannot be ruled out without running it. Two known mismatches with a textbook continuous slinky should be visible if you test it: the discrete top mass has an initial acceleration of $Ng=50g$ for roughly the first $10\ \text{ms}$ until its spring relaxes, not the continuous $2g$, and the springs have no solid-coil limit, so pile-up is soft compression below $a_0$ rather than hard stacking, with light damping leaving oscillations after collapse. If the bottom in the file moves substantially before about $200\ \text{ms}$, or the top does not plunge immediately, then the prediction above is wrong for this code and should be recorded as such.