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. 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 . python import asyncio import json import math import websockets Configuration SCREEN WIDTH = 800 SCREEN HEIGHT = 600 GRAVITY = 0.5 SWING SPEED = 0.1 MAX ITERATIONS = 500 Building anchors x, y - top of buildings 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: Find nearest anchor ahead within range 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 Initialize angle from vertical 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: Pendulum physics 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 Check release condition: at bottom and moving forward if abs self.angle < 0.1 and self.angular velocity 0: Release self.anchor = None return {'action': 'release', 'vx': self.vx, 'vy': self.vy} else: Update position from pendulum 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