Where ideas percolate and thoughts brew

The Simplicity Trap

About This Sketch

A living visualization of the simplicity trap. A complex, interconnected network of points flows organically in the background—representing reality's true complexity. Overlaid on this are clean geometric shapes with reductive labels like "X → Y" and "ONE CAUSE"—representing our simple explanations.

The shapes are clear and easy to understand. The network beneath is messy and hard to track. But watch the "complexity captured" metric: our simple explanations typically capture only 20-40% of what's actually happening. The rest is discarded, ignored, or explained away.

Simple explanations feel like understanding. But this sketch makes visible what they cost—the 60-80% of reality we pretend doesn't exist because it doesn't fit our clean framework.

Algorithm

This sketch visualizes the gap between complex reality and simple explanations. The gray network represents underlying complexity—50 interconnected points moving organically according to Perlin noise fields. Each point is connected to its four nearest neighbors, creating a dynamic web of relationships that shift over time. Overlaid on this complexity are three simple geometric shapes (circle, square, line) labeled with reductive explanations like "X → Y" and "ONE CAUSE." These shapes remain static while the complexity flows around and beneath them. The sketch calculates what percentage of the complex reality is "captured" by the simple explanations—typically around 20-40%. This quantifies the gap between what we think we understand (the simple shapes) and what's actually happening (the complex network). This accompanies the blog post "The Simplicity Trap" and explores how we mistake the clarity of our simple explanations for understanding of complex phenomena.

Pseudocode

SETUP:
  Create 50 complex points at random positions
  Give each point velocity and noise offset
  Connect each point to its 4 nearest neighbors
  Create 3 simple geometric overlays (circle, square, line)
  Initialize each overlay with a reductive label

DRAW (every frame):
  Get current theme colors
  Clear background

  For each complex point:
    Update position using Perlin noise field
    Apply velocity and damping
    Wrap around canvas edges

  Draw connections between nearby points (< 80px apart)
  Draw all complex points as small circles

  Overlay simple geometric shapes with labels
  Fade in overlays gradually

  Calculate how many complex points fall within simple shapes
  Display percentage of "complexity captured"

  Show alternating text about the trap

Source Code

