oneshotlm
Compare all models on this prompt →

Double pendulum · qwen3.6-plus

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 · 8 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 a single-page HTML file with a chaotic double pendulum simulation. Let me build this with:

1. HTML structure with a canvas element
2. CSS styling in a `<style>` tag
3. JavaScript in a `<script>` tag that:
   - Uses the correct Lagrangian-derived equations of motion for a double pendulum
   - Renders the pendulum arms on a canvas
   - Draws a colorful fading trail of the tip of the second pendulum

The equations of motion for a double pendulum (derived from the Lagrangian):

Let:
- θ₁, θ₂ = angles of the two pendulums from vertical
- ω₁, ω₂ = angular velocities
- m₁, m₂ = masses
- l₁, l₂ = lengths
- g = gravity

The equations are:

θ₁'' = (-g(2m₁+m₂)sin(θ₁) - m₂g*sin(θ₁-2θ₂) - 2sin(θ₁-θ₂)m₂(θ₂'²l₂ + θ₁'²l₁cos(θ₁-θ₂))) / (l₁(2m₁+m₂-m₂cos(2θ₁-2θ₂)))

θ₂'' = (2sin(θ₁-θ₂)(θ₁'²l₁(m₁+m₂) + g(m₁+m₂)cos(θ₁) + θ₂'²l₂m₂cos(θ₁-θ₂))) / (l₂(2m₁+m₂-m₂cos(2θ₁-2θ₂)))

I'll use RK4 integration for accuracy.

For the trail, I'll store previous positions and draw them with decreasing opacity and a color that shifts over time.

Let me write this as a complete, beautiful single-file HTML app.
write /tmp/ws/index.html (9039 bytes)
toolResult

Successfully wrote 9039 bytes to /tmp/ws/index.html

assistant
thinking
The file has been created successfully. Let me verify it's present and well-formed.
bash {"command":"ls -la /tmp/ws/index.html"}
toolResult

-rw-r--r--. 1 node node 9039 Aug 1 17:40 /tmp/ws/index.html

assistant
thinking
The user asked me to build a single-page web app simulating a chaotic double pendulum. Let me review what I've created and verify it's correct and complete.

Let me read the file to verify its contents and make sure everything is correct.

Let me review the file to make sure the physics equations and implementation are correct:

