The Coherence Trap
About This Sketch
Evidence streams in from the edges. Confirming ideas are captured and orbit the belief core. Challenging ideas are deflected — and each deflection makes the well slightly larger, slightly stronger. Watch the counters diverge.
Algorithm
A gravity-well simulation representing the coherence trap in belief formation.
A central "belief core" exerts gravitational pull on incoming evidence particles.
Two types of particles stream in from the canvas edges: confirming evidence (warm)
and challenging evidence (muted). When a particle reaches the well boundary,
confirming evidence is captured and begins to orbit; challenging evidence is
deflected outward. Each rejection causes the well to pulse and grow slightly
larger — making it harder for future challenging evidence to penetrate.
Over time, the well grows denser as rejections accumulate, visually representing
how coherence-seeking compounds: the stronger the worldview, the less new
challenging evidence can reach it.
This sketch accompanies the blog post "The Coherence Trap."
Pseudocode
SETUP:
Initialize 400x300 canvas
Set wellMass = 1.0, captured/rejected counters = 0
SPAWN (every 42 frames):
Create particle at random point on circle of radius ~155 around center
Aim loosely toward center with slight angular variance
Mark as confirming (38% chance) or challenging (62% chance)
DRAW (every frame):
Compute wellR = 28 + wellMass * 10 + pulseAmt * 12
Render belief core as layered glow circles at center
FOR each particle:
IF active:
Apply gravity toward center (strength proportional to wellMass)
IF reached inner boundary (d < wellR * 0.78):
IF confirming: transition to orbiting state, increment captured
IF challenging: deflect outward, grow wellMass, pulse, increment rejected
IF orbiting:
Rotate around center, slowly spiral inward
IF deflected:
Continue outward with friction
Display counters (integrated / rejected) and caption
Source Code
let sketch = function(p) {
const W = 400, H = 300;
const CX = W / 2, CY = H / 2;
let wellMass = 1.0;
let particles = [];
let captured = 0, rejected = 0;
let spawnTimer = 0;
let pulseAmt = 0;
function spawnParticle() {
let angle = p.random(p.TWO_PI);
let dist = 155 + p.random(25);
let x = p.constrain(CX + Math.cos(angle) * dist, 6, W - 6);
let y = p.constrain(CY + Math.sin(angle) * dist, 6, H - 6);
let toCenter = Math.atan2(CY - y, CX - x) + p.random(-0.45, 0.45);
let speed = p.random(1.1, 1.9);
let isConfirming = p.random() < 0.38;
return {
x, y,
vx: Math.cos(toCenter) * speed,
vy: Math.sin(toCenter) * speed,
isConfirming,
state: 'active',
orbitAngle: 0,
orbitRadius: 0,
age: 0
};
}
p.setup = function() {
p.createCanvas(W, H);
p.randomSeed(42);
};
p.draw = function() {
const colors = getThemeColors();
p.background(...colors.bg);
spawnTimer++;
if (spawnTimer > 42) {
particles.push(spawnParticle());
spawnTimer = 0;
}
pulseAmt *= 0.88;
let wellR = 28 + wellMass * 10 + pulseAmt * 12;
p.noStroke();
for (let r = wellR * 2.8; r > wellR * 0.4; r -= 10) {
let a = p.map(r, wellR * 2.8, wellR * 0.4, 0, 45);
p.fill(...colors.accent2, a);
p.circle(CX, CY, r);
}
p.fill(...colors.accent2, 95);
p.circle(CX, CY, wellR * 1.35);
p.fill(...colors.accent2, 220);
p.circle(CX, CY, 15);
for (let i = particles.length - 1; i >= 0; i--) {
let pt = particles[i];
pt.age++;
if (pt.state === 'active') {
let dx = CX - pt.x;
let dy = CY - pt.y;
let d = Math.sqrt(dx * dx + dy * dy);
let grav = (wellMass * 0.028) / Math.max(d * 0.032, 0.6);
pt.vx += (dx / d) * grav;
pt.vy += (dy / d) * grav;
pt.vx *= 0.975;
pt.vy *= 0.975;
pt.x += pt.vx;
pt.y += pt.vy;
if (d < wellR * 0.78) {
if (pt.isConfirming) {
pt.state = 'orbiting';
pt.orbitAngle = Math.atan2(pt.y - CY, pt.x - CX);
pt.orbitRadius = wellR * (0.85 + p.random(0.65));
captured++;
} else {
pt.state = 'deflected';
let outAngle = Math.atan2(pt.y - CY, pt.x - CX);
pt.vx = Math.cos(outAngle) * 2.2 + p.random(-0.4, 0.4);
pt.vy = Math.sin(outAngle) * 2.2 + p.random(-0.4, 0.4);
wellMass = Math.min(5.5, wellMass + 0.18);
pulseAmt = 1.0;
rejected++;
}
}
} else if (pt.state === 'orbiting') {
let spd = 0.013 / (pt.orbitRadius / 50);
pt.orbitAngle += spd;
pt.orbitRadius = Math.max(wellR * 0.52, pt.orbitRadius * 0.9985);
pt.x = CX + Math.cos(pt.orbitAngle) * pt.orbitRadius;
pt.y = CY + Math.sin(pt.orbitAngle) * pt.orbitRadius;
} else {
pt.x += pt.vx;
pt.y += pt.vy;
pt.vx *= 0.975;
pt.vy *= 0.975;
}
let col = pt.isConfirming ? colors.accent1 : colors.accent3;
let dim = pt.state === 'deflected';
p.noStroke();
p.fill(...col, dim ? 48 : 75);
p.circle(pt.x, pt.y, 10);
p.fill(...col, dim ? 75 : 200);
p.circle(pt.x, pt.y, 4);
if (pt.age > 520 || pt.x < -35 || pt.x > W + 35 || pt.y < -35 || pt.y > H + 35) {
particles.splice(i, 1);
}
}
p.noStroke();
p.fill(...colors.accent2, 130);
p.textAlign(p.CENTER);
p.textSize(9);
p.text('belief core', CX, CY + wellR + 17);
p.textAlign(p.LEFT);
p.textSize(8);
p.fill(...colors.accent1, 115);
p.text('integrated: ' + captured, 10, 14);
p.fill(...colors.accent3, 115);
p.text('rejected: ' + rejected, 10, 26);
p.noStroke();
p.fill(...colors.accent3, 50);
p.textAlign(p.CENTER);
p.textSize(8);
p.text('each rejection deepens the well', W / 2, H - 5);
};
};