Where ideas percolate and thoughts brew

The Praise Trap

About This Sketch

Twelve potential paths radiate from a center point. One path is repeatedly reinforced by praise events — traveling flashes that strengthen it and dim the others. Over time, the praised path brightens while unchosen paths fade toward invisibility. A particle moves along only the reinforced trajectory. The paths not taken are still there, barely, if you look.

Algorithm

Twelve potential paths radiate from a central origin point. A single path is designated as the "praised path." At regular intervals, a praise event fires along that path — a traveling flash of light — and reinforces its strength while slightly dimming all other paths. Transient flashes occasionally appear on other paths but fade quickly. A particle oscillates along the praised path. Path strength values are tracked and displayed.

Pseudocode

SETUP:
  Create 12 paths at evenly-spaced angles from center
  Assign equal initial strength to all paths
  Mark one path as the reinforced (praised) path

DRAW each frame:
  Increment praise timer
  If timer threshold reached:
    Fire praise event: traveling flash along praised path
    Increase praised path strength
    Decrease all other path strengths slightly
    Reset timer
  Occasionally fire transient flash on random non-praised path

  For each path:
    Draw line from center with alpha proportional to strength
    Add glow if strength is high
    Draw arrowhead at tip
  For each active praise event:
    Advance flash along path
    Draw glowing circle at current position
    Remove when complete

  Draw particle oscillating along praised path
  Display strength readout
  Draw caption

Source Code

let sketch = function(p) {
    const N_PATHS = 12;

    let pathAngles = [];
    let pathStrengths = [];
    let praiseIndex = 2;
    let particleT = 0;
    let praiseEvents = [];
    let praiseTimer = 0;
    let phase = 0;

    function pathEnd(idx, len) {
        let angle = pathAngles[idx];
        return {
            x: 200 + Math.cos(angle) * len,
            y: 150 + Math.sin(angle) * len
        };
    }

    p.setup = function() {
        p.createCanvas(400, 300);
        p.randomSeed(7);
        for (let i = 0; i < N_PATHS; i++) {
            pathAngles.push((i / N_PATHS) * p.TWO_PI);
            pathStrengths.push(i === praiseIndex ? 0.22 : 0.18);
        }
    };

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

        phase += 0.008;
        praiseTimer++;

        if (praiseTimer > 90) {
            praiseEvents.push({ age: 0, pathIdx: praiseIndex });
            pathStrengths[praiseIndex] = Math.min(1.0, pathStrengths[praiseIndex] + 0.09);
            for (let i = 0; i < N_PATHS; i++) {
                if (i !== praiseIndex) {
                    pathStrengths[i] = Math.max(0.02, pathStrengths[i] - 0.012);
                }
            }
            praiseTimer = 0;
        }

        if (p.frameCount % 210 === 0) {
            let rnd = Math.floor(p.random(N_PATHS));
            if (rnd !== praiseIndex) {
                praiseEvents.push({ age: 0, pathIdx: rnd, transient: true });
            }
        }

        for (let i = 0; i < N_PATHS; i++) {
            let s = pathStrengths[i];
            let end = pathEnd(i, 118);

            if (s > 0.3) {
                p.stroke(...colors.accent1, s * 22);
                p.strokeWeight(6 * s);
                p.noFill();
                p.line(200, 150, end.x, end.y);
            }

            let alpha = s * 200;
            let col = i === praiseIndex ? colors.accent2 : colors.accent3;
            p.stroke(...col, alpha);
            p.strokeWeight(i === praiseIndex ? 1.8 * s + 0.4 : 0.8 * s + 0.2);
            p.noFill();
            p.line(200, 150, end.x, end.y);

            if (s > 0.08) {
                let tipA = Math.atan2(end.y - 150, end.x - 200);
                p.fill(...col, alpha * 0.9);
                p.noStroke();
                p.push();
                p.translate(end.x, end.y);
                p.rotate(tipA);
                p.triangle(0, 0, -5, -2.5, -5, 2.5);
                p.pop();
            }
        }

        for (let i = praiseEvents.length - 1; i >= 0; i--) {
            let ev = praiseEvents[i];
            ev.age++;
            let t = ev.age / 55;
            if (t > 1) { praiseEvents.splice(i, 1); continue; }

            let end = pathEnd(ev.pathIdx, 118 * t);
            let a = ev.transient ? 120 : 220;
            let fade = ev.transient ? (1 - t) * a : (1 - Math.abs(t - 0.5) * 2) * a;
            let col = ev.transient ? colors.accent3 : colors.accent1;
            p.noStroke();
            p.fill(...col, fade);
            p.circle(end.x, end.y, ev.transient ? 5 : 8);
            if (!ev.transient) {
                p.fill(...col, fade * 0.4);
                p.circle(end.x, end.y, 16);
            }
        }

        particleT = (Math.sin(phase) * 0.5 + 0.5);
        let pEnd = pathEnd(praiseIndex, 118 * particleT);
        p.noStroke();
        p.fill(...colors.accent2, 55);
        p.circle(pEnd.x, pEnd.y, 18);
        p.fill(...colors.accent2, 200);
        p.circle(pEnd.x, pEnd.y, 8);
        p.fill(...colors.accent2, 255);
        p.circle(pEnd.x, pEnd.y, 4);

        p.noStroke();
        p.fill(...colors.accent3, 100);
        p.circle(200, 150, 10);
        p.fill(...colors.accent3, 200);
        p.circle(200, 150, 5);

        let otherAvg = 0, otherCount = 0;
        for (let i = 0; i < N_PATHS; i++) {
            if (i !== praiseIndex) { otherAvg += pathStrengths[i]; otherCount++; }
        }
        otherAvg = otherAvg / otherCount;

        p.noStroke();
        p.textAlign(p.LEFT);
        p.textSize(8);
        p.fill(...colors.accent2, 130);
        p.text('reinforced path: ' + Math.round(pathStrengths[praiseIndex] * 100) + '%', 10, 14);
        p.fill(...colors.accent3, 100);
        p.text('other paths avg: ' + Math.round(otherAvg * 100) + '%', 10, 26);

        p.noStroke();
        p.fill(...colors.accent3, 55);
        p.textAlign(p.CENTER);
        p.textSize(8);
        p.text('praised paths persist \u00b7 unpraised paths fade', 200, 295);
    };
};