Where ideas percolate and thoughts brew

The Performance of Learning

About This Sketch

A split-screen visualization comparing private, messy learning (which goes deep through confusion and breakthrough) versus public performance of learning (which stays shallow but looks polished and consistent). The left side shows erratic exploration with occasional bursts of insight, while the right side shows steady, linear "progress" that never achieves real depth.

Algorithm

This sketch visualizes the contrast between private learning and public performance of learning. Two parallel streams of particles represent different approaches to learning: - Left side (Private Learning): Particles move erratically, wandering and exploring, occasionally experiencing "breakthrough" moments where they suddenly gain depth. The path is messy and non-linear, but the particles grow larger (representing deeper understanding). - Right side (Public Performance): Particles move smoothly and consistently downward in a neat line, but remain small (shallow understanding). They prioritize visible progress over actual depth. The sketch accompanies the blog post "The Performance of Learning" and explores how documentation and public sharing can interfere with the messy, private process of genuine learning.

Pseudocode

SETUP:
  Initialize canvas (400x300)
  Create empty arrays for two particle types

DRAW (every frame):
  Get current theme colors
  Clear background
  Draw labels and dividing line

  Spawn new particles periodically for both paths

  FOR EACH learning particle (private):
    Update position with random wobbling (confusion/exploration)
    Gradually increase depth
    Randomly trigger breakthrough moments (sudden insight)
    Draw with size proportional to depth
    Show glow effect during breakthrough
    Remove if too old or off screen

  FOR EACH performance particle (public):
    Update position moving steadily downward (consistent "progress")
    Minimally increase depth
    Keep movement smooth and predictable
    Draw small, polished circles
    Connect to previous particle with line
    Remove if too old or off screen

  Display depth comparison labels
  Show insight message after initial period

Source Code

let sketch = function(p) {
    // Visualization: The Performance of Learning
    // Two parallel paths: one showing messy, deep learning (private)
    // and one showing polished, shallow documentation (public)
    // The private path goes deeper while public path stays surface-level

    let time = 0;
    let learningParticles = [];
    let performanceParticles = [];

    class LearningParticle {
        constructor() {
            this.x = 50;
            this.y = 100;
            this.depth = 0;
            this.confusion = p.random(0, p.TWO_PI);
            this.speed = p.random(0.3, 0.8);
            this.wobble = p.random(2, 5);
            this.age = 0;
            this.breakthrough = false;
            this.breakthroughAt = p.random(150, 300);
        }

        update() {
            this.age++;

            // Messy, non-linear progress with occasional breakthroughs
            if (this.age > this.breakthroughAt && !this.breakthrough) {
                this.breakthrough = true;
                this.depth += p.random(20, 40); // Sudden insight
            }

            // Wander and explore (confusion)
            this.x += p.cos(this.confusion) * this.wobble;
            this.y += p.sin(this.confusion) * 0.5;
            this.confusion += p.random(-0.1, 0.1);

            // Gradual depth increase (real learning)
            this.depth += this.speed * 0.05;

            // Stay in bounds but allow exploration
            this.x = p.constrain(this.x, 40, 180);
            this.y = p.constrain(this.y, 60, 240);
        }

        display(colors) {
            let alpha = p.map(this.age, 0, 400, 255, 50);

            // Size represents depth of understanding
            let size = p.map(this.depth, 0, 100, 3, 12);

            // Trail showing messy exploration
            p.noStroke();
            p.fill(...colors.accent1, alpha * 0.3);
            p.circle(this.x, this.y, size);

            // Glow for breakthrough moments
            if (this.breakthrough && this.age - this.breakthroughAt < 30) {
                let glowSize = size + (30 - (this.age - this.breakthroughAt));
                p.fill(...colors.accent2, 100);
                p.circle(this.x, this.y, glowSize);
            }
        }

        isDone() {
            return this.age > 400 || this.y > 250;
        }
    }

    class PerformanceParticle {
        constructor() {
            this.x = 250;
            this.y = 100;
            this.depth = 0;
            this.speed = p.random(0.8, 1.2);
            this.age = 0;
            this.polish = 1.0; // Always polished
        }

        update() {
            this.age++;

            // Smooth, linear progress (appearance of learning)
            this.y += this.speed;

            // Minimal depth increase (shallow understanding)
            this.depth += 0.02;

            // Stay in lane (performative consistency)
            this.x += p.sin(this.age * 0.05) * 0.5;
            this.x = p.constrain(this.x, 220, 280);
        }

        display(colors) {
            let alpha = p.map(this.age, 0, 300, 255, 50);

            // Always small (shallow understanding)
            let size = p.map(this.depth, 0, 100, 3, 6);

            // Perfect circles (polished presentation)
            p.noStroke();
            p.fill(...colors.accent2, alpha * 0.5);
            p.circle(this.x, this.y, size);

            // Connecting line showing "progress"
            if (performanceParticles.length > 1) {
                let prev = performanceParticles[performanceParticles.length - 2];
                p.stroke(...colors.accent2, alpha * 0.2);
                p.strokeWeight(1);
                p.line(this.x, this.y, prev.x, prev.y);
            }
        }

        isDone() {
            return this.age > 300 || this.y > 250;
        }
    }

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

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

        time++;

        // Labels
        p.fill(...colors.accent3, 220);
        p.noStroke();
        p.textAlign(p.CENTER, p.TOP);
        p.textSize(11);
        p.textFont('Georgia');
        p.text('Private Learning', 115, 15);
        p.text('Public Performance', 265, 15);

        // Dividing line
        p.stroke(...colors.accent3, 80);
        p.strokeWeight(1);
        p.line(200, 40, 200, 260);

        // Spawn particles
        if (time % 40 === 0) {
            learningParticles.push(new LearningParticle());
        }

        if (time % 35 === 0) {
            performanceParticles.push(new PerformanceParticle());
        }

        // Update and draw learning particles (messy, deep)
        for (let i = learningParticles.length - 1; i >= 0; i--) {
            learningParticles[i].update();
            learningParticles[i].display(colors);

            if (learningParticles[i].isDone()) {
                learningParticles.splice(i, 1);
            }
        }

        // Update and draw performance particles (polished, shallow)
        for (let i = performanceParticles.length - 1; i >= 0; i--) {
            performanceParticles[i].update();
            performanceParticles[i].display(colors);

            if (performanceParticles[i].isDone()) {
                performanceParticles.splice(i, 1);
            }
        }

        // Starting points
        p.noStroke();
        p.fill(...colors.accent1, 200);
        p.circle(115, 50, 8);
        p.circle(265, 50, 8);

        // Depth indicators at bottom
        p.textAlign(p.CENTER);
        p.textSize(9);
        p.fill(...colors.accent3, 180);

        // Calculate average depth for each path
        let avgPrivateDepth = 0;
        if (learningParticles.length > 0) {
            for (let particle of learningParticles) {
                avgPrivateDepth += particle.depth;
            }
            avgPrivateDepth /= learningParticles.length;
        }

        let avgPublicDepth = 0;
        if (performanceParticles.length > 0) {
            for (let particle of performanceParticles) {
                avgPublicDepth += particle.depth;
            }
            avgPublicDepth /= performanceParticles.length;
        }

        p.text(`Messy, but deep`, 115, 270);
        p.text(`Polished, but shallow`, 265, 270);

        // Key insight
        if (time > 200) {
            p.fill(...colors.accent2, 180);
            p.textSize(8);
            p.text('Real learning requires private confusion', 200, 285);
        }
    };
};