The Optionality Trap
About This Sketch
A split canvas showing optionality versus commitment. On the left, a particle oscillates between three dim targets — never fully reaching any of them. On the right, a particle commits to a single destination: the target blooms as it arrives, then new connections cascade outward. The contrast captures the core argument: hedging spreads attention thin and keeps everything dim, while commitment concentrates energy and unlocks compounding.
Algorithm
Split-canvas animation contrasting two decision strategies. Left side shows a
particle cycling between three dim option nodes, drawing faint connections to
each but never arriving. Right side shows a particle committing to a single
target: it moves with eased motion, the target grows brighter and larger as the
particle approaches, and on arrival a cascade of new connections blooms outward.
The animation resets every 12 seconds.
Pseudocode
SETUP:
Place 3 option nodes on left half of canvas
Define 4 branch vectors for committed target cascade
Set committed target position on right half
DRAW:
Increment time counter; reset at 12s
Draw dividing line and side labels
LEFT (hedging):
Compute oscillating position cycling through options (smoothstepped lerp)
Draw dim lines from hedger to all option nodes
Draw dim option nodes
Draw dim hedger particle
RIGHT (committed):
Compute progress (0→1 over 5 seconds, then hold)
Apply smoothstep easing to committer position
Draw expanding glow rings around target (scale with progress)
Draw brightening target core
If progress > 75%: animate cascade branches sprouting from target
Draw trail line from committer to target
Draw bright committer particle at current position
Source Code
let t = 0;
let options = [];
let hedgerX, hedgerY;
let targetX = 310, targetY = 140;
let startX = 240, startY = 165;
let branches = [];
p.setup = function() {
p.createCanvas(400, 300);
options = [
{x: 70, y: 75},
{x: 135, y: 215},
{x: 48, y: 195}
];
branches = [
{dx: 38, dy: -52},
{dx: 58, dy: 22},
{dx: -18, dy: 58},
{dx: 52, dy: -22}
];
};
p.draw = function() {
const colors = getThemeColors();
p.background(...colors.bg);
t += 0.015;
if (t > 12) t = 0;
p.stroke(...colors.accent3, 50);
p.strokeWeight(1);
p.line(200, 15, 200, 280);
p.noStroke();
p.textSize(9);
p.textAlign(p.CENTER);
p.fill(...colors.accent3, 160);
p.text("optionality", 100, 272);
p.text("commitment", 300, 272);
let phase = (t * 0.75) % options.length;
let fromIdx = Math.floor(phase);
let toIdx = (fromIdx + 1) % options.length;
let raw = phase - fromIdx;
let eased = raw * raw * (3 - 2 * raw);
hedgerX = p.lerp(options[fromIdx].x, options[toIdx].x, eased);
hedgerY = p.lerp(options[fromIdx].y, options[toIdx].y, eased);
for (let i = 0; i < options.length; i++) {
p.stroke(...colors.accent1, 45);
p.strokeWeight(0.8);
p.line(hedgerX, hedgerY, options[i].x, options[i].y);
p.noStroke();
p.fill(...colors.accent2, 55);
p.ellipse(options[i].x, options[i].y, 24, 24);
p.fill(...colors.accent2, 90);
p.ellipse(options[i].x, options[i].y, 13, 13);
}
p.noStroke();
p.fill(...colors.accent1, 95);
p.ellipse(hedgerX, hedgerY, 11, 11);
let progress = Math.min(t / 5.0, 1.0);
let easeP = progress * progress * (3 - 2 * progress);
let cx = p.lerp(startX, targetX, easeP);
let cy = p.lerp(startY, targetY, easeP);
p.noStroke();
p.fill(...colors.accent2, 18 * easeP);
p.ellipse(targetX, targetY, 95 + 30 * easeP, 95 + 30 * easeP);
p.fill(...colors.accent2, 35 * easeP);
p.ellipse(targetX, targetY, 55 + 20 * easeP, 55 + 20 * easeP);
p.fill(...colors.accent2, 85 + 115 * easeP);
p.ellipse(targetX, targetY, 20 + 14 * easeP, 20 + 14 * easeP);
if (progress > 0.75) {
let bAlpha = (progress - 0.75) / 0.25;
for (let i = 0; i < branches.length; i++) {
let delay = i * 0.2;
let bp = Math.max(0, bAlpha - delay);
if (bp > 0) {
let bpE = Math.min(bp * 3, 1);
let bx = targetX + branches[i].dx * bpE;
let by = targetY + branches[i].dy * bpE;
p.stroke(...colors.accent1, 110 * bpE);
p.strokeWeight(1);
p.line(targetX, targetY, bx, by);
p.noStroke();
p.fill(...colors.accent1, 170 * bpE);
p.ellipse(bx, by, 9 * bpE, 9 * bpE);
}
}
}
p.stroke(...colors.accent1, 70 + 50 * easeP);
p.strokeWeight(1);
p.line(cx, cy, targetX, targetY);
p.noStroke();
p.fill(...colors.accent1, 225);
p.ellipse(cx, cy, 12, 12);
};