{"slug": "i-made-spider-man-swing-without-animating-a-single-frame", "title": "I Made Spider-Man Swing Without Animating a Single Frame", "summary": "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.", "body_md": "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.\n\nAnimation 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.\n\nThe 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.\n\nSwinging 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:\n\nThis is a classic control loop. The challenge is making it robust and fast enough for real-time.\n\nI built a simple agent loop in Python that runs at 30 Hz. It uses a state machine with explicit termination conditions:\n\nThe 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.\n\nThis code runs a WebSocket server that sends commands to the browser. It uses `websockets` and `asyncio`. Install with `pip install websockets`.\n\n``` python\nimport asyncio\nimport json\nimport math\nimport websockets\n\n# Configuration\nSCREEN_WIDTH = 800\nSCREEN_HEIGHT = 600\nGRAVITY = 0.5\nSWING_SPEED = 0.1\nMAX_ITERATIONS = 500\n\n# Building anchors (x, y) - top of buildings\nANCHORS = [(100, 100), (250, 80), (400, 120), (550, 90), (700, 110)]\n\nclass SwingAgent:\n    def __init__(self):\n        self.x = 50.0\n        self.y = 300.0\n        self.vx = 5.0\n        self.vy = 0.0\n        self.anchor = None\n        self.angle = 0.0\n        self.angular_velocity = 0.0\n        self.iterations = 0\n\n    def observe(self, state):\n        self.x = state['x']\n        self.y = state['y']\n        self.vx = state['vx']\n        self.vy = state['vy']\n\n    def decide(self):\n        if self.anchor is None:\n            # Find nearest anchor ahead within range\n            best = None\n            best_dist = float('inf')\n            for ax, ay in ANCHORS:\n                if ax > self.x and ax - self.x < 300:\n                    dist = math.hypot(ax - self.x, ay - self.y)\n                    if dist < best_dist:\n                        best_dist = dist\n                        best = (ax, ay)\n            if best:\n                self.anchor = best\n                # Initialize angle from vertical\n                dx = self.x - best[0]\n                dy = self.y - best[1]\n                self.angle = math.atan2(dx, dy)\n                self.angular_velocity = 0.0\n                return {'action': 'attach', 'anchor': best}\n        else:\n            # Pendulum physics\n            length = math.hypot(self.x - self.anchor[0], self.y - self.anchor[1])\n            if length < 1:\n                length = 1\n            alpha = -GRAVITY / length * math.sin(self.angle)\n            self.angular_velocity += alpha * 0.1\n            self.angle += self.angular_velocity * 0.1\n            # Check release condition: at bottom and moving forward\n            if abs(self.angle) < 0.1 and self.angular_velocity > 0:\n                # Release\n                self.anchor = None\n                return {'action': 'release', 'vx': self.vx, 'vy': self.vy}\n            else:\n                # Update position from pendulum\n                new_x = self.anchor[0] + length * math.sin(self.angle)\n                new_y = self.anchor[1] + length * math.cos(self.angle)\n                self.vx = (new_x - self.x) / 0.1\n                self.vy = (new_y - self.y) / 0.1\n                self.x = new_x\n                self.y = new_y\n                return {'action': 'swing', 'x': self.x, 'y': self.y}\n        return {'action': 'none'}\n\n    def should_terminate(self):\n        return self.x > SCREEN_WIDTH or self.iterations > MAX_ITERATIONS\n\nasync def handler(websocket):\n    agent = SwingAgent()\n    async for message in websocket:\n        state = json.loads(message)\n        agent.observe(state)\n        if agent.should_terminate():\n            await websocket.send(json.dumps({'action': 'stop'}))\n            break\n        command = agent.decide()\n        agent.iterations += 1\n        await websocket.send(json.dumps(command))\n\nasync def main():\n    async with websockets.serve(handler, \"localhost\", 8765):\n        await asyncio.Future()  # run forever\n\nif __name__ == \"__main__\":\n    asyncio.run(main())\n```\n\nThe client renders a stick figure and sends state updates. It uses the Canvas API.\n\n```\n<!DOCTYPE html>\n<html>\n<head><title>Spider-Man Swing</title></head>\n<body>\n<canvas id=\"canvas\" width=\"800\" height=\"600\"></canvas>\n<script>\nconst canvas = document.getElementById('canvas');\nconst ctx = canvas.getContext('2d');\nconst ws = new WebSocket('ws://localhost:8765');\n\nlet state = { x: 50, y: 300, vx: 5, vy: 0 };\nlet anchor = null;\n\nws.onmessage = (event) => {\n    const cmd = JSON.parse(event.data);\n    if (cmd.action === 'attach') {\n        anchor = cmd.anchor;\n    } else if (cmd.action === 'release') {\n        anchor = null;\n    } else if (cmd.action === 'swing') {\n        state.x = cmd.x;\n        state.y = cmd.y;\n    } else if (cmd.action === 'stop') {\n        ws.close();\n    }\n};\n\nfunction draw() {\n    ctx.clearRect(0, 0, 800, 600);\n    // Draw buildings\n    ctx.fillStyle = '#333';\n    [[100, 100], [250, 80], [400, 120], [550, 90], [700, 110]].forEach(([x, y]) => {\n        ctx.fillRect(x - 20, y, 40, 600 - y);\n    });\n    // Draw web\n    if (anchor) {\n        ctx.beginPath();\n        ctx.moveTo(state.x, state.y);\n        ctx.lineTo(anchor[0], anchor[1]);\n        ctx.strokeStyle = 'white';\n        ctx.stroke();\n    }\n    // Draw Spider-Man (stick figure)\n    ctx.beginPath();\n    ctx.arc(state.x, state.y, 10, 0, Math.PI * 2);\n    ctx.fillStyle = 'red';\n    ctx.fill();\n    ctx.beginPath();\n    ctx.moveTo(state.x, state.y + 10);\n    ctx.lineTo(state.x, state.y + 30);\n    ctx.strokeStyle = 'red';\n    ctx.stroke();\n    // Send state\n    ws.send(JSON.stringify(state));\n    requestAnimationFrame(draw);\n}\n\ndraw();\n</script>\n</body>\n</html>\n```\n\n`server.py` and run `python server.py`.` index.html` in a browser.\n**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.\n\n`x > SCREEN_WIDTH` and a max iteration cap.\nThis 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.", "url": "https://wpnews.pro/news/i-made-spider-man-swing-without-animating-a-single-frame", "canonical_source": "https://dev.to/gu_cci_f94bedb90083e6aab4/i-made-spider-man-swing-without-animating-a-single-frame-5g78", "published_at": "2026-10-07 02:42:06+00:00", "updated_at": "2026-10-07 02:47:35.969512+00:00", "lang": "en", "topics": ["ai-agents", "developer-tools"], "entities": ["Spider-Man", "Python", "WebSocket"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/i-made-spider-man-swing-without-animating-a-single-frame", "markdown": "https://wpnews.pro/news/i-made-spider-man-swing-without-animating-a-single-frame.md", "text": "https://wpnews.pro/news/i-made-spider-man-swing-without-animating-a-single-frame.txt", "jsonld": "https://wpnews.pro/news/i-made-spider-man-swing-without-animating-a-single-frame.jsonld"}}