Where ideas percolate and thoughts brew

Learning From Failure

About This Sketch

Two panels showing what failure produces with and without deliberate structure. On the left, particles driven by turbulence with no organizing force — they scatter, bounce, and never converge. On the right, the same initial chaos, but with a growing "pull" toward an orbit that represents accumulated structured analysis. Over time, the right side resolves from disorder into organized circulation. The learning isn't in the failure itself — it's in what you build around it.

Algorithm

Two-panel particle simulation contrasting unstructured and structured responses to failure. Left panel: particles driven by Perlin noise turbulence with no organizing force — perpetual disorder. Right panel: identical initial chaos, but each particle acquires a growing "pull" toward an orbital path around a central point. The pull strength (learnFactor) increases over time, representing accumulated structured analysis. Noise amplitude decreases as structure takes hold. Particle color warms from muted to accent as convergence progresses.

Pseudocode

SETUP:
  Create 16 left particles with random positions and velocities in left half
  Create 16 right particles with random positions, velocities, and orbit phases in right half

DRAW EACH FRAME:
  learnFactor = min(frameCount * 0.00048, 0.12)

  LEFT PANEL (raw failure):
    For each particle:
      Apply Perlin noise turbulence (constant amplitude)
      Cap speed at 1.6
      Bounce off left-panel walls
      Draw as muted circle

  RIGHT PANEL (structured analysis):
    Draw faint orbit ring (visibility scales with learnFactor)
    For each particle:
      Compute orbit target position (rotating around center)
      Apply pull toward orbit target (strength = learnFactor * 0.055)
      Apply Perlin noise (amplitude decreases as learnFactor grows)
      Cap speed at 1.9
      Bounce off right-panel walls
      Interpolate color from muted to warm based on learnFactor progress
      Draw circle

  Draw panel labels

Source Code

let sketch = function(p) {
    const W = 400, H = 300, MID = W / 2;
    const N = 16;

    let leftP = [];
    let rightP = [];

    p.setup = function() {
        p.createCanvas(W, H);
        p.colorMode(p.RGB);

        for (let i = 0; i < N; i++) {
            leftP.push({
                x: p.random(12, MID - 12),
                y: p.random(12, H - 12),
                vx: p.random(-1.2, 1.2),
                vy: p.random(-1.2, 1.2)
            });
            rightP.push({
                x: p.random(MID + 12, W - 12),
                y: p.random(12, H - 12),
                vx: p.random(-1.2, 1.2),
                vy: p.random(-1.2, 1.2),
                phase: (i / N) * p.TWO_PI
            });
        }
    };

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

        const t = p.frameCount * 0.013;
        const learnFactor = p.min(p.frameCount * 0.00048, 0.12);

        p.stroke(...colors.accent3, 35);
        p.strokeWeight(1);
        p.line(MID, 14, MID, H - 14);

        for (let a of leftP) {
            a.vx += (p.noise(a.x * 0.025, a.y * 0.025, t) - 0.5) * 0.28;
            a.vy += (p.noise(a.x * 0.025 + 60, a.y * 0.025, t) - 0.5) * 0.28;

            let spL = p.sqrt(a.vx * a.vx + a.vy * a.vy);
            if (spL > 1.6) { a.vx *= 1.6 / spL; a.vy *= 1.6 / spL; }
            if (spL < 0.3) { a.vx += p.random(-0.15, 0.15); a.vy += p.random(-0.15, 0.15); }

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

            if (a.x < 10) { a.x = 10; a.vx = Math.abs(a.vx); }
            if (a.x > MID - 10) { a.x = MID - 10; a.vx = -Math.abs(a.vx); }
            if (a.y < 10) { a.y = 10; a.vy = Math.abs(a.vy); }
            if (a.y > H - 10) { a.y = H - 10; a.vy = -Math.abs(a.vy); }

            p.fill(...colors.accent3, 165);
            p.noStroke();
            p.circle(a.x, a.y, 7);
        }

        const gcx = MID + (W - MID) / 2;
        const gcy = H / 2;

        let ringAlpha = p.map(learnFactor, 0, 0.12, 15, 80);
        p.noFill();
        p.stroke(...colors.accent1, ringAlpha);
        p.strokeWeight(1.2);
        p.circle(gcx, gcy, 58);
        p.fill(...colors.accent1, ringAlpha * 1.8);
        p.noStroke();
        p.circle(gcx, gcy, 7);

        for (let a of rightP) {
            const orbitR = 22 + 6 * p.sin(a.phase * 1.7);
            const tx = gcx + orbitR * p.cos(a.phase + t * 1.05);
            const ty = gcy + orbitR * p.sin(a.phase + t * 1.05);

            a.vx += (tx - a.x) * learnFactor * 0.055;
            a.vy += (ty - a.y) * learnFactor * 0.055;

            const noiseAmp = 0.22 * (1 - learnFactor / 0.12);
            a.vx += (p.noise(a.x * 0.022, a.y * 0.022, t) - 0.5) * noiseAmp;
            a.vy += (p.noise(a.x * 0.022 + 60, a.y * 0.022, t) - 0.5) * noiseAmp;

            let spR = p.sqrt(a.vx * a.vx + a.vy * a.vy);
            if (spR > 1.9) { a.vx *= 1.9 / spR; a.vy *= 1.9 / spR; }

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

            if (a.x < MID + 6) { a.x = MID + 6; a.vx = Math.abs(a.vx); }
            if (a.x > W - 6) { a.x = W - 6; a.vx = -Math.abs(a.vx); }
            if (a.y < 6) { a.y = 6; a.vy = Math.abs(a.vy); }
            if (a.y > H - 6) { a.y = H - 6; a.vy = -Math.abs(a.vy); }

            const progress = learnFactor / 0.12;
            const r = colors.accent3[0] + (colors.accent2[0] - colors.accent3[0]) * progress;
            const g = colors.accent3[1] + (colors.accent2[1] - colors.accent3[1]) * progress;
            const b = colors.accent3[2] + (colors.accent2[2] - colors.accent3[2]) * progress;
            p.fill(r, g, b, 175);
            p.noStroke();
            p.circle(a.x, a.y, 7);
        }

        p.noStroke();
        p.fill(...colors.accent3, 90);
        p.textAlign(p.CENTER);
        p.textSize(8.5);
        p.text("raw failure", MID / 2, H - 10);
        p.text("structured analysis", MID + (W - MID) / 2, H - 10);
    };
};