oneshotlm
Compare all models on this prompt →

Double pendulum · laguna-xs-2.1

Simulate a chaotic double pendulum on a canvas using the correct equations of motion, drawing the arms and a colorful fading trail of the tip.

⛨ sandboxed Open in new tab ↗
transcript · 14 messages
user

Simulate a chaotic double pendulum on a canvas using the correct equations of motion, drawing the arms and a colorful fading trail of the tip.

assistant
thinking
The user wants me to create a single-page web app (index.html) that simulates a chaotic double pendulum. This is a complex physics simulation that requires:

1. Correct equations of motion for a double pendulum
2. Canvas rendering
3. Drawing the pendulum arms
4. A colorful fading trail of the tip

Let me think about the physics:

A double pendulum consists of two pendulums attached to each other. The equations of motion are derived from the Lagrangian.

For a double pendulum with:
- m1, m2: masses
- L1, L2: lengths
- θ1, θ2: angles from vertical
- θ̇1, θ̇2: angular velocities

The equations of motion are quite complex. Let me recall them:

The angular accelerations are:
θ̈1 and θ̈2

These involve the masses, lengths, gravity, and the angles.

The standard equations are:

θ̈1 = [−g(2m1+m2)sinθ1 − m2*g*sin(θ1−2θ2) − 2sin(θ1−θ2)*m2*(θ̇2²*L2 + θ̇1²*L1*cos(θ1−θ2))] / [L1*(2m1+m2−m2*cos(2θ1−2θ2))]

θ̈2 = [2sin(θ1−θ2)*(θ̇1²*L1*(m1+m2) + g*(m1+m2)*cosθ1 + θ̇2²*L2*m2*cos(θ1−θ2))] / [L2*(2m1+m2−m2*cos(2θ1−2θ2))]

For the trail, I'll use an array to store the previous positions and draw fading circles.

Let me write this as a complete HTML file with embedded CSS and JavaScript.

I'll create a complete double pendulum simulation with the correct physics equations. Let me write this as a single self-contained HTML file.

write /workspace/index.html (8959 bytes)
toolResult

Successfully wrote 8959 bytes to /workspace/index.html

assistant
thinking
The file has been written. Let me verify it was created correctly by reading it.

Let me verify the file was created correctly:

