# Black Hole — `black-hole@1.0.0`

A black hole bending the light of the stars around it, a glowing accretion disk wrapping over and under its shadow. Loops perfectly.

A black hole ray-traced in curved space: every pixel's ray is bent by gravity, so the disk of glowing gas is wrapped over the top and under the bottom of the black shadow, the stars are stretched into arcs and a thin halo rings the edge of the shadow. The disk is hotter and brighter on the side moving towards you and turns in rings, each ring a whole number of turns per loop, while the camera sways slowly round. Choose the colours (gold, ice blue, red ember or violet), the viewing angle from edge-on to nearly face-on, how fast the disk spins, how bright it is, how much brighter the approaching side is, how strongly gravity bends light, the stars and the Milky Way.

- Kind: background; status: published; digest sha256:9cfb60601095016a26c321a859edd9f95b29c7c66ff3bfdc2a9ebd3d9e0c4842
- Role: Background (WebGL shader, full frame): an awe-inspiring space scene for science, sci-fi, music and title content.
- Implementation: three; entry `src/black-hole.js`
- Files: https://aipixelwizard.com/registry/black-hole/1.0.0/<path> (`LICENSE`, `asset.json`, `demo.js`, `index.html`, `src/black-hole.js`)
- Manifest JSON: https://aipixelwizard.com/api/v1/visuals/black-hole/versions/1.0.0
- Runnable demo: https://aipixelwizard.com/registry/black-hole/1.0.0/index.html (open over http; it imports sibling packages by relative path)
- Dependencies (exact): `three@0.185.1` (source) — Renderer, the full-screen shader and the bloom targets.; `apw-runtime@1.0.0` (demo) — Demo harness.

## Exports
- `createBlackHole({ palette?, tilt?, spin?, brightness?, doppler?, gravity?, stars?, milkyWay?, seed? }) => { scene, camera, material, setSize(width, height), update(time, loopSeconds), dispose() }` — The whole scene as one full-screen shader: render its scene with its camera after setSize and update, ideally through createBloomPass.
- `createBloomPass(renderer, { width, height, glow?, exposure?, grain? }) => { render(scene, camera, output?), dispose() }` — A small bloom with filmic tone mapping and a fixed grain: draws a scene to the canvas with its glow.
- `PALETTES: { gold, ice, ember, violet }` — The temperature ramps of the disk: five colours from cool to white-hot.

## Parameters
- `palette` (select one of gold | ice | ember | violet, default "gold") — Colours
- `tilt` (number 3–70, default 8) — Viewing angle: Degrees above the plane of the disk: low is edge-on, high looks down on it.
- `spin` (integer 3–16, default 5) — Disk spin: Turns the innermost ring makes in one loop; outer rings turn slower.
- `brightness` (number 0.5–1.8, default 1) — Brightness
- `doppler` (number 0–1, default 0.7) — Bright side: How much brighter the side of the disk coming towards you is.
- `gravity` (number 0.7–1.3, default 1) — Bending of light: 1 is the real thing; less or more changes the size of the shadow and the rings.
- `stars` (number 0.3–1.6, default 1) — Stars
- `milkyWay` (number 0–1.5, default 1) — Milky Way
- `seed` (integer 1–999, default 5) — Seed: Changes the stars, the Milky Way and the streaks in the disk.

## Composition
- Inputs: Time within the loop; the colours, viewing angle, spin, brightness, bright side, bending, stars and Milky Way.
- Outputs: A full frame, drawn by one ray-tracing shader on a full-screen quad.
- Coordinates: Units of the hole's mass M: the horizon is at 2, the photon sphere at 3, the disk from 6 to 18; the camera sits 36 away, looking at the centre.
- Lighting: Self-lit: the disk glows, with the Doppler and gravitational shifts of its colour and brightness.
- Timing: update(time, loopSeconds) sets the loop phase; frames render in any order.
- Works with: kinetic-title, number-reveal
- Light is bent with the acceleration a = -(3/2) r_s h² p / |p|^5, the shape of a photon's orbit in Schwarzschild space, stepped in 170 steps at most.
- The brightness and colour of the disk follow the photon's redshift factor g = sqrt(1 - 3M/r) / (1 - Omega lambda) for gas on circular orbits.

## Output and loop
- 1080×1080, 30 fps, 12 s, procedural
- Seamless loop: yes — The gas turns in rings, each a whole number of turns per loop (the dark lanes between them hide the seams), the turbulence in the disk is noise read round a circle in time, and the camera's sway is a sine of the loop, so the last frame meets the first.
- Verified 2026-10-05 in Chrome 154.0.8037.97 headless on Linux 7.0.0-38-generic (x64), Node 22.14.0 (WebGL 2 via SwiftShader (software)); deterministic: yes; loop check: Frame 0 vs end state (progress 1): mean |Δ| 0.0000, max |Δ| 0 of 255.

## Use it
New project: Create a WebGLRenderer, createBlackHole() and createBloomPass(renderer, { width, height }); call hole.setSize(width, height), then each frame hole.update(time, loopSeconds) and bloom.render(hole.scene, hole.camera).

Existing project: Copy src/black-hole.js; render hole.scene with hole.camera into a render target (or through the bloom pass) as the backdrop of your scene.

Reuse ideas:
- Sci-fi title: Gold disk behind a kinetic title.
- Music or podcast loop: Red ember, slow.
- Science explainer: Blue quasar, tilted, with a narrator.

## Limitations
- A non-spinning (Schwarzschild) hole: the shadow is a circle; the disk is a thin sheet with no vertical thickness or jets.
- Rays that orbit the photon sphere many times are cut off after 170 steps, so the very thinnest higher-order rings are missing.

## License
MIT. Copyright (c) 2026 AI Pixel Wizard. Released under the MIT License. See LICENSE in this folder.
- three.js 0.185.1: MIT (https://github.com/mrdoob/three.js)

Provenance: Adapted from the owner's threejs-sketches/space-cosmic/black-hole (with webgpu-black-hole, accretion-disk and gravitational-lens, MIT) and rebuilt for AI Pixel Wizard in October 2026 as an original ray-tracer: light bent by the Schwarzschild photon orbit, a thin accretion disk with Doppler and gravitational shifts, lensed stars and Milky Way, rings that turn whole times a loop, an exact loop and the frame/time contract.

