oneshotlm
Compare all models on this prompt →

Double pendulum · gemini-2.5-flash-lite

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 · 4 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
**Developing Interactive Canvas**

I'm currently focusing on establishing the foundational HTML structure, specifically integrating a `<canvas>` element that will serve as the primary rendering surface. My next steps involve defining the necessary CSS to ensure the canvas is properly styled and ready for dynamic content.

**Simulating Pendulum Dynamics**

I'm now diving into the core physics, focusing on the complex equations of motion for the double pendulum. My immediate goal is to select and implement a suitable numerical integration method to accurately simulate the system's behavior over time.

**Implementing Physics Engine**

I'm now focused on translating the derived double pendulum equations into JavaScript, selecting a robust numerical integration method, likely RK4, for accurate simulation. My immediate task is to set up the initial conditions and parameters for this physics engine.

**Refining Pendulum Dynamics**

I'm now focused on the integration method for the pendulum's motion. I've begun implementing the RK4 algorithm to ensure greater accuracy and stability for the chaotic system, with an eye on setting up initial conditions and system parameters next.

**Refining Equations of Motion**

I'm now focused on precisely formulating the differential equations governing the double pendulum's movement. My primary effort is directed towards adapting these into a form suitable for the Runge-Kutta 4th order integration method.

**Developing Physics Core**

I am currently focused on implementing the core physics engine, specifically the complex differential equations of motion for the double pendulum. My next step is to integrate these equations using a suitable numerical method like RK4 to accurately simulate the system's behavior.

I am currently formulating the specific differential equations required for the double pendulum's movement. My primary focus is adapting these into a structure compatible with the Runge-Kutta 4th order integration method for accurate simulation.

**Formulating Equations of Motion**

I'm currently focused on deriving and structuring the complex differential equations that govern the double pendulum's behavior. My immediate task is to adapt these equations into a format amenable to the Runge-Kutta 4th order integration method for accurate simulation.

**Developing Physics Core**

I am currently focused on implementing the core physics engine, specifically the complex differential equations of motion for the double pendulum. My next step is to integrate these equations using a suitable numerical method like RK4 to accurately simulate the system's behavior.

**Formulating Differential Equations**

I am currently focused on adapting the complex differential equations that govern the double pendulum's behavior. My immediate priority is to structure these equations in a way that is directly compatible with the Runge-Kutta 4th order integration method for simulation.

**Formulating Differential Equations**

I am currently focused on adapting the complex differential equations that govern the double pendulum's behavior. My immediate priority is to structure these equations in a way that is directly compatible with the Runge-Kutta 4th order integration method for simulation.

**Formulating Differential Equations**

I am currently focused on adapting the complex differential equations that govern the double pendulum's behavior. My immediate priority is to structure these equations in a way that is directly compatible with the Runge-Kutta 4th order integration method for simulation.

**Formulating Differential Equations**

I'm currently focused on adapting the complex differential equations that govern the double pendulum's behavior. My immediate priority is to structure these equations in a way that is directly compatible with the Runge-Kutta 4th order integration method for simulation.

**Developing Physics Core**

I am now focused on adapting the complex differential equations that govern the double pendulum's behavior. My immediate priority is to structure these equations in a way that is directly compatible with the Runge-Kutta 4th order integration method for simulation.

**Refining Pendulum Physics**

I'm currently focused on integrating the complex differential equations governing the double pendulum's motion. My immediate priority is adapting these into a structure directly compatible with the Runge-Kutta 4th order integration method for accurate simulation.

**Refining Simulation Dynamics**

I am now focused on precisely implementing the Runge-Kutta 4th order integration for the double pendulum's equations of motion. My immediate task is to accurately calculate the intermediate steps (`k1`, `k2`, `k3`, `k4`) and then average them to update the system's state variables.

**Implementing RK4 Steps**

I am now focused on accurately calculating the intermediate `k` values (`k1` through `k4`) for the Runge-Kutta 4th order integration. My next step will be to use these to update the system's state variables and then render the pendulum's movement.

