Where ideas percolate and thoughts brew

The Intelligence Trap

About This Sketch

Ninety particles orbit a fixed central point, pulled inward by gravity while Perlin noise creates the appearance of complex, searching motion. Every path spirals toward the same conclusion. The central dot pulsesβ€”the predetermined answer around which all the "thinking" organizes itself.

A visualization of motivated reasoning: the feeling of thorough inquiry, the reality of a predetermined destination.

Algorithm

Ninety particles are seeded at random positions around a central fixed point (the attractor). Each frame, every particle experiences two forces: a gravitational pull toward the attractor (stronger when further away) and a small turbulence force driven by Perlin noise. The noise gives motion the appearance of complex, searching exploration. But the attractor always winsβ€” every particle spirals inward eventually and is reset to the outer ring to begin again. The central dot pulses gently, representing the predetermined conclusion that organizes all the apparently dynamic thinking around it. This sketch accompanies the blog post "The Intelligence Trap" and visualizes motivated reasoning: how sophisticated thinking can feel like genuine inquiry while every consideration gets funneled toward a conclusion that was never seriously in doubt.

Pseudocode

SETUP:
  Initialize canvas (400x300)
  Place attractor at canvas center
  Seed 90 particles at random radii (40–190px) around attractor
  Each particle has: position, velocity, Perlin noise offsets, age counter

DRAW (every frame):
  Get current theme colors
  Apply semi-transparent background overlay (creates motion trails)
  Increment global time counter

  FOR each particle:
    Compute vector from particle to attractor
    Apply pull force (stronger at distance, weaker near center)
    Apply Perlin noise turbulence (creates complex-seeming motion)
    Apply velocity damping (0.96x per frame)
    Update position
    Decrement age

    IF particle reaches center OR age expires:
      Reset particle to random point on outer ring
      Reset age

    Draw particle with theme-appropriate color
      (outer ring: accent1, near center: accent2)

  Draw pulsing ring and dot at attractor position
  Render caption text

Source Code

let sketch = function(p) {
    // Motivated reasoning visualization
    // Particles swirl dynamically but are all drawn toward a fixed central attractor
    // Represents how sophisticated thinking can feel like exploration
    // while every path leads back to the same predetermined conclusion

    let particles = [];
    let attractor;
    let time = 0;

    p.setup = function() {
        p.createCanvas(400, 300);
        p.colorMode(p.RGB);
        attractor = { x: 200, y: 148 };

        for (let i = 0; i < 90; i++) {
            let angle = p.random(p.TWO_PI);
            let radius = p.random(40, 190);
            particles.push({
                x: attractor.x + p.cos(angle) * radius,
                y: attractor.y + p.sin(angle) * radius,
                vx: p.random(-0.8, 0.8),
                vy: p.random(-0.8, 0.8),
                age: p.random(80, 260),
                maxAge: p.random(80, 260),
                nx: p.random(1000),
                ny: p.random(1000)
            });
        }
    };

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

        // Soft fade for trail effect
        p.noStroke();
        p.fill(...colors.bg, 28);
        p.rect(0, 0, 400, 300);

        time += 0.009;

        for (let i = 0; i < particles.length; i++) {
            let pt = particles[i];

            // Pull force toward attractor
            let dx = attractor.x - pt.x;
            let dy = attractor.y - pt.y;
            let dist = p.sqrt(dx * dx + dy * dy) + 0.001;
            let pullStrength = p.map(dist, 0, 200, 0.55, 0.04);
            pt.vx += (dx / dist) * pullStrength;
            pt.vy += (dy / dist) * pullStrength;

            // Perlin noise turbulence β€” "thinking," but still drawn inward
            let noiseAngle = p.noise(pt.nx + time * 0.4, pt.ny + time * 0.35) * p.TWO_PI * 2;
            pt.vx += p.cos(noiseAngle) * 0.12;
            pt.vy += p.sin(noiseAngle) * 0.12;

            // Gentle damping
            pt.vx *= 0.96;
            pt.vy *= 0.96;

            pt.x += pt.vx;
            pt.y += pt.vy;
            pt.age--;

            // Reset when too close to center or age runs out
            if (dist < 6 || pt.age <= 0) {
                let angle = p.random(p.TWO_PI);
                let radius = p.random(90, 190);
                pt.x = attractor.x + p.cos(angle) * radius;
                pt.y = attractor.y + p.sin(angle) * radius;
                pt.vx = p.random(-0.6, 0.6);
                pt.vy = p.random(-0.6, 0.6);
                pt.age = pt.maxAge;
            }

            // Color: further from center = cooler accent, closer = warmer
            let alpha = p.map(pt.age, 0, 40, 0, 200);
            if (pt.age > 40) alpha = 200;

            if (dist > 80) {
                p.fill(...colors.accent1, alpha * 0.85);
            } else {
                p.fill(...colors.accent2, alpha);
            }
            p.noStroke();
            p.circle(pt.x, pt.y, 2.2);
        }

        // Draw attractor β€” the fixed conclusion
        let pulse = p.sin(time * 2.5) * 4 + 13;
        p.noFill();
        p.stroke(...colors.accent2, 130);
        p.strokeWeight(1.5);
        p.circle(attractor.x, attractor.y, pulse);
        p.fill(...colors.accent2, 240);
        p.noStroke();
        p.circle(attractor.x, attractor.y, 5.5);

        // Caption
        p.fill(...colors.accent3, 110);
        p.noStroke();
        p.textAlign(p.CENTER);
        p.textSize(9);
        p.text('all roads lead to the conclusion you started with', 200, 293);
    };
};