read {"path":"/workspace/index.html"}
toolResult

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Chaotic Double Pendulum</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { background: #000; overflow: hidden; font-family: sans-serif; display: flex; flex-direction: column; align-items: center; padding: 20px; color: #fff; } canvas { border: 1px solid #444; background: #111; box-shadow: 0 0 20px rgba(0,0,0,0.5); } .info { margin-top: 15px; text-align: center; max-width: 600px; line-height: 1.5; } .controls { margin-top: 10px; } button { background: #4a90d9; color: white; border: none; padding: 10px 20px; margin: 5px; border-radius: 5px; cursor: pointer; font-size: 14px; } button:hover { background: #357abd; } .param { font-size: 12px; color: #aaa; } </style> </head> <body> <h1>Chaotic Double Pendulum</h1> <div class="info"> <p class="param">Correct equations of motion from Lagrangian mechanics. The system exhibits chaotic behavior - small changes in initial conditions lead to dramatically different trajectories.</p> <div class="controls"> <button onclick="reset()">Reset</button> <button onclick="toggleTrail()">Toggle Trail</button> </div> </div> <canvas id="canvas" width="800" height="600"></canvas> <script> const canvas = document.getElementById('canvas'); const ctx = canvas.getContext('2d'); // Physical constants const G = 9.81; // gravity // Pendulum parameters let L1 = 150; // length of rod 1 let L2 = 150; // length of rod 2 let m1 = 1; // mass 1 let m2 = 1; // mass 2 // State variables: [theta1, omega1, theta2, omega2] let theta1 = Math.PI / 4; // initial angle 1 let omega1 = 0; // initial angular velocity 1 let theta2 = Math.PI / 3; // initial angle 2 let omega2 = 0; // initial angular velocity 2 // Trail storage let trail = []; let maxTrailPoints = 200; let showTrail = true; // Canvas center const cx = canvas.width / 2; const cy = 80; // Animation let animationId; // Double pendulum equations of motion (from Lagrangian) function derivatives(state) { const [th1, w1, th2, w2] = state; const delta = th1 - th2; const sinDelta = Math.sin(delta); const cosDelta = Math.cos(delta); // Denominator terms const den1 = (m1 + m2) * L1 - m2 * L1 * Math.cos(2 * delta); const den2 = (L2 / L1) * den1; // Angular acceleration 1 const num1 = m2 * L1 * w1 * w1 * sinDelta * cosDelta; const num2 = m2 * G * Math.sin(th2) * 2 * cosDelta; const num3 = m2 * L2 * w2 * w2 * sinDelta; const num4 = (m1 + m2) * G * Math.sin(2 * th1); const alpha1 = (-num1 - num2 - num3 - num4) / den1; // Angular acceleration 2 const num5 = -m2 * L2 * w2 * w2 * sinDelta * cosDelta; const num6 = (m1 + m2) * (G * Math.sin(th1) * cosDelta - L1 * w1 * w1 * sinDelta - G * Math.sin(th2))); const alpha2 = (num5 + num6) / den2; return [w1, alpha1, w2, alpha2]; } // Runge-Kutta 4th order integration function rk4(state, dt) { const k1 = derivatives(state); const k2 = derivatives(state.map((s, i) => s + 0.5 * dt * k1[i])); const k3 = derivatives(state.map((s, i) => s + 0.5 * dt * k2[i])); const k4 = derivatives(state.map((s, i) => s + dt * k3[i])); return state.map((s, i) => s + dt * (k1[i] + 2*k2[i] + 2*k3[i] + k4[i]) / 6); } // Convert angles to Cartesian coordinates function getPositions() { const x1 = L1 * Math.sin(theta1); const y1 = L1 * Math.cos(theta1); const x2 = x1 + L2 * Math.sin(theta2); const y2 = y1 + L2 * Math.cos(theta2); return { x1: cx + x1, y1: cy + y1, x2: cx + x2, y2: cy + y2 }; } // Update physics function update(dt) { const state = [theta1, omega1, theta2, omega2]; const newState = rk4(state, dt); theta1 = newState[0]; omega1 = newState[1]; theta2 = newState[2]; omega2 = newState[3]; } // Draw the trail function drawTrail() { if (!showTrail || trail.length < 2) return; for (let i = 0; i < trail.length - 1; i++) { const p1 = trail[i]; const p2 = trail[i + 1]; // Color fades based on position in trail (older = more blue, newer = more red) const age = 1 - i / trail.length; const hue = (180 + age * 180) % 360; // Blue to red const alpha = 0.3 * age; ctx.strokeStyle = `hsla(${hue}, 80%, 60%, ${alpha})`; ctx.lineWidth = 2; ctx.lineCap = 'round'; ctx.beginPath(); ctx.moveTo(p1.x, p1.y); ctx.lineTo(p2.x, p2.y); ctx.stroke(); } } // Draw the pendulum function draw() { ctx.fillStyle = 'rgba(0, 0, 0, 0.1)'; ctx.fillRect(0, 0, canvas.width, canvas.height); const pos = getPositions(); // Add tip to trail trail.push({ x: pos.x2, y: pos.y2 }); if (trail.length > maxTrailPoints) { trail.shift(); } // Draw trail drawTrail(); // Draw pivot point ctx.fillStyle = '#fff'; ctx.beginPath(); ctx.arc(cx, cy, 8, 0, Math.PI * 2); ctx.fill(); // Draw rod 1 ctx.strokeStyle = '#aaa'; ctx.lineWidth = 4; ctx.beginPath(); ctx.moveTo(cx, cy); ctx.lineTo(pos.x1, pos.y1); ctx.stroke(); // Draw connection between rods ctx.strokeStyle = '#888'; ctx.lineWidth = 3; ctx.beginPath(); ctx.moveTo(pos.x1, pos.y1); ctx.lineTo(pos.x2, pos.y2); ctx.stroke(); // Draw mass 1 ctx.fillStyle = '#ff6b6b'; ctx.beginPath(); ctx.arc(pos.x1, pos.y1, 12, 0, Math.PI * 2); ctx.fill(); ctx.strokeStyle = '#fff'; ctx.lineWidth = 2; ctx.stroke(); // Draw mass 2 ctx.fillStyle = '#4ecdc4'; ctx.beginPath(); ctx.arc(pos.x2, pos.y2, 12, 0, Math.PI * 2); ctx.fill(); ctx.strokeStyle = '#fff'; ctx.lineWidth = 2; ctx.stroke(); // Draw highlight on mass 1 ctx.fillStyle = 'rgba(255, 255, 255, 0.4)'; ctx.beginPath(); ctx.arc(pos.x1 - 4, pos.y1 - 4, 4, 0, Math.PI * 2); ctx.fill(); // Draw highlight on mass 2 ctx.fillStyle = 'rgba(255, 255, 255, 0.4)'; ctx.beginPath(); ctx.arc(pos.x2 + 4, pos.y2 - 4, 4, 0, Math.PI * 2); ctx.fill(); } // Animation loop function animate() { update(0.02); draw(); animationId = requestAnimationFrame(animate); } // Reset simulation function reset() { cancelAnimationFrame(animationId); theta1 = Math.PI / 4 + (Math.random() - 0.5) * 0.5; omega1 = (Math.random() - 0.5) * 0.5; theta2 = Math.PI / 3 + (Math.random() - 0.5) * 0.5; omega2 = (Math.random() - 0.5) * 0.5; trail = []; animate(); } // Toggle trail function toggleTrail() { showTrail = !showTrail; if (!showTrail) { trail = []; } } // Start reset(); </script> </body> </html>

assistant
thinking
I notice there's a syntax error on line 128 - there's an extra closing parenthesis in the num6 calculation. Let me fix that.

I notice there's a syntax error in the equations. Let me fix it:

edit {"path":"/workspace/index.html","edits":[{"oldText":"const num6 = (m1 + m2) * (G * Math.sin(th1) * cosDelta - L1 * w1 * w1 * sinDelta - G *
toolResult

Successfully replaced 1 block(s) in /workspace/index.html.

assistant
thinking
Good, I fixed the syntax error. Now let me also verify that the equations of motion are correct. Looking at the standard double pendulum equations, I should double-check them.

The correct equations for a double pendulum are:

For θ̈1:
θ̈1 = [-g(2m1+m2)sinθ1 - m2*g*sin(θ1-2θ2) - 2sin(θ1-θ2)*m2*(ω2²*L2 + ω1²*L1*cos(θ1-θ2))] / [L1*(2m1+m2-m2*cos(2θ1-2θ2))]

For θ̈2:
θ̈2 = [2sin(θ1-θ2)*(ω1²*L1*(m1+m2) + g*(m1+m2)*cosθ1 + ω2²*L2*m2*cos(θ1-θ2))] / [L2*(2m1+m2-m2*cos(2θ1-2θ2))]

My current implementation uses a different formulation. Let me rewrite it with the correct equations to ensure accuracy.

Let me also verify and correct the equations of motion - I should use the standard form:

edit {"path":"/workspace/index.html","edits":[{"oldText":"// Double pendulum equations of motion (from Lagrangian)\n function derivatives(
toolResult

Successfully replaced 1 block(s) in /workspace/index.html.

assistant
thinking
Good, I've fixed the equations of motion to use the standard form. Let me verify the file is correct now.

Let me verify the corrected file:

read {"path":"/workspace/index.html"}
toolResult

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Chaotic Double Pendulum</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { background: #000; overflow: hidden; font-family: sans-serif; display: flex; flex-direction: column; align-items: center; padding: 20px; color: #fff; } canvas { border: 1px solid #444; background: #111; box-shadow: 0 0 20px rgba(0,0,0,0.5); } .info { margin-top: 15px; text-align: center; max-width: 600px; line-height: 1.5; } .controls { margin-top: 10px; } button { background: #4a90d9; color: white; border: none; padding: 10px 20px; margin: 5px; border-radius: 5px; cursor: pointer; font-size: 14px; } button:hover { background: #357abd; } .param { font-size: 12px; color: #aaa; } </style> </head> <body> <h1>Chaotic Double Pendulum</h1> <div class="info"> <p class="param">Correct equations of motion from Lagrangian mechanics. The system exhibits chaotic behavior - small changes in initial conditions lead to dramatically different trajectories.</p> <div class="controls"> <button onclick="reset()">Reset</button> <button onclick="toggleTrail()">Toggle Trail</button> </div> </div> <canvas id="canvas" width="800" height="600"></canvas> <script> const canvas = document.getElementById('canvas'); const ctx = canvas.getContext('2d'); // Physical constants const G = 9.81; // gravity // Pendulum parameters let L1 = 150; // length of rod 1 let L2 = 150; // length of rod 2 let m1 = 1; // mass 1 let m2 = 1; // mass 2 // State variables: [theta1, omega1, theta2, omega2] let theta1 = Math.PI / 4; // initial angle 1 let omega1 = 0; // initial angular velocity 1 let theta2 = Math.PI / 3; // initial angle 2 let omega2 = 0; // initial angular velocity 2 // Trail storage let trail = []; let maxTrailPoints = 200; let showTrail = true; // Canvas center const cx = canvas.width / 2; const cy = 80; // Animation let animationId; // Double pendulum equations of motion (from Lagrangian) function derivatives(state) { const [th1, w1, th2, w2] = state; const delta = th1 - th2; const sinDelta = Math.sin(delta); const cosDelta = Math.cos(delta); const sinTh1 = Math.sin(th1); const sinTh2 = Math.sin(th2); const cosTh1 = Math.cos(th1); // Denominator (same for both equations) const den = 2*m1 + m2 - m2*Math.cos(2*delta); // Angular acceleration 1 const alpha1 = (-G*(2*m1 + m2)*sinTh1 - m2*G*Math.sin(th1 - 2*th2) - 2*sinDelta*m2*(w2*w2*L2 + w1*w1*L1*cosDelta)) / (L1 * den); // Angular acceleration 2 const alpha2 = (2*sinDelta*(w1*w1*L1*(m1 + m2) + G*(m1 + m2)*cosTh1 + w2*w2*L2*m2*cosDelta)) / (L2 * den); return [w1, alpha1, w2, alpha2]; } // Runge-Kutta 4th order integration function rk4(state, dt) { const k1 = derivatives(state); const k2 = derivatives(state.map((s, i) => s + 0.5 * dt * k1[i])); const k3 = derivatives(state.map((s, i) => s + 0.5 * dt * k2[i])); const k4 = derivatives(state.map((s, i) => s + dt * k3[i])); return state.map((s, i) => s + dt * (k1[i] + 2*k2[i] + 2*k3[i] + k4[i]) / 6); } // Convert angles to Cartesian coordinates function getPositions() { const x1 = L1 * Math.sin(theta1); const y1 = L1 * Math.cos(theta1); const x2 = x1 + L2 * Math.sin(theta2); const y2 = y1 + L2 * Math.cos(theta2); return { x1: cx + x1, y1: cy + y1, x2: cx + x2, y2: cy + y2 }; } // Update physics function update(dt) { const state = [theta1, omega1, theta2, omega2]; const newState = rk4(state, dt); theta1 = newState[0]; omega1 = newState[1]; theta2 = newState[2]; omega2 = newState[3]; } // Draw the trail function drawTrail() { if (!showTrail || trail.length < 2) return; for (let i = 0; i < trail.length - 1; i++) { const p1 = trail[i]; const p2 = trail[i + 1]; // Color fades based on position in trail (older = more blue, newer = more red) const age = 1 - i / trail.length; const hue = (180 + age * 180) % 360; // Blue to red const alpha = 0.3 * age; ctx.strokeStyle = `hsla(${hue}, 80%, 60%, ${alpha})`; ctx.lineWidth = 2; ctx.lineCap = 'round'; ctx.beginPath(); ctx.moveTo(p1.x, p1.y); ctx.lineTo(p2.x, p2.y); ctx.stroke(); } } // Draw the pendulum function draw() { ctx.fillStyle = 'rgba(0, 0, 0, 0.1)'; ctx.fillRect(0, 0, canvas.width, canvas.height); const pos = getPositions(); // Add tip to trail trail.push({ x: pos.x2, y: pos.y2 }); if (trail.length > maxTrailPoints) { trail.shift(); } // Draw trail drawTrail(); // Draw pivot point ctx.fillStyle = '#fff'; ctx.beginPath(); ctx.arc(cx, cy, 8, 0, Math.PI * 2); ctx.fill(); // Draw rod 1 ctx.strokeStyle = '#aaa'; ctx.lineWidth = 4; ctx.beginPath(); ctx.moveTo(cx, cy); ctx.lineTo(pos.x1, pos.y1); ctx.stroke(); // Draw connection between rods ctx.strokeStyle = '#888'; ctx.lineWidth = 3; ctx.beginPath(); ctx.moveTo(pos.x1, pos.y1); ctx.lineTo(pos.x2, pos.y2); ctx.stroke(); // Draw mass 1 ctx.fillStyle = '#ff6b6b'; ctx.beginPath(); ctx.arc(pos.x1, pos.y1, 12, 0, Math.PI * 2); ctx.fill(); ctx.strokeStyle = '#fff'; ctx.lineWidth = 2; ctx.stroke(); // Draw mass 2 ctx.fillStyle = '#4ecdc4'; ctx.beginPath(); ctx.arc(pos.x2, pos.y2, 12, 0, Math.PI * 2); ctx.fill(); ctx.strokeStyle = '#fff'; ctx.lineWidth = 2; ctx.stroke(); // Draw highlight on mass 1 ctx.fillStyle = 'rgba(255, 255, 255, 0.4)'; ctx.beginPath(); ctx.arc(pos.x1 - 4, pos.y1 - 4, 4, 0, Math.PI * 2); ctx.fill(); // Draw highlight on mass 2 ctx.fillStyle = 'rgba(255, 255, 255, 0.4)'; ctx.beginPath(); ctx.arc(pos.x2 + 4, pos.y2 - 4, 4, 0, Math.PI * 2); ctx.fill(); } // Animation loop function animate() { update(0.02); draw(); animationId = requestAnimationFrame(animate); } // Reset simulation function reset() { cancelAnimationFrame(animationId); theta1 = Math.PI / 4 + (Math.random() - 0.5) * 0.5; omega1 = (Math.random() - 0.5) * 0.5; theta2 = Math.PI / 3 + (Math.random() - 0.5) * 0.5; omega2 = (Math.random() - 0.5) * 0.5; trail = []; animate(); } // Toggle trail function toggleTrail() { showTrail = !showTrail; if (!showTrail) { trail = []; } } // Start reset(); </script> </body> </html>

assistant
thinking
The file looks good now. The double pendulum simulation is complete with:

1. Correct equations of motion derived from Lagrangian mechanics
2. Runge-Kutta 4th order numerical integration for accuracy
3. Canvas rendering with:
   - Two pendulum arms (rods)
   - Two masses (circles) with highlights
   - A colorful fading trail of the tip
   - A pivot point
4. Controls (Reset and Toggle Trail buttons)
5. All CSS and JavaScript embedded in the single HTML file
6. No external resources

The simulation will exhibit chaotic behavior - the path of the second mass will be highly sensitive to initial conditions, creating interesting patterns with the trail.

Let me verify the file exists in the current directory.

The double pendulum simulation is complete. Let me verify the file exists:

bash {"command":"ls -la /workspace/index.html"}
toolResult

-rw-r--r-- 1 node node 8832 Jul 22 17:17 /workspace/index.html

assistant

The double pendulum simulation is complete at `/workspace/index.html`. The simulation features: **Physics:** - Correct equations of motion derived from Lagrangian mechanics - Runge-Kutta 4th order numerical integration for accurate simulation - Chaotic behavior - small changes in initial conditions create dramatically different trajectories **Visuals:** - Canvas rendering with two pendulum arms (rods) and masses (circles) - Colorful fading trail of the tip (blue → red gradient) - Pivot point with highlights on masses - Semi-transparent trail fading effect **Controls:** - Reset button - randomizes initial conditions - Toggle Trail button - shows/hides the tip trajectory

TAKE IT TO CODE

A single self-contained HTML file — copy it and open in any browser, or reuse the prompt with your own pi-agent run.

RUN

modellaguna-xs-2.1
vendorPoolside
harnesspi-agent
generated2026-07-22

EVALUATION

4/5 working

Double pendulum animates continuously with colorful fading trail; inputs like click/drag/keys don't add interactivity but simulation itself runs correctly.

Vision grade · anthropic/claude-sonnet-5

MEASUREMENTS

Raw readings from the capture, not a verdict — the grade above is the only judgement. Percentages are how much of the screen changed; the measure under-reports sparse motion on a dark background.

idle motion1.4%
on click1.5%
on drag1.4%
on wheel1.4%
on enter + space1.4%
on arrow keys1.2%
on w/a/s/d1.2%
frame spread17.9 / 255
console errors0
js errors none

METRICS

runtime31.3s
tokens in9.9k
tokens out4.8k
cached44k
cost / run$0.0293