**Simulating Pendulum Motion**

I'm currently refining the RK4 integration steps, focusing on the precise calculation of intermediate `k` values and their weighted averaging to update the system's state. My next step involves rendering the pendulum's movement based on these updated states.

**Refining Pendulum Drawing**

I'm now focused on refining how the pendulum's trail is rendered. My immediate task is to adjust the alpha fading and hue cycling of trail points to create a more visually appealing, smooth, and colorful fading effect.

**Refining Trail Rendering**

I'm now focusing on the visual presentation of the pendulum's trail. My current efforts are directed towards optimizing the alpha fading and hue cycling of trail points, aiming for a more fluid and visually engaging fading effect.

**Refining Integration Steps**

I am now precisely calculating the intermediate `k` values (`k1` through `k4`) for the Runge-Kutta 4th order integration. My next step will be to use these to update the system's state variables and then render the pendulum's movement.

**Refining Trail Colors**

I'm now focusing on enhancing the visual appeal of the pendulum's trail. My current efforts are concentrated on adjusting the hue cycling mechanism to ensure a more vibrant and continuous color transition as points fade.

**Refining Trail Colors**

I'm now focusing on enhancing the visual appeal of the pendulum's trail. My current efforts are concentrated on adjusting the hue cycling mechanism to ensure a more vibrant and continuous color transition as points fade.

**Formulating Equations of Motion**

I am currently focused on deriving and structuring the complex differential equations that govern the double pendulum's behavior. My immediate task is to adapt these equations into a format amenable to the Runge-Kutta 4th order integration method for accurate simulation.

**Developing Physics Core**

I am currently focused on adapting the complex differential equations that govern the double pendulum's behavior. My immediate priority is to structure these equations in a way that is directly compatible with the Runge-Kutta 4th order integration method for simulation.

**Refining Trail Colors**

My focus is now on enhancing the visual appeal of the pendulum's trail. I'm adjusting the hue cycling mechanism to achieve more vibrant and continuous color transitions as the points fade, aiming for a more fluid and captivating visual effect.

