The Intuition Trap
About This Sketch
When does trusting your gut work? This sketch visualizes the answer: only when your intuition has been shaped by real feedback from a stable environment. The left panel shows particles with accurate prediction (their "gut" points toward the actual attractor, and feedback loops close). The right shows the same particles with equally confident but uncalibrated predictions โ pointing in directions unrelated to their actual movement. Same confidence, different underlying structure.
Algorithm
Two panels showing particle systems in contrasting environments.
Left (high-validity): particles are attracted to a slowly drifting target.
Their "prediction ghosts" point accurately toward the attractor, and
feedback arcs close the loop when particles approach it โ representing
genuine pattern recognition built from real signal.
Right (low-validity): particles drift via noise fields. Their prediction
ghosts point confidently but in directions uncorrelated with actual motion.
No feedback arc ever closes โ representing intuition trained on noise.
Pseudocode
SETUP:
Create N particles in each panel
Assign each random position, velocity, noise offset
LEFT PANEL each frame:
Move attractor slowly (sine wave drift)
Pull each particle toward attractor with dampening
Draw trail behind particle
Draw prediction ghost ahead in attractor direction
If particle near attractor, draw closing feedback arc
RIGHT PANEL each frame:
Move particle along perlin noise field
Draw trail behind particle
Draw prediction ghost in DIFFERENT noise direction
No feedback arc drawn (loop never closes)
BOTH PANELS:
Draw panel labels and divider
Caption: "same confidence ยท different calibration"
Source Code
let sketch = function(p) {
const W = 400, H = 300;
const HALF = W / 2;
const N = 18;
let highParticles = [];
let lowParticles = [];
let time = 0;
function makeParticle(px, py) {
return {
x: px, y: py,
vx: p.random(-0.4, 0.4),
vy: p.random(-0.4, 0.4),
phase: p.random(p.TWO_PI),
noiseOffset: p.random(1000),
trail: []
};
}
p.setup = function() {
p.createCanvas(W, H);
p.randomSeed(99);
for (let i = 0; i < N; i++) {
highParticles.push(makeParticle(p.random(16, HALF - 16), p.random(30, H - 20)));
lowParticles.push(makeParticle(p.random(HALF + 16, W - 16), p.random(30, H - 20)));
}
};
function drawFeedbackArc(colors, x1, y1, x2, y2, alpha) {
p.noFill();
p.stroke(...colors.accent1, alpha);
p.strokeWeight(0.8);
let mx = (x1 + x2) / 2;
let my = Math.min(y1, y2) - 18;
p.beginShape();
p.vertex(x1, y1);
p.quadraticVertex(mx, my, x2, y2);
p.endShape();
}
p.draw = function() {
const colors = getThemeColors();
p.background(...colors.bg);
time += 0.017;
p.noStroke();
p.textAlign(p.CENTER);
p.textSize(9);
p.fill(...colors.accent2, 170);
p.text('high-validity domain', HALF / 2, 16);
p.fill(...colors.accent3, 120);
p.text('low-validity domain', HALF + HALF / 2, 16);
p.stroke(...colors.accent3, 20);
p.strokeWeight(0.7);
p.line(HALF, 22, HALF, H - 8);
let ax = HALF / 2 + 28 * Math.cos(time * 0.31);
let ay = H / 2 + 22 * Math.sin(time * 0.22);
p.noStroke();
p.fill(...colors.accent2, 30);
p.circle(ax, ay, 28);
p.fill(...colors.accent2, 80);
p.circle(ax, ay, 10);
for (let pt of highParticles) {
let dx = ax - pt.x;
let dy = ay - pt.y;
let d = Math.sqrt(dx * dx + dy * dy);
let pull = 0.09 / Math.max(d * 0.04, 1);
pt.vx += dx * pull + p.random(-0.04, 0.04);
pt.vy += dy * pull + p.random(-0.04, 0.04);
pt.vx *= 0.88;
pt.vy *= 0.88;
pt.x += pt.vx;
pt.y += pt.vy;
pt.x = p.constrain(pt.x, 8, HALF - 8);
pt.y = p.constrain(pt.y, 26, H - 8);
pt.trail.push({ x: pt.x, y: pt.y });
if (pt.trail.length > 18) pt.trail.shift();
p.noStroke();
for (let i = 0; i < pt.trail.length - 1; i++) {
let a = (i / pt.trail.length) * 55;
p.fill(...colors.accent1, a);
p.circle(pt.trail[i].x, pt.trail[i].y, 2.5);
}
let predX = p.constrain(pt.x + dx * 0.25, 10, HALF - 10);
let predY = p.constrain(pt.y + dy * 0.25, 26, H - 8);
p.fill(...colors.accent1, 38);
p.circle(predX, predY, 8);
if (d < 55) {
drawFeedbackArc(colors, pt.x, pt.y, ax, ay, 28 * (1 - d / 55));
}
p.noStroke();
p.fill(...colors.accent1, 75);
p.circle(pt.x, pt.y, 10);
p.fill(...colors.accent1, 200);
p.circle(pt.x, pt.y, 5);
}
for (let pt of lowParticles) {
let noiseAngle = p.noise(pt.noiseOffset + time * 0.4) * p.TWO_PI * 3;
pt.vx += Math.cos(noiseAngle) * 0.22;
pt.vy += Math.sin(noiseAngle) * 0.22;
pt.vx *= 0.86;
pt.vy *= 0.86;
pt.x += pt.vx;
pt.y += pt.vy;
pt.x = p.constrain(pt.x, HALF + 8, W - 8);
pt.y = p.constrain(pt.y, 26, H - 8);
pt.trail.push({ x: pt.x, y: pt.y });
if (pt.trail.length > 18) pt.trail.shift();
p.noStroke();
for (let i = 0; i < pt.trail.length - 1; i++) {
let a = (i / pt.trail.length) * 45;
p.fill(...colors.accent3, a);
p.circle(pt.trail[i].x, pt.trail[i].y, 2.5);
}
let predAngle = p.noise(pt.noiseOffset + 500 + time * 0.15) * p.TWO_PI * 3;
let predX = p.constrain(pt.x + Math.cos(predAngle) * 24, HALF + 10, W - 10);
let predY = p.constrain(pt.y + Math.sin(predAngle) * 24, 26, H - 8);
p.stroke(...colors.accent3, 55);
p.strokeWeight(0.9);
p.line(pt.x, pt.y, predX, predY);
p.noStroke();
p.fill(...colors.accent3, 50);
p.circle(predX, predY, 7);
p.noStroke();
p.fill(...colors.accent3, 65);
p.circle(pt.x, pt.y, 10);
p.fill(...colors.accent3, 190);
p.circle(pt.x, pt.y, 5);
}
p.noStroke();
p.fill(...colors.accent3, 55);
p.textAlign(p.CENTER);
p.textSize(8);
p.text('same confidence \u00b7 different calibration', W / 2, H - 4);
};
};