The Empathy Trap
About This Sketch
A visualization of the identifiable victim effect — the psychological phenomenon at the heart of empathy's failure as a moral guide. One vivid, glowing particle drifts at the center of attention while 280 dim statistical lives scatter across the field, nearly invisible. The eye tracks what it can name. The field remains unseen.
Accompanies the post "The Empathy Trap," which argues that emotional empathy is a cognitive bias, not a moral virtue.
Algorithm
A single bright particle (the identifiable victim) drifts slowly near canvas center, surrounded by 280 dim, nearly invisible particles (the statistical field). The bright particle has layered glow rings that draw the eye; the dim particles move via Perlin noise fields but remain barely perceptible. This visualizes the identifiable victim effect: one vivid case commands attention while the larger field of suffering stays invisible.
Pseudocode
SETUP:
Initialize one bright particle at canvas center
Spawn 280 dim particles at random positions
DRAW (every frame):
Get current theme colors
Fade background slightly (trail effect)
For each dim particle:
Move via Perlin noise field
Draw with very low opacity
Move bright particle via slow Perlin noise + gentle centering pull
Draw bright particle with glow halo layers
Draw caption at bottom
Source Code
let sketch = function(p) {
// Identifiable victim effect visualization
// One bright, named particle drifts slowly at center attention
// Hundreds of dim statistical particles scatter around the field
// The eye tracks the bright one; the field of dim ones fades into background
// Represents how empathy sees one, not thousands
let bright;
let dimParticles = [];
let time = 0;
p.setup = function() {
p.createCanvas(400, 300);
p.colorMode(p.RGB);
bright = {
x: 200,
y: 148,
nx: p.random(1000),
ny: p.random(1000),
vx: 0,
vy: 0
};
for (let i = 0; i < 280; i++) {
dimParticles.push({
x: p.random(400),
y: p.random(300),
nx: p.random(10000),
ny: p.random(10000),
vx: 0,
vy: 0,
size: p.random(1.2, 2.2),
speed: p.random(0.15, 0.4)
});
}
};
p.draw = function() {
const colors = getThemeColors();
p.noStroke();
p.fill(...colors.bg, 40);
p.rect(0, 0, 400, 300);
time += 0.007;
for (let dp of dimParticles) {
let angle = p.noise(dp.nx + time * 0.3, dp.ny + time * 0.25) * p.TWO_PI * 2;
dp.vx = p.lerp(dp.vx, p.cos(angle) * dp.speed, 0.04);
dp.vy = p.lerp(dp.vy, p.sin(angle) * dp.speed, 0.04);
dp.x += dp.vx;
dp.y += dp.vy;
if (dp.x < 0) dp.x = 400;
if (dp.x > 400) dp.x = 0;
if (dp.y < 0) dp.y = 300;
if (dp.y > 300) dp.y = 0;
p.noStroke();
p.fill(...colors.accent3, 55);
p.circle(dp.x, dp.y, dp.size);
}
let brightAngle = p.noise(bright.nx + time * 0.18, bright.ny + time * 0.14) * p.TWO_PI * 2;
bright.vx = p.lerp(bright.vx, p.cos(brightAngle) * 0.55, 0.03);
bright.vy = p.lerp(bright.vy, p.sin(brightAngle) * 0.55, 0.03);
bright.vx += (200 - bright.x) * 0.001;
bright.vy += (148 - bright.y) * 0.001;
bright.x += bright.vx;
bright.y += bright.vy;
for (let r = 28; r >= 5; r -= 4) {
let a = p.map(r, 5, 28, 140, 0);
p.noStroke();
p.fill(...colors.accent2, a);
p.circle(bright.x, bright.y, r);
}
p.fill(...colors.accent2, 255);
p.noStroke();
p.circle(bright.x, bright.y, 5.5);
p.noStroke();
p.fill(...colors.accent3, 100);
p.textAlign(p.CENTER);
p.textSize(9);
p.text('one story seen clearly — the field unseen', 200, 293);
};
};