Empty space is not empty: pairs of particles appear and annihilate constantly. Do that right at a horizon and sometimes one falls in while the other escapes, and the hole pays for it.
EXP-011
Move the cursor over the canvas — it is part of the simulation.
Drag your finger across the canvas — it is part of the simulation.
What to look for
The pace. It sits there for a long time barely radiating, and then the last stretch happens fast and ends in a burst. That asymmetry is the whole physics: the final second of a black hole releases more energy than everything before it.
The maths
T = ħc³/(8πGMk_B)
The Hawking temperature, inversely proportional to mass. A hole the mass of the Sun is at 60 nanokelvin, colder than the cosmic background, so it absorbs more than it radiates.
L ∝ 1/M²
Luminosity. Losing mass raises the temperature, which raises the output, which loses mass faster. The process runs away instead of settling.
t_evap ∝ M³
Integrating that gives the lifetime. A solar mass hole would take 10⁶⁷ years, far longer than the age of the universe. A mountain-mass one would be ending about now.
How it is built
Pairs are spawned at the horizon radius and separated: the outward one drifts away and fades, the inward one sinks. Mass is integrated with dM/dt proportional to minus one over M squared, which is what makes the collapse accelerate on its own. When the mass runs out there is a flash and it restarts.
The code, step by step
Cut straight from lab.js. These are the real lines that run above, not a simplified version.
01
Evaporation accelerates itself
const T = 60 / M;
// Luminosidad ∝ 1/M², así que dM/dt ∝ −1/M² y el final es abrupto
if (P.rate > 0) M -= (P.rate * 40) / (M * M);
if (M < 6) { flash = 1; M = P.mass; pairs = []; }
Temperature goes as one over mass and luminosity as one over mass squared, so dM/dt is proportional to minus one over M squared. Losing mass makes it hotter, which makes it lose mass faster. Nothing damps it.
02
One out, one in
pairs = pairs.filter(p => {
p.life += 0.016;
p.out += 0.8 + T * 6; // el que escapa se aleja
const ro = rs + p.out, ri = Math.max(0, rs - p.out * 0.55);
const fade = Math.max(0, 1 - p.out / (Math.min(w, h) * 0.45));
// El que escapa: energía positiva, se ve
ctx.fillStyle = `rgba(${acc},${(fade * 0.9).toFixed(3)})`;
ctx.beginPath();
ctx.arc(cx + ro * Math.cos(p.a), cy + ro * Math.sin(p.a), 1.8, 0, TAU); ctx.fill();
// El que cae: energía NEGATIVA, y por eso el agujero adelgaza
ctx.fillStyle = `rgba(235,238,245,${(fade * 0.4).toFixed(3)})`;
ctx.beginPath();
ctx.arc(cx + ri * Math.cos(p.a), cy + ri * Math.sin(p.a), 1.4, 0, TAU); ctx.fill();
return fade > 0.02;
Both members of the pair are drawn from the same spawn point on the horizon. The escaping one carries positive energy away; the infalling one carries negative energy in, and that is the accounting that makes the hole shrink.
Why it matters
Hawking derived this in 1974 and it broke something. General relativity says a black hole has no properties beyond mass, charge and spin, so if it evaporates completely the information about what fell in appears to be gone, which quantum mechanics forbids. That paradox is still open.
The whole thing
Everything above, in one piece. This is the entire experiment as it lives in lab.js.
FULL
lab.js · hawking
{ id: 'hawking',
name: L('Hawking radiation', 'Radiación de Hawking'),
note: L('Pairs born at the horizon. One escapes, one falls in, and the hole loses mass.',
'Pares que nacen en el horizonte. Uno escapa, otro cae, y el agujero pierde masa.'),
params: [
{ key: 'mass', label: L('Initial mass', 'Masa inicial'), min: 20, max: 120, step: 2, def: 80 },
{ key: 'rate', label: L('Evaporation rate', 'Ritmo de evaporación'), min: 0, max: 3, step: 0.05, def: 0.6 },
{ key: 'pairs', label: L('Pair density', 'Densidad de pares'), min: 2, max: 40, step: 1, def: 16 }
],
make() {
let M = 80, pairs = [], flash = 0;
return {
reset() { M = 0; pairs = []; flash = 0; },
step(ctx, w, h, t, acc, P, M0) {
if (M <= 0) M = P.mass;
const cx = w / 2, cy = h / 2;
// El horizonte crece con la masa: r_s = 2GM/c²
const rs = M * 0.9;
// Temperatura de Hawking: T = ħc³/8πGMk_B → T ∝ 1/M.
// Por eso evaporar CALIENTA: mientras menos masa, más radia.
const T = 60 / M;
// Luminosidad ∝ 1/M², así que dM/dt ∝ −1/M² y el final es abrupto
if (P.rate > 0) M -= (P.rate * 40) / (M * M);
if (M < 6) { flash = 1; M = P.mass; pairs = []; }
flash *= 0.9;
// Esfera de fotones en 1.5 r_s: la última órbita posible de la luz
ctx.strokeStyle = `rgba(${acc},0.22)`; ctx.lineWidth = 1;
ctx.setLineDash([3, 5]);
ctx.beginPath(); ctx.arc(cx, cy, rs * 1.5, 0, TAU); ctx.stroke();
ctx.setLineDash([]);
// Nacimiento de pares en el horizonte
const want = P.pairs | 0;
if (pairs.length < want && Math.random() < 0.5) {
const a = Math.random() * TAU;
pairs.push({ a, r: rs, out: 0, life: 0 });
}
pairs = pairs.filter(p => {
p.life += 0.016;
p.out += 0.8 + T * 6; // el que escapa se aleja
const ro = rs + p.out, ri = Math.max(0, rs - p.out * 0.55);
const fade = Math.max(0, 1 - p.out / (Math.min(w, h) * 0.45));
// El que escapa: energía positiva, se ve
ctx.fillStyle = `rgba(${acc},${(fade * 0.9).toFixed(3)})`;
ctx.beginPath();
ctx.arc(cx + ro * Math.cos(p.a), cy + ro * Math.sin(p.a), 1.8, 0, TAU); ctx.fill();
// El que cae: energía NEGATIVA, y por eso el agujero adelgaza
ctx.fillStyle = `rgba(235,238,245,${(fade * 0.4).toFixed(3)})`;
ctx.beginPath();
ctx.arc(cx + ri * Math.cos(p.a), cy + ri * Math.sin(p.a), 1.4, 0, TAU); ctx.fill();
return fade > 0.02;
});
// El horizonte: un disco perfectamente negro con borde
const grd = ctx.createRadialGradient(cx, cy, rs * 0.9, cx, cy, rs * 1.25);
grd.addColorStop(0, 'rgba(0,0,0,1)');
grd.addColorStop(1, 'rgba(0,0,0,0)');
ctx.fillStyle = grd;
ctx.beginPath(); ctx.arc(cx, cy, rs * 1.25, 0, TAU); ctx.fill();
ctx.fillStyle = '#000';
ctx.beginPath(); ctx.arc(cx, cy, rs, 0, TAU); ctx.fill();
ctx.strokeStyle = `rgba(${acc},${(0.5 + T * 2).toFixed(2)})`; ctx.lineWidth = 1.4;
ctx.beginPath(); ctx.arc(cx, cy, rs, 0, TAU); ctx.stroke();
if (flash > 0.02) { // el destello final
ctx.fillStyle = `rgba(${acc},${(flash * 0.5).toFixed(3)})`;
ctx.beginPath(); ctx.arc(cx, cy, rs * (1 + (1 - flash) * 6), 0, TAU); ctx.fill();
}
ctx.font = '11px monospace';
ctx.fillStyle = `rgba(${acc},0.55)`;
ctx.fillText('T ∝ 1/M L ∝ 1/M² lifetime ∝ M³', 12, h - 28);
ctx.fillStyle = `rgba(${acc},0.95)`;
ctx.fillText('M = ' + M.toFixed(1) + ' T = ' + T.toFixed(3) + ' (rising)', 12, h - 12);
}
};
}
},