let sketch = function(p) {
    // Visualization: The Simplicity Trap
    // Shows a complex underlying reality (organic, flowing, interconnected)
    // Overlaid with simple geometric shapes that attempt to "explain" it
    // The simple shapes are clear and clean but miss most of the complexity

    let complexity = []; // Underlying complex reality
    let simplifications = []; // Simple explanations overlaid
    let time = 0;

    class ComplexPoint {
        constructor(x, y) {
            this.x = x;
            this.y = y;
            this.vx = p.random(-0.5, 0.5);
            this.vy = p.random(-0.5, 0.5);
            this.offset = p.random(1000);
            this.connections = [];
        }

        update() {
            // Complex, organic movement
            let noiseX = p.noise(this.x * 0.01, this.y * 0.01, time * 0.01 + this.offset);
            let noiseY = p.noise(this.x * 0.01 + 100, this.y * 0.01 + 100, time * 0.01 + this.offset);

            this.vx += (noiseX - 0.5) * 0.1;
            this.vy += (noiseY - 0.5) * 0.1;

            // Damping
            this.vx *= 0.95;
            this.vy *= 0.95;

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

            // Wrap around
            if (this.x < 0) this.x = 400;
            if (this.x > 400) this.x = 0;
            if (this.y < 0) this.y = 300;
            if (this.y > 300) this.y = 0;
        }

        display(colors) {
            p.noStroke();
            p.fill(...colors.accent3, 100);
            p.circle(this.x, this.y, 3);
        }

        drawConnections(colors) {
            for (let other of this.connections) {
                let d = p.dist(this.x, this.y, other.x, other.y);
                if (d < 80) {
                    let alpha = p.map(d, 0, 80, 60, 0);
                    p.stroke(...colors.accent3, alpha);
                    p.strokeWeight(0.5);
                    p.line(this.x, this.y, other.x, other.y);
                }
            }
        }
    }

    class Simplification {
        constructor(type, x, y, size) {
            this.type = type; // 'circle', 'square', 'line'
            this.x = x;
            this.y = y;
            this.size = size;
            this.alpha = 0;
            this.targetAlpha = 180;
            this.label = this.generateLabel();
        }

        generateLabel() {
            const labels = ['X → Y', 'SIMPLE', 'A = B', 'ONE CAUSE', 'CLEAR'];
            return p.random(labels);
        }

        update() {
            // Fade in
            if (this.alpha < this.targetAlpha) {
                this.alpha += 3;
            }
        }

        display(colors) {
            p.noFill();
            p.stroke(...colors.accent2, this.alpha);
            p.strokeWeight(2);

            if (this.type === 'circle') {
                p.circle(this.x, this.y, this.size);
            } else if (this.type === 'square') {
                p.rectMode(p.CENTER);
                p.rect(this.x, this.y, this.size, this.size);
            } else if (this.type === 'line') {
                p.line(this.x - this.size/2, this.y, this.x + this.size/2, this.y);
            }

            // Label
            p.noStroke();
            p.fill(...colors.accent2, this.alpha);
            p.textAlign(p.CENTER, p.CENTER);
            p.textSize(9);
            p.text(this.label, this.x, this.y);
        }
    }

    p.setup = function() {
        p.createCanvas(400, 300);

        // Create complex underlying reality
        for (let i = 0; i < 50; i++) {
            complexity.push(new ComplexPoint(
                p.random(400),
                p.random(300)
            ));
        }

        // Set up connections
        for (let i = 0; i < complexity.length; i++) {
            // Connect to nearest neighbors
            let distances = [];
            for (let j = 0; j < complexity.length; j++) {
                if (i !== j) {
                    let d = p.dist(complexity[i].x, complexity[i].y, complexity[j].x, complexity[j].y);
                    distances.push({point: complexity[j], dist: d});
                }
            }
            distances.sort((a, b) => a.dist - b.dist);
            complexity[i].connections = distances.slice(0, 4).map(d => d.point);
        }

        // Create simple explanations that overlay the complexity
        simplifications.push(new Simplification('circle', 200, 100, 80));
        simplifications.push(new Simplification('square', 100, 200, 60));
        simplifications.push(new Simplification('line', 300, 200, 100));
    };

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

        time++;

        // Title
        p.fill(...colors.accent3, 200);
        p.noStroke();
        p.textAlign(p.LEFT);
        p.textSize(11);
        p.text('The Simplicity Trap', 15, 25);

        // Update and display complex reality (background layer)
        for (let point of complexity) {
            point.update();
        }

        // Draw connections first (behind)
        for (let point of complexity) {
            point.drawConnections(colors);
        }

        // Draw points
        for (let point of complexity) {
            point.display(colors);
        }

        // Overlay simple explanations
        for (let simp of simplifications) {
            simp.update();
            simp.display(colors);
        }

        // Legend
        p.textAlign(p.LEFT);
        p.textSize(7);
        p.fill(...colors.accent3, 150);
        p.text('Gray network = Complex reality', 15, 45);

        p.fill(...colors.accent2, 150);
        p.text('Geometric shapes = Simple explanations', 15, 55);

        // Bottom text
        p.textAlign(p.CENTER);
        p.textSize(7);
        p.fill(...colors.accent3, 180);

        if (time < 240) {
            p.text('Reality is complex, interconnected, context-dependent.', 200, 275);
            p.text('Our explanations are clean, geometric, certain—and wrong.', 200, 285);
        } else if (time < 480) {
            p.text('Simple explanations feel like understanding.', 200, 275);
            p.text('But they discard essential complexity.', 200, 285);
        } else {
            time = 0; // Reset
        }

        // Show how much complexity is "missed"
        let captured = 0;
        let total = complexity.length;

        for (let point of complexity) {
            for (let simp of simplifications) {
                let d = p.dist(point.x, point.y, simp.x, simp.y);
                if (simp.type === 'circle' && d < simp.size/2) {
                    captured++;
                    break;
                } else if (simp.type === 'square' &&
                          Math.abs(point.x - simp.x) < simp.size/2 &&
                          Math.abs(point.y - simp.y) < simp.size/2) {
                    captured++;
                    break;
                }
            }
        }

        p.textAlign(p.RIGHT);
        p.textSize(7);
        p.fill(...colors.accent2, 180);
        let percent = Math.round((captured / total) * 100);
        p.text(`Complexity captured: ${percent}%`, 385, 285);
    };
};