Where ideas percolate and thoughts brew

The Problem with Empathy

About This Sketch

A generative visualization contrasting empathy-driven and compassion-driven resource allocation. Small agents move between targets representing people or causes in need. Bright circles are emotionally salient (compelling stories, photogenic victims). Faded circles have low emotional appeal but high actual need. Watch how empathy mode clusters resources around visible targets while ignoring greater needs, versus compassion mode which allocates based on actual impact.

Algorithm

This sketch visualizes the difference between empathy-driven and compassion-driven resource allocation. Small moving dots represent helpers/agents who can provide assistance. Larger circles represent people or causes in need. The bright, visible circles represent emotionally salient targets (photogenic victims, compelling stories). The faded circles represent targets with low emotional appeal but high actual need. The visualization alternates between two modes every 5 seconds: **Empathy Mode**: Agents are attracted only to emotionally salient targets (bright circles), regardless of actual need. Watch how resources cluster around the visible, compelling cases while high-need but non-emotional targets (faded circles) get ignored. **Compassion Mode**: Agents distribute themselves based on actual need, prioritizing the most needy targets first regardless of emotional salience. Resources are allocated more fairly and effectively. The fill inside each target circle grows as it receives help, allowing you to see the dramatic difference in outcomes between empathy-driven and compassion-driven allocation. This accompanies the blog post "The Problem with Empathy" which argues that empathy, while valuable for connection, is a flawed tool for moral decision-making and resource allocation.

Pseudocode

SETUP:
  Create 15 helper agents at random positions
  Create needy targets with varying combinations of:
    - Emotional salience (high = visible/bright, low = faded)
    - Actual need (represented by circle size)

DRAW (every frame):
  Get current theme colors for rendering

  IF time to switch modes (every 300 frames):
    Toggle all agents between empathy/compassion mode

  FOR each target:
    Draw circle sized by actual need
    Use bright color if emotionally salient
    Use faded color if not emotionally salient
    Show progress fill based on help received

  FOR each agent:
    IF in empathy mode:
      Find closest emotionally salient target
      Move toward it
    ELSE (compassion mode):
      Find target with highest unmet need
      Move toward it

    Apply velocity with damping
    Check if close enough to help any target
    If yes, increment that target's help counter

    Draw agent (colored by mode)

  Display current mode indicator
  Display legend explaining circle meanings

Source Code

let sketch = function(p) {
    let agents = [];
    let needyTargets = [];

    class Agent {
        constructor() {
            this.x = p.random(p.width);
            this.y = p.random(p.height);
            this.vx = 0;
            this.vy = 0;
            this.empathyMode = true;
        }

        update() {
            const colors = getThemeColors();

            if (this.empathyMode) {
                // Empathy mode: attracted to emotionally salient target
                let closest = null;
                let closestDist = Infinity;

                for (let target of needyTargets) {
                    if (target.emotional) {
                        let d = p.dist(this.x, this.y, target.x, target.y);
                        if (d < closestDist) {
                            closestDist = d;
                            closest = target;
                        }
                    }
                }

                if (closest) {
                    let angle = p.atan2(closest.y - this.y, closest.x - this.x);
                    this.vx += p.cos(angle) * 0.15;
                    this.vy += p.sin(angle) * 0.15;
                }
            } else {
                // Compassion mode: distributes evenly to all targets
                let mostNeedy = null;
                let highestNeed = -1;

                for (let target of needyTargets) {
                    if (target.actualNeed > highestNeed && target.helped < 1) {
                        highestNeed = target.actualNeed;
                        mostNeedy = target;
                    }
                }

                if (mostNeedy) {
                    let angle = p.atan2(mostNeedy.y - this.y, mostNeedy.x - this.x);
                    this.vx += p.cos(angle) * 0.15;
                    this.vy += p.sin(angle) * 0.15;
                }
            }

            // Damping
            this.vx *= 0.95;
            this.vy *= 0.95;

            this.x += this.vx;
            this.y += this.vy;

            // Bounds
            this.x = p.constrain(this.x, 5, p.width - 5);
            this.y = p.constrain(this.y, 5, p.height - 5);

            // Check if helping any target
            for (let target of needyTargets) {
                let d = p.dist(this.x, this.y, target.x, target.y);
                if (d < 15) {
                    target.helped += 0.02;
                }
            }
        }

        draw() {
            const colors = getThemeColors();
            if (this.empathyMode) {
                p.fill(...colors.accent2, 200);
            } else {
                p.fill(...colors.accent1, 200);
            }
            p.noStroke();
            p.circle(this.x, this.y, 8);
        }
    }

    class NeedyTarget {
        constructor(emotional, actualNeed) {
            this.x = p.random(50, p.width - 50);
            this.y = p.random(50, p.height - 50);
            this.emotional = emotional; // How emotionally salient
            this.actualNeed = actualNeed; // How much they actually need help
            this.helped = 0;
        }

        draw() {
            const colors = getThemeColors();

            // Size based on actual need
            let size = 15 + this.actualNeed * 15;

            // Color based on how much help received vs need
            let helpRatio = this.helped / this.actualNeed;

            if (this.emotional) {
                // Emotional targets: bright and visible
                p.fill(...colors.accent2, 150);
                p.stroke(...colors.accent2);
            } else {
                // Non-emotional targets: faded and less visible
                p.fill(...colors.accent3, 100);
                p.stroke(...colors.accent3, 150);
            }

            p.strokeWeight(2);
            p.circle(this.x, this.y, size);

            // Show help progress
            if (this.helped > 0) {
                p.fill(...colors.accent1, 150);
                p.noStroke();
                let progressSize = size * p.min(helpRatio, 1);
                p.circle(this.x, this.y, progressSize);
            }
        }
    }

    p.setup = function() {
        p.createCanvas(400, 300);
        p.colorMode(p.RGB);

        // Create agents (helpers)
        for (let i = 0; i < 15; i++) {
            agents.push(new Agent());
        }

        // Create needy targets
        // Emotional but low need
        needyTargets.push(new NeedyTarget(true, 0.3));
        needyTargets.push(new NeedyTarget(true, 0.4));

        // Non-emotional but high need
        needyTargets.push(new NeedyTarget(false, 0.9));
        needyTargets.push(new NeedyTarget(false, 0.8));
        needyTargets.push(new NeedyTarget(false, 0.7));

        // Mixed
        needyTargets.push(new NeedyTarget(false, 0.5));
    };

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

        // Switch modes periodically
        if (p.frameCount % 300 === 0) {
            for (let agent of agents) {
                agent.empathyMode = !agent.empathyMode;
            }
        }

        // Draw targets
        for (let target of needyTargets) {
            target.draw();
        }

        // Update and draw agents
        for (let agent of agents) {
            agent.update();
            agent.draw();
        }

        // Draw mode indicator
        p.fill(...colors.accent3);
        p.noStroke();
        p.textAlign(p.LEFT, p.TOP);
        p.textSize(11);
        let modeText = agents[0].empathyMode ? "Empathy Mode" : "Compassion Mode";
        p.text(modeText, 10, 10);

        // Legend
        p.textSize(9);
        p.text("Bright circles: Emotionally salient", 10, 270);
        p.text("Faded circles: Low emotional appeal, high need", 10, 283);
    };
};