cd /news/ai-agents/i-made-spider-man-swing-without-anim… · home › topics › ai-agents › article
[ARTICLE · art-146489] src=dev.to ↗ pub= topic=ai-agents verified=true sentiment=↑ positive

I Made Spider-Man Swing Without Animating a Single Frame

A developer built a browser-based Spider-Man swinging effect that renders entirely procedurally, with no pre-rendered animation, sprite sheets, or video files. A rule-based Python control loop running at 30 Hz generates physics parameters — rope length, swing angle, and release timing — and streams them to a canvas renderer over WebSocket, producing a 60 FPS swing. The agent uses an observe-decide-act architecture inspired by AI agent designs but does not use machine learning or an LLM.

by read5 min views2 publishedOct 7, 2026

I built a Spider-Man swing effect that runs entirely in the browser, with no pre-rendered animation, no sprite sheets, and no video files. Instead, a small AI agent loop generates motion parameters in real time. Here’s how it works, with runnable Python code you can adapt.

Animation is expensive. Traditional 2D or 3D animation requires keyframes, interpolation curves, and often a rendering pipeline. For a simple web demo, that’s overkill. I wanted Spider-Man to swing across a city skyline using only procedural motion driven by an agent. The agent doesn’t generate pixels; it generates physics parameters (rope length, swing angle, release timing) that a canvas renderer uses to draw a stick-figure Spider-Man.

The result: a 60 FPS swing that feels dynamic, with zero pre-baked frames. The agent runs in Python (using a lightweight loop) and communicates with the browser via WebSocket. This post explains the problem, the solution, and the implementation.

Swinging is a physics problem: a pendulum with a changing pivot point. You could hardcode a sine wave, but it looks robotic. You could use a full physics engine, but that adds dependencies and complexity. I wanted an agent that decides when to release the web and where to attach the next one, based on the current state. The agent must:

This is a classic control loop. The challenge is making it robust and fast enough for real-time.

I built a simple agent loop in Python that runs at 30 Hz. It uses a state machine with explicit termination conditions:

The agent doesn’t use machine learning; it’s a rule-based controller. But it’s an “agent” in the sense of perceiving, deciding, and acting in a loop. I call it an “agent-style” loop because it’s inspired by AI agent architectures (observe-decide-act) but doesn’t use an LLM.

This code runs a WebSocket server that sends commands to the browser. It uses websockets and asyncio. Install with pip install websockets.

import asyncio
import json
import math
import websockets

SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
GRAVITY = 0.5
SWING_SPEED = 0.1
MAX_ITERATIONS = 500

ANCHORS = [(100, 100), (250, 80), (400, 120), (550, 90), (700, 110)]

class SwingAgent:
    def __init__(self):
        self.x = 50.0
        self.y = 300.0
        self.vx = 5.0
        self.vy = 0.0
        self.anchor = None
        self.angle = 0.0
        self.angular_velocity = 0.0
        self.iterations = 0

    def observe(self, state):
        self.x = state['x']
        self.y = state['y']
        self.vx = state['vx']
        self.vy = state['vy']

    def decide(self):
        if self.anchor is None:
            best = None
            best_dist = float('inf')
            for ax, ay in ANCHORS:
                if ax > self.x and ax - self.x < 300:
                    dist = math.hypot(ax - self.x, ay - self.y)
                    if dist < best_dist:
                        best_dist = dist
                        best = (ax, ay)
            if best:
                self.anchor = best
                dx = self.x - best[0]
                dy = self.y - best[1]
                self.angle = math.atan2(dx, dy)
                self.angular_velocity = 0.0
                return {'action': 'attach', 'anchor': best}
        else:
            length = math.hypot(self.x - self.anchor[0], self.y - self.anchor[1])
            if length < 1:
                length = 1
            alpha = -GRAVITY / length * math.sin(self.angle)
            self.angular_velocity += alpha * 0.1
            self.angle += self.angular_velocity * 0.1
            if abs(self.angle) < 0.1 and self.angular_velocity > 0:
                self.anchor = None
                return {'action': 'release', 'vx': self.vx, 'vy': self.vy}
            else:
                new_x = self.anchor[0] + length * math.sin(self.angle)
                new_y = self.anchor[1] + length * math.cos(self.angle)
                self.vx = (new_x - self.x) / 0.1
                self.vy = (new_y - self.y) / 0.1
                self.x = new_x
                self.y = new_y
                return {'action': 'swing', 'x': self.x, 'y': self.y}
        return {'action': 'none'}

    def should_terminate(self):
        return self.x > SCREEN_WIDTH or self.iterations > MAX_ITERATIONS