read {"path":"/tmp/ws/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: #0a0a0f; display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100vh; font-family: 'Courier New', monospace; overflow: hidden; color: #aaa; } canvas { border-radius: 12px; box-shadow: 0 0 60px rgba(80, 60, 200, 0.15); } .controls { position: fixed; bottom: 20px; display: flex; gap: 12px; align-items: center; z-index: 10; } button { background: rgba(255,255,255,0.07); color: #ccc; border: 1px solid rgba(255,255,255,0.12); padding: 8px 20px; border-radius: 8px; cursor: pointer; font-family: inherit; font-size: 13px; transition: all 0.2s; } button:hover { background: rgba(255,255,255,0.14); color: #fff; } label { font-size: 12px; display: flex; align-items: center; gap: 6px; color: #888; } input[type=range] { accent-color: #7b5ea7; width: 90px; } .title { position: fixed; top: 20px; font-size: 14px; letter-spacing: 3px; text-transform: uppercase; color: rgba(255,255,255,0.25); } .energy { position: fixed; top: 50px; font-size: 11px; color: rgba(255,255,255,0.15); } </style> </head> <body> <div class="title">Double Pendulum</div> <div class="energy" id="energy"></div> <canvas id="canvas"></canvas> <div class="controls"> <button id="resetBtn">Reset</button> <label>Speed <input type="range" id="speedSlider" min="1" max="20" value="5"></label> <label>Trail <input type="range" id="trailSlider" min="100" max="3000" value="1200"></label> <label> <input type="checkbox" id="ghostCheck"> Ghost twin </label> </div> <script> const canvas = document.getElementById('canvas'); const ctx = canvas.getContext('2d'); // --- Sizing --- function resize() { const s = Math.min(window.innerWidth, window.innerHeight) * 0.88; canvas.width = s; canvas.height = s; } resize(); window.addEventListener('resize', () => { resize(); }); // --- Physics constants --- const g = 9.81; let m1 = 2, m2 = 1.5; let l1 = 1.4, l2 = 1.2; // State: [theta1, omega1, theta2, omega2] let state = [Math.PI * 0.75, 0, Math.PI * 0.5, 0]; // Ghost twin starts slightly different let ghostState = [Math.PI * 0.75 + 0.01, 0, Math.PI * 0.5 + 0.01, 0]; // Trail history let trail = []; let ghostTrail = []; const MAX_TRAIL_DEFAULT = 1200; // Speed control let speedMul = 5; let showGhost = false; // --- Equations of motion (Lagrangian derivation) --- function derivatives(s) { const [t1, w1, t2, w2] = s; const dt = t1 - t2; const sinD = Math.sin(dt); const cosD = Math.cos(dt); const M = m1 + m2; const den = 2 * m1 + m2 - m2 * Math.cos(2 * dt); const alpha1 = (-g * (2 * m1 + m2) * Math.sin(t1) - m2 * g * Math.sin(t1 - 2 * t2) - 2 * sinD * m2 * (w2 * w2 * l2 + w1 * w1 * l1 * cosD)) / (l1 * den); const alpha2 = (2 * sinD * (w1 * w1 * l1 * M + g * M * Math.cos(t1) + w2 * w2 * l2 * m2 * cosD)) / (l2 * den); return [w1, alpha1, w2, alpha2]; } // --- RK4 integration --- function rk4Step(s, dt) { const k1 = derivatives(s); const s2 = s.map((v, i) => v + 0.5 * dt * k1[i]); const k2 = derivatives(s2); const s3 = s.map((v, i) => v + 0.5 * dt * k2[i]); const k3 = derivatives(s3); const s4 = s.map((v, i) => v + dt * k3[i]); const k4 = derivatives(s4); return s.map((v, i) => v + (dt / 6) * (k1[i] + 2*k2[i] + 2*k3[i] + k4[i])); } // --- Convert state to cartesian (in physics coords, centered at pivot) --- function toXY(s) { const [t1, , t2] = s; const x1 = l1 * Math.sin(t1); const y1 = l1 * Math.cos(t1); const x2 = x1 + l2 * Math.sin(t2); const y2 = y1 + l2 * Math.cos(t2); return { x1, y1, x2, y2 }; } // --- Physics-to-screen transform --- function toScreen(px, py) { const cx = canvas.width / 2; const cy = canvas.height * 0.32; const scale = Math.min(canvas.width, canvas.height) * 0.22; return [cx + px * scale, cy + py * scale]; } // --- Color helpers --- function hsl(h, s, l, a = 1) { return `hsla(${h % 360},${s}%,${l}%,${a})`; } function trailColor(i, len) { const t = i / len; const hue = (t * 360 + performance.now() * 0.02) % 360; return hsl(hue, 85, 55 + t * 20, t * 0.9); } // --- Drawing --- function drawPendulum(s, alpha = 1) { const { x1, y1, x2, y2 } = toXY(s); const [sx1, sy1] = toScreen(x1, y1); const [sx2, sy2] = toScreen(x2, y2); const [spx, spy] = toScreen(0, 0); ctx.globalAlpha = alpha; // Arms ctx.strokeStyle = 'rgba(220,210,240,0.85)'; ctx.lineWidth = 3; ctx.lineCap = 'round'; ctx.beginPath(); ctx.moveTo(spx, spy); ctx.lineTo(sx1, sy1); ctx.lineTo(sx2, sy2); ctx.stroke(); // Pivot ctx.fillStyle = 'rgba(255,255,255,0.5)'; ctx.beginPath(); ctx.arc(spx, spy, 4, 0, Math.PI * 2); ctx.fill(); // Bob 1 const r1 = 6 + m1 * 3; ctx.fillStyle = `rgba(180,160,230,${alpha})`; ctx.beginPath(); ctx.arc(sx1, sy1, r1, 0, Math.PI * 2); ctx.fill(); ctx.strokeStyle = `rgba(220,200,255,${alpha * 0.6})`; ctx.lineWidth = 1.5; ctx.stroke(); // Bob 2 (tip) const r2 = 6 + m2 * 3; ctx.fillStyle = `rgba(130,190,255,${alpha})`; ctx.beginPath(); ctx.arc(sx2, sy2, r2, 0, Math.PI * 2); ctx.fill(); ctx.strokeStyle = `rgba(160,210,255,${alpha * 0.6})`; ctx.lineWidth = 1.5; ctx.stroke(); ctx.globalAlpha = 1; } function drawTrail(t, colorFn) { const len = t.length; if (len < 2) return; for (let i = 1; i < len; i++) { const [x0, y0] = toScreen(t[i - 1].x, t[i - 1].y); const [x1, y1] = toScreen(t[i].x, t[i].y); ctx.strokeStyle = colorFn(i, len); ctx.lineWidth = 1 + 1.5 * (i / len); ctx.lineCap = 'round'; ctx.beginPath(); ctx.moveTo(x0, y0); ctx.lineTo(x1, y1); ctx.stroke(); } } // --- Energy calculation (for display) --- function energy(s) { const [t1, w1, t2, w2] = s; const dt = t1 - t2; const K = 0.5 * m1 * l1 * l1 * w1 * w1 + 0.5 * m2 * (l1 * l1 * w1 * w1 + l2 * l2 * w2 * w2 + 2 * l1 * l2 * w1 * w2 * Math.cos(dt)); const U = (m1 + m2) * g * l1 * (1 - Math.cos(t1)) + m2 * g * l2 * (1 - Math.cos(t2)); return K + U; } // --- Main loop --- const physDt = 0.002; let frameTrailMax = MAX_TRAIL_DEFAULT; function loop() { // Physics substeps const steps = Math.round(speedMul * 4); for (let i = 0; i < steps; i++) { state = rk4Step(state, physDt); if (showGhost) ghostState = rk4Step(ghostState, physDt); } // Record trail const pos = toXY(state); trail.push({ x: pos.x2, y: pos.y2 }); if (trail.length > frameTrailMax) trail.shift(); if (showGhost) { const gp = toXY(ghostState); ghostTrail.push({ x: gp.x2, y: gp.y2 }); if (ghostTrail.length > frameTrailMax) ghostTrail.shift(); } else { ghostTrail = []; } // --- Draw --- // Semi-transparent clear for subtle glow ctx.fillStyle = 'rgba(10,10,15,1)'; ctx.fillRect(0, 0, canvas.width, canvas.height); // Subtle radial gradient background const grad = ctx.createRadialGradient( canvas.width / 2, canvas.height * 0.32, 0, canvas.width / 2, canvas.height * 0.32, canvas.width * 0.6 ); grad.addColorStop(0, 'rgba(40,30,70,0.12)'); grad.addColorStop(1, 'rgba(10,10,15,0)'); ctx.fillStyle = grad; ctx.fillRect(0, 0, canvas.width, canvas.height); // Ghost trail if (showGhost && ghostTrail.length > 1) { drawTrail(ghostTrail, (i, len) => { const t = i / len; const hue = (t * 360 + 180 + performance.now() * 0.02) % 360; return hsl(hue, 80, 55, t * 0.5); }); } // Main trail if (trail.length > 1) { drawTrail(trail, trailColor); } // Pendulum drawPendulum(state); if (showGhost) drawPendulum(ghostState, 0.4); // Energy display document.getElementById('energy').textContent = `E = ${energy(state).toFixed(4)} J`; requestAnimationFrame(loop); } // --- Controls --- document.getElementById('resetBtn').addEventListener('click', () => { state = [Math.PI * 0.75, 0, Math.PI * 0.5, 0]; ghostState = [Math.PI * 0.75 + 0.01, 0, Math.PI * 0.5 + 0.01, 0]; trail = []; ghostTrail = []; }); document.getElementById('speedSlider').addEventListener('input', e => { speedMul = +e.target.value; }); document.getElementById('trailSlider').addEventListener('input', e => { frameTrailMax = +e.target.value; trail = trail.slice(-frameTrailMax); ghostTrail = ghostTrail.slice(-frameTrailMax); }); document.getElementById('ghostCheck').addEventListener('change', e => { showGhost = e.target.checked; if (!showGhost) ghostTrail = []; if (showGhost) ghostState = [state[0] + 0.01, state[1], state[2] + 0.01, state[3]]; }); // Go! loop(); </script> </body> </html>

assistant
thinking
Let me check the equations of motion for the double pendulum. The standard Lagrangian-derived equations are:

For a double pendulum with angles measured from vertical (downward = 0):

θ₁'' = (-g(2m₁+m₂)sin(θ₁) - m₂g sin(θ₁-2θ₂) - 2sin(θ₁-θ₂)m₂(θ₂'²L₂ + θ₁'²L₁cos(θ₁-θ₂))) / (L₁(2m₁+m₂-m₂cos(2θ₁-2θ₂)))

θ₂'' = (2sin(θ₁-θ₂)(θ₁'²L₁(m₁+m₂) + g(m₁+m₂)cos(θ₁) + θ₂'²L₂m₂cos(θ₁-θ₂))) / (L₂(2m₁+m₂-m₂cos(2θ₁-2θ₂)))

Let me verify my implementation:

```
const dt = t1 - t2;
const sinD = Math.sin(dt);
const cosD = Math.cos(dt);
const M = m1 + m2;
const den = 2 * m1 + m2 - m2 * Math.cos(2 * dt);
```

Note: cos(2dt) = cos(2θ₁-2θ₂), which is correct since dt = t1-t2.

For alpha1:
```
const alpha1 = (-g * (2 * m1 + m2) * Math.sin(t1)
                 - m2 * g * Math.sin(t1 - 2 * t2)
                 - 2 * sinD * m2 * (w2 * w2 * l2 + w1 * w1 * l1 * cosD))
               / (l1 * den);
```

This matches the standard formula:
-g(2m₁+m₂)sin(θ₁) ✓
-m₂g sin(θ₁-2θ₂) ✓
-2sin(θ₁-θ₂)m₂(w₂²L₂ + w₁²L₁cos(θ₁-θ₂)) ✓

For alpha2:
```
const alpha2 = (2 * sinD * (w1 * w1 * l1 * M
                 + g * M * Math.cos(t1)
                 + w2 * w2 * l2 * m2 * cosD))
               / (l2 * den);
```

Let me check: The standard formula is:
θ₂'' = 2sin(θ₁-θ₂)(θ₁'²L₁(m₁+m₂) + g(m₁+m₂)cos(θ₁) + θ₂'²L₂m₂cos(θ₁-θ₂)) / (L₂(2m₁+m₂-m₂cos(2θ₁-2θ₂)))

2sinD ✓
w1² * l1 * M = θ₁'²L₁(m₁+m₂) ✓
g * M * cos(t1) = g(m₁+m₂)cos(θ₁) ✓
w2² * l2 * m2 * cosD = θ₂'²L₂m₂cos(θ₁-θ₂) ✓
denominator: l2 * den ✓

Everything looks correct! The implementation looks solid. Let me confirm the file is present.

Everything looks good. The double pendulum uses the correct Lagrangian-derived equations of motion: - **θ₁''** = [−g(2m₁+m₂)sin θ₁ − m₂g sin(θ₁−2θ₂) − 2sin(θ₁−θ₂)m₂(θ₂'²L₂ + θ₁'²L₁cos(θ₁−θ₂))] / [L₁(2m₁+m₂ − m₂cos(2θ₁−2θ₂))] - **θ₂''** = [2sin(θ₁−θ₂)(θ₁'²L₁(m₁+m₂) + g(m₁+m₂)cos θ₁ + θ₂'²L₂m₂cos(θ₁−θ₂))] / [L₂(2m₁+m₂ − m₂cos(2θ₁−2θ₂))] Integrated with **RK4** for stability. Features: - **Colorful fading trail** — hue shifts over time, opacity fades from recent to old - **Ghost twin** toggle — starts 0.01 rad offset so you can watch chaotic divergence in real time - **Speed & trail length** sliders - **Reset** button - **Energy display** (conservation check — should stay nearly constant) The `index.html` file is ready at `/tmp/ws/index.html`.

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

modelqwen3.6-plus
vendorQwen
harnesspi-agent
generated2026-08-01

EVALUATION

4/5 working

Chaotic double pendulum simulation with colorful fading trails, animates continuously and reacts to inputs by resetting/altering trajectory.

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.8%
on click14.5%
on drag13.6%
on wheel11.4%
on enter + space13.4%
on arrow keys13.9%
on w/a/s/d20.6%
frame spread21.7 / 255
console errors0
js errors none

METRICS

runtime92.8s
tokens in21k
tokens out5.2k
cached0
cost / run$0.0170