Where ideas percolate and thoughts brew

Two Decision Paths

About This Sketch

Two decision paths visualized side by side — the direct route of expert pattern recognition versus the wandering route of deliberate analysis. Both reach the same destination. The difference is in the path taken and the time spent.

Algorithm

Two animated particles travel from START to GOAL on separate paths. The expert particle (top half) traces a nearly direct route with minor variations. The novice particle (bottom half) wanders, backtracks, and takes a longer winding route. Both eventually arrive at the same destination. The visualization cycles automatically, showing the expert path first, then the novice path, then resetting.

Pseudocode

SETUP:
  Build expert path: 20 points in near-straight line with small sine jitter
  Build novice path: 15 waypoints that wander and backtrack

PHASE CYCLE (loops):
  Phase 0 (30 frames): Brief pause
  Phase 1: Animate expert particle along direct path (fast, 0.025/frame)
  Phase 2 (60 frames): Pause - expert has arrived
  Phase 3: Animate novice particle along winding path (slow, 0.012/frame)
  Phase 4 (90 frames): Both arrived, prepare to reset

DRAW each frame:
  Clear background with theme colors
  Draw labels and start/goal markers
  Draw trail and moving dot for each active particle
  Draw dashed dividing line between the two regions

Source Code

let nodes = [];
let expertPath = [];
let novicePath = [];
let expertProgress = 0;
let noviceProgress = 0;
let phase = 0;
let phaseTimer = 0;
let canvasW = 400;
let canvasH = 300;

let sketch = function(p) {
    p.setup = function() {
        let cnv = p.createCanvas(canvasW, canvasH);
        cnv.parent('sketch-container');
        p.frameRate(30);
        buildPaths(p);
    };

    function buildPaths(p) {
        let startX = 40;
        let endX = 360;
        let midY = canvasH / 2;

        expertPath = [];
        let steps = 20;
        for (let i = 0; i <= steps; i++) {
            let t = i / steps;
            let x = p.lerp(startX, endX, t);
            let jitter = (i === 0 || i === steps) ? 0 : p.sin(i * 1.7) * 8;
            expertPath.push({ x: x, y: midY - 45 + jitter });
        }

        novicePath = [];
        novicePath.push({ x: startX, y: midY + 45 });
        novicePath.push({ x: 90, y: midY + 20 });
        novicePath.push({ x: 130, y: midY + 65 });
        novicePath.push({ x: 100, y: midY + 45 });
        novicePath.push({ x: 150, y: midY + 30 });
        novicePath.push({ x: 180, y: midY + 70 });
        novicePath.push({ x: 160, y: midY + 50 });
        novicePath.push({ x: 210, y: midY + 35 });
        novicePath.push({ x: 240, y: midY + 65 });
        novicePath.push({ x: 220, y: midY + 45 });
        novicePath.push({ x: 270, y: midY + 30 });
        novicePath.push({ x: 300, y: midY + 55 });
        novicePath.push({ x: 310, y: midY + 40 });
        novicePath.push({ x: 340, y: midY + 45 });
        novicePath.push({ x: endX, y: midY + 45 });
    }

    function getPathPoint(path, t) {
        let total = path.length - 1;
        let idx = t * total;
        let i = p.floor(idx);
        let frac = idx - i;
        if (i >= total) return path[total];
        return {
            x: p.lerp(path[i].x, path[i + 1].x, frac),
            y: p.lerp(path[i].y, path[i + 1].y, frac)
        };
    }

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

        phaseTimer++;

        if (phase === 0 && phaseTimer > 30) {
            phase = 1; phaseTimer = 0; expertProgress = 0;
        } else if (phase === 1) {
            expertProgress += 0.025;
            if (expertProgress >= 1) { expertProgress = 1; phase = 2; phaseTimer = 0; }
        } else if (phase === 2 && phaseTimer > 60) {
            phase = 3; phaseTimer = 0; noviceProgress = 0;
        } else if (phase === 3) {
            noviceProgress += 0.012;
            if (noviceProgress >= 1) { noviceProgress = 1; phase = 4; phaseTimer = 0; }
        } else if (phase === 4 && phaseTimer > 90) {
            phase = 0; phaseTimer = 0; expertProgress = 0; noviceProgress = 0;
        }

        p.textFont('Georgia');
        p.textSize(11);
        p.textAlign(p.LEFT);
        p.fill(...colors.text, 160);
        p.noStroke();
        p.text('Expert (fast thinking)', 40, 50);
        p.text('Novice (slow deliberation)', 40, canvasH / 2 + 10);

        let startX = 40, endX = 360;
        p.fill(...colors.accent2, 180);
        p.noStroke();
        p.circle(startX, canvasH / 2 - 45, 10);
        p.circle(endX, canvasH / 2 - 45, 10);
        p.circle(startX, canvasH / 2 + 45, 10);
        p.circle(endX, canvasH / 2 + 45, 10);

        p.textAlign(p.CENTER);
        p.textSize(10);
        p.fill(...colors.accent2, 200);
        p.text('START', startX, canvasH / 2 - 55);
        p.text('GOAL', endX, canvasH / 2 - 55);

        if (expertProgress > 0) {
            let trailCount = p.floor(expertProgress * (expertPath.length - 1));
            p.stroke(...colors.accent2, 200);
            p.strokeWeight(2.5);
            p.noFill();
            p.beginShape();
            for (let i = 0; i <= trailCount && i < expertPath.length; i++) {
                p.vertex(expertPath[i].x, expertPath[i].y);
            }
            let ept2 = getPathPoint(expertPath, expertProgress);
            p.vertex(ept2.x, ept2.y);
            p.endShape();
            p.noStroke();
            p.fill(...colors.accent2);
            p.circle(ept2.x, ept2.y, 12);
        }

        if (noviceProgress > 0) {
            let trailCount = p.floor(noviceProgress * (novicePath.length - 1));
            p.stroke(...colors.accent1, 180);
            p.strokeWeight(2);
            p.noFill();
            p.beginShape();
            for (let i = 0; i <= trailCount && i < novicePath.length; i++) {
                p.vertex(novicePath[i].x, novicePath[i].y);
            }
            let npt2 = getPathPoint(novicePath, noviceProgress);
            p.vertex(npt2.x, npt2.y);
            p.endShape();
            p.noStroke();
            p.fill(...colors.accent1);
            p.circle(npt2.x, npt2.y, 12);
        }

        p.stroke(...colors.accent3, 60);
        p.strokeWeight(1);
        p.drawingContext.setLineDash([4, 6]);
        p.line(0, canvasH / 2, canvasW, canvasH / 2);
        p.drawingContext.setLineDash([]);
    };
};