async def handler(websocket):
    agent = SwingAgent()
    async for message in websocket:
        state = json.loads(message)
        agent.observe(state)
        if agent.should_terminate():
            await websocket.send(json.dumps({'action': 'stop'}))
            break
        command = agent.decide()
        agent.iterations += 1
        await websocket.send(json.dumps(command))

async def main():
    async with websockets.serve(handler, "localhost", 8765):
        await asyncio.Future()  # run forever

if __name__ == "__main__":
    asyncio.run(main())

The client renders a stick figure and sends state updates. It uses the Canvas API.

<!DOCTYPE html>
<html>
<head><title>Spider-Man Swing</title></head>
<body>
<canvas id="canvas" width="800" height="600"></canvas>
<script>
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const ws = new WebSocket('ws://localhost:8765');

let state = { x: 50, y: 300, vx: 5, vy: 0 };
let anchor = null;

ws.onmessage = (event) => {
    const cmd = JSON.parse(event.data);
    if (cmd.action === 'attach') {
        anchor = cmd.anchor;
    } else if (cmd.action === 'release') {
        anchor = null;
    } else if (cmd.action === 'swing') {
        state.x = cmd.x;
        state.y = cmd.y;
    } else if (cmd.action === 'stop') {
        ws.close();
    }
};

function draw() {
    ctx.clearRect(0, 0, 800, 600);
    // Draw buildings
    ctx.fillStyle = '#333';
    [[100, 100], [250, 80], [400, 120], [550, 90], [700, 110]].forEach(([x, y]) => {
        ctx.fillRect(x - 20, y, 40, 600 - y);
    });
    // Draw web
    if (anchor) {
        ctx.beginPath();
        ctx.moveTo(state.x, state.y);
        ctx.lineTo(anchor[0], anchor[1]);
        ctx.strokeStyle = 'white';
        ctx.stroke();
    }
    // Draw Spider-Man (stick figure)
    ctx.beginPath();
    ctx.arc(state.x, state.y, 10, 0, Math.PI * 2);
    ctx.fillStyle = 'red';
    ctx.fill();
    ctx.beginPath();
    ctx.moveTo(state.x, state.y + 10);
    ctx.lineTo(state.x, state.y + 30);
    ctx.strokeStyle = 'red';
    ctx.stroke();
    // Send state
    ws.send(JSON.stringify(state));
    requestAnimationFrame(draw);
}

draw();
</script>
</body>
</html>

server.py and run python server.py. index.html in a browser. Security warning: This code uses eval? No. It doesn’t. But if you extend it to execute arbitrary code from the browser, never use eval or exec on untrusted input. Always validate and sanitize. The WebSocket server here only accepts JSON with numeric fields; it does not execute code.

x > SCREEN_WIDTH and a max iteration cap. This approach is extensible: you could swap the rule-based agent for a reinforcement learning policy, or add more complex environments. But even this minimal version shows how an agent-style loop can create dynamic, interactive experiences without traditional animation.

── more in #ai-agents 4 stories · sorted by recency
── more on @spider-man 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/i-made-spider-man-sw…] indexed:0 read:5min 2026-10-07 · —