Where ideas percolate and thoughts brew

The Motivation Trap

About This Sketch

Thirty-eight points of light populate the canvas, most of them still — barely visible, pulsing faintly. Every few moments, one of them activates. It receives a velocity and begins to move, steering through a noise field, leaving a glowing trail. For a time it is bright and directional, tracing a path through the space. Then it settles, dormant again, in a new place.

The waiting agents are dim. The moving ones illuminate. The canvas holds both at once: the quiet field of things not yet started, and the bright arcs of things in motion. Motion creates momentum. Waiting creates more waiting.

This sketch accompanies the post The Motivation Trap.

Algorithm

38 agents are scattered across the canvas, each beginning in a DORMANT state — dim, gently pulsing in place, waiting. Each dormant agent has a small probability (0.4% per frame) of transitioning to ACTIVE. When it does, it receives an initial random velocity and begins navigating the canvas using a noise field for steering. It leaves a fading luminous trail behind it. After a variable lifetime, it returns to DORMANT at its new position — changed by having moved. The visual contrast is the point: still agents are barely visible; moving agents are bright and leave history behind them. The canvas is populated by a mix at any given time, with the occasional spontaneous activation creating bursts of brightness against the quiet field. This sketch accompanies the blog post "The Motivation Trap" and visualizes its central claim: agents that wait remain dim and static; agents that start moving build their own momentum, illuminate the space, and arrive somewhere new.

Pseudocode

SETUP:
  Create 38 agents at random positions
  Each agent: position, velocity, state (dormant), phase offset, size, trail

DRAW (every frame):
  Get current theme colors
  Lightly clear background (trail persistence)
  Increment time

  For each agent:
    If DORMANT:
      With small probability (0.004): transition to ACTIVE, assign random velocity and lifetime
      Draw: dim pulsing circle at fixed position

    If ACTIVE:
      Sample noise field at agent's position to get steering direction
      Lerp velocity toward noise direction (smooth steering)
      Advance position, bounce off walls
      Record position in trail buffer (max 20 points)
      Compute fade-out as lifetime approaches end

      Draw trail: each point fades with distance from head
      Draw agent: bright glowing circle with halo

      If lifetime expired: return to DORMANT at current position, clear trail

Source Code

let sketch = function(p) {
    // Agents exist in two states: DORMANT (still, dim, waiting) and ACTIVE (moving, bright, building trails).
    // Each dormant agent has a small random chance each frame to "start".
    // Once started, it moves freely with noise-driven velocity and leaves a fading trail.
    // After its run, it settles back to dormant at wherever it landed — changed by the movement.
    // Represents: action generates momentum; waiting generates more waiting.

    let agents = [];
    let time = 0;

    function makeAgent(x, y) {
        return {
            x: x, y: y,
            vx: 0, vy: 0,
            state: 'dormant',
            age: 0,
            lifetime: 0,
            trail: [],
            ph: p.random(p.TWO_PI),
            size: p.random(2.6, 4.2),
            dormantX: x,
            dormantY: y
        };
    }

    p.setup = function() {
        p.createCanvas(400, 300);
        p.colorMode(p.RGB);
        for (let i = 0; i < 38; i++) {
            agents.push(makeAgent(p.random(28, 372), p.random(22, 278)));
        }
    };

    p.draw = function() {
        const colors = getThemeColors();

        p.noStroke();
        p.fill(...colors.bg, 30);
        p.rect(0, 0, 400, 300);

        time += 0.013;

        for (let ag of agents) {
            ag.age++;

            if (ag.state === 'dormant') {
                if (p.random() < 0.004) {
                    ag.state = 'active';
                    ag.lifetime = p.floor(p.random(130, 240));
                    ag.age = 0;
                    let ang = p.random(p.TWO_PI);
                    let speed = p.random(1.4, 2.6);
                    ag.vx = Math.cos(ang) * speed;
                    ag.vy = Math.sin(ang) * speed;
                    ag.trail = [];
                }

                let pulse = 0.72 + 0.28 * Math.sin(time * 1.6 + ag.ph);
                p.noStroke();
                p.fill(...colors.accent3, 50 * pulse);
                p.circle(ag.dormantX, ag.dormantY, ag.size * 1.5 * pulse);

            } else {
                let nx = p.noise(ag.x * 0.006 + time * 0.48, ag.y * 0.006 + time * 0.38);
                let ang = nx * p.TWO_PI * 2.1;
                ag.vx = p.lerp(ag.vx, Math.cos(ang) * 2.6, 0.05);
                ag.vy = p.lerp(ag.vy, Math.sin(ang) * 2.6, 0.05);

                ag.x += ag.vx;
                ag.y += ag.vy;

                if (ag.x < 10) { ag.x = 10; ag.vx = Math.abs(ag.vx); }
                if (ag.x > 390) { ag.x = 390; ag.vx = -Math.abs(ag.vx); }
                if (ag.y < 10) { ag.y = 10; ag.vy = Math.abs(ag.vy); }
                if (ag.y > 290) { ag.y = 290; ag.vy = -Math.abs(ag.vy); }

                ag.trail.push({ x: ag.x, y: ag.y });
                if (ag.trail.length > 20) ag.trail.shift();

                let progress = ag.age / ag.lifetime;
                let fadeOut = progress > 0.72 ? 1 - (progress - 0.72) / 0.28 : 1;

                p.noStroke();
                for (let t = 0; t < ag.trail.length; t++) {
                    let tfrac = t / ag.trail.length;
                    let tr = ag.trail[t];
                    let alpha = tfrac * tfrac * 110 * fadeOut;
                    p.fill(...colors.accent1, alpha);
                    p.circle(tr.x, tr.y, ag.size * tfrac * 1.3);
                }

                let brightPulse = 0.87 + 0.13 * Math.sin(time * 3.8 + ag.ph);
                p.fill(...colors.accent1, 50 * fadeOut);
                p.circle(ag.x, ag.y, ag.size * 5.5 * brightPulse);
                p.fill(...colors.accent2, 215 * brightPulse * fadeOut);
                p.circle(ag.x, ag.y, ag.size * 2.1 * brightPulse);

                if (ag.age >= ag.lifetime) {
                    ag.state = 'dormant';
                    ag.dormantX = ag.x;
                    ag.dormantY = ag.y;
                    ag.trail = [];
                }
            }
        }

        p.noStroke();
        p.fill(...colors.accent3, 75);
        p.textAlign(p.CENTER);
        p.textSize(9);
        p.text('motion creates momentum', 200, 294);
    };
};