The Commitment Trap
About This Sketch
Two particles start from the same point. One stays free to update โ it quickly finds and orbits the optimum. The other accumulates commitment inertia over time, making course correction progressively harder and harder. A visualization of why the cost of updating a committed direction compounds the longer you wait. Accompanies "The Commitment Trap."
Algorithm
Two particles start at the same position with the same initial velocity, both
pulled toward a central "optimum" point.
The free particle updates quickly: strong pull, high damping, rapid convergence
to the optimum.
The committed particle accumulates "commitment inertia" over time โ its pull
toward the optimum is divided by its growing mass, and its damping decreases.
The longer it has been committed, the harder course correction becomes.
A growing circle size on the committed particle visualizes accumulated inertia.
A progress bar shows commitment mass. Trails show diverging paths from the same
starting point. This accompanies "The Commitment Trap."
Pseudocode
SETUP:
Initialize two particles at same position with same velocity
committed.mass = 1.0 (will grow)
DRAW (every frame):
Get theme colors
Clear background
committed.mass = min(6.0, 1.0 + frame * 0.008)
FREE PARTICLE:
Pull toward optimum with full strength
Damp velocity strongly
Update position
COMMITTED PARTICLE:
Pull toward optimum divided by mass (harder to update)
Damp less as mass grows (inertia)
Update position
Draw optimum glow and center point
Draw particle trails (fading)
Draw particles (committed grows larger over time)
Draw commitment inertia bar
Draw legend and caption
Source Code
let sketch = function(p) {
// The Commitment Trap: a particle locked to a trajectory by "commitment gravity."
// A second free particle is shown side-by-side: same setup, but no commitment
// mass โ it quickly finds and orbits the optimum.
// Caption: "inertia compounds ยท late updates cost more"
const W = 400, H = 300;
const OPT_X = W / 2, OPT_Y = H / 2;
const OPT_R = 10;
let committed = {
x: 60, y: 60, vx: 1.4, vy: 0.7,
mass: 1.0,
trail: []
};
let free = {
x: 60, y: 60, vx: 1.4, vy: 0.7,
trail: []
};
let frame = 0;
const MAX_TRAIL = 55;
p.setup = function() {
p.createCanvas(W, H);
p.randomSeed(3);
};
p.draw = function() {
const colors = getThemeColors();
p.background(...colors.bg);
frame++;
committed.mass = p.min(6.0, 1.0 + frame * 0.008);
{
let dx = OPT_X - free.x;
let dy = OPT_Y - free.y;
let d = Math.sqrt(dx * dx + dy * dy) + 1;
let pull = 0.18;
free.vx += (dx / d) * pull;
free.vy += (dy / d) * pull;
free.vx *= 0.91;
free.vy *= 0.91;
free.x += free.vx;
free.y += free.vy;
if (free.x < 5) { free.x = 5; free.vx *= -0.6; }
if (free.x > W - 5) { free.x = W - 5; free.vx *= -0.6; }
if (free.y < 5) { free.y = 5; free.vy *= -0.6; }
if (free.y > H - 5) { free.y = H - 5; free.vy *= -0.6; }
free.trail.push({ x: free.x, y: free.y });
if (free.trail.length > MAX_TRAIL) free.trail.shift();
}
{
let dx = OPT_X - committed.x;
let dy = OPT_Y - committed.y;
let d = Math.sqrt(dx * dx + dy * dy) + 1;
let pull = 0.18 / committed.mass;
committed.vx += (dx / d) * pull;
committed.vy += (dy / d) * pull;
let damp = p.lerp(0.91, 0.97, (committed.mass - 1) / 5);
committed.vx *= damp;
committed.vy *= damp;
committed.x += committed.vx;
committed.y += committed.vy;
if (committed.x < 5) { committed.x = 5; committed.vx *= -0.6; }
if (committed.x > W - 5) { committed.x = W - 5; committed.vx *= -0.6; }
if (committed.y < 5) { committed.y = 5; committed.vy *= -0.6; }
if (committed.y > H - 5) { committed.y = H - 5; committed.vy *= -0.6; }
committed.trail.push({ x: committed.x, y: committed.y });
if (committed.trail.length > MAX_TRAIL) committed.trail.shift();
}
for (let r = 40; r > 0; r -= 6) {
let a = p.map(r, 0, 40, 30, 0);
p.noStroke();
p.fill(...colors.accent1, a);
p.circle(OPT_X, OPT_Y, r * 2);
}
p.fill(...colors.accent1);
p.noStroke();
p.circle(OPT_X, OPT_Y, OPT_R * 2);
p.fill(...colors.bg);
p.circle(OPT_X, OPT_Y, OPT_R);
p.textAlign(p.CENTER);
p.textSize(8);
p.fill(...colors.accent1, 130);
p.noStroke();
p.text('optimum', OPT_X, OPT_Y + OPT_R + 10);
p.noFill();
for (let i = 1; i < free.trail.length; i++) {
let t = i / free.trail.length;
let a = t * 160;
p.stroke(...colors.accent2, a);
p.strokeWeight(1.5);
p.line(free.trail[i-1].x, free.trail[i-1].y, free.trail[i].x, free.trail[i].y);
}
for (let i = 1; i < committed.trail.length; i++) {
let t = i / committed.trail.length;
let a = t * 120;
p.stroke(...colors.accent3, a);
p.strokeWeight(1.5);
p.line(committed.trail[i-1].x, committed.trail[i-1].y, committed.trail[i].x, committed.trail[i].y);
}
p.noStroke();
p.fill(...colors.accent2, 35);
p.circle(free.x, free.y, 16);
p.fill(...colors.accent2, 230);
p.circle(free.x, free.y, 8);
let cSize = p.map(committed.mass, 1, 6, 8, 18);
p.fill(...colors.accent3, 30);
p.circle(committed.x, committed.y, cSize + 10);
p.fill(...colors.accent3, 185);
p.circle(committed.x, committed.y, cSize);
let barX = 10, barY = 12, barW = 90, barH = 7;
p.noStroke();
p.fill(...colors.accent3, 28);
p.rect(barX, barY, barW, barH, 2);
let massFraction = (committed.mass - 1) / 5;
p.fill(...colors.accent3, 155);
p.rect(barX, barY, barW * massFraction, barH, 2);
p.fill(...colors.accent3, 130);
p.textSize(8);
p.textAlign(p.LEFT);
p.text('commitment inertia', barX, barY - 2);
p.textSize(8);
p.fill(...colors.accent2, 190);
p.circle(W - 110, 16, 6);
p.fill(...colors.accent2, 160);
p.textAlign(p.LEFT);
p.text('free to update', W - 102, 20);
p.fill(...colors.accent3, 160);
p.circle(W - 110, 30, 8);
p.fill(...colors.accent3, 130);
p.text('committed', W - 102, 34);
p.noStroke();
p.fill(...colors.accent3, 50);
p.textAlign(p.CENTER);
p.textSize(8);
p.text('inertia compounds \u00b7 late updates cost more', W / 2, H - 5);
};
};