state[0] + k3[0] * dt, state[1] + k3[1] * dt,
                state[2] + k3[2] * dt, state[3] + k3[3] * dt
            ];
            const [alpha1_k4, alpha2_k4] = getAccelerations(state_k4[0], state_k4[1], state_k4[2], state_k4[3]);
            const k4 = [state_k4[1], alpha1_k4, state_k4[3], alpha2_k4];

            // --- Update state using RK4 average ---
            // The new state is the old state plus the weighted average of the k values times dt
            theta1 += (k1[0] + 2 * k2[0] + 2 * k3[0] + k4[0]) * dt / 6;
            omega1 += (k1[1] + 2 * k2[1] + 2 * k3[1] + k4[1]) * dt / 6;
            theta2 += (k1[2] + 2 * k2[2] + 2 * k3[2] + k4[2]) * dt / 6;
            omega2 += (k1[3] + 2 * k2[3] + 2 * k3[3] + k4[3]) * dt / 6;

            // --- Update Trail ---
            // Calculate the position of the tip of the second pendulum
            const x2 = originX + L2 * Math.sin(theta2);
            const y2 = originY + L2 * Math.cos(theta2);

            // Add the new trail point. Hue cycles to create a rainbow effect.
            // Alpha is 1.0 initially, it will fade over time.
            const hue = (trail.length * 1.0) % 360; // Cycle hue from 0 to 359
            trail.push({x: x2, y: y2, hue: hue, alpha: 1.0});

            // Limit the trail length to prevent performance issues
            if (trail.length > MAX_TRAIL_POINTS) {
                trail.shift(); // Remove the oldest point from the start of the array
            }
        }

        // Function to draw the pendulum and its trail on the canvas
        function draw() {
            // Clear the canvas with a dark background
            ctx.fillStyle = '#111';
            ctx.fillRect(0, 0, canvas.width, canvas.height);

            // --- Draw the Trail ---
            ctx.lineWidth = 2;
            for (let i = 0; i < trail.length; i++) {
                const point = trail[i];
                point.alpha -= TRAIL_FADE_RATE; // Decrease alpha for fading effect

                if (point.alpha > 0) { // Only draw if the point is still visible
                    ctx.strokeStyle = `hsl(${point.hue}, 100%, 50%, ${point.alpha})`;

                    // Draw a line segment from the previous visible point to the current one
                    if (i > 0) {
                        const prevPoint = trail[i - 1];
                        // Ensure the previous point is also visible to draw a continuous line
                        if (prevPoint.alpha > 0) {
                            ctx.beginPath();
                            ctx.moveTo(prevPoint.x, prevPoint.y);
                            ctx.lineTo(point.x, point.y);
                            ctx.stroke();
                        }
                    }
                }
            }

            // --- Calculate Pendulum Arm Endpoints ---
            // Position of the end of the first arm (connected to the first mass)
            const x1 = originX + L1 * Math.sin(theta1);
            const y1 = originY + L1 * Math.cos(theta1);
            // Position of the end of the second arm (connected to the second mass)
            const x2 = x1 + L2 * Math.sin(theta2);
            const y2 = y1 + L2 * Math.cos(theta2);

            // --- Draw Pendulum Arms ---
            ctx.strokeStyle = '#fff'; // White for the arms
            ctx.lineWidth = 3;

            // Arm 1: From origin to first mass
            ctx.beginPath();
            ctx.moveTo(originX, originY);
            ctx.lineTo(x1, y1);
            ctx.stroke();

            // Arm 2: From first mass to second mass
            ctx.beginPath();
            ctx.moveTo(x1, y1);
            ctx.lineTo(x2, y2);
            ctx.stroke();

            // --- Draw Masses ---
            const massRadius = 7;
            ctx.fillStyle = '#ff0000'; // Red for mass 1
            ctx.beginPath();
            ctx.arc(x1, y1, massRadius, 0, Math.PI * 2);
            ctx.fill();

            ctx.fillStyle = '#0000ff'; // Blue for mass 2
            ctx.beginPath();
            ctx.arc(x2, y2, massRadius, 0, Math.PI * 2);
            ctx.fill();

            // --- Draw Origin Point ---
            ctx.fillStyle = '#ffff00'; // Yellow for the fixed origin
            ctx.beginPath();
            ctx.arc(originX, originY, 5, 0, Math.PI * 2);
            ctx.fill();
        }

        // --- Animation Loop ---
        function animate() {
            originX = canvas.width / 2; // Recenter origin if window resizes
            originY = canvas.height / 2;

            updatePhysics(); // Update the state of the pendulum
            draw();          // Render the pendulum and its trail
            requestAnimationFrame(animate); // Request the next frame
        }

        // Start the animation
        animate();

    </script>
</body>
</html>
</script>

**Formulating Differential Equations**

I'm currently focused on adapting the complex differential equations that govern the double pendulum's behavior. My immediate priority is to structure these equations in a way that is directly compatible with the Runge-Kutta 4th order integration method for simulation.

**Refining Trail Rendering**

My focus is now on the visual presentation of the pendulum's trail. I'm adjusting the hue cycling and alpha fading mechanisms to create a more fluid, vibrant, and aesthetically pleasing fading effect as the trail progresses.

**Refining Trail Rendering**

My current focus is on enhancing the visual appeal of the pendulum's trail. I'm adjusting the hue cycling and alpha fading mechanisms to create a more fluid, vibrant, and aesthetically pleasing fading effect as the trail progresses. I'm also ensuring that line segments are only drawn between visible trail points.

**Refining Trail Rendering**

My current focus is on enhancing the visual appeal of the pendulum's trail. I'm adjusting the hue cycling and alpha fading mechanisms to create a more fluid, vibrant, and aesthetically pleasing fading effect as the trail progresses. I'm also ensuring that line segments are only drawn between visible trail points.

