Where ideas percolate and thoughts brew

The Failure Fetish

About This Sketch

Visualizing two paths: the failure path (chaotic, expensive) vs. the learning path (efficient, controlled) Left side: "Learning through failure" - chaotic bouncing ball that crashes repeatedly Right side: "Learning through preparation" - smooth, careful path that navigates efficiently

This sketch accompanies the blog post "The Failure Fetish" and visualizes its core concepts through generative art.

Algorithm

Visualizing two paths: the failure path (chaotic, expensive) vs. the learning path (efficient, controlled) Left side: "Learning through failure" - chaotic bouncing ball that crashes repeatedly Right side: "Learning through preparation" - smooth, careful path that navigates efficiently This sketch was originally created as a visual companion to the blog post "The Failure Fetish" and explores its themes through generative art.

Pseudocode

SETUP:
  Initialize canvas (400x300)
  Set up drawing parameters

DRAW (every frame):
  Get current theme colors
  Clear background
  Draw generative visualization
  Update animation state

Source Code

let sketch = function(p) {

    // Visualizing two paths: the failure path (chaotic, expensive) vs. the learning path (efficient, controlled)
    // Left side: "Learning through failure" - chaotic bouncing ball that crashes repeatedly
    // Right side: "Learning through preparation" - smooth, careful path that navigates efficiently

    let failureBall;
    let learningPath = [];
    let time = 0;
    let crashes = [];

    class FailureBall {
        constructor() {
            this.reset();
        }

        reset() {
            this.x = 50;
            this.y = 50;
            this.vx = p.random(2, 4);
            this.vy = p.random(-2, 2);
            this.radius = 8;
            this.crashed = false;
            this.crashTime = 0;
        }

        update() {
            if (this.crashed) {
                // After crash, pause briefly then reset
                if (time - this.crashTime > 60) {
                    this.reset();
                }
                return;
            }

            // Move with unpredictable velocity (represents lack of planning)
            this.vx += p.random(-0.3, 0.3);
            this.vy += p.random(-0.3, 0.3);

            // Keep speed in range
            this.vx = p.constrain(this.vx, 1, 4);

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

            // Crash at boundaries or obstacles (expensive failures)
            if (this.x < 20 || this.x > 180 || this.y < 40 || this.y > 260) {
                this.crash();
            }

            // Random obstacles representing predictable problems
            if ((this.x > 80 && this.x < 100 && this.y > 120 && this.y < 140) ||
                (this.x > 60 && this.x < 80 && this.y > 180 && this.y < 200)) {
                this.crash();
            }
        }

        crash() {
            this.crashed = true;
            this.crashTime = time;
            crashes.push({x: this.x, y: this.y, time: time});

            // Keep only recent crashes
            crashes = crashes.filter(c => time - c.time < 120);
        }

        display(colors) {
            if (this.crashed) {
                // Show crash as expanding X
                let crashAge = time - this.crashTime;
                let alpha = p.map(crashAge, 0, 60, 255, 0);
                let size = p.map(crashAge, 0, 60, 5, 20);

                p.stroke(...colors.accent2, alpha);
                p.strokeWeight(2);
                p.line(this.x - size, this.y - size, this.x + size, this.y + size);
                p.line(this.x - size, this.y + size, this.x + size, this.y - size);
            } else {
                // Draw moving ball
                p.noStroke();
                p.fill(...colors.accent2, 200);
                p.ellipse(this.x, this.y, this.radius * 2);

                // Trail showing chaotic path
                p.fill(...colors.accent2, 80);
                p.ellipse(this.x - this.vx * 2, this.y - this.vy * 2, this.radius);
            }
        }
    }

    class LearningPoint {
        constructor(x, y, size) {
            this.x = x;
            this.y = y;
            this.targetX = x;
            this.targetY = y;
            this.size = size;
            this.age = 0;
        }

        update() {
            // Smooth, planned movement
            this.x += (this.targetX - this.x) * 0.1;
            this.y += (this.targetY - this.y) * 0.1;
            this.age++;
        }

        display(colors) {
            let alpha = p.min(this.age * 5, 180);
            p.noStroke();
            p.fill(...colors.accent1, alpha);
            p.ellipse(this.x, this.y, this.size);
        }
    }

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

        failureBall = new FailureBall();

        // Create smooth learning path (right side) - efficient, navigates around problems
        let pathPoints = [
            {x: 250, y: 50},
            {x: 280, y: 80},   // Careful navigation
            {x: 320, y: 90},
            {x: 340, y: 120},
            {x: 330, y: 150},  // Deliberate path around obstacles
            {x: 310, y: 180},
            {x: 340, y: 210},
            {x: 360, y: 240},
            {x: 370, y: 270}
        ];

        for (let point of pathPoints) {
            learningPath.push(new LearningPoint(point.x, point.y, 6));
        }
    };

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

        // Dividing line
        p.stroke(...colors.accent3, 60);
        p.strokeWeight(1);
        p.line(200, 0, 200, 300);

        // Labels
        p.noStroke();
        p.fill(...colors.accent3, 200);
        p.textAlign(p.CENTER, p.TOP);
        p.textSize(10);
        p.text("Learning Through", 100, 10);
        p.text("Failure", 100, 22);

        p.text("Learning Through", 300, 10);
        p.text("Preparation", 300, 22);

        // Descriptions
        p.textSize(8);
        p.fill(...colors.accent3, 140);
        p.text("Chaotic, expensive,", 100, 270);
        p.text("repeat mistakes", 100, 280);

        p.text("Efficient, deliberate,", 300, 270);
        p.text("avoid problems", 300, 280);

        // Draw boundaries for failure side
        p.noFill();
        p.stroke(...colors.accent3, 80);
        p.strokeWeight(1);
        p.rect(20, 40, 160, 220);

        // Draw obstacles (predictable problems that should be avoided)
        p.fill(...colors.accent3, 60);
        p.noStroke();
        p.rect(80, 120, 20, 20);
        p.rect(60, 180, 20, 20);

        // Draw past crashes with fading markers
        for (let crash of crashes) {
            let age = time - crash.time;
            let alpha = p.map(age, 0, 120, 100, 0);
            let size = 4;

            p.fill(...colors.accent2, alpha);
            p.noStroke();
            p.ellipse(crash.x, crash.y, size);
        }

        // Update and display failure path
        failureBall.update();
        failureBall.display(colors);

        // Update and display learning path
        for (let i = 0; i < learningPath.length; i++) {
            let point = learningPath[i];

            // Stagger appearance
            if (time > i * 15) {
                point.update();
                point.display(colors);

                // Connect to previous point with smooth line
                if (i > 0 && time > (i - 1) * 15) {
                    let prev = learningPath[i - 1];
                    p.stroke(...colors.accent1, 120);
                    p.strokeWeight(1.5);
                    p.line(prev.x, prev.y, point.x, point.y);
                }
            }
        }

        // Crash counter
        p.textAlign(p.LEFT, p.BOTTOM);
        p.textSize(9);
        p.fill(...colors.accent2, 180);
        p.noStroke();
        let recentCrashes = crashes.filter(c => time - c.time < 120).length;
        if (recentCrashes > 0) {
            p.text("Failures: " + recentCrashes, 25, 260);
        }

        // Success indicator for learning path
        if (time > learningPath.length * 15 + 30) {
            p.textAlign(p.RIGHT, p.BOTTOM);
            p.fill(...colors.accent1, 180);
            p.text("Goal reached", 375, 260);
        }

        time++;

        // Loop after learning path completes and failure ball has crashed a few times
        if (time > 500) {
            time = 0;
            crashes = [];
            failureBall.reset();
            learningPath = [];
            p.setup(); // Reset learning path
        }
    };
};