Where ideas percolate and thoughts brew

Calibration vs. Confidence

About This Sketch

Eighteen particles — nine overconfident, nine calibrated — each chasing a fixed target. On the left, high ambition and internal noise create perpetual oscillation: always moving, never arriving, color warming with each overshoot. On the right, steady convergence: each particle finds its mark and rests there, pulsing softly.

Algorithm

Two panels compare calibration dynamics. Left: overconfident particles use a high-gain spring force plus sinusoidal noise injection, producing perpetual oscillation — they approach targets but never settle, and their color shifts warmer as distance from target grows, encoding error in real time. Right: calibrated particles use a gentle damped spring that converges precisely to targets; once settled, each particle pulses softly with a glowing ring, indicating stable accuracy.

Pseudocode

SETUP:
  Place 9 particles in 3x3 grid on each panel half
  Each particle starts at target position + random offset
  Assign random phase for desynchronized motion

DRAW:
  Left panel (overconfident):
    For each particle:
      Compute displacement toward target
      Apply high-gain spring + phase-shifted sinusoidal noise
      Update position with low damping coefficient
      Constrain to left panel bounds
      Map distance from target to color (warm = far, cool = near)
      Draw target ring and colored particle

  Right panel (calibrated):
    For each particle:
      Compute displacement toward target
      If not settled: apply low-gain spring with high damping
      If settled: pulse gently at target with sub-pixel offset
      Draw target ring (glowing when settled) and steady particle

Source Code

let sketch = function(p) {
    const W = 400, H = 300, MID = W / 2;
    let lp = [], rp = [];

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

        for (let row = 0; row < 3; row++) {
            for (let col = 0; col < 3; col++) {
                let ltx = 30 + col * 55;
                let lty = 55 + row * 80;
                lp.push({
                    x: ltx + p.random(-40, 40),
                    y: lty + p.random(-28, 28),
                    tx: ltx, ty: lty,
                    vx: p.random(-2, 2),
                    vy: p.random(-2, 2),
                    ph: p.random(p.TWO_PI)
                });

                let rtx = 230 + col * 55;
                let rty = 55 + row * 80;
                rp.push({
                    x: rtx + p.random(-22, 22),
                    y: rty + p.random(-22, 22),
                    tx: rtx, ty: rty,
                    vx: 0, vy: 0,
                    ph: p.random(p.TWO_PI),
                    settled: false
                });
            }
        }
    };

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

        let t = p.frameCount * 0.022;

        p.stroke(...c.accent3, 45);
        p.strokeWeight(0.5);
        p.line(MID, 16, MID, H - 8);

        p.noStroke();
        p.fill(...c.accent3);
        p.textAlign(p.CENTER);
        p.textSize(9);
        p.text("overconfident", MID / 2, 13);
        p.text("calibrated", MID + (W - MID) / 2, 13);

        for (let pt of lp) {
            let dx = pt.tx - pt.x;
            let dy = pt.ty - pt.y;

            let noise = 1.5 + p.sin(t * 2.3 + pt.ph) * 1.2;
            pt.vx = pt.vx * 0.77 + dx * 0.12 + p.cos(t * 1.9 + pt.ph) * noise;
            pt.vy = pt.vy * 0.77 + dy * 0.12 + p.sin(t * 2.1 + pt.ph * 0.9) * noise;
            pt.x = p.constrain(pt.x + pt.vx, 6, MID - 6);
            pt.y = p.constrain(pt.y + pt.vy, 20, H - 6);

            let dist = p.dist(pt.x, pt.y, pt.tx, pt.ty);
            let err = p.constrain(dist / 36, 0, 1);

            p.noFill();
            p.stroke(...c.accent3, 80);
            p.strokeWeight(1);
            p.circle(pt.tx, pt.ty, 14);

            p.noStroke();
            p.fill(
                p.lerp(c.accent1[0], c.accent2[0], err),
                p.lerp(c.accent1[1], c.accent2[1], err),
                p.lerp(c.accent1[2], c.accent2[2], err),
                210
            );
            p.circle(pt.x, pt.y, 6);
        }

        for (let pt of rp) {
            let dx = pt.tx - pt.x;
            let dy = pt.ty - pt.y;
            let dist = p.dist(pt.x, pt.y, pt.tx, pt.ty);

            if (dist > 1.8) {
                pt.vx = pt.vx * 0.89 + dx * 0.036;
                pt.vy = pt.vy * 0.89 + dy * 0.036;
                pt.x += pt.vx;
                pt.y += pt.vy;
            } else {
                pt.settled = true;
                pt.x = pt.tx + p.sin(t * 1.3 + pt.ph) * 0.7;
                pt.y = pt.ty + p.cos(t * 1.1 + pt.ph) * 0.7;
            }

            p.noFill();
            if (pt.settled) {
                let alpha = 50 + p.sin(t * 1.3 + pt.ph) * 22;
                p.stroke(...c.accent1, alpha);
                p.strokeWeight(1.5);
                p.circle(pt.tx, pt.ty, 20);
            } else {
                p.stroke(...c.accent3, 80);
                p.strokeWeight(1);
                p.circle(pt.tx, pt.ty, 14);
            }

            p.noStroke();
            p.fill(...c.accent1, 210);
            p.circle(pt.x, pt.y, 6);
        }
    };
};