docs: make Chinese README the default
This commit is contained in:
@@ -1,163 +1,79 @@
|
||||
<!-- WEHUB_ZH_README -->
|
||||
> [!NOTE]
|
||||
> 本文档由 WeHub 基于上游 README 翻译整理,属于社区翻译,非官方中文文档。
|
||||
> [English](./README.en.md) · [原始项目](https://github.com/s0xDk/ghostty-blackhole) · [上游 README](https://github.com/s0xDk/ghostty-blackhole/blob/HEAD/README.md)
|
||||
> 原作者、版权与许可证归属以原始项目及本仓库 LICENSE 文件为准。
|
||||
|
||||
# Ghostty Blackhole
|
||||
|
||||

|
||||
|
||||
**Landing page:** [s13k.dev/blackhole](https://s13k.dev/blackhole/)
|
||||
|
||||
A black hole floating inside your [Ghostty](https://ghostty.org) terminal. It
|
||||
starts small and grows to swallow your screen, driven by whichever **size mode**
|
||||
you pick: a built-in *pomodoro* clock (grow through the hour, demand a break,
|
||||
leave you alone once you take it), or *token mode*, where it tracks how full
|
||||
**Claude Code's context window** is in real time.
|
||||
一个漂浮在你 [Ghostty](https://ghostty.org) 终端里的黑洞。它从小开始生长,最终吞噬整个屏幕,具体行为取决于你选择的 **尺寸模式(size mode)**:内置的 *番茄钟(pomodoro)* 时钟(在一小时内逐渐变大、提醒你休息、休息后就不再打扰),或 *token 模式(token mode)*,实时追踪 **Claude Code 上下文窗口(context window)** 的填充程度。
|
||||
|
||||
Modeled on [Eric Bruneton's black hole shader](https://ebruneton.github.io/black_hole_shader/),
|
||||
which beam-traces Schwarzschild geodesics against precomputed lookup tables.
|
||||
A Ghostty custom shader is a single Shadertoy-style fragment pass with no
|
||||
custom textures, so the lookup tables are replaced by doing the physics live:
|
||||
every pixel near the hole **integrates its own null geodesic** through the
|
||||
Schwarzschild metric (the Binet-form acceleration `a = -3/2 h² x/r⁵`, which
|
||||
gives the exact photon bending). Your terminal contents play the role of the
|
||||
lensed background sky. Nothing below is painted on — it all falls out of the
|
||||
ray tracing:
|
||||
本效果基于 [Eric Bruneton 的黑洞着色器](https://ebruneton.github.io/black_hole_shader/),,该着色器通过光束追踪(beam-trace)史瓦西(Schwarzschild)测地线,并对预计算的查找表进行采样。Ghostty 自定义着色器是单遍 Shadertoy 风格的片段着色器,没有自定义纹理,因此查找表被替换为实时物理计算:黑洞附近每个像素都会沿史瓦西度规(Schwarzschild metric)**积分各自的零测地线(null geodesic)**(Binet 形式的加速度 `a = -3/2 h² x/r⁵`,可给出精确的光子偏折)。你的终端内容充当透镜背景天空的角色。下方没有任何东西是画上去的——一切都来自光线追踪:
|
||||
|
||||
## What it renders
|
||||
## 渲染内容
|
||||
|
||||
- **The shadow** — rays with impact parameter under `b_crit = (3√3/2) r_s`
|
||||
spiral through the horizon and come back black. Text near the edge is
|
||||
stretched into the photon ring before it disappears; text behind the hole
|
||||
really is gone.
|
||||
- **Gravitational lensing** — escaped rays are projected back onto the
|
||||
terminal "sky" plane: text bends, magnifies, and shows the mirrored
|
||||
secondary image inside the Einstein ring. Far from the hole this hands off
|
||||
to the analytic weak-field deflection (`α = 2r_s/b`), so only pixels near
|
||||
the hole pay for the integration. Blue bends slightly more than red out
|
||||
there for a touch of chromatic aberration.
|
||||
- **Accretion disk** — a thin Keplerian disk that a ray can pierce several
|
||||
times: the far side arcs *over and under* the shadow (the Interstellar
|
||||
look), and the photon-ring region shows higher-order disk images.
|
||||
Coloring is physical: a Shakura–Sunyaev temperature profile rendered as
|
||||
blackbody color, shifted and beamed by the relativistic factor
|
||||
`g = √(1 − 1.5 r_s/r)/(1 − β·k̂)` — the approaching side is blue-hot and
|
||||
boosted by `g^N`, the receding side dim and red.
|
||||
- **Photon ring** — rays winding near the `1.5 r_s` photon sphere pick up
|
||||
every disk crossing; the bright thin ring is emergent, not drawn.
|
||||
- **Lensed starfield** — a faint procedural sky sampled with the *bent* ray
|
||||
direction, so stars smear into arcs around the hole (off by default —
|
||||
raise `STAR_GAIN` to enable).
|
||||
- **Gravitational time dilation** — the disk pattern at radius `r` advances
|
||||
at the proper rate `√(1 − 1.5 r_s/r)`, so the inner orbits visibly freeze;
|
||||
the whole disk also winds down as the hole grows heavier (`DILATION_MIN`).
|
||||
- The hole drifts on a slow Lissajous path, confined to the upper part of
|
||||
the screen — the bottom `WORK_AREA` fraction (your prompt) is never
|
||||
distorted. Drift speed and reach follow its size: small and calm, big
|
||||
and restless.
|
||||
- **阴影(shadow)** — 碰撞参数低于 `b_crit = (3√3/2) r_s` 的光线螺旋穿过视界后返回纯黑。边缘附近的文字在消失前会被拉伸进光子环(photon ring);黑洞后方的文字确实消失了。
|
||||
- **引力透镜(gravitational lensing)** — 逃逸光线被投影回终端的「天空」平面:文字弯曲、放大,并在爱因斯坦环(Einstein ring)内呈现镜像的次级像。远离黑洞处会交接给解析弱场偏折(`α = 2r_s/b`),因此只有黑洞附近的像素需要承担积分开销。远处蓝光比红光偏折略多,带来一丝色差(chromatic aberration)。
|
||||
- **吸积盘(accretion disk)** — 薄开普勒盘(Keplerian disk),光线可多次穿透:远侧在阴影上下方形成弧线(*星际穿越* 风格),光子环区域显示更高阶的盘面像。着色遵循物理:Shakura–Sunyaev 温度分布以黑体色渲染,再经相对论因子 `g = √(1 − 1.5 r_s/r)/(1 − β·k̂)` 红移与束流化(beaming)—— 接近侧蓝热,并由 `g^N` 增强;远离侧暗淡偏红。
|
||||
- **光子环(photon ring)** — 在 `1.5 r_s` 光子球(photon sphere)附近缠绕的光线会拾取每一次盘面穿越;明亮细环是涌现效果,并非直接绘制。
|
||||
- **透镜星场(lensed starfield)** — 以 *弯曲* 后的光线方向采样的微弱程序化天空,恒星在黑洞周围拖成弧线(默认关闭 — 提高 `STAR_GAIN` 即可启用)。
|
||||
- **引力时间膨胀(gravitational time dilation)** — 半径 `r` 处的盘面图案以固有时速率 `√(1 − 1.5 r_s/r)` 前进,内轨道可见地「冻结」;随着黑洞变重(`DILATION_MIN`),整个盘面也会减速。
|
||||
- 黑洞沿缓慢的利萨如(Lissajous)路径漂移,限制在屏幕上半部分 — 底部 `WORK_AREA` 比例区域(你的提示符)永远不会被扭曲。漂移速度与活动范围随尺寸变化:小时平静,大时躁动。
|
||||
|
||||
## Pomodoro mode
|
||||
## 番茄钟模式
|
||||
|
||||
The break reminder is computed entirely inside the shader — no daemon, no
|
||||
shell hooks, nothing outside `blackhole.glsl`.
|
||||
休息提醒完全在着色器内计算 — 无需守护进程、shell 钩子,`blackhole.glsl` 之外什么都不需要。
|
||||
|
||||
Shaders are stateless (no buffers persist between frames, and Ghostty has no
|
||||
custom uniforms), so a shader cannot remember when *your* work streak began.
|
||||
Instead the schedule is anchored to the wall clock via `iDate`:
|
||||
着色器是无状态的(帧间不保留缓冲区,且 Ghostty 没有自定义 uniform),因此着色器无法记住 *你的* 工作时段何时开始。取而代之的是,时间表通过 `iDate` 锚定到挂钟时间:
|
||||
|
||||
- **Cycle**: the hole is always present while you work — it starts small at
|
||||
the cycle floor, grows over `WORK_PERIOD_MIN` (default 55 min), collapses
|
||||
back to small in the last minute, and stays small through `BREAK_MIN`
|
||||
(default 5 min). With 55+5 the peak hits at five-to-the-hour — a fixed,
|
||||
predictable rhythm.
|
||||
- **Typing detector**: `iTimeCursorChange` tracks cursor activity in your
|
||||
terminal. Stop using it for `IDLE_FADE_SEC` (default 90 s) and the hole
|
||||
shrinks live, gone entirely after a few minutes of quiet — it never nags
|
||||
while you aren't actually working.
|
||||
- **周期(cycle)**:工作时黑洞始终存在 — 在周期下限处从小开始,在 `WORK_PERIOD_MIN`(默认 55 分钟)内逐渐变大,最后一分钟缩回小尺寸,并在 `BREAK_MIN`(默认 5 分钟)内保持小尺寸。55+5 时,峰值落在每小时差 5 分 — 固定、可预测的节奏。
|
||||
- **输入检测器(typing detector)**:`iTimeCursorChange` 追踪终端中的光标活动。停止使用 `IDLE_FADE_SEC`(默认 90 秒)后,黑洞会实时缩小,安静几分钟后完全消失 — 你实际没在工作时它不会烦你。
|
||||
|
||||
The trade-off of self-containment: the cycle won't re-anchor to a break you
|
||||
take at an odd time — it's an hourly bell, not a per-streak stopwatch.
|
||||
自包含的代价是:周期不会在你于非常规时间休息时重新锚定 — 它是每小时一次的钟声,而非按工作 streak 计时的秒表。
|
||||
|
||||
> **Caveat:** stock Ghostty (through 1.3) declares `iDate` but never
|
||||
> populates it — it is always zero — so on current releases the wall-clock
|
||||
> schedule doesn't advance: the hole sits at its small cycle-floor size and
|
||||
> only the typing detector works. The full cycle comes alive once Ghostty
|
||||
> wires `iDate` up (you can preview it today with `TIME_SCALE`, which runs
|
||||
> off `iTime` instead). Token mode, the default, is unaffected.
|
||||
> **注意:** 原版 Ghostty(截至 1.3)声明了 `iDate` 但从未填充 — 它始终为零 — 因此在当前版本中挂钟时间表不会推进:黑洞停留在小尺寸周期下限,只有输入检测器有效。一旦 Ghostty 接入 `iDate`,完整周期就会生效(今天可用 `TIME_SCALE` 预览,它改用 `iTime` 运行)。Token 模式(默认)不受影响。
|
||||
|
||||
## Size modes
|
||||
## 尺寸模式
|
||||
|
||||
What drives the hole is selected by `SIZE_MODE` near the top of `blackhole.glsl`:
|
||||
驱动黑洞的因素由 `blackhole.glsl` 顶部附近的 `SIZE_MODE` 选择:
|
||||
|
||||
- `MODE_POMODORO` — the self-contained wall-clock schedule described above. Works
|
||||
standalone, no setup beyond the shader (but see the `iDate` caveat above).
|
||||
- `MODE_TOKENS` *(default)* — the hole tracks **Claude Code's context-window
|
||||
fill**. Requires the bundled command (below).
|
||||
- `MODE_DEMO` — a self-running **42-second showcase loop** for recording demos:
|
||||
the hole grows from the corner seed to 100 % exactly as token mode would,
|
||||
while the disk look tours the tuner presets (Inferno → Gargantua → M87* donut
|
||||
→ Face-on ember → Quasar → Blazar → Pure lens → Inferno), crossfading at each
|
||||
~5 s slot boundary. Everything runs off `iTime` inside one compiled shader —
|
||||
no reloads, so a recording never hitches. Toggle it with `./demo-mode.sh
|
||||
on|off` (which also reloads Ghostty); the cursor channel is ignored in demo
|
||||
mode, so a live Claude session can't disturb a recording. Record any full
|
||||
cycle — the loop restart is obvious (the hole snaps back to the corner
|
||||
seed).
|
||||
- `MODE_POMODORO` — 上文所述的自包含挂钟时间表。可独立工作,除着色器外无需配置(但请参阅上文 `iDate` 的注意项)。
|
||||
- `MODE_TOKENS` *(默认)* — 黑洞追踪 **Claude Code 上下文窗口填充度**。需要配套命令(见下文)。
|
||||
- `MODE_DEMO` — 自运行的 **42 秒展示循环**,用于录制演示:黑洞从角落种子长到 100%,与 token 模式完全一致,同时盘面外观依次浏览调谐预设(Inferno → Gargantua → M87* 甜甜圈 → Face-on ember → Quasar → Blazar → Pure lens → Inferno),在每个约 5 秒的时段边界交叉淡入淡出。一切都在单个编译着色器内由 `iTime` 驱动 — 无需重载,录制不会卡顿。用 `./demo-mode.sh on|off` 切换(也会重载 Ghostty);演示模式下忽略光标通道,因此实时 Claude 会话不会干扰录制。可录制任意完整周期 — 循环重启很明显(黑洞会弹回角落种子)。
|
||||
|
||||
### Token mode
|
||||
### Token 模式
|
||||
|
||||
The hole reflects how full Claude's context window is, *live*:
|
||||
黑洞实时反映 Claude 上下文窗口的填充程度:
|
||||
|
||||
- **Empty context** — a small hole in the **top-right corner**, sized to
|
||||
cover `TOKEN_AREA_MIN` (default **0.06 %**) of the terminal area — the same
|
||||
felt size on any window shape.
|
||||
- **Filling up** — it grows toward `TOKEN_AREA_MAX` (default **~3 %** of the
|
||||
terminal at 100 % context — that's the *shadow*; the bright disk reaches
|
||||
~3× past it, so it reads far bigger), moves **faster**, and its allowed roam box
|
||||
expands out of the corner — left and down — until it covers the whole
|
||||
playable screen above the work area; the hole wanders pseudo-randomly
|
||||
through all of it. The orbit is scaled to the box (never clipped —
|
||||
clipping would park it dead at the boundary), with margins that keep the
|
||||
shadow and bright inner disk on screen while it's small.
|
||||
- **`/compact` or a new session** — snaps back to the corner seed.
|
||||
- **No Claude session running** — the hole disappears entirely; you get a plain
|
||||
terminal.
|
||||
- **空上下文** — **右上角** 一个小黑洞,大小覆盖终端面积的 `TOKEN_AREA_MIN`(默认 **0.06 %**)— 任意窗口形状下体感大小一致。
|
||||
- **逐渐填满** — 向 `TOKEN_AREA_MAX` 生长(100 % 上下文时默认约为终端的 **~3 %** — 那是 *阴影*;明亮盘面可延伸到约 3 倍之外,因此视觉上更大),移动 **更快**,允许漫游区域从角落向左、向下扩展,直到覆盖工作区上方整个可玩屏幕;黑洞在其中伪随机游荡。轨道按区域缩放(永不裁剪 — 裁剪会把它钉死在边界),边距确保阴影和明亮内盘在较小时仍留在屏幕上。
|
||||
- **`/compact` 或新会话** — 弹回角落种子。
|
||||
- **未运行 Claude 会话** — 黑洞完全消失;你得到普通终端。
|
||||
|
||||
#### How it works
|
||||
#### 工作原理
|
||||
|
||||
Ghostty custom shaders take no custom uniforms — but they *do* get the cursor
|
||||
color (`iCurrentCursorColor`), and any program can set the cursor color with a
|
||||
standard OSC 12 escape. So the token count rides in on the cursor: a single
|
||||
bundled script, `claude-token.py`, is wired into Claude Code three ways and
|
||||
encodes the context fill into the low nibbles of an amber cursor color
|
||||
(`#f5b000` empty → `#f0bf0a` full); the shader decodes it back out every
|
||||
frame. The fixed high nibbles plus a 4-bit checksum form a 16-bit signature,
|
||||
so a theme's own cursor color can't accidentally summon a black hole. No file
|
||||
is rewritten, nothing reloads, there is no recompile hitch — updates land on
|
||||
the next frame.
|
||||
Ghostty 自定义着色器不接受自定义 uniform — 但 *会* 获得光标颜色(`iCurrentCursorColor`),任何程序都可用标准 OSC 12 转义序列设置光标颜色。因此 token 计数通过光标传入:单个配套脚本 `claude-token.py` 以三种方式接入 Claude Code,将上下文填充编码进琥珀色光标颜色的低 4 位(`#f5b000` 空 → `#f0bf0a` 满);着色器每帧解码。固定高 4 位加上 4 位校验和构成 16 位签名,因此主题自带的光标颜色不会意外召唤黑洞。不重写文件、不重载、无重编译卡顿 — 更新在下一帧生效。
|
||||
|
||||
Level steps land smoothly, too: Ghostty bumps `iTimeCursorChange` on any
|
||||
cursor change *including color* and snapshots the old color into
|
||||
`iPreviousCursorColor`, so the shader decodes both and glides between the two
|
||||
levels — discrete updates read as continuous motion instead of popping the
|
||||
whole warp field. The glide time scales with the jump (`TOKEN_GLIDE_*`):
|
||||
1 % ticks ease over 0.3 s, a 10 % jump takes 1 s, capped at 1.5 s.
|
||||
级别阶梯也会平滑过渡:Ghostty 在任何光标变化(*包括颜色*)时递增 `iTimeCursorChange`,并将旧颜色快照到 `iPreviousCursorColor`,着色器解码两者并在两级之间平滑过渡 — 离散更新呈现为连续运动,而非整个扭曲场突然弹出。平滑时间随跳变幅度缩放(`TOKEN_GLIDE_*`):1 % 步进在 0.3 秒内缓动,10 % 跳变需 1 秒,上限 1.5 秒。
|
||||
|
||||
| Wiring | When it fires | What it does |
|
||||
| 接线 | 触发时机 | 作用 |
|
||||
|--------|---------------|--------------|
|
||||
| `statusLine` | every assistant turn | encodes the context fill (`0..1`, 1/250 steps) into the cursor color and prints a built-in-style line: `⚫️ ██████░░░░ 61% · Fable 5 · Projects/blackhole · ⎇ main · $1.27 · 5h 24% · wk 41%` (the last segment is your Claude usage limits; a window past 80 % also shows its reset time) |
|
||||
| `SessionStart` hook | session start / resume / `/clear` | resets to the corner seed (`0.0`) |
|
||||
| `SessionEnd` hook | session exit (`/exit`, `ctrl-d`, …) | resets the cursor color (OSC 112) — no signature means no hole |
|
||||
| `statusLine` | 每次 assistant 回合 | 将上下文填充(`0..1`,1/250 步进)编码进光标颜色,并打印内置风格行:`⚫️ ██████░░░░ 61% · Fable 5 · Projects/blackhole · ⎇ main · $1.27 · 5h 24% · wk 41%`(最后一段是你的 Claude 用量限制;窗口超过 80 % 时还会显示重置时间) |
|
||||
| `SessionStart` hook | 会话开始 / 恢复 / `/clear` | 重置为角落种子(`0.0`) |
|
||||
| `SessionEnd` hook | 会话退出(`/exit`、`ctrl-d`、…) | 重置光标颜色(OSC 112)— 无签名即无黑洞 |
|
||||
|
||||
A cursor color without the signature means "no session", so a bare install
|
||||
shows nothing until a live session encodes a real fill. (The shader's
|
||||
`TOKEN_LEVEL` define remains as a manual override for hand-testing a size;
|
||||
it only applies while the cursor carries no signal.)
|
||||
没有签名特征的光标颜色表示「无会话」,因此全新安装后不会显示任何效果,直到活跃会话编码出真实的填充值。(着色器中的 `TOKEN_LEVEL` 定义仍可作为手动覆盖,用于手动测试尺寸;它仅在光标未携带信号时生效。)
|
||||
|
||||
#### Install token mode
|
||||
#### 安装 token 模式
|
||||
|
||||
Two pieces — the shader and the command:
|
||||
需要两部分——着色器和命令:
|
||||
|
||||
1. Point Ghostty at the shader (see [Install](#install) below) with
|
||||
`SIZE_MODE MODE_TOKENS` (the default).
|
||||
2. Add this to **`~/.claude/settings.json`** (adjust the path), then start a new
|
||||
Claude Code session:
|
||||
1. 将 Ghostty 指向该着色器(见下方 [安装](#install)),使用 `SIZE_MODE MODE_TOKENS`(默认值)。
|
||||
2. 将以下内容添加到 **`~/.claude/settings.json`**(请调整路径),然后启动新的 Claude Code 会话:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -172,139 +88,95 @@ Two pieces — the shader and the command:
|
||||
}
|
||||
```
|
||||
|
||||
This is global, so the hole reacts to *any* Claude Code session. A few notes:
|
||||
这是全局配置,因此黑洞会对*任意* Claude Code 会话做出反应。几点说明:
|
||||
|
||||
- The cursor color is per-surface state, so every Ghostty split/window gets
|
||||
its **own** hole — concurrent sessions in different surfaces don't fight.
|
||||
- Your cursor turns accretion-disk amber while a session is live (that *is*
|
||||
the data channel). Anything else recoloring the cursor gets overwritten at
|
||||
the next statusline refresh; a cursor without the signature simply means
|
||||
"no hole".
|
||||
- Inside tmux/screen the OSC would need a passthrough to reach Ghostty —
|
||||
token mode is built for plain Ghostty sessions.
|
||||
- To opt out, set `SIZE_MODE MODE_POMODORO` and remove the entries above.
|
||||
- 光标颜色是按 surface 维护的状态,因此每个 Ghostty 分屏/窗口都有**各自**的黑洞——不同 surface 中的并发会话不会互相干扰。
|
||||
- 会话活跃时,光标会变为吸积盘琥珀色(那*就是*数据通道)。其他任何会重着色光标的设置都会在下次 statusline 刷新时被覆盖;没有签名特征的光标 simply 表示「无黑洞」。
|
||||
- 在 tmux/screen 内,OSC 需要透传才能到达 Ghostty——token 模式面向纯 Ghostty 会话设计。
|
||||
- 若要退出,请设置 `SIZE_MODE MODE_POMODORO` 并移除上述条目。
|
||||
|
||||
## Install
|
||||
## 安装
|
||||
|
||||
Requires Ghostty 1.3+ (for the cursor shader uniforms).
|
||||
需要 Ghostty 1.3+(用于光标着色器 uniform)。
|
||||
|
||||
Clone the repo, then add to your Ghostty config (`~/.config/ghostty/config`
|
||||
or `~/Library/Application Support/com.mitchellh.ghostty/config` on macOS):
|
||||
克隆仓库,然后添加到你的 Ghostty 配置(macOS 上使用 `~/.config/ghostty/config` 或 `~/Library/Application Support/com.mitchellh.ghostty/config`):
|
||||
|
||||
```ini
|
||||
custom-shader = /path/to/blackhole_ghostty/blackhole.glsl
|
||||
custom-shader-animation = true
|
||||
```
|
||||
|
||||
Reload the config (`cmd+shift+,` on macOS) or open a new window.
|
||||
重新加载配置(macOS 上使用 `cmd+shift+,`)或打开新窗口。
|
||||
|
||||
## Tuning
|
||||
## 调参
|
||||
|
||||
### Tuner app (macOS)
|
||||
### 调参应用(macOS)
|
||||
|
||||
A native SwiftUI control panel in the spirit of
|
||||
[Bruneton's demo page](https://ebruneton.github.io/black_hole_shader/demo/demo.html)
|
||||
lives in `tuner/`: grouped sliders for every shader tunable, presets
|
||||
(*Gargantua*, *Quasar*, *M87\* donut*, *Blazar*, *Inferno*, *Zen*, …),
|
||||
live two-way sync with the file, and every nudge hot-reloads Ghostty
|
||||
instantly via `SIGUSR2`.
|
||||
一款原生 SwiftUI 控制面板,风格类似 [Bruneton's demo page](https://ebruneton.github.io/black_hole_shader/demo/demo.html)
|
||||
位于 `tuner/`:为每个着色器可调参数提供分组滑块、预设(*Gargantua*、*Quasar*、*M87\* donut*、*Blazar*、*Inferno*、*Zen* 等),与文件双向实时同步,且每次微调都会通过 `SIGUSR2` 立即热重载 Ghostty。
|
||||
|
||||

|
||||

|
||||
|
||||
```sh
|
||||
cd tuner && swift run # run it
|
||||
./tuner/make-app.sh # or bundle tuner/dist/Black Hole Tuner.app
|
||||
```
|
||||
|
||||
Or just edit the constants at the top of `blackhole.glsl` and reload
|
||||
(`cmd+shift+,`).
|
||||
或直接编辑 `blackhole.glsl` 顶部的常量并重新加载(`cmd+shift+,`)。
|
||||
|
||||
### Constants
|
||||
### 常量
|
||||
|
||||
At the top of `blackhole.glsl`. Radii are in Schwarzschild radii (`r_s`);
|
||||
the ISCO — the innermost stable circular orbit — is at `3 r_s`.
|
||||
位于 `blackhole.glsl` 顶部。半径以史瓦西半径(`r_s`)为单位;ISCO(最内稳定圆轨道,innermost stable circular orbit)位于 `3 r_s`。
|
||||
|
||||
| Constant | Effect |
|
||||
|-------------------|---------------------------------------------------------|
|
||||
| `HOLE_RADIUS` | Size dial — pomodoro: shadow radius at full size (fraction of screen height); token mode: scales the area calibration (exact at 0.08) |
|
||||
| `LENS_DEPTH` | Distance from hole to the terminal "sky" plane, in `r_s` — bigger bends text harder |
|
||||
| `STAR_GAIN` | Lensed starfield brightness (0 = off) |
|
||||
| `DISK_INNER` / `DISK_OUTER` | Disk inner/outer edge in `r_s` (inner clamps to stay outside the photon sphere) |
|
||||
| `DISK_INCL` | Disk inclination, radians: `0` face-on, `π/2` edge-on |
|
||||
| `DISK_ROLL` | Rotation of the whole system in the screen plane |
|
||||
| `DISK_GAIN` | Disk emission brightness |
|
||||
| `DISK_OPACITY` | How much the near disk hides what's behind it |
|
||||
| `DISK_TEMP` | Blackbody temperature of the hottest annulus, Kelvin |
|
||||
| `DOPPLER_MIX` | Relativistic color/brightness asymmetry: `0` off, `1` full |
|
||||
| `DISK_BEAM` | Beaming exponent — intensity scales as `g^N` |
|
||||
| `DISK_SPEED` | Streak pattern speed; negative reverses the orbit |
|
||||
| `DISK_WIND` | Spiral winding tightness of the streaks |
|
||||
| `DISK_CONTRAST` | Streak contrast: `0` = smooth haze, higher = sharp filaments |
|
||||
| `EXPOSURE` | Tonemap exposure for disk light (text is never tonemapped) |
|
||||
| `DRIFT_SPEED` | How fast the hole floats around |
|
||||
| `WORK_AREA` | Bottom screen fraction kept completely undistorted |
|
||||
| `DILATION_MIN` | Disk's pattern time rate when the hole is fully grown (lower = more slowdown) |
|
||||
| `TOKEN_AREA_MIN` | Token mode: shadow area at 0% context, as a fraction of the terminal area (default 0.06%) |
|
||||
| `TOKEN_AREA_MAX` | Token mode: shadow area at 100% context (default ~3% of the terminal — render cost scales with it) |
|
||||
| `TOKEN_HOME_X` / `TOKEN_HOME_Y` | Token mode: corner-home position in uv (`1,0` = exact top-right; y runs top-down) |
|
||||
| `TOKEN_EASE` | Token mode: growth curve exponent — `1` = proportional, `<1` front-loads growth, `>1` keeps it small until late |
|
||||
| `TOKEN_REACH` | Token mode: how much of the playable screen the roam box covers at 100% context |
|
||||
| `TOKEN_CALM` / `TOKEN_RUSH` | Token mode: drift speed at 0% / 100% context |
|
||||
| `WORK_PERIOD_MIN` | Work minutes per pomodoro cycle (growth phase) |
|
||||
| `BREAK_MIN` | Break minutes per cycle (hole stays small) |
|
||||
| `IDLE_FADE_SEC` | Typing pause after which the hole starts to fade |
|
||||
| `TIME_SCALE` | Testing only: `1` = real schedule; `>1` fast-forwards growth via `iTime` |
|
||||
| `HOLE_RADIUS` | 尺寸旋钮——番茄钟:满尺寸时的阴影半径(占屏幕高度的比例);token 模式:缩放面积校准(在 0.08 时精确) |
|
||||
| `LENS_DEPTH` | 黑洞到终端「天空」平面的距离,单位为 `r_s`——数值越大,文字弯曲越明显 |
|
||||
| `STAR_GAIN` | 透镜化星场的亮度(0 = 关闭) |
|
||||
| `DISK_INNER` / `DISK_OUTER` | 吸积盘内/外缘,单位为 `r_s`(内缘会钳制在光子球之外) |
|
||||
| `DISK_INCL` | 吸积盘倾角,弧度:`0` 为正面朝向,`π/2` 为边缘朝向 |
|
||||
| `DISK_ROLL` | 整个系统在屏幕平面内的旋转 |
|
||||
| `DISK_GAIN` | 吸积盘发射亮度 |
|
||||
| `DISK_OPACITY` | 近侧吸积盘遮挡后方内容的程度 |
|
||||
| `DISK_TEMP` | 最热环带的黑体温度,开尔文 |
|
||||
| `DOPPLER_MIX` | 相对论性颜色/亮度不对称:`0` 关闭,`1` 全开 |
|
||||
| `DISK_BEAM` | 束流指数——强度按 `g^N` 缩放 |
|
||||
| `DISK_SPEED` | 条纹图案速度;负值反转轨道方向 |
|
||||
| `DISK_WIND` | 条纹螺旋缠绕紧密度 |
|
||||
| `DISK_CONTRAST` | 条纹对比度:`0` = 柔和雾状,更高 = 锐利丝状结构 |
|
||||
| `EXPOSURE` | 吸积盘光的色调映射曝光(文字从不做色调映射) |
|
||||
| `DRIFT_SPEED` | 黑洞漂浮移动的速度 |
|
||||
| `WORK_AREA` | 屏幕底部保持完全无畸变的比例 |
|
||||
| `DILATION_MIN` | 黑洞完全展开时吸积盘图案的时间速率(更低 = 减速更明显) |
|
||||
| `TOKEN_AREA_MIN` | token 模式:0% context 时的阴影面积,占终端面积的比例(默认 0.06%) |
|
||||
| `TOKEN_AREA_MAX` | token 模式:100% context 时的阴影面积(默认约为终端的 ~3%——渲染成本随之缩放) |
|
||||
| `TOKEN_HOME_X` / `TOKEN_HOME_Y` | token 模式:角落归位位置,uv 坐标(`1,0` = 精确右上角;y 轴自上而下) |
|
||||
| `TOKEN_EASE` | token 模式:增长曲线指数——`1` = 按比例,`<1` 前置增长,`>1` 直到后期才保持较小 |
|
||||
| `TOKEN_REACH` | token 模式:100% context 时漫游框覆盖的可玩屏幕比例 |
|
||||
| `TOKEN_CALM` / `TOKEN_RUSH` | token 模式:0% / 100% context 时的漂移速度 |
|
||||
| `WORK_PERIOD_MIN` | 每个番茄钟周期的工作分钟数(增长阶段) |
|
||||
| `BREAK_MIN` | 每个周期的休息分钟数(黑洞保持较小) |
|
||||
| `IDLE_FADE_SEC` | 停止输入后黑洞开始淡出的时间 |
|
||||
| `TIME_SCALE` | 仅用于测试:`1` = 真实日程;`>1` 通过 `iTime` 快进增长 |
|
||||
|
||||
`N_STEPS` (a `#define`) sets the geodesic integration budget per pixel; only
|
||||
pixels inside the ray-traced circle around the hole pay it. It's the main
|
||||
performance dial — that circle scales with the hole, so a big hole on a big
|
||||
high-DPI display is where frames go to die. Lower `N_STEPS` (and/or
|
||||
`TOKEN_AREA_MAX`) if the terminal gets sluggish at high context fill.
|
||||
`N_STEPS`(一个 `#define`)设置每像素的测地线积分预算;只有黑洞周围光线追踪圆内的像素才需要支付这一成本。它是主要的性能旋钮——该圆随黑洞缩放,因此大屏幕高 DPI 上的大黑洞正是帧率崩溃之处。若终端在高 context 填充时变慢,请降低 `N_STEPS`(和/或 `TOKEN_AREA_MAX`)。
|
||||
|
||||
To eyeball any token level without a Claude session, drive the cursor-color
|
||||
channel by hand from a plain shell inside Ghostty: `./token-test.sh 0.42`
|
||||
holds a level, `./token-test.sh sweep` runs 0 → 100 % over 25 s, and
|
||||
`./token-test.sh off` hides the hole again. (Don't run it in a surface with a
|
||||
live session — its statusline re-emits the real level every refresh.) Note
|
||||
that a size glide only survives while the cursor holds still — any cursor
|
||||
move makes Ghostty snapshot previous = current, ending the transition early —
|
||||
which is why the hold mode lingers ~1.6 s before exiting back to the prompt.
|
||||
若要在没有 Claude 会话的情况下目测任意 token 级别,可在 Ghostty 内的普通 shell 中手动驱动光标颜色通道:`./token-test.sh 0.42` 保持某一级别,`./token-test.sh sweep` 在 25 秒内从 0 → 100% 运行,`./token-test.sh off` 再次隐藏黑洞。(不要在有活跃会话的 surface 中运行——其 statusline 每次刷新都会重新发出真实级别。)请注意,尺寸平滑过渡仅在光标保持静止时才能持续——任何光标移动都会让 Ghostty 将 previous 快照为 current,从而提前结束过渡——这也是 hold 模式在退出回到提示符前会停留约 1.6 秒的原因。
|
||||
|
||||
For a fast debug loop, set `TIME_SCALE` to e.g. `100` to watch a complete
|
||||
pomodoro cycle — growth, collapse, break — in about 36 seconds, then set it
|
||||
back to `1`. (It fast-forwards via `iTime` rather than the wall clock, so it
|
||||
works even on builds where `iDate` is stuck at zero.) The period knobs also accept
|
||||
fractional minutes if you'd rather shorten the real schedule itself.
|
||||
若要快速调试循环,将 `TIME_SCALE` 设为例如 `100`,即可在约 36 秒内观看完整的番茄钟周期——增长、坍缩、休息——然后将其设回 `1`。(它通过 `iTime` 快进,而非挂钟,因此在 `iDate` 卡在零的构建上也能工作。)周期旋钮也接受小数分钟,若你更希望缩短真实日程本身。
|
||||
|
||||
## Uniforms Ghostty gives custom shaders (1.3)
|
||||
## Ghostty 提供给自定义着色器的 Uniform(1.3)
|
||||
|
||||
`iResolution`, `iTime`, `iTimeDelta`, `iFrameRate`, `iFrame`, `iMouse`
|
||||
(unused), `iDate` (wall clock — declared but stuck at zero through Ghostty
|
||||
1.3, see the pomodoro caveat), `iChannel0` (the terminal, `iChannel1-3`
|
||||
unused), `iCurrentCursor`/`iPreviousCursor` (xy position, zw size),
|
||||
`iCurrentCursorColor`/`iPreviousCursorColor`, `iCurrentCursorStyle`/
|
||||
`iPreviousCursorStyle`, `iCursorVisible`, `iTimeCursorChange`, `iFocus`,
|
||||
`iTimeFocus`, `iPalette[256]`, `iBackgroundColor`, `iForegroundColor`,
|
||||
`iCursorColor`, `iCursorText`, `iSelectionForegroundColor`,
|
||||
`iSelectionBackgroundColor`. No persistent buffers between frames — shaders
|
||||
are stateless, which is why the pomodoro is wall-clock-anchored.
|
||||
`iResolution`、`iTime`、`iTimeDelta`、`iFrameRate`、`iFrame`、`iMouse`(未使用)、`iDate`(挂钟——已声明但在 Ghostty 1.3 之前一直卡在零,见番茄钟注意事项)、`iChannel0`(终端,`iChannel1-3` 未使用)、`iCurrentCursor`/`iPreviousCursor`(xy 位置,zw 尺寸)、`iCurrentCursorColor`/`iPreviousCursorColor`、`iCurrentCursorStyle`/`iPreviousCursorStyle`、`iCursorVisible`、`iTimeCursorChange`、`iFocus`、`iTimeFocus`、`iPalette[256]`、`iBackgroundColor`、`iForegroundColor`、`iCursorColor`、`iCursorText`、`iSelectionForegroundColor`、`iSelectionBackgroundColor`。帧之间没有持久缓冲区——着色器是无状态的,因此番茄钟锚定在挂钟时间上。
|
||||
|
||||
Three gotchas worth knowing if you hack on this:
|
||||
若你要动手修改,有三点值得注意:
|
||||
|
||||
- Ghostty's `fragCoord` y-axis runs **top-down**, opposite of the Shadertoy
|
||||
convention it otherwise follows.
|
||||
- To trigger a config reload from a script, send `SIGUSR2` — but find the
|
||||
PID with `ps`, not `pgrep`/`pkill`: those silently exclude their own
|
||||
ancestors, and Ghostty is an ancestor of any shell running inside it.
|
||||
- Claude Code spawns statusLine/hook commands with **no controlling
|
||||
terminal**, so `/dev/tty` fails there — `claude-token.py` finds the
|
||||
session's pty by walking its ancestors with `ps -o ppid=,tty=`.
|
||||
- Ghostty 的 `fragCoord` y 轴为**自上而下**,与其在其他方面遵循的 Shadertoy 约定相反。
|
||||
- 要从脚本触发配置重载,请发送 `SIGUSR2`——但请用 `ps` 查找 PID,不要用 `pgrep`/`pkill`:它们会静默排除自身祖先,而 Ghostty 是任何在其内运行的 shell 的祖先。
|
||||
- Claude Code 启动 statusLine/hook 命令时**没有控制终端**,因此 `/dev/tty` 在那里会失败——`claude-token.py` 通过 `ps -o ppid=,tty=` 遍历其祖先来找到会话的 pty。
|
||||
|
||||
## License
|
||||
|
||||
MIT — see [LICENSE](LICENSE).
|
||||
MIT — 参见 [LICENSE](LICENSE)。
|
||||
|
||||
Inspired by [Eric Bruneton's black hole shader](https://github.com/ebruneton/black_hole_shader)
|
||||
(BSD-3-Clause). No code from that project is used here — this shader is an
|
||||
independent screen-space approximation written from scratch; the credit is
|
||||
for the idea and the physics it demonstrates.
|
||||
灵感来自 [Eric Bruneton 的黑洞着色器(black hole shader)](https://github.com/ebruneton/black_hole_shader))(BSD-3-Clause)。本项目未使用该项目中的任何代码——本着色器是独立编写的屏幕空间(screen-space)近似实现,从零开始编写;致谢是针对其思路及其所展示的物理学原理。
|
||||
|
||||
Reference in New Issue
Block a user