The page you are reading looks like an ordinary physics explainer: fifteen equations, two figures, a table of contents. It also hosts a black hole, already live in the corner. Grab any block of content (this very paragraph will do) and throw it. It will arc into orbit, precess, spiral inward, stretch into spaghetti, blush red, dim, and freeze just above a perfectly black disk. Then it vanishes. At that moment the disk grows, rings like a struck bell, and flares with the light of its meal.
你正在读的这个页面,看上去是一篇普通的物理科普:十五条方程、两张图、一份目录。但它还养着一个黑洞,此刻就在角落里运行着。 抓起任何一块内容(就拿你正在读的这一段试试),甩出去。 它会划一道弧线进入轨道,近日点进动,盘旋着向内坠落,被拉成面条,泛红、变暗, 最后冻结在一个纯黑圆盘上方一线之处。然后它消失。就在那一刻,圆盘长大一圈, 像被敲响的钟一样震荡,并为这顿晚餐迸出一次耀发。
None of that is keyframed animation. Every frame, the browser numerically integrates the geodesic equations of the Schwarzschild metric, the exact solution of Einstein’s field equations for the spacetime outside every non-rotating black hole in the universe. The block you threw plays the role of not one test particle but a whole mesh of them, one geodesic per vertex. The screen is treated as the black hole’s equatorial plane for everything you throw. The hole itself is rendered by tracing a light ray backward through the exact geometry for every pixel, with the disk tilted at 77° like the iconic renderings. Your mouse throw sets the conserved energy and angular momentum. General relativity does the rest.
这一切都不是关键帧动画。每一帧,浏览器都在数值积分史瓦西度规的测地线方程。 它正是爱因斯坦场方程的那个精确解,描述宇宙中每一个不旋转黑洞外部的时空。 你甩出去的那块内容,扮演的不是一颗试验粒子,而是一整张试验粒子网格:每个顶点一条测地线。 对你抛出的所有东西,屏幕被当作黑洞的赤道面;黑洞本身则是逐像素向后追踪光线、 穿过精确几何渲染出来的,吸积盘倾斜 77°,正如那些标志性的模拟图像。 你甩鼠标的那一下,定下了守恒的能量与角动量。剩下的,交给广义相对论。
The rest of this page is the lab notebook, and every entry in it is edible. It walks through all fifteen equations the demo uses, in the order they act on a doomed paragraph: the stage (the metric), the fall (geodesics and frozen time), the look of the fall (redshift and tides), the aftermath (horizon growth, ringdown, flares), and the permanent scenery (lensing, the photon ring, the accretion disk). Each section ends with where the equation lives in the source, using the real function names, and what it makes happen on screen.
页面剩下的部分是实验记录本,而且每一条记录都可以吃。它按方程作用在一段注定毁灭的文字上的顺序, 讲完演示用到的全部十五条方程:舞台(度规)、坠落(测地线与冻结的时间)、坠落的样子(红移与潮汐)、 余波(视界生长、铃宕、耀发),以及永久的布景(引力透镜、光子环、吸积盘)。 每一节的结尾都会告诉你:这条方程住在源码的哪个函数里(用真实的函数名), 以及它让屏幕上发生了什么。
The honesty box诚实清单
A demo like this earns trust only if it is explicit about where the physics is exact and where it cheats for the sake of a 60 fps web page. Here is the full ledger.
这样的演示要赢得信任,就必须讲清楚:哪里的物理是精确的,哪里为了一张 60 fps 的网页而作了弊。下面是完整的账本。
Exact GR (in pixel units)精确广相(像素单位)
- Equatorial Schwarzschild geodesics:
accel()is the exact radial equation, integrated by step-doubling adaptive RK4 with stored conserved \( E, L \) and a constraint projection each step; energy drift < 10−9 over 5×104 steps in the unit tests. - The coordinate-time step \( d\tau = f\,\Delta t / E \): the freeze at the horizon is the real effect, not an easing curve.
- Redshift factor \( \sqrt{f} \) and the static-observer hover acceleration
\( M / (r^2 \sqrt{f}) \) in
hoverAccel(). - Photon trajectories: near the hole the shader integrates the exact null geodesic
per pixel (Binet \( u'' + u = 3Mu^2 \) in vector form); far from it, pixels take
the weak-field shortcut of equation 9. Lensing, the shadow, and the
photon ring all emerge from the integration:
\( b_c = (3\sqrt{3}/2)\,r_s \) is measured off the screen, not painted
(
bCrit()survives as the theory value and the 2D fallback). - The disk’s brightness asymmetry: the circular-orbit
\( g = \sqrt{1-3M/r}\,/\,(1-\beta\mu) \) (gravitational redshift plus
Doppler), with observed brightness \( \propto g^3 \) in
diskEmission(). - Tidal warping of falling elements: every vertex of the released mesh rides its own geodesic, so spaghettification is emergent geodesic deviation, not a painted transform.
- ISCO at \( 3\,r_s \) (
isco()) and the Keplerian \( \Omega = \sqrt{M/r^3} \): exact in Schwarzschild coordinates. The Shakura–Sunyaev profile \( T(r) \) indiskTemp()is that model’s textbook formula, rendered verbatim. - Quasinormal frequency \( M\omega = 0.37367 - 0.08896\,i \) (
QNM) with its physical scaling: pitch \( \propto 1/M \), decay time \( \propto M \). - Accretion efficiency \( \eta = 1 - \sqrt{8/9} \approx 5.7\% \)
(
ETA_ISCO).
- 赤道面史瓦西测地线:
accel()是精确的径向方程,由步长加倍的自适应 RK4 积分,守恒量 \( E, L \) 显式存储,每步做一次约束投影;单元测试中 5×104 步的能量漂移 < 10−9。 - 坐标时步长 \( d\tau = f\,\Delta t / E \):视界处的冻结是真实效应,不是缓动曲线。
- 红移因子 \( \sqrt{f} \),以及静止观察者的悬停加速度
\( M / (r^2 \sqrt{f}) \)(
hoverAccel())。 - 光子轨迹:靠近黑洞时,着色器逐像素积分精确的零测地线(Binet 方程 \( u'' + u = 3Mu^2 \)
的矢量形式);远处的像素则走方程 9 的弱场近似。透镜、黑影和光子环都是从积分中长出来的:
\( b_c = (3\sqrt{3}/2)\,r_s \) 是在屏幕上量出来的,不是画上去的
(
bCrit()保留为理论值和 2D 降级)。 - 盘面的亮度不对称:圆轨道的 \( g = \sqrt{1-3M/r}\,/\,(1-\beta\mu) \)
(引力红移加多普勒),观测亮度 \( \propto g^3 \),在
diskEmission()里。 - 下落元素的潮汐形变:释放的网格每个顶点各骑一条测地线, 面条化是涌现出来的测地偏离,不是画上去的形变。
- ISCO 在 \( 3\,r_s \)(
isco())与开普勒角速度 \( \Omega = \sqrt{M/r^3} \):在史瓦西坐标下精确。Shakura–Sunyaev 温度轮廓 \( T(r) \)(diskTemp())则是该模型的标准公式,照原样实现。 - 似正规模频率 \( M\omega = 0.37367 - 0.08896\,i \)(
QNM) 及其物理标度:音高 \( \propto 1/M \),衰减时间 \( \propto M \)。 - 吸积效率 \( \eta = 1 - \sqrt{8/9} \approx 5.7\% \)(
ETA_ISCO)。
Artistic license艺术加工
- The ray tracer uses semi-implicit Euler with at most
MAXSTEP = 168steps per pixel, not RK4: the shadow edge is a few pixels soft, and step-exhausted rays are counted as captured. - The camera is orthographic (parallel rays from infinity), and the disk is a thin, planar, fbm-textured annulus: no radiative transfer, no volumetric structure.
- Mixed perspective, on purpose: thrown elements move in the screen plane
(top-down equatorial dynamics) while the hole and disk are ray-traced at a
77° inclination (
u_tilt) like the iconic renderings; the elements’ own light is not itself ray-traced. RINGDOWN_TSCALE = 20slows the ringdown twenty-fold. The real oscillation of the demo’s hole (∼12 Hz, gone in ∼56 ms) would be invisible.- The \( (v/c)^4 \) drag in
stepBody()is a gameplay term inspired by 2.5PN gravitational-wave reaction, with a tunabledragK. Real radiation reaction is not this formula. DISK_SPEED = 0.35slows the disk pattern’s clock so the differential rotation reads as texture rather than strobe.- Element snapshots (computed-style inlining + SVG
foreignObject) have limits: webfonts fall back to system fonts, and external images taint the canvas; tainted or failed snapshots drop to the rigid CSS-transform path. - The HUD entropy row’s “1 px = Planck length” conversion is a joke calibration, flagged as such on the HUD itself.
- The swallow threshold \( \sqrt{f} \lt 0.12 \) (
SWALLOW_REDSHIFT) truncates an infinite asymptotic freeze so the page can finish eating; throws are clamped to \( 0.985c \).
- 光线追踪用半隐式欧拉,每像素至多
MAXSTEP = 168步,不是 RK4: 影子边缘有几个像素的软边,步数耗尽的光线一律按被俘获处理。 - 相机是正交投影(来自无穷远的平行光),盘是一圈薄的、平面的、fbm 纹理圆环: 没有辐射转移,没有立体结构。
- 透视是故意混搭的:被甩的元素在屏幕平面内运动(俯视的赤道面动力学),
而黑洞和盘以 77° 倾角(
u_tilt)做光线追踪,像那些标志性的模拟图像; 元素自己发的光并没有被追踪。 RINGDOWN_TSCALE = 20把铃宕放慢二十倍。演示黑洞真实的振荡 (约 12 Hz,约 56 ms 内消失)根本来不及看见。stepBody()里的 \( (v/c)^4 \) 阻尼是游戏项,灵感来自 2.5PN 引力波反作用,系数dragK可调。真实的辐射反作用不是这个公式。DISK_SPEED = 0.35放慢了盘面纹理的时钟, 让差速旋转读起来像纹理,而不是频闪。- 元素快照(内联计算样式 + SVG
foreignObject)有局限: webfont 会退回系统字体,外链图片会污染画布;被污染或失败的快照退回刚体 CSS 变换路径。 - HUD 熵行的"1 px = 普朗克长度"换算是玩笑标定,HUD 上自己就标明了。
- 吞噬阈值 \( \sqrt{f} \lt 0.12 \)(
SWALLOW_REDSHIFT)截断了无限的渐近冻结, 好让页面能把饭吃完;投掷速度钳制在 \( 0.985c \)。
1The Schwarzschild metric: the stage史瓦西度规:舞台
A metric is the Pythagorean theorem for spacetime: feed it two nearby events, it returns the “distance” between them. Around a mass the recipe warps. For a non-rotating black hole the entire warp is carried by one scalar function, \( f(r) = 1 - r_s/r \). Karl Schwarzschild found it in 1915, weeks after Einstein published the field equations. Read the two places \( f \) appears. It multiplies \( dt^2 \): clocks deep in the well tick slowly, and at \( r = r_s \) they appear to stop. It divides \( dr^2 \): radial rulers stretch. Every effect on this page (the frozen fall, the reddening, the light bending, even the pitch of the ringdown) is this one function expressing itself in different ways.
度规是时空版的勾股定理:喂给它两个相邻事件,它吐出二者之间的"距离"。在质量附近, 这份配方会扭曲。对一个不旋转的黑洞,全部扭曲都装在一个标量函数里: \( f(r) = 1 - r_s/r \)。它由卡尔·史瓦西于 1915 年找到,距爱因斯坦发表场方程只有几周。 看 \( f \) 出现的两个位置。它乘在 \( dt^2 \) 上:井底深处的钟走得慢,到 \( r = r_s \) 处看起来干脆停摆。它除在 \( dr^2 \) 下:径向的尺子被拉长。这个页面上的每一种效应 (冻结的坠落、泛红、光线弯折、甚至铃宕的音高),都是这一个函数换着方式表达自己。
The demo splits the metric’s work in two. For everything you throw, the page is declared to be the black hole’s equatorial plane: the polar angle is pinned at \( \theta = \pi/2 \), and distances on your screen, measured in pixels from the hole’s center, literally are the Schwarzschild coordinate \( r \). Light gets the full treatment: the renderer traces photons in three dimensions (each ray stays in its own orbital plane anyway, which is angular-momentum conservation), viewing the scene from a 77° inclination. Two perspectives, one metric; the honesty box owns the mismatch.
演示把度规的工作一分为二。对你抛出的一切,页面被宣布为黑洞的赤道面: 极角钉死在 \( \theta = \pi/2 \),屏幕上以黑洞中心为原点、按像素度量的距离, 就是史瓦西坐标 \( r \) 本身。光则享受完整待遇:渲染器在三维里追踪光子 (反正每条光线都待在自己的轨道平面里,那正是角动量守恒),从 77° 的倾角观看场景。 两套视角,一个度规;这笔混搭记在诚实清单上。
fOf(r, rs) in src/physics.js is \( f(r) \), and nearly every
other function calls it. The unit system (\( G = c = 1 \), lengths in px,
\( M = r_s / 2 \)) is set in the same file’s header. The screen-to-plane mapping
lives in makeBody(): \( r = \sqrt{dx^2 + dy^2} \),
\( \varphi = \operatorname{atan2}(dy, dx) \) around the hole’s center. The
fragment shader in src/renderer.js works in units of \( M = 1 \) instead
(pixels-per-\( M \) is u_rs/2), so the horizon sits at \( r = 2 \)
(R_HORIZON): the same metric, rescaled.
src/physics.js 里的 fOf(r, rs) 就是 \( f(r) \),
几乎所有其他函数都要调用它。单位制(\( G = c = 1 \),长度用 px,\( M = r_s / 2 \))
定在同一文件的开头。屏幕到赤道面的映射在 makeBody():
以黑洞中心为原点,\( r = \sqrt{dx^2 + dy^2} \),\( \varphi = \operatorname{atan2}(dy, dx) \)。
src/renderer.js 的片元着色器改用 \( M = 1 \) 单位(每 \( M \) 合多少像素是
u_rs/2),于是视界落在 \( r = 2 \)(R_HORIZON):
同一个度规,换了把尺子。
On the page — the whole viewport is a slice through a black hole’s equator; every pixel of distance you see is the coordinate \( r \) of this metric.
你能看到 —— 整个视口就是黑洞赤道的一个切片; 你看到的每一像素距离,都是这条度规里的坐标 \( r \)。
2Geodesics: the fall itself测地线:坠落本身
Freefall is the laziest possible motion: no engine, no steering, just spacetime telling the object how to move. Because the Schwarzschild geometry doesn’t change with time or angle, an energy \( E \) and an angular momentum \( L \) are conserved along the path, and the whole trajectory collapses to a single equation for \( r(\tau) \), with \( d\varphi/d\tau = L/r^2 \) tagging along. The right-hand side reads like an argument between three forces. Newton’s inverse-square pull. The familiar centrifugal barrier, which grows as \( 1/r^3 \): in Newtonian gravity it always wins at small \( r \), so carry enough angular momentum and you can never fall in. And Einstein’s twist, growing as \( 1/r^4 \): close enough in, it always beats the barrier. The floor of the well drops out. This one term is why black holes eat things Newton says should orbit forever. It is why orbits precess (Mercury’s 43 arcseconds per century), and why there is an innermost stable circular orbit at all.
自由落体是最偷懒的运动:没有引擎,没有转向,只有时空在告诉物体怎么走。 由于史瓦西几何不随时间也不随角度改变,沿途守恒着一份能量 \( E \) 和一份角动量 \( L \), 整条轨迹于是坍缩成关于 \( r(\tau) \) 的一条方程,\( d\varphi/d\tau = L/r^2 \) 顺路跟着。 右边读起来像三股力在吵架。牛顿的平方反比引力。熟悉的离心势垒,按 \( 1/r^3 \) 增长: 在牛顿引力里,小 \( r \) 处永远是它赢,角动量够大,你就永远掉不进去。 还有爱因斯坦的加戏,按 \( 1/r^4 \) 增长:足够靠近时,它永远压过势垒。 井底被抽掉了。就是这一项,让黑洞吃掉牛顿说应当永远绕行的东西。 也是它让轨道进动(水星每世纪 43 角秒),让"最内稳定圆轨道"这个概念得以存在。
A tidy way to see all of it at once is the effective potential. The conserved-energy constraint can be rearranged into \( E^2 = \dot r^2 + V_{\mathrm{eff}}^2 \) with \( V_{\mathrm{eff}}(r) = \sqrt{ f(r)\,\bigl( 1 + L^2 / r^2 \bigr) } \): the particle behaves like a bead sliding in this one-dimensional landscape. Where the landscape has a valley, circular orbits live; where the valley disappears, so does any hope of orbiting. Drag the slider below across \( L = \sqrt{12}\,M \approx 3.46\,M \) and watch the valley evaporate.
要把这一切一眼看尽,最利落的办法是有效势。守恒能量约束可以改写成 \( E^2 = \dot r^2 + V_{\mathrm{eff}}^2 \),其中 \( V_{\mathrm{eff}}(r) = \sqrt{ f(r)\,\bigl( 1 + L^2 / r^2 \bigr) } \): 粒子就像一颗珠子,在这条一维地形上滑动。地形有山谷的地方,住着圆轨道; 山谷消失的地方,绕行的指望也一并消失。拖动下面的滑块,穿过 \( L = \sqrt{12}\,M \approx 3.46\,M \),看着山谷蒸发。
accel(r, L, M) in src/physics.js is the radial equation
term for term: -M/r2 + L2/(r2*r) - (3*M*L2)/(r2*r2). The private
rk4Step() advances the state \( (r, p_r, \varphi) \) one proper-time step
with classic fourth-order Runge–Kutta; on top of it, stepBody() runs
step-doubling error control against a relative tolerance of 10−7
(STEP_TOL), the accepted error setting the next step size
(hAdapt). The constants of motion are treated as exactly that: \( E \) and
\( L \) are stored explicitly (body.E), and after each accepted step
\( p_r \) is projected back onto the constraint surface
\( p_r^2 = E^2 - f\,(1 + L^2/r^2) \). The module is DOM-free, so node unit tests check
energy drift (< 10−9 over 5×104 steps),
circular-orbit stability inside/outside the ISCO, geodesic deviation, and perihelion
precession against \( \Delta\varphi \approx 6\pi M / [a(1-e^2)] \).
src/physics.js 里的 accel(r, L, M) 逐项就是那条径向方程:
-M/r2 + L2/(r2*r) - (3*M*L2)/(r2*r2)。私有的 rk4Step()
用经典四阶龙格–库塔把状态 \( (r, p_r, \varphi) \) 推进一个固有时步;
在它之上,stepBody() 做步长加倍的误差控制,相对容差 10−7
(STEP_TOL),被接受的误差决定下一步步长(hAdapt)。
运动常数就按常数对待:\( E \) 与 \( L \) 显式存储(body.E),
每个被接受的步之后,把 \( p_r \) 投影回约束面 \( p_r^2 = E^2 - f\,(1 + L^2/r^2) \)。
模块不碰 DOM,node 单测据此检查能量漂移(5×104 步
< 10−9)、ISCO 内外圆轨道的稳定性、测地偏离,
以及对照 \( \Delta\varphi \approx 6\pi M / [a(1-e^2)] \) 的近日点进动。
On the page — throw a block sideways and it traces a rosette: an ellipse whose perihelion visibly precesses, the same GR term that moved Mercury’s. Throw it gently and it skips the ellipse and spirals straight in.
你能看到 —— 侧向甩出一个块,它会画出一朵玫瑰花结: 一只近日点肉眼可见地进动的椭圆,正是挪动了水星轨道的那一项。轻轻一丢, 它就跳过椭圆,直接盘旋坠入。
3Coordinate time: why the fall freezes坐标时:坠落为何冻结
There are two clocks in this story. The falling paragraph carries its own wristwatch, ticking proper time \( \tau \); by that watch, the plunge through the horizon is quick and uneventful. You, the distant observer scrolling the page, run on coordinate time \( t \). The conversion between the two is the conserved energy relation \( E = f \, dt/d\tau \), flipped around: for each second of your time, the faller experiences only \( f/E \) seconds of its own. Near the horizon \( f \to 0 \), so the exchange rate collapses. The object’s remaining story gets smeared over an infinite amount of your time. From outside, nothing ever crosses: it just slows, dims, and freezes onto the horizon like a fly in amber. Astrophysicists in the 1960s called these objects “frozen stars” before “black hole” won the naming war, and this equation is why.
这个故事里有两只钟。下落的段落戴着自己的腕表,滴答走着固有时 \( \tau \); 按那只表,穿过视界的坠落又快又平淡。而你(滚动页面的远方观察者)用的是坐标时 \( t \)。两者之间的汇率就是守恒能量关系 \( E = f \, dt/d\tau \) 反过来写: 你的每一秒,坠落者只经历它自己的 \( f/E \) 秒。临近视界 \( f \to 0 \), 汇率崩盘。物体剩下的故事被摊到你的无限长时间上。从外面看,什么都没有越过视界: 它只是变慢、变暗,像琥珀里的飞虫一样冻结在视界上。二十世纪六十年代的天体物理学家 把这类天体叫"冻结星",直到"黑洞"赢下命名之争;原因就是这条方程。
The browser necessarily runs on your clock: requestAnimationFrame
hands the demo wall-clock seconds, which are distant-observer coordinate time. So the
integrator works in the physically natural variable \( \tau \), but spends it against a
coordinate-time budget, converting through this equation each substep. The freeze then
emerges by itself: no easing function, no scripted “swallow”
animation. As \( f \to 0 \), each frame simply buys less and less proper time. The
on-screen motion grinds to a halt exactly the way general relativity says it should.
浏览器必然跑在你的钟上:requestAnimationFrame 递给演示的是挂钟秒,
也就是远方观察者的坐标时。所以积分器在物理上更自然的变量 \( \tau \) 里工作,
却按坐标时预算来开销,每个子步都用这条方程换算一次。于是冻结自己涌现出来:
没有缓动函数,没有写好脚本的"吞噬"动画。当 \( f \to 0 \),每一帧能买到的固有时
越来越少。屏幕上的运动就照着广义相对论说的那样,一点点碾停。
stepBody(body, {rs, cPx, dragK}, dtSec) in src/physics.js
converts the frame to a coordinate-time budget remaining = cPx * dtSec
(px), then loops: take a proper-time step
h = min((f * dt) / E, hmax, hAdapt) (the frame budget converted
through this equation, capped near the hole and by the error-controlled
hAdapt), advance with the step-doubled RK4, and charge
remaining -= h * E / f back in coordinate time. The on-screen crawl is
visible in screenState(): the coordinate velocities it reports are
\( v_r = p_r f / E \), \( v_t = L f / (r E) \): both proportional to \( f \),
both dying at the horizon.
src/physics.js 里的 stepBody(body, {rs, cPx, dragK}, dtSec)
先把这一帧折算成坐标时预算 remaining = cPx * dtSec(px),然后循环:
取一个固有时步 h = min((f * dt) / E, hmax, hAdapt)(帧预算经这条方程换算,
在黑洞附近另有上限,并受误差控制的 hAdapt 约束),用步长加倍的 RK4 推进,
再按坐标时记账 remaining -= h * E / f。屏幕上的爬行在
screenState() 里看得见:它报告的坐标速度是 \( v_r = p_r f / E \)、
\( v_t = L f / (r E) \):都正比于 \( f \),都在视界处熄灭。
On the page — elements never visibly cross the black edge: they decelerate, hang, and fade at its rim, and only the threshold rule of equation 5 finally lets the page delete them.
你能看到 —— 元素永远不会肉眼可见地越过黑边: 它们减速、悬住、在边缘上淡出,直到方程 5 的阈值规则最终允许页面删掉它们。
4A mouse throw becomes a four-velocity一次甩鼠标,变成一个四速度
When you let go of a dragged element, the demo knows four mundane numbers: where the pointer is and how fast it was moving, in pixels per second of your time. The geodesic integrator wants something more refined: the conserved pair \( (E, L) \) and a proper-time radial velocity. The bridge is relativity’s type system: the rule that every massive object’s four-velocity has unit length, \( u \cdot u = -1 \). Think of it as a normalization check the initial conditions must pass before the physics will accept them. Given the throw’s coordinate-time rates \( \dot r, \dot\varphi \), the constraint fixes the missing component \( dt/d\tau \) outright. From it, \( E = f\,dt/d\tau \) and \( L = r^2 \dot\varphi \, dt/d\tau \) follow immediately.
你松开被拖拽的元素时,演示只知道四个平凡的数字:指针在哪里、它刚才移动得多快, 单位是你的每秒像素。测地线积分器想要的东西更讲究:守恒对 \( (E, L) \) 和一个固有时径向速度。桥梁是相对论的类型系统:每个有质量物体的四速度必须是单位长度, \( u \cdot u = -1 \)。把它当成初始条件必须先通过的归一化检查。给定投掷的坐标时速率 \( \dot r, \dot\varphi \),这条约束直接定出缺失的分量 \( dt/d\tau \)。 随后,\( E = f\,dt/d\tau \) 与 \( L = r^2 \dot\varphi \, dt/d\tau \) 立刻跟上。
The denominator also enforces the speed limit. The combination \( v_{\mathrm{loc}}^2 = \dot r^2 / f^2 + r^2 \dot\varphi^2 / f \) is the speed a local static observer (someone hovering right at the release point) would measure with their own rulers and clocks. Ask for \( v_{\mathrm{loc}} \ge 1 \) (a superluminal throw, easy to do with a flick of the wrist near the hole where \( f \) is small) and the bracket goes non-positive: \( dt/d\tau \) has no real solution. The universe rejects your input. Because \( v_{\mathrm{loc}}^2 \) is a homogeneous quadratic in \( (\dot r, \dot\varphi) \), the demo handles this gracefully: it rescales both rates by one factor until the local speed is exactly \( 0.985c \). The throw keeps its direction and loses only its ambition.
分母还顺手执行了限速。组合 \( v_{\mathrm{loc}}^2 = \dot r^2 / f^2 + r^2 \dot\varphi^2 / f \) 是局部静止观察者(悬停在释放点上的人)用自己的尺和钟量出的速度。 如果你要求 \( v_{\mathrm{loc}} \ge 1 \)(超光速投掷;在 \( f \) 很小的黑洞附近, 手腕一抖就能做到),括号就变成非正:\( dt/d\tau \) 没有实数解。宇宙拒收你的输入。 好在 \( v_{\mathrm{loc}}^2 \) 是 \( (\dot r, \dot\varphi) \) 的齐次二次式, 演示可以优雅收场:把两个速率按同一个因子缩小,直到局部速度恰为 \( 0.985c \)。 保住投掷的方向,压住它的野心。
makeBody({x, y, vx, vy, cx, cy, rs, cPx}) in src/physics.js
does the whole conversion: radial/tangential projections
rdot = (dx*vx + dy*vy)/(r*cPx) and
phidot = (dx*vy - dy*vx)/(r*r*cPx), the clamp against
VMAX_LOCAL = 0.985, then ut = 1/sqrt(f*(1 - vloc2)) and the
returned state {r, phi, pr: rdot*ut, L: r*r*phidot*ut}. The throw velocity
itself comes from interaction.js, whose onMove handler keeps
an exponential moving average of pointer velocity with an 80 ms time constant,
so the release uses your recent flick, not one noisy frame. Release points are
floored at \( 1.05\,r_s \) so a throw can’t start inside the horizon.
src/physics.js 里的 makeBody({x, y, vx, vy, cx, cy, rs, cPx})
一手包办整个换算:径向/切向投影 rdot = (dx*vx + dy*vy)/(r*cPx) 与
phidot = (dx*vy - dy*vx)/(r*r*cPx),对 VMAX_LOCAL = 0.985
的钳制,然后 ut = 1/sqrt(f*(1 - vloc2)),返回状态
{r, phi, pr: rdot*ut, L: r*r*phidot*ut}。投掷速度本身来自
interaction.js:它的 onMove 处理器维护一个时间常数 80 ms
的指针速度指数滑动平均,所以释放用的是你最近的那一甩,而不是某个噪声帧。
释放点的半径下限是 \( 1.05\,r_s \),投掷不可能从视界里面开始。
On the page — flick an element as violently as you like: it leaves your cursor at most at 98.5% of the (pixel) speed of light, and the physics downstream never sees an illegal state.
你能看到 —— 随你多用力地甩:元素离开光标时至多是 (像素)光速的 98.5%,下游的物理永远看不到非法状态。
5Gravitational redshift: the fade to black引力红移:渐隐成黑
Light climbing out of a gravitational well pays a toll in energy. A photon emitted at radius \( r \) with frequency \( \nu_{\mathrm{emit}} \) arrives at your distant screen stretched to \( \nu_\infty = \sqrt{f}\,\nu_{\mathrm{emit}} \); the same factor is the rate of a static clock at \( r \) compared to yours: redshift and time dilation are one phenomenon wearing two hats. This factor is the toll a hovering emitter pays; a genuinely infalling one is Doppler-shifted further, a surcharge the demo quietly forgives. For something falling toward the horizon, this is a dimmer switch wired to geometry. Blues sag toward red, then infrared. And the object dims twice over: each photon arrives with less energy, and the frozen-time effect of equation 3 means photons arrive less and less often. The last light from an infalling object is a trickle of ever-redder, ever-rarer photons: a fade-out the universe performs without a compositor.
从引力井里向上爬的光,要交能量的过路费。在半径 \( r \) 处以频率 \( \nu_{\mathrm{emit}} \) 发出的光子,抵达你远方的屏幕时被拉伸到 \( \nu_\infty = \sqrt{f}\,\nu_{\mathrm{emit}} \);同一个因子也是 \( r \) 处静止时钟相对你的快慢:红移和时间膨胀是同一现象戴了两顶帽子。 这个因子是悬停发射者交的过路费;真正下落的发射者还会再被多普勒频移一道,演示悄悄免掉了这一笔。 对一件正落向视界的东西,这是一只接在几何上的调光开关。蓝光垂向红光,再垂向红外。 而且物体是双重变暗:每个光子到达时能量更少,而且方程 3 的冻结时间效应 让光子到达得越来越稀。坠落物的最后一缕光,是一串越来越红、越来越稀的光子: 一场宇宙不用合成器就能演出的淡出。
Strictly, that fade never finishes: \( \sqrt{f} \) reaches zero only at the horizon, which by equation 3 takes forever on your clock. The demo therefore draws an honest, practical line. When \( \sqrt{f} \) drops below \( 0.12 \) (at \( r \approx 1.015\,r_s \), where the element renders at a few percent brightness), it is declared swallowed and its mass is transferred to the hole. Think of it as garbage-collecting an object whose remaining observable lifetime is pure asymptote.
严格说,这场淡出永远演不完:\( \sqrt{f} \) 只有在视界上才到零,而按方程 3, 那在你的钟上要花无限久。所以演示画了一条诚实而务实的线。当 \( \sqrt{f} \) 跌破 \( 0.12 \)(也就是 \( r \approx 1.015\,r_s \),元素只剩下百分之几的亮度), 它就被宣布吞噬,质量转入黑洞。可以把这理解为垃圾回收: 回收一个可观测余生只剩纯渐近线的对象。
The threshold is SWALLOW_REDSHIFT = 0.12 in src/physics.js;
stepBody() returns false once
Math.sqrt(fNew) < SWALLOW_REDSHIFT (or \( r \le 1.001\,r_s \)).
screenState() exports redshift = Math.sqrt(f), and
interaction.js’s tick() computes it per vertex
of the falling mesh, feeding the mesh fragment shader in
src/renderer.js: each fragment is dimmed by its interpolated \( z \) and
faded toward an ember tint as \( z \to 0 \), with per-vertex opacity a smoothstep of
\( (z - 0.12)/0.18 \) so every corner finishes fading right as its own threshold trips.
The rigid fallback drives CSS filters with the same number:
brightness(0.25 + 0.75z), sepia((1-z)*0.85),
hue-rotate(-25(1-z)deg). The disk’s light gets the circular-orbit
generalization of the same factor: the \( \sqrt{1 - 3M/r} \) inside equation
11’s \( g \).
阈值是 src/physics.js 里的 SWALLOW_REDSHIFT = 0.12;
一旦 Math.sqrt(fNew) < SWALLOW_REDSHIFT(或 \( r \le 1.001\,r_s \)),
stepBody() 就返回 false。screenState() 导出
redshift = Math.sqrt(f),interaction.js 的 tick()
对下落网格逐顶点计算它,喂给 src/renderer.js 里的网格片元着色器:
每个片元按插值出的 \( z \) 变暗,并在 \( z \to 0 \) 时向余烬色调褪去,
逐顶点的不透明度是 \( (z - 0.12)/0.18 \) 的 smoothstep,每个角都恰好在自己的阈值
触发时褪完。刚体降级路径用同一个数字驱动 CSS 滤镜:
brightness(0.25 + 0.75z)、sepia((1-z)*0.85)、
hue-rotate(-25(1-z)deg)。盘面的光享受同一因子的圆轨道推广:
方程 11 的 \( g \) 里那个 \( \sqrt{1 - 3M/r} \)。
On the page — falling blocks turn amber, then ember, then translucent, and wink out a hair’s breadth above the shadow, never visibly crossing it.
你能看到 —— 坠落的块先变琥珀色,再变余烬色, 再变半透明,最后在影子上方一发丝处熄灭,从不肉眼可见地越过它。
6Tidal stretching: spaghettification潮汐拉伸:面条化
Gravity you can’t feel; gravity’s gradient you can. In freefall every part of you falls, so no scale would register weight. But your feet, being closer to the hole, are asked to fall on a slightly tighter trajectory than your head. That disagreement between neighboring geodesics is tidal force. Near a black hole it stops being poetic: stretched lengthwise by \( +2M/r^3 \) per unit length, squeezed from the sides by \( -M/r^3 \), an infalling astronaut becomes what Martin Rees memorably named spaghetti. Note the ratio: the stretch is exactly twice the squeeze, so to first order volume is preserved. You are extruded, not compacted. The \( 1/r^3 \) scaling is the practical menace: halve your distance and the tide is eight times stronger. (For supermassive holes \( M/r^3 \) at the horizon is tiny; you’d cross Sagittarius A*’s horizon intact and un-noodled. The demo’s hole is small and mean.)
引力本身感觉不到;引力的梯度感觉得到。自由落体中你的每一部分都在坠落, 体重秤读不出任何重量。但你的脚比头更靠近黑洞,被要求沿一条更紧的轨迹下坠。 相邻测地线之间的这点分歧,就是潮汐力。在黑洞附近它不再诗意: 沿纵向按每单位长度 \( +2M/r^3 \) 拉伸,从两侧按 \( -M/r^3 \) 挤压, 坠入的宇航员变成马丁·里斯起的那个难忘的名字:意面。注意比例: 拉伸恰是挤压的两倍,一阶近似下体积守恒。你是被挤成条,不是被压成饼。 \( 1/r^3 \) 的标度才是现实的威胁:距离减半,潮汐强八倍。 (对超大质量黑洞,视界处的 \( M/r^3 \) 反而微小;你能完好无损、不成面条地穿过人马座 A* 的视界。演示里这颗洞又小又凶。)
The demo does not paint this equation onto the element; it lets it happen. A released paragraph is sampled into a grid of test particles, one geodesic per vertex, all launched with your throw’s velocity from slightly different positions. The \( \pm M/r^3 \) law above is then simply what you watch: neighboring geodesics disagree more and more violently as \( r \) shrinks, and the mesh stretches because spacetime pulls its sample points apart. Spaghettification here is emergent geodesic deviation, not a transform.
演示并不把这条方程画到元素上;它让方程自己发生。被释放的段落被采样成一张 试验粒子网格,每个顶点一条测地线,全部带着你投掷的速度、从略有不同的位置出发。 于是上面那条 \( \pm M/r^3 \) 定律就是你看到的东西本身:随着 \( r \) 缩小, 相邻测地线的分歧越来越剧烈,网格被拉开,因为时空在把它的采样点扯散。 这里的面条化是涌现出来的测地偏离,不是一个变换。
releaseItem() in src/interaction.js subdivides the
element’s interior into a (gx+1)×(gy+1) grid (one cell per
∼40 px) and calls makeBody() once per vertex:
same initial velocity, different position. Each frame, every vertex is advanced
independently by stepBody(), and the WebGL mesh from
createMesh() in src/renderer.js warps as those geodesics
diverge (there is a unit test asserting radial separations stretch and transverse ones
squeeze mid-fall). The old CSS route survives as the rigid fallback:
screenState() exports tidal = min(1, 4*(rs/r)^3) (the
correct cubic law with a gain and a cap), applied as
scale(1 + 2*tidal, 1/(1 + tidal)), the 2:1 anatomy, rotated so the long
axis chases vAngle.
src/interaction.js 里的 releaseItem() 把元素内部细分成
(gx+1)×(gy+1) 网格(约每 40 px 一格),对每个顶点
各调一次 makeBody():初速相同,位置不同。每一帧,每个顶点由
stepBody() 独立推进,src/renderer.js 里
createMesh() 建的 WebGL 网格随这些测地线的发散而扭曲
(有一条单测断言:坠落途中径向间距拉伸、切向间距压缩)。旧的 CSS 路线作为刚体降级保留:
screenState() 导出 tidal = min(1, 4*(rs/r)^3)
(正确的三次方定律加增益和上限),以 scale(1 + 2*tidal, 1/(1 + tidal))
施加,正是 2:1 的解剖比例,并旋转到长轴追着 vAngle。
On the page — in its final orbits a paragraph visibly elongates and thins as its vertices’ geodesics fan apart, the near edge falling ahead of the far one: typography, al dente.
你能看到 —— 在最后几圈里,一个段落肉眼可见地被拉长、 变细,顶点们的测地线呈扇形散开,近端坠得比远端快:排版,煮到弹牙。
7The horizon grows视界在长大
Black holes are the universe’s simplest bookkeepers. Feed one a paragraph of mass \( m \) and its mass becomes \( M + m \); no digestion, no remainder. Because the horizon radius is strictly linear in mass, \( r_s = 2M \), the shadow’s growth is a direct readout of everything ever eaten. The horizon area grows as \( M^2 \), and by Hawking’s area theorem it can never shrink. That is the thermodynamic thread we pick up again in equation 15.
黑洞是宇宙里最简单的记账员。喂它一段质量为 \( m \) 的文字,它的质量就变成 \( M + m \);不用消化,没有余数。由于视界半径严格线性于质量,\( r_s = 2M \), 影子的增长就是"迄今吃掉的一切"的直接读数。视界面积按 \( M^2 \) 增长, 而按霍金的面积定理,它永远不会缩小。这根热力学线头,我们在方程 15 再捡起来。
What is the mass of a <div>? The demo makes the simplest defensible
choice: mass proportional to pixel area, \( m = k_m \cdot (\text{width} \times
\text{height}) \). The constant \( k_m \) is calibrated at activation time: sum
the area of every devourable element on the page, then solve for the coefficient that
makes the fully-fed hole reach its configured maximum. The default maximum is a
horizon of one fifth of the viewport’s short side. Your page’s layout literally
determines the black hole’s final size.
一个 <div> 的质量是多少?演示做了最简单而站得住脚的选择:
质量正比于像素面积,\( m = k_m \cdot (\text{width} \times \text{height}) \)。
常数 \( k_m \) 在激活时标定:把页面上所有可吞元素的面积求和,反解出一个系数,
使得吃饱的黑洞恰好达到配置的最大值。默认的最大值,是视界为视口短边的五分之一。
你页面的排版,毫不比喻地决定了这颗黑洞的最终尺寸。
In blackhole.js, calibrate() measures
getBoundingClientRect() areas of all targets and sets
kM = (Mmax - M) / sum, with Mmax = rsMax()/2 and
rsMax = maxRsFraction * min(vw, vh). Equation 7 is then applied as a
stream: each grid vertex of a falling element carries an equal share of its area,
and the moment that vertex freezes onto the horizon the onAccrete(area)
callback books it — M = Math.min(Mmax, M + kM * area) — so
the hole grows while the block is going down, not in one gulp at the end.
onSwallow() merely bumps the counter and rings the bell. The rendered
radius rsShown relaxes toward \( 2M \) with a 0.25 s time constant,
so growth reads as a swell rather than a pop.
在 blackhole.js 里,calibrate() 用
getBoundingClientRect() 量出所有目标的面积并设定
kM = (Mmax - M) / sum,其中 Mmax = rsMax()/2、
rsMax = maxRsFraction * min(vw, vh)。方程 7 以流的方式施行:
坠落元素的每个格点均分它的面积,哪个格点冻结在视界上,
onAccrete(area) 回调就当刻入账那一份 ——
M = Math.min(Mmax, M + kM * area) —— 黑洞在块坠入的途中长大,
而不是最后一口吞成。onSwallow() 只负责计数与敲钟。
展示半径 rsShown 以 0.25 s 的时间常数向
\( 2M \) 松弛,生长读起来是一次鼓胀,而不是一声爆开。
On the page — every meal visibly enlarges the shadow, the photon ring, and the disk with it; the HUD’s \( r_s \) row and mass progress bar tick upward in exact proportion to the pixels consumed.
你能看到 —— 每顿饭都肉眼可见地撑大影子、光子环和盘; HUD 的 \( r_s \) 一行与质量进度条,严格按吃进的像素数往上走。
8Ringdown: the bell of spacetime铃宕:时空之钟
Strike a bell and it doesn’t emit noise. It rings at frequencies set by its own shape, each overtone dying away at its own rate. A perturbed black hole does exactly the same with spacetime itself. Swallow something, and the horizon wobbles through a set of quasinormal modes: damped oscillations whose complex frequencies are fixed entirely by the hole’s mass (and spin, for rotating holes). The real part is the pitch; the imaginary part is the decay. For a Schwarzschild hole’s dominant (quadrupole, \( \ell = 2 \)) mode that number is \( M\omega = 0.37367 - 0.08896\,i \). Since the product \( M\omega \) is fixed, frequency scales as \( 1/M \) and damping time as \( M \): big bells ring low and long. This is not a metaphor from acoustics: it is what LIGO actually hears at the end of every merger. Matching the ringdown’s pitch-and-decay pair against tables of such numbers, this one and its spinning-hole cousins, is how the remnant’s mass and spin are weighed.
敲一口钟,它发出的不是噪声。它按自己形状规定的频率鸣响,每个泛音以各自的速率消逝。 被扰动的黑洞对时空本身做的事一模一样。吞下点什么,视界就会经历一组似正规模的晃动: 阻尼振荡,其复频率完全由黑洞的质量决定(旋转黑洞还要加上自旋)。实部是音高,虚部是衰减。 史瓦西黑洞占主导的(四极,\( \ell = 2 \))模式,那个数就是 \( M\omega = 0.37367 - 0.08896\,i \)。既然乘积 \( M\omega \) 固定, 频率就按 \( 1/M \) 标度,阻尼时间按 \( M \) 标度:大钟低鸣而悠长。 这不是从声学借来的比喻:LIGO 在每次并合的尾声里听到的正是它。 把铃宕的"音高–衰减"对与这类数字(这个值及其旋转黑洞的表亲)比对,正是称量并合残骸质量与自旋的办法。
Scaled to the demo: the initial hole (\( M = 8 \) px, \( c = 1600 \) px/s) would ring at about 12 Hz and fade in roughly 56 ms, over before your next glance. So the demo slows time twentyfold for this one effect (declared in the honesty box), giving a stately ∼0.6 Hz wobble that decays in about a second. The physical scalings survive intact: each meal makes the hole bigger, hence the ring lower-pitched and longer-lived.
换算到演示里:初始黑洞(\( M = 8 \) px,\( c = 1600 \) px/s)会以约 12 Hz 鸣响, 约 56 ms 内消退,你还没来得及看它一眼就结束了。所以演示单独为这一个效应把时间 放慢二十倍(已写进诚实清单),换来一个从容的约 0.6 Hz 晃动,约一秒内衰减。 物理标度忠实保留:每吃一顿,黑洞更大,铃声便更低沉、余音更长。
The mode constant is QNM = { re: 0.37367, im: 0.08896 } in
src/physics.js. ringdownOffset(tSec, rsPx, cPx, amp) builds
the damped sine with omega = QNM.re * cPx / (M * RINGDOWN_TSCALE) and
tauD = M * RINGDOWN_TSCALE / (QNM.im * cPx): the \( 1/M \) and
\( M \) scalings verbatim, stretched by RINGDOWN_TSCALE = 20. The sine
phase matters: the excitation is a velocity kick, so the displacement starts at zero
and the rim never jumps on the swallow frame. Each meal appends one such mode and the
main loop in blackhole.js adds their linear superposition to the
displayed radius rsShown — perturbation theory taken at its word
— with amplitude proportional to the horizon growth that very meal caused
(\( m/M \) in disguise, exactly the scale linear perturbation theory assigns): the
bell rings in proportion to how hard it was struck, and the wobble never outweighs
the growth it decorates. Even the hole’s birth animation reuses it, growing
from zero with a ring.
模式常数是 src/physics.js 里的
QNM = { re: 0.37367, im: 0.08896 }。
ringdownOffset(tSec, rsPx, cPx, amp) 用
omega = QNM.re * cPx / (M * RINGDOWN_TSCALE) 与
tauD = M * RINGDOWN_TSCALE / (QNM.im * cPx) 搭出阻尼正弦:
\( 1/M \) 与 \( M \) 两条标度原样保留,再被 RINGDOWN_TSCALE = 20 拉长。
正弦的初相有讲究:激发是一次速度冲击,位移从零起振,吞噬那一帧视界边缘不会跳变。
每顿饭追加一个这样的模式,blackhole.js 的主循环把它们的线性叠加
加到展示半径 rsShown 上 —— 把微扰论的字面意思当真 ——
振幅正比于这顿饭本身带来的视界增量(伪装成像素的 \( m/M \),恰是线性微扰论
规定的量级):钟响的响度正比于敲击的力度,摆动永远不会盖过它所装饰的生长。
连黑洞的出生动画也复用它:从零长起,带着一声铃。
On the page — right after each swallow the shadow’s rim breathes in and out a few times and settles; as the hole fattens, listen with your eyes: the wobble gets slower and lingers longer.
你能看到 —— 每次吞噬后,影子的边缘呼吸般胀缩几下, 然后安定;黑洞越吃越胖,请用眼睛去听:晃动越来越慢,余音越拖越长。
9Bending light: weak lensing to the brink光线弯折:从弱透镜到悬崖边
A photon doesn’t orbit or fall in the everyday sense; it grazes. Its whole encounter with the hole is summarized by one number, the impact parameter \( b \) (how far off-center it was aimed), and the geometry replies with one number back, the deflection angle \( \alpha \). Far from the hole, the reply is the celebrated weak-field result \( \alpha = 4GM/(c^2 b) = 2 r_s / b \): the 1.75 arcseconds Eddington confirmed at the 1919 eclipse, the formula that made Einstein a headline.
光子既不绕行也不坠落;按日常语义,它是擦过。它与黑洞的整场遭遇被一个数概括: 瞄准参数 \( b \)(瞄得偏离中心多远);几何也回敬一个数:偏折角 \( \alpha \)。 远离黑洞处,回答是那条著名的弱场结果 \( \alpha = 4GM/(c^2 b) = 2 r_s / b \): 爱丁顿在 1919 年日食测到的 1.75 角秒,让爱因斯坦登上头条的那条公式。
Aim closer and politeness ends. As \( b \) approaches the critical value \( b_c \) (equation 10), the photon whips through larger and larger angles (a U-turn, a full loop, two loops), and the deflection diverges logarithmically: each extra winding costs an exponentially finer adjustment of aim. Bozza’s strong-deflection expansion above captures this with a universal \( -\ln(b/b_c - 1) \) plus a Schwarzschild-specific constant, \( \ln[216(7 - 4\sqrt{3})] - \pi \approx -0.40 \). The divergence is why a black hole’s photon ring is theoretically an infinite stack of nested images of the entire sky.
瞄得再近些,客气就到头了。当 \( b \) 逼近临界值 \( b_c \)(方程 10), 光子甩过的角度越来越大(一个 U 形弯、一整圈、两圈),偏折按对数发散: 每多绕一圈,瞄准就要做指数级更精细的调整。上面 Bozza 的强偏折展开用一个普适的 \( -\ln(b/b_c - 1) \) 加一个史瓦西专属常数 \( \ln[216(7 - 4\sqrt{3})] - \pi \approx -0.40 \) 捕捉了这一切。 这个发散,正是黑洞光子环在理论上是"整个天空的无穷层嵌套镜像"的原因。
In the fragment shader in src/renderer.js, every pixel within an impact
radius of \( 20M \) launches a ray from an orthographic camera (inclination
u_tilt, default 1.35 rad ≈ 77°) and integrates the exact
null geodesic backward: in \( M = 1 \) units,
\( d^2\mathbf{x}/d\lambda^2 = -3M h^2\, \mathbf{x}/r^5 \) with
\( h = |\mathbf{x} \times \mathbf{v}| \) conserved, the vector form of the
Binet equation \( u'' + u = 3Mu^2 \). The march takes up to MAXSTEP = 168
semi-implicit Euler steps of adaptive length dl = 0.22*r/(1.0 + 8.0/r)
(fine near the hole, coarse far away). A ray that escapes past \( 44M \) samples the
procedural starfield along its final bent direction,
skyColor(normalize(v)). That is lensing done the honest way: no
displacement map, just where the ray actually ends up pointing. Pixels beyond
\( 20M \) take a cheap path built from exactly this section’s weak-field
formula, defl = 4.0 / b (that is \( 4M/b = 2 r_s / b \)).
在 src/renderer.js 的片元着色器里,瞄准半径 \( 20M \) 以内的每个像素
都从一台正交相机(倾角 u_tilt,默认 1.35 rad ≈ 77°)
发出一条光线,向后积分精确零测地线:在 \( M = 1 \) 单位下,
\( d^2\mathbf{x}/d\lambda^2 = -3M h^2\, \mathbf{x}/r^5 \),其中
\( h = |\mathbf{x} \times \mathbf{v}| \) 守恒,正是 Binet 方程
\( u'' + u = 3Mu^2 \) 的矢量形式。行进至多走 MAXSTEP = 168 个半隐式欧拉步,
步长自适应 dl = 0.22*r/(1.0 + 8.0/r)(近处细,远处粗)。
逃出 \( 44M \) 的光线,沿它最终被弯过的方向采样程序化星空,
skyColor(normalize(v))。这就是最诚实的透镜做法:没有位移贴图,
只有光线真正指向的地方。\( 20M \) 以外的像素走一条便宜路径,
用的恰是本节的弱场公式,defl = 4.0 / b(即 \( 4M/b = 2 r_s / b \))。
On the page — stars near the hole are visibly dragged outward and brighten into an arc-like crowd just outside the ring; the closer to the shadow, the stronger the smear.
你能看到 —— 靠近黑洞的星星被肉眼可见地向外拖拽, 在环外挤成一圈弧光;离影子越近,涂抹越重。
10The photon sphere and the shadow光子球与黑影
At one and a half Schwarzschild radii, light can do the most literal thing imaginable: orbit. The photon sphere is the radius where the geometry bends a photon’s path into a perfect circle. The orbit is unstable: a photon there is a pencil balanced on its tip, one nudge from spiraling in or escaping. It is not the point of no return for light: that is the horizon. What it sets is the capture threshold for incoming rays, aim inside the matching impact parameter and there is no return, and with it the most photographed property of black holes.
在一点五个史瓦西半径处,光可以做一件最字面意义的事:绕圈。光子球就是几何把光子路径 弯成正圆的那个半径。这条轨道不稳定:待在那里的光子像笔尖上立着的铅笔, 一碰就要么盘旋坠入要么逃逸。它并不是光的不归界,那是视界的事; 它定下的,是入射光的俘获门槛:瞄进对应的瞄准距离以内,光便有去无回, 黑洞被拍得最多的那个性质也随之而来。
Here is the delightful subtlety: the black disk you see is not the horizon. A photon skimming the photon sphere at \( r_{\mathrm{ph}} = 1.5\,r_s \) climbs out through curved space. The aiming distance a distant observer attributes to it is inflated by the redshift factor there: \( b_c = r_{\mathrm{ph}} / \sqrt{f(r_{\mathrm{ph}})} = 1.5\,r_s / \sqrt{1/3} = (3\sqrt{3}/2)\, r_s \). The shadow is 2.6× wider than the horizon: gravity operating as its own magnifying glass. The Event Horizon Telescope’s images of M87* and Sgr A* show essentially this \( b_c \), ringed by the photon pile-up of equation 9.
妙处在这里:你看到的黑盘并不是视界。贴着光子球 \( r_{\mathrm{ph}} = 1.5\,r_s \) 掠过的光子,要穿过弯曲空间爬出来。 远方观察者归给它的瞄准距离,被当地的红移因子放大: \( b_c = r_{\mathrm{ph}} / \sqrt{f(r_{\mathrm{ph}})} = 1.5\,r_s / \sqrt{1/3} = (3\sqrt{3}/2)\, r_s \)。黑影比视界宽 2.6 倍:引力当了自己的放大镜。 事件视界望远镜拍下的 M87* 与人马座 A*,显示的大致正是这个 \( b_c \), 外面箍着方程 9 堆起来的光子。
Nothing in the shader draws the shadow. A backward ray that dips below
\( r = 2.02M \) (R_HORIZON with a 1% pad) is flagged captured and
composited pure black; a ray that exhausts its MAXSTEP budget is counted
captured too, since only near-critical rays winding at the photon sphere can burn 168
steps, and that is precisely the shadow’s edge. Measure the black disk on
screen and you get \( b_c = (3\sqrt{3}/2)\,r_s \) to within a few pixels: it
emerges from the integration. So does the photon ring, as
near-critical rays cross the disk plane again and again, piling its light into a thin
bright band. bCrit(rs) in src/physics.js survives as the
theory value, still drawn explicitly by the ray-tracer-less 2D fallback. Even the
shader’s ambient glow is centered at \( 5.196M \): recognize
\( 3\sqrt{3} \).
着色器里没有任何一行在"画"影子。向后追踪的光线一旦跌破 \( r = 2.02M \)
(R_HORIZON 加 1% 余量)就被标记为俘获,合成为纯黑;
耗尽 MAXSTEP 步数预算的光线同样按俘获计,因为只有在光子球上缠绕的
近临界光线才烧得掉 168 步,而那恰好就是影子的边缘。在屏幕上量那个黑盘,
你会得到误差仅几个像素的 \( b_c = (3\sqrt{3}/2)\,r_s \):它是从积分里长出来的。
光子环也一样:近临界光线一次次穿过盘面,把盘光叠成一条细亮带。
src/physics.js 里的 bCrit(rs) 作为理论值幸存,
仍由不带光线追踪的 2D 降级显式画出。连着色器的环境辉光都定心在 \( 5.196M \):
认出来了吗,\( 3\sqrt{3} \)。
On the page — the black disk in the corner is \( b_c \), not \( r_s \): the actual horizon would fit well inside it, and the thin white-hot ring hugging its edge marks the light that almost stayed forever.
你能看到 —— 角落里那个黑盘就是 \( b_c \), 不是 \( r_s \):真正的视界能整个装进它里面;贴着盘缘的白热细环, 标记着那些差一点就永远留下的光。
11The accretion disk: a thermometer in orbit吸积盘:在轨道上的温度计
Matter rarely falls straight in; it has angular momentum to lose, so it queues. The queue is the accretion disk: gas spiraling gradually inward, rubbing against itself, converting orbital energy into heat and light. Shakura and Sunyaev’s 1973 thin-disk model (one of the most-cited papers in astrophysics) gives the surface temperature profile above. Far out it falls as \( r^{-3/4} \); at the inner edge the \( 1 - \sqrt{r_{\mathrm{isco}}/r} \) factor (a no-torque boundary condition) drags it to zero; in between it peaks at \( r = \tfrac{49}{36}\, r_{\mathrm{isco}} \approx 1.36\, r_{\mathrm{isco}} \). Hottest, whitest light comes not from the innermost gas but from a ring just outside it.
物质很少笔直坠入;它有角动量要卸,所以要排队。这条队就是吸积盘: 气体缓缓盘旋内移,自我摩擦,把轨道能变成热与光。Shakura 与 Sunyaev 1973 年的薄盘模型 (天体物理引用量最高的论文之一),给出上面的表面温度轮廓。远处按 \( r^{-3/4} \) 下降;内缘处 \( 1 - \sqrt{r_{\mathrm{isco}}/r} \) 因子(无力矩边界条件)把它拽到零; 中间在 \( r = \tfrac{49}{36}\, r_{\mathrm{isco}} \approx 1.36\, r_{\mathrm{isco}} \) 处到达峰值。最热最白的光不是来自最内侧的气体,而是来自紧挨其外的一圈。
Why does the disk stop at \( 3\,r_s = 6M \)? That is equation 2’s doing: the ISCO, the innermost stable circular orbit, where the effective potential’s valley flattens away (Figure 1). Inside it, no amount of angular momentum buys a stable circle, so gas reaching the ISCO simply lets go and plunges. The disk truncates with a clean edge. And the rotation law is a genuine surprise of Schwarzschild geometry: the angular velocity of circular orbits, measured in coordinate time, is exactly Kepler’s \( \Omega = \sqrt{M/r^3} \), no relativistic correction at all. Inner gas laps outer gas, shearing any pattern into differential swirls.
盘为什么停在 \( 3\,r_s = 6M \)?那是方程 2 干的:ISCO,最内稳定圆轨道, 有效势的山谷在那里被抹平(图 1)。再往里,多大的角动量都买不到一个稳定的圆, 抵达 ISCO 的气体干脆松手坠落。盘于是带着一条干净的边缘截断。 而旋转定律是史瓦西几何一个货真价实的惊喜:圆轨道的角速度,按坐标时间量, 恰好就是开普勒的 \( \Omega = \sqrt{M/r^3} \),一点相对论修正都没有。 内圈气体一圈圈超过外圈,把任何图案剪切成差速的涡旋。
isco(rs) returns 3 * rs in src/physics.js. In
the shader, diskTemp(r) evaluates the bracketed profile in \( M = 1 \)
units (inner edge DISK_IN = 6.0, i.e. \( 3\,r_s \); outer
DISK_OUT = 13.0), normalized by its own peak;
blackbodyRamp() maps it red → orange → overexposed white. Each
backward ray that crosses the disk plane between those radii calls
diskEmission(). Because bent rays cross the plane more than
once, the disk appears more than once: the direct image, plus the arcs over and under
the shadow, which are the far side of the disk seen above and below the hole.
(The Interstellar look, for exactly the Interstellar reason.) Emission
is weighted by a circular-orbit emitter’s
\( g = \sqrt{1 - 3M/r}\,\big/\,(1 - \beta\mu) \), with
beta = inversesqrt(max(rd - 2.0, 0.25)) (the local orbital speed
\( 1/\sqrt{r - 2} \), an even \( 0.5c \) at the ISCO) and \( \mu \) the cosine
between the Keplerian flow and the photon direction. Observed brightness goes as
pow(g, 3.0). The turbulent texture is fbm noise
counter-rotated at the exact \( \Omega = r^{-3/2} \): real differential
rotation, artistically slowed by DISK_SPEED = 0.35.
src/physics.js 里的 isco(rs) 返回 3 * rs。
在着色器里,diskTemp(r) 用 \( M = 1 \) 单位求方括号里的轮廓
(内缘 DISK_IN = 6.0,即 \( 3\,r_s \);外缘 DISK_OUT = 13.0),
按自身峰值归一;blackbodyRamp() 把它映射成红 → 橙 → 过曝的白。
每条在这段半径间穿过盘面的向后光线都会调用 diskEmission()。
由于弯曲的光线会不止一次穿过盘面,盘也不止出现一次:直接像,
加上攀在影子上下的弧,那是从黑洞上方与下方看到的盘的远侧。
(《星际穿越》的样子,出于和《星际穿越》一模一样的理由。)发射按圆轨道发射体的
\( g = \sqrt{1 - 3M/r}\,\big/\,(1 - \beta\mu) \) 加权,其中
beta = inversesqrt(max(rd - 2.0, 0.25))(局部轨道速度 \( 1/\sqrt{r - 2} \),
在 ISCO 处整整 \( 0.5c \)),\( \mu \) 是开普勒流与光子方向的夹角余弦;
观测亮度按 pow(g, 3.0)。湍流纹理是以精确的 \( \Omega = r^{-3/2} \)
反向旋转的 fbm 噪声:真实的差速旋转,被 DISK_SPEED = 0.35
艺术性放慢。
On the page — the tilted disk begins abruptly at \( r = 3\,r_s \); the side orbiting toward you blazes white while the receding side smolders (that is \( g^3 \)); the arcs hugging the shadow’s top and bottom are the disk’s far side, bent over and under the hole; and the streaks visibly shear, inner filaments overtaking outer ones lap after lap.
你能看到 —— 倾斜的盘在 \( r = 3\,r_s \) 处戛然开始; 朝你转来的一侧白热刺眼,退行的一侧闷燃(那就是 \( g^3 \)); 抱着影子上下的弧,是被弯过黑洞的盘的远侧;条纹肉眼可见地剪切, 内圈的丝缕一圈又一圈超过外圈。
12Accretion flares: dinner, converted to light吸积耀发:晚餐变成光
Accretion is the most efficient furnace in the universe short of matter–antimatter annihilation. Hydrogen fusion (the Sun’s entire business model) liberates 0.7% of rest mass. Matter spiraling down to a Schwarzschild hole’s ISCO releases 5.7%: eight times better. That number isn’t an engineering estimate; it falls straight out of equation 2. A particle parked on the ISCO has conserved energy \( E_{\mathrm{isco}} = \sqrt{8/9} \) of its rest mass. The journey from far away down to the last stable orbit must therefore have radiated the difference, \( \eta = 1 - \sqrt{8/9} \). This is why quasars (black holes eating a few solar masses a year) outshine their entire host galaxies.
吸积是宇宙里仅次于正反物质湮灭的最高效熔炉。氢聚变(太阳的全部商业模式) 释放静质量的 0.7%。而盘旋落到史瓦西黑洞 ISCO 的物质释放 5.7%:高出八倍。 这个数不是工程估算,它直接从方程 2 里掉出来:停在 ISCO 上的粒子, 守恒能量是其静质量的 \( E_{\mathrm{isco}} = \sqrt{8/9} \)。 那么从远方一路降到最后稳定轨道的旅程,必然辐射掉了差额 \( \eta = 1 - \sqrt{8/9} \)。这就是类星体(每年吃几个太阳质量的黑洞) 光芒盖过整个宿主星系的原因。
The demo honors the spirit at page scale — and takes \( \dot m \) literally. As an element streams in, every grid vertex that freezes onto the horizon is a parcel of mass arriving, and each parcel buys its share of disk luminosity the moment it lands. The disk brightens while the meal is in progress — bigger blocks, brighter flares — and once the last parcel is in, the pulse relaxes exponentially, the way a real accretion transient decays as the extra matter drains through the disk.
演示在页面尺度上致敬这套精神 —— 而且把 \( \dot m \) 当真:元素流入时, 每个冻结在视界上的格点都是一小包到账的质量,每包质量落地的当刻就换来它那份盘面光度。 盘在进食途中就亮起来 —— 块越大,耀发越亮 —— 最后一包入账之后, 脉冲按指数松弛,就像真实的吸积暂现源那样,随多余物质排空盘面而衰减。
The efficiency is exported as ETA_ISCO = 1 - Math.sqrt(8/9) in
src/physics.js. In blackhole.js, every accreted parcel feeds
the flare through onAccrete(area, total):
flare = min(1, flare + 0.35*area/total + area/90000) — summed over
a whole element that is exactly the old lump 0.35 + A/90000, but paid out
with the mass flux instead of all at once. The frame loop drains the reservoir with
flare *= exp(-dt/0.8), and a 0.12 s low-pass (the disk’s
response time) smooths what the shader sees, so even a rigid fallback landing in one
frame cannot strobe. The renderer receives the result as the uniform
u_flare: diskEmission() multiplies its brightness by
1.0 + 3.0*u_flare and the ambient glow scales with it too. The
photon ring, being made of ray-traced disk light, surges on its own.
效率以 ETA_ISCO = 1 - Math.sqrt(8/9) 导出在 src/physics.js。
在 blackhole.js 里,每包入账的质量都经
onAccrete(area, total) 喂给耀发:
flare = min(1, flare + 0.35*area/total + area/90000) ——
对整个元素求和,恰好等于旧的一次性入账 0.35 + A/90000,
只是改成随质量流分期支付。帧循环用 flare *= exp(-dt/0.8) 排空储能,
再过一道 0.12 s 的低通(盘的响应时间)才交给着色器,
即便刚体降级在一帧内整块落账,也闪不起来。渲染器以 uniform
u_flare 接收:diskEmission() 把亮度乘上
1.0 + 3.0*u_flare,环境辉光也随之缩放。光子环由光线追踪的盘光构成,
于是自己就会跟着涌亮。
On the page — as a block streams in, the disk and ring brighten with the mass flux, peak around the last mouthful, and relax over a second or so; sacrifice a big card and the surge noticeably outclasses a nibbled list item.
你能看到 —— 块流入的一路上,盘和环随质量流渐次增亮, 在最后一口前后到达峰值,再在一秒上下松弛回去;献祭一大块内容, 涌亮会明显盖过啃掉的一小行。
13The cost of hovering悬停的代价
Standing still is not free near a black hole. In Newton’s world, hovering at radius \( r \) costs thrust \( M/r^2 \), full stop. In Schwarzschild geometry the bill acquires a \( 1/\sqrt{f} \) surcharge. That changes everything, because the surcharge diverges at the horizon. At \( r = 1.33\,r_s \) hovering costs double Newton; at \( 1.01\,r_s \), ten times; at the horizon, no finite rocket, no engine of any power, can hold position. The horizon isn’t where gravity becomes strong so much as where staying put stops being a thing the universe offers.
在黑洞附近,原地不动不是免费的。牛顿的世界里,悬停在半径 \( r \) 花费推力 \( M/r^2 \),仅此而已。史瓦西几何给账单加了一笔 \( 1/\sqrt{f} \) 的附加费。 这改变了一切,因为这笔附加费在视界处发散。在 \( r = 1.33\,r_s \), 悬停要花牛顿的两倍;在 \( 1.01\,r_s \),十倍;到了视界,任何有限的火箭、 任何功率的引擎,都保不住位置。与其说视界是引力变强的地方, 不如说是"待在原地"从宇宙的选项里下架的地方。
While you drag an element, you are that rocket. The cursor pins the element against gravity, and the demo lets you feel the bill: a displacement bias toward the hole, proportional to the true hover acceleration (capped, because your monitor has edges and your patience has limits). Far away it’s imperceptible; skim the shadow and the element strains against your grip like a fish on a line.
当你拖着一个元素,你就是那枚火箭。光标把元素钉在引力之上,而演示让你摸到这张账单: 一个朝向黑洞的位移偏置,正比于真实的悬停加速度(有上限,因为你的显示器有边, 你的耐心也有)。离得远时难以察觉;贴着影子掠过,元素就像上了钩的鱼一样挣你的手。
hoverAccel(r, rs) in src/physics.js computes
(rs/2) / (r*r*Math.sqrt(f)), with a guard for \( f \le 10^{-4} \) since
the true value diverges. In interaction.js, the drag branch of
tick() converts it to pixels of tug:
pull = min(hoverAccel(dist, rs) * PULL_GAIN, PULL_MAX)
(gain 900, cap 26 px), applied along the unit vector toward the hole’s
center, with the distance floored at \( 1.05\,r_s \).
src/physics.js 里的 hoverAccel(r, rs) 计算
(rs/2) / (r*r*Math.sqrt(f)),并对 \( f \le 10^{-4} \) 设了护栏,
因为真值是发散的。在 interaction.js 里,tick() 的拖拽分支
把它换算成像素级的拽力:pull = min(hoverAccel(dist, rs) * PULL_GAIN, PULL_MAX)
(增益 900,上限 26 px),沿指向黑洞中心的单位向量施加,
距离下限取 \( 1.05\,r_s \)。
On the page — a dragged block leans toward the hole; the closer you carry it, the harder it pulls off-cursor, a haptic rendering of “you cannot hover at the horizon.”
你能看到 —— 被拖拽的块朝黑洞倾身;你把它带得越近, 它挣脱光标的力道越大:一次"视界处无法悬停"的触觉渲染。
14Radiation drag: the honest thumb on the scale辐射阻尼:坦白按在秤上的拇指
Pure geodesic motion has a gameplay flaw: it is too good. A test particle thrown onto a bound orbit outside the ISCO will, in exact Schwarzschild physics, orbit until the end of time. Your headline would precess forever, never feeding the hole. Real binaries escape this fate because orbiting masses radiate gravitational waves, bleeding energy until they merge. That dissipation enters the post-Newtonian expansion at the famous 2.5PN order, a first correction suppressed by \( (v/c)^5 \). It is precisely what made the Hulse–Taylor pulsar’s orbit shrink on schedule and LIGO’s chirps chirp.
纯测地线运动有个游戏性缺陷:它太完美了。被甩上 ISCO 之外束缚轨道的试验粒子, 在精确的史瓦西物理里会一直绕到时间尽头。你的标题会永远进动下去,永远喂不进黑洞。 真实的双星逃过这个命运,是因为互绕的质量辐射引力波,失血般漏掉能量直到并合。 这份耗散在后牛顿展开中出现在著名的 2.5PN 阶,是被 \( (v/c)^5 \) 压低的首个修正。 正是它让赫尔斯–泰勒脉冲双星的轨道如期收缩,让 LIGO 的啁啾得以啁啾。
But a <div>’s self-gravitational-wave emission is, let us
say, below the noise floor. So the demo adds a phenomenological drag with the same
moral character: an acceleration opposing the velocity, switched on steeply,
as the fourth power of the locally measured speed. Slow,
distant orbits are essentially untouched; fast, tight ones decay in a few laps.
To be clear, and the honesty box says it too: this is a gameplay term inspired
by the 2.5PN scaling, not derived from it. Its one design goal is that every
orbit is ultimately a spiral.
可一个 <div> 的自发引力波辐射,委婉地说,在噪声底之下。
于是演示加了一项唯象阻尼,道德品格与之相同:一个与速度反向的加速度,
以局部测得速度的四次方陡峭开启。慢而远的轨道几乎不受打扰;
快而紧的轨道几圈内衰亡。说清楚(诚实清单里也写着):这是一个受
2.5PN 标度启发的游戏项,不是从它推导出来的。它唯一的设计目标是:
每条轨道终究是一条螺线。
Inside stepBody() in src/physics.js: when
dragK > 0, each substep computes v = localSpeed(body, rs)
(the static-observer speed \( \sqrt{1 - f/E^2} \)) and multiplies both
momenta by d = max(0, 1 - dragK * v^4 * dtau):
body.pr *= d; body.L *= d. It then re-derives the stored energy via
energyOf(), since drag is one of only two processes allowed to touch
the conserved \( E \) (the other being a fattening hole, handled adiabatically). The
knob is dragK (default 2.0 in
blackhole.js’s DEFAULTS); set it to 0
and you get pure, eternal geodesics.
在 src/physics.js 的 stepBody() 内部:当
dragK > 0,每个子步计算 v = localSpeed(body, rs)
(静止观察者速度 \( \sqrt{1 - f/E^2} \)),再把两个动量同乘
d = max(0, 1 - dragK * v^4 * dtau):
body.pr *= d; body.L *= d。然后经 energyOf() 重新导出
所存的能量,因为阻尼是仅有的两个获准触碰守恒量 \( E \) 的过程之一
(另一个是变胖的黑洞,按绝热处理)。旋钮是 dragK
(blackhole.js 的 DEFAULTS 里默认 2.0);
调成 0,你就得到纯粹而永恒的测地线。
On the page — orbits that would circle indefinitely tighten a little each pass, imperceptibly at first, then decisively; every thrown element’s story ends at the shadow.
你能看到 —— 本可无限绕转的轨道每一圈都收紧一点, 起初难以察觉,继而不容分说;每个被甩出的元素,故事都终结在影子上。
15Entropy: the hole is a hard drive熵:黑洞是一块硬盘
The strangest fact on this page: a black hole’s entropy (its information capacity, the count of microscopic ways to be that exact hole) scales with its surface area, not its volume. Bekenstein conjectured it from thought experiments about hiding entropy behind horizons. Hawking’s discovery of black hole radiation fixed the coefficient at exactly one quarter of the area in Planck units, \( \ell_P^2 = G\hbar/c^3 \approx (1.6 \times 10^{-35}\,\mathrm{m})^2 \). Every quantum-gravity candidate is judged against this formula. Its area scaling seeded the holographic principle: the suspicion that everything inside a region is describable on its boundary. As storage, it reads: roughly one bit per \( 4 \ln 2 \) Planck areas of horizon. There is no denser way to pack information; a black hole is the thermodynamic limit of a hard drive.
本页最古怪的事实:黑洞的熵(它的信息容量,"成为这颗黑洞"的微观方式的数目), 正比于它的表面积,而不是体积。贝肯斯坦从"把熵藏进视界"的思想实验里猜到了它。 霍金发现黑洞辐射后,把系数钉死在普朗克单位下面积的恰好四分之一, \( \ell_P^2 = G\hbar/c^3 \approx (1.6 \times 10^{-35}\,\mathrm{m})^2 \)。 每个量子引力候选理论都要拿这条公式来审。它的面积标度还播下了全息原理的种子: 那个"区域内的一切都可以写在边界上"的怀疑。当成存储器来读: 视界每 \( 4 \ln 2 \) 个普朗克面积,约存一比特。没有比这更稠密的信息打包方式; 黑洞是硬盘的热力学极限。
The demo’s HUD runs the number as a wink. Declare 1 px = 1 Planck length (a lie of a mere thirty-odd orders of magnitude), and the equatorial-plane formula gives \( S / (k_B \ln 2) = \pi r_s^2 / \ln 2 \) bits for a horizon of radius \( r_s \) pixels. The starting 16-px hole “stores” about 1.2 kbit; swallow the whole page and it climbs roughly a hundredfold, toward the megabit mark, growing monotonically as thermodynamics insists. Your deleted content is not gone, merely very well encrypted.
演示的 HUD 把这个数当彩蛋来算。宣布 1 px = 1 普朗克长度 (只撒了三十来个数量级的谎),赤道面公式便给出:半径 \( r_s \) 像素的视界, 存有 \( S / (k_B \ln 2) = \pi r_s^2 / \ln 2 \) 比特。开局 16 px 的黑洞"存储"约 1.2 kbit;吞掉整个页面,它会涨上约百倍、直逼兆比特,如热力学所坚持的那样单调增长。 你被删掉的内容并没有消失,只是被非常妥善地加了密。
The HUD entropy row lives in src/hud.js: update() computes
fmtBits((Math.PI * rsPx * rsPx) / Math.LN2) and renders it next to the
label “Entropy S (1px=ℓP)”, the joke
calibration disclosed in-line. It grows as \( r_s^2 \), i.e. as horizon area, and
because accretion only ever adds mass (equation 7), the displayed entropy never
decreases: the second law, enforced by onAccrete(), one frozen vertex at
a time. When everything is eaten, the HUD concedes
“Heat death reached.”
HUD 的熵行住在 src/hud.js:update() 计算
fmtBits((Math.PI * rsPx * rsPx) / Math.LN2),渲染在标签
"Entropy S (1px=ℓP)"旁边,玩笑标定当场声明。
它按 \( r_s^2 \)、也就是按视界面积增长;又因为吸积只会增加质量(方程 7),
显示的熵从不减少:热力学第二定律,由 onAccrete() 逐格点执法。
当一切都被吃光,HUD 认输:"Heat death reached."(热寂已达)
On the page — watch the HUD’s entropy row while you feed the hole: every paragraph becomes a few hundred more bits of horizon, and the number only ever goes up.
你能看到 —— 喂黑洞时盯住 HUD 的熵行: 每个段落都化作几百比特新视界,而这个数字只涨不跌。