**Refining Trail Colors**

My current focus is on enhancing the visual appeal of the pendulum's trail. I'm adjusting the hue cycling and alpha fading mechanisms to create a more fluid, vibrant, and aesthetically pleasing fading effect as the trail progresses. I'm also ensuring that line segments are only drawn between visible trail points.

**Refining Trail Fading**

My current focus is on enhancing the visual appeal of the pendulum's trail. I'm adjusting the hue cycling and alpha fading mechanisms to create a more fluid, vibrant, and aesthetically pleasing fading effect as the trail progresses. I'm also ensuring that line segments are only drawn between visible trail points.

**Refining Trail Fading**

My current focus is on enhancing the visual appeal of the pendulum's trail. I'm adjusting the hue cycling and alpha fading mechanisms to create a more fluid, vibrant, and aesthetically pleasing fading effect as the trail progresses. I'm also ensuring that line segments are only drawn between visible trail points.

write index.html (10809 bytes)
toolResult

Successfully wrote 10809 bytes to index.html

assistant

Here is the `index.html` file containing the double pendulum simulation: ```html <!DOCTYPE html> <html> <head> <title>Double Pendulum Simulation</title> <style> body { margin: 0; overflow: hidden; background-color: #000; display: flex; justify-content: center; align-items: center; min-height: 100vh; font-family: sans-serif; color: #fff; } canvas { border: 1px solid #333; /* Slightly softer border */ background-color: #111; /* Dark background for canvas */ display: block; /* Ensure canvas takes up available space and removes extra space below */ } h1 { position: absolute; top: 20px; left: 50%; transform: translateX(-50%); margin: 0; font-size: 2em; z-index: 10; text-shadow: 0 0 10px #fff, 0 0 20px #0ff, 0 0 30px #0ff; } </style> </head> <body> <h1>Chaotic Double Pendulum</h1> <canvas id="pendulumCanvas"></canvas> <script> const canvas = document.getElementById('pendulumCanvas'); const ctx = canvas.getContext('2d'); // Adjust canvas size to fill the window function resizeCanvas() { canvas.width = window.innerWidth; canvas.height = window.innerHeight; } window.addEventListener('resize', resizeCanvas); resizeCanvas(); // Initial call // --- Physics Parameters --- const g = 0.5; // Gravity (adjust for speed) const L1 = 100; // Length of pendulum 1 (pixels) const L2 = 100; // Length of pendulum 2 (pixels) const M1 = 1.0; // Mass of pendulum 1 (arbitrary units) const M2 = 1.0; // Mass of pendulum 2 (arbitrary units) const dt = 0.02; // Time step for integration (smaller for more accuracy, larger for speed) // --- Pendulum State Variables --- // Angles measured from the vertical. // Using radians. PI = 180 degrees. let theta1 = Math.PI * 0.8; // Initial angle of pendulum 1 let omega1 = 0.0; // Initial angular velocity of pendulum 1 let theta2 = Math.PI * 0.9; // Initial angle of pendulum 2 let omega2 = 0.0; // Initial angular velocity of pendulum 2 // --- Screen/Drawing Parameters --- // Origin point for the pendulum (center of the canvas) let originX = canvas.width / 2; let originY = canvas.height / 2; // Trail data: array of {x, y, hue, alpha} objects const trail = []; const MAX_TRAIL_POINTS = 600; // Maximum number of points to store in the trail const TRAIL_FADE_RATE = 0.006; // How much alpha decreases per frame per point // Function to calculate angular accelerations (d_omega/dt) using the derived equations of motion function getAccelerations(th1, om1, th2, om2) { const delta = th1 - th2; const cos_delta = Math.cos(delta); const sin_delta = Math.sin(delta); const cos_2delta = Math.cos(2 * delta); const sin_th1 = Math.sin(th1); const sin_th1_2th2 = Math.sin(th1 - 2 * th2); const cos_th1 = Math.cos(th1); // Denominator for both accelerations (simplified since L1=L2) // It can be zero if 2*M1 + M2 = M2*cos(2*delta), which is rare. const common_den = L1 * (2 * M1 + M2 - M2 * cos_2delta); // Calculate alpha1 (angular acceleration of pendulum 1) let alpha1_num = -g * (2 * M1 + M2) * sin_th1; alpha1_num -= M2 * g * sin_th1_2th2; alpha1_num -= 2 * sin_delta * M2 * (om2 * om2 * L2 + om1 * om1 * L1 * cos_delta); const alpha1 = alpha1_num / common_den; // Calculate alpha2 (angular acceleration of pendulum 2) let alpha2_num = 2 * sin_delta; alpha2_num *= (om1 * om1 * L1 * (M1 + M2)); // Term 1 alpha2_num += g * (M1 + M2) * cos_th1; // Term 2 alpha2_num += om2 * om2 * L2 * M2 * cos_delta; // Term 3 const alpha2_den = L2 * (2 * M1 + M2 - M2 * cos_2delta); // Same as common_den if L1=L2 const alpha2 = alpha2_num / alpha2_den; return [alpha1, alpha2]; } // RK4 (Runge-Kutta 4th order) integration to update pendulum state function updatePhysics() { // Current state as an array [theta1, omega1, theta2, omega2] const state = [theta1, omega1, theta2, omega2]; // --- RK4 Step 1 (k1) --- const [alpha1_k1, alpha2_k1] = getAccelerations(state[0], state[1], state[2], state[3]); const k1 = [state[1], alpha1_k1, state[3], alpha2_k1]; // [d(theta1)/dt, d(omega1)/dt, d(theta2)/dt, d(omega2)/dt] // --- RK4 Step 2 (k2) --- const state_k2 = [ state[0] + k1[0] * dt / 2, state[1] + k1[1] * dt / 2, state[2] + k1[2] * dt / 2, state[3] + k1[3] * dt / 2 ]; const [alpha1_k2, alpha2_k2] = getAccelerations(state_k2[0], state_k2[1], state_k2[2], state_k2[3]); const k2 = [state_k2[1], alpha1_k2, state_k2[3], alpha2_k2]; // --- RK4 Step 3 (k3) --- const state_k3 = [ state[0] + k2[0] * dt / 2, state[1] + k2[1] * dt / 2, state[2] + k2[2] * dt / 2, state[3] + k2[3] * dt / 2 ]; const [alpha1_k3, alpha2_k3] = getAccelerations(state_k3[0], state_k3[1], state_k3[2], state_k3[3]); const k3 = [state_k3[1], alpha1_k3, state_k3[3], alpha2_k3]; // --- RK4 Step 4 (k4) --- const state_k4 = [ state[0] + k3[0] * dt, state[1] + k3[1] * dt, state[2] + k3[2] * dt, state[3] + k3[3] * dt ]; const [alpha1_k4, alpha2_k4] = getAccelerations(state_k4[0], state_k4[1], state_k4[2], state_k4[3]); const k4 = [state_k4[1], alpha1_k4, state_k4[3], alpha2_k4]; // --- Update state using weighted average of k1, k2, k3, k4 --- // The derivatives returned by getAccelerations are angular velocities and accelerations. // k1-k4 are [d(theta)/dt, d(omega)/dt, d(theta)/dt, d(omega)/dt] for the two pendulums. // We update theta using omega (which is d(theta)/dt) and omega using alpha (which is d(omega)/dt). theta1 += (k1[0] + 2 * k2[0] + 2 * k3[0] + k4[0]) * dt / 6; omega1 += (k1[1] + 2 * k2[1] + 2 * k3[1] + k4[1]) * dt / 6; theta2 += (k1[2] + 2 * k2[2] + 2 * k3[2] + k4[2]) * dt / 6; omega2 += (k1[3] + 2 * k2[3] + 2 * k3[3] + k4[3]) * dt / 6; // --- Trail Management --- // Calculate Cartesian coordinates of the second pendulum's tip // Note: Angles are from the vertical. cos for y, sin for x. const x2 = originX + L2 * Math.sin(theta2); const y2 = originY + L2 * Math.cos(theta2); // Canvas Y increases downwards // Add current tip position to trail // Hue cycles through 360 degrees. Adjust multiplier for speed of color change. const hue = (trail.length * 1.5) % 360; trail.push({x: x2, y: y2, hue: hue, alpha: 1.0}); // Add new point with full alpha // Limit trail length by removing the oldest point if necessary if (trail.length > MAX_TRAIL_POINTS) { trail.shift(); // Remove the first element (oldest) } } // Function to draw the pendulum and its trail on the canvas function draw() { // Clear the canvas with a dark background ctx.fillStyle = '#111'; ctx.fillRect(0, 0, canvas.width, canvas.height); // --- Draw the Fading Trail --- ctx.lineWidth = 2; for (let i = 0; i < trail.length; i++) { const point = trail[i]; point.alpha -= TRAIL_FADE_RATE; // Decrease alpha for older points // Only draw if the point is still visible (alpha > 0) if (point.alpha > 0) { let prevPoint = null; if (i > 0) { prevPoint = trail[i-1]; } // Draw line segments connecting visible points. if (prevPoint && prevPoint.alpha > 0) { ctx.strokeStyle = `hsl(${point.hue}, 100%, 50%, ${point.alpha})`; ctx.beginPath(); ctx.moveTo(prevPoint.x, prevPoint.y); ctx.lineTo(point.x, point.y); ctx.stroke(); } } } // --- Calculate Pendulum Arm Endpoints --- // First arm goes from origin to (x1, y1) const x1 = originX + L1 * Math.sin(theta1); const y1 = originY + L1 * Math.cos(theta1); // Canvas Y increases downwards // Second arm goes from (x1, y1) to (x2, y2) const x2 = x1 + L2 * Math.sin(theta2); const y2 = y1 + L2 * Math.cos(theta2); // --- Draw Pendulum Arms --- ctx.strokeStyle = '#fff'; // White arms ctx.lineWidth = 3; // Arm 1 (from origin to mass 1) ctx.beginPath(); ctx.moveTo(originX, originY); ctx.lineTo(x1, y1); ctx.stroke(); // Arm 2 (from mass 1 to mass 2) ctx.beginPath(); ctx.moveTo(x1, y1); ctx.lineTo(x2, y2); ctx.stroke(); // --- Draw Masses (Circles at ends of arms) --- const massRadius = 7; ctx.fillStyle = '#ff4500'; // Orange-red for mass 1 ctx.beginPath(); ctx.arc(x1, y1, massRadius, 0, Math.PI * 2); ctx.fill(); ctx.fillStyle = '#1e90ff'; // Dodger blue for mass 2 ctx.beginPath(); ctx.arc(x2, y2, massRadius, 0, Math.PI * 2); ctx.fill(); // --- Draw Origin Point (optional, for reference) --- ctx.fillStyle = '#ffff00'; // Yellow for origin ctx.beginPath(); ctx.arc(originX, originY, 5, 0, Math.PI * 2); ctx.fill(); } // --- Animation Loop --- function animate() { originX = canvas.width / 2; // Update origin if canvas resized originY = canvas.height / 2; updatePhysics(); draw(); requestAnimationFrame(animate); // Request the next frame } // Start the animation animate(); </script> </body> </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

modelgemini-2.5-flash-lite
vendorGoogle
harnesspi-agent
generated2026-07-22

EVALUATION

2/5 working

Double pendulum animates smoothly but trail is nearly invisible, only a tiny faint teal streak visible.

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 motion0.6%
on drag0.6%
on wheel0.6%
on enter + space0.6%
on arrow keys0.6%
on w/a/s/d0.3%
frame spread16.9 / 255
console errors0
js errors none

METRICS

runtime58.5s
tokens in18k
tokens out19k
cached0
cost / run$0.0095