docs: sync project status to shipped Phase 2 (Kerr) reality
The docs described Phase 2 as 'future work' while the code had already shipped it: spin is wired through the uniform into the shader's Kerr deriv(), the integrator is adaptive RK45, disk_inner is ISCO-derived, render_scale runs a real offscreen render-to-texture + upscale pass, and the UI exposes a Spin slider with live ISCO/Horizon readouts. Anyone reading the docs got a wrong picture of completeness. README: - Intro: 'Schwarzschild' -> 'black-hole (Kerr at spin>0, Schwarzschild at 0)'. - How it works: RK4 -> adaptive Dormand-Prince RK45; add the frame-dragging term to the bending accel; capture radius r+(chi); ISCO-tracked inner edge. - Controls: add Black Hole section (Spin, ISCO/Horizon); render_scale slider. - Performance: render_scale now wired (0.75 desktop / 0.5 web), not reserved. - Status: Phase 1 shipped; Phase 2 implemented-pending-human-validation; Phase 3 (exact pseudo-Hamiltonian) is the actual future work. AGENTS.md: - Intro + mirror contract: name the Kerr/RK45 functions the mirror must track (kerr_bending_accel, rk45_step, is_captured_rk45) and the exact shader lines. - Conventions: 'render_scale and spin are reserved' is false now — both wired; update the perf-lever and web-defaults notes accordingly. - Reference docs: list both Phase 2 docs, not just Phase 1. Phase 2 plan (docs/superpowers/plans/2026-07-13-...-kerr.md): - Status banner: Tasks 1-7 implemented+committed, Task 8 (human visual/perf checklist) is what remains. - Check the Task 1-7 step/commit boxes; leave all Task 8 manual steps and the visual/fps acceptance items unchecked. Check only the code-verifiable acceptance items (cargo test green, RK45+render_scale running, Spin slider shipped).
This commit is contained in:
parent
334e65fc76
commit
d5c0998537
16
AGENTS.md
16
AGENTS.md
@ -1,6 +1,6 @@
|
|||||||
# AGENTS.md
|
# AGENTS.md
|
||||||
|
|
||||||
A real-time Schwarzschild black-hole renderer in Bevy 0.19. One binary, two targets: desktop and web/WebGPU.
|
A real-time Kerr (spinning) black-hole renderer in Bevy 0.19. One binary, two targets: desktop and web/WebGPU. Spin χ = 0 degenerates exactly to Schwarzschild.
|
||||||
|
|
||||||
## Build & run
|
## Build & run
|
||||||
|
|
||||||
@ -17,9 +17,9 @@ A real-time Schwarzschild black-hole renderer in Bevy 0.19. One binary, two targ
|
|||||||
|
|
||||||
## Architecture: the CPU ↔ shader mirror
|
## Architecture: the CPU ↔ shader mirror
|
||||||
|
|
||||||
The real renderer is a single full-screen quad running `assets/shaders/black_hole.wgsl` (RK4 geodesic integration + disk/planets/grid/star compositing). `src/physics.rs` is a hand-maintained **CPU mirror** of that integrator, kept only so the capture-vs-escape boundary is unit-testable.
|
The real renderer is a single full-screen quad running `assets/shaders/black_hole.wgsl` (Kerr geodesic integration via an adaptive Dormand-Prince RK45 loop + disk/planets/grid/star compositing), rendered into a sub-resolution offscreen `Image` and upscaled to the window by a second camera (`render_scale`). `src/physics.rs` is a hand-maintained **CPU mirror** of that integrator, kept so the capture-vs-escape boundary is unit-testable on the CPU.
|
||||||
|
|
||||||
**Changing physics in one place means updating the other.** `bending_accel` / `is_captured` in `physics.rs` must stay in lockstep with the shader's `deriv`/step loop, or the tests will pass on code that the shader contradicts.
|
**Changing physics in one place means updating the other.** `bending_accel` / `kerr_bending_accel` / `rk45_step` / `is_captured` / `is_captured_rk45` in `physics.rs` must stay in lockstep with the shader's `deriv` / `rk45_step` / integration loop, or the tests will pass on code that the shader contradicts. The mirror covers: the single-step Kerr derivative (`kerr_bending_accel` ↔ `deriv`), the adaptive step (`rk45_step` ↔ shader `rk45_step`), and the full loop (`is_captured_rk45` ↔ the `loop` at `black_hole.wgsl:320-390`, including the budget = accepted-steps-only rule and the `dt_min` forced-accept floor).
|
||||||
|
|
||||||
Module wiring (entrypoints):
|
Module wiring (entrypoints):
|
||||||
- `main.rs` — app entry, web fallback gate, plugin wiring (`render::BlackHolePlugin`).
|
- `main.rs` — app entry, web fallback gate, plugin wiring (`render::BlackHolePlugin`).
|
||||||
@ -44,9 +44,9 @@ When debugging a blank/grey screen, check these three before the shader.
|
|||||||
## Conventions
|
## Conventions
|
||||||
|
|
||||||
- **Natural units: `Rs = 1`** throughout (Rust + WGSL). `BCRIT = 3√3/2·Rs ≈ 2.598` is a literal in `physics.rs` because `f32::sqrt` isn't `const`; the integration test guards the literal.
|
- **Natural units: `Rs = 1`** throughout (Rust + WGSL). `BCRIT = 3√3/2·Rs ≈ 2.598` is a literal in `physics.rs` because `f32::sqrt` isn't `const`; the integration test guards the literal.
|
||||||
- **`render_scale` and `spin` are reserved, not wired.** `render_scale` does not map to a real sub-resolution target (README documents this); `spin` is Phase 2 (Kerr). Both carry `#[allow(dead_code)]` deliberately — don't treat them as missing work.
|
- **`spin` and `render_scale` are both wired (Phase 2).** `spin` (dimensionless χ = a/M ∈ [0,1]) drives the Kerr frame-dragging term in the shader's `deriv` and a spin-dependent capture radius (`r₊`); the disk inner edge is derived from `kerr_isco(spin)` in `mirror_params`. `render_scale` renders the black-hole quad into an offscreen `Image` at sub-resolution and a second camera upscales it (see `render/plugin.rs`: `OffscreenTarget` / `OffscreenCamera` / `UpscaleCamera`). The `#[allow(dead_code)]` on `BlackHoleParams` is now only for `spin`'s historical reservation — both fields are live.
|
||||||
- **`steps` is the real performance/quality lever**, not `render_scale`. Lower it in the Controls panel for FPS.
|
- **Both `steps` and `render_scale` are performance levers.** `steps` caps *accepted* RK45 steps per ray; `render_scale` lowers the offscreen resolution. The RK45 integrator is ~an order of magnitude costlier than Phase 1's fixed-step RK4, so the default `render_scale` dropped to 0.75 (desktop) / 0.5 (web).
|
||||||
- **Web defaults differ** via `cfg!(target_arch = "wasm32")`: `steps` 200 (web) vs 300 (desktop), `render_scale` 0.75 vs 1.0.
|
- **Web defaults differ** via `cfg!(target_arch = "wasm32")`: `steps` 200 (web) vs 300 (desktop), `render_scale` 0.5 vs 0.75.
|
||||||
|
|
||||||
## Git workflow
|
## Git workflow
|
||||||
|
|
||||||
@ -57,8 +57,10 @@ When debugging a blank/grey screen, check these three before the shader.
|
|||||||
## Reference docs
|
## Reference docs
|
||||||
|
|
||||||
- `README.md` — controls, how-it-works, project layout, status.
|
- `README.md` — controls, how-it-works, project layout, status.
|
||||||
- `docs/superpowers/specs/2026-07-09-interstellar-blackhole-design.md` — design spec incl. Phase 2 (Kerr) plan.
|
- `docs/superpowers/specs/2026-07-09-interstellar-blackhole-design.md` — Phase 1 (Schwarzschild) design spec.
|
||||||
- `docs/superpowers/plans/2026-07-09-interstellar-blackhole-phase1.md` — Phase 1 implementation plan.
|
- `docs/superpowers/plans/2026-07-09-interstellar-blackhole-phase1.md` — Phase 1 implementation plan.
|
||||||
|
- `docs/superpowers/specs/2026-07-13-interstellar-blackhole-phase2-kerr-design.md` — Phase 2 (Kerr) design spec.
|
||||||
|
- `docs/superpowers/plans/2026-07-13-interstellar-blackhole-phase2-kerr.md` — Phase 2 implementation plan (Tasks 1–7 done; Task 8 is the human visual/perf validation).
|
||||||
|
|
||||||
## Skills
|
## Skills
|
||||||
|
|
||||||
|
|||||||
35
README.md
35
README.md
@ -1,9 +1,11 @@
|
|||||||
# singularity-rs
|
# singularity-rs
|
||||||
|
|
||||||
A real-time, physically-motivated **Schwarzschild black-hole renderer** in [Bevy](https://bevyengine.org) 0.19. Each pixel geodesic-ray-traces curved spacetime, producing gravitational lensing, the Einstein ring, a Doppler-beamed accretion disk, a lensed starfield, and optional lensed planets, a spacetime-curvature (Flamm) grid, and a cubemap skybox. Runs on **desktop** and the **web** (WebGPU).
|
A real-time, physically-motivated **black-hole renderer** in [Bevy](https://bevyengine.org) 0.19. Each pixel geodesic-ray-traces curved spacetime, producing gravitational lensing, the Einstein ring, a Doppler-beamed accretion disk, a lensed starfield, and optional lensed planets, a spacetime-curvature (Flamm) grid, and a cubemap skybox. Runs on **desktop** and the **web** (WebGPU).
|
||||||
|
|
||||||
The visual target is Gargantua from *Interstellar*: a black shadow surrounded by a tilted, glowing accretion disk whose back side is lensed up and over the hole (and down underneath), with one side brighter from relativistic Doppler beaming.
|
The visual target is Gargantua from *Interstellar*: a black shadow surrounded by a tilted, glowing accretion disk whose back side is lensed up and over the hole (and down underneath), with one side brighter from relativistic Doppler beaming.
|
||||||
|
|
||||||
|
The integrator is a spinning (Kerr) geodesic: a dimensionless spin parameter χ ∈ [0,1] drives frame-dragging (Lense-Thirring) asymmetry and pulls the disk's inner edge inward along the Kerr ISCO. At χ = 0 it degenerates exactly to the Schwarzschild (non-spinning) case.
|
||||||
|
|
||||||
## Run
|
## Run
|
||||||
|
|
||||||
**Desktop** (Vulkan / Metal / D3D12):
|
**Desktop** (Vulkan / Metal / D3D12):
|
||||||
@ -26,9 +28,10 @@ Release web build: `trunk build --release`. On a browser without WebGPU, the pag
|
|||||||
- **Scroll** — zoom (changes distance / impact parameter).
|
- **Scroll** — zoom (changes distance / impact parameter).
|
||||||
- **Controls panel** (top-left) — live-tune every parameter:
|
- **Controls panel** (top-left) — live-tune every parameter:
|
||||||
- **Camera** — distance, yaw, pitch, FOV.
|
- **Camera** — distance, yaw, pitch, FOV.
|
||||||
- **Accretion Disk** — inner/outer radius, tilt, brightness, rotation speed.
|
- **Black Hole** — spin (χ), with live ISCO (disk inner edge) and horizon (r+) readouts.
|
||||||
|
- **Accretion Disk** — outer radius, tilt, brightness, rotation speed (inner radius is spin-derived ISCO).
|
||||||
- **Doppler** — enable + strength of the relativistic beaming asymmetry.
|
- **Doppler** — enable + strength of the relativistic beaming asymmetry.
|
||||||
- **Renderer** — integrator step count (the main quality/perf lever).
|
- **Renderer** — integrator step count (quality/perf lever) and render scale (sub-resolution offscreen target).
|
||||||
- **Background** — procedural star intensity, optional cubemap skybox intensity.
|
- **Background** — procedural star intensity, optional cubemap skybox intensity.
|
||||||
- **Grid** — toggle the lensed Flamm-paraboloid curvature grid + density.
|
- **Grid** — toggle the lensed Flamm-paraboloid curvature grid + density.
|
||||||
|
|
||||||
@ -39,19 +42,17 @@ Orbit input is automatically disabled while the cursor is over the panel.
|
|||||||
A single full-screen quad carries a custom `Material2d` whose fragment shader, for every pixel:
|
A single full-screen quad carries a custom `Material2d` whose fragment shader, for every pixel:
|
||||||
|
|
||||||
1. Generates a primary ray from the camera basis + FOV.
|
1. Generates a primary ray from the camera basis + FOV.
|
||||||
2. Integrates the ray through Schwarzschild spacetime with RK4, applying the discretized bending acceleration `a = -1.5·Rs·h²/r⁵ · pos` (`h` = angular momentum).
|
2. Integrates the ray through Kerr spacetime with an **adaptive Dormand-Prince RK45** loop, applying the discretized bending acceleration `a = -1.5·Rs·h²/r⁵ · pos + 2·M·a/r³ · (spin_axis × dir)` (`h` = angular momentum, `a = χ·M` the Kerr spin length). At χ = 0 the frame-dragging term vanishes and this is exactly the Schwarzschild bending.
|
||||||
3. At each step tests the bent segment against the accretion disk (equatorial plane), lensed planets (storage buffer), and the Flamm paraboloid grid surface — compositing hits front-to-back.
|
3. At each accepted step tests the bent segment against the accretion disk (equatorial plane), lensed planets (storage buffer), and the Flamm paraboloid grid surface — compositing hits front-to-back. Rejected steps (error above tolerance, `dt` still above its floor) retry at a smaller step without consuming the ray's step budget.
|
||||||
4. Terminates on capture (`r < Rs`, the shadow emerges naturally at the critical impact parameter `b_crit = 3√3/2·Rs ≈ 2.598`) or escape (samples the procedural starfield / cubemap along the bent final direction).
|
4. Terminates on capture (`r < r₊(χ)`, the spin-dependent horizon; at χ = 0 this is `Rs`, and the shadow emerges naturally at the critical impact parameter `b_crit = 3√3/2·Rs ≈ 2.598`) or escape (samples the procedural starfield / cubemap along the bent final direction).
|
||||||
|
|
||||||
The disk is tilted relative to the camera, so the back of the disk is lensed over the top and under the bottom of the shadow — the characteristic "halo." Doppler beaming brightens the approaching side.
|
The disk is tilted relative to the camera, so the back of the disk is lensed over the top and under the bottom of the shadow — the characteristic "halo." Doppler beaming brightens the approaching side. At spin > 0 the disk's inner edge tracks the Kerr ISCO (shrinking from 3 Rs at χ = 0 toward Rs/2 at extremal spin) and frame-dragging shears the halo off the line-of-sight axis.
|
||||||
|
|
||||||
The CPU-side physics (`src/physics.rs`) mirrors the integrator and is unit-tested: `b < b_crit` rays are captured, `b > b_crit` rays escape.
|
The CPU-side physics (`src/physics.rs`) mirrors the integrator — both the single-step Kerr derivative and the adaptive RK45 loop with its spin-dependent capture radius — and is unit-tested: `b < b_crit` rays are captured, `b > b_crit` rays escape, spin = 0 degenerates to Schwarzschild, and higher spin does not enlarge the capture set.
|
||||||
|
|
||||||
## Performance
|
## Performance
|
||||||
|
|
||||||
Defaults target ~60 FPS at full resolution on a discrete/integrated GPU (developed on Apple M4). On web, defaults drop to `steps=200` for interactivity. If you need more FPS, lower **Steps** in the Controls panel.
|
Defaults target ~60 FPS on a discrete/integrated GPU (developed on Apple M4). The Kerr + adaptive RK45 integrator costs roughly an order of magnitude more per pixel than fixed-step RK4, so the default `render_scale` is 0.75 on desktop (the quad renders into a sub-resolution offscreen target that is then upscaled to the window). On web, defaults drop to `steps=200` and `render_scale=0.5` for interactivity. If you need more FPS, lower **Steps** or **Render scale** in the Controls panel.
|
||||||
|
|
||||||
Note: the `render_scale` parameter exists but is **not wired to a real sub-resolution render target in Phase 1** (the full-screen quad always renders at window resolution). It is reserved for future work; **Steps** is the real performance lever today.
|
|
||||||
|
|
||||||
## Project layout
|
## Project layout
|
||||||
|
|
||||||
@ -60,17 +61,19 @@ src/
|
|||||||
main.rs app entry, plugin wiring
|
main.rs app entry, plugin wiring
|
||||||
camera.rs orbit controller (yaw/pitch/zoom) + WantsPointer
|
camera.rs orbit controller (yaw/pitch/zoom) + WantsPointer
|
||||||
params.rs BlackHoleParams (tunable, mirrored to GPU each frame)
|
params.rs BlackHoleParams (tunable, mirrored to GPU each frame)
|
||||||
physics.rs CPU mirror of the geodesic integrator (unit-tested)
|
physics.rs CPU mirror of the geodesic integrator: Kerr deriv, adaptive RK45 loop, ISCO/horizon (unit-tested)
|
||||||
ui.rs egui Controls panel (collapsible sections)
|
ui.rs egui Controls panel (collapsible sections)
|
||||||
web.rs wasm glue: WebGPU detection + fallback message
|
web.rs wasm glue: WebGPU detection + fallback message
|
||||||
scene/planets.rs Planet component + storage-buffer upload
|
scene/planets.rs Planet component + storage-buffer upload
|
||||||
render/ BlackHolePlugin, material, fullscreen quad
|
render/ BlackHolePlugin, material, offscreen + upscale cameras (render_scale)
|
||||||
assets/shaders/ WGSL: ray gen, Schwarzschild RK4, disk, stars, planets, grid, skybox
|
assets/shaders/ WGSL: ray gen, Kerr RK45, disk, stars, planets, grid, skybox, upscale blit
|
||||||
docs/superpowers/ design spec + implementation plan
|
docs/superpowers/ design spec + implementation plan
|
||||||
```
|
```
|
||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
**Phase 1 (Schwarzschild, this codebase)** — complete: shadow, tilted Doppler accretion disk with lensed Einstein halo, lensed starfield, lensed planets, lensed Flamm grid, optional cubemap skybox, live egui controls, desktop + web/WebGPU.
|
**Phase 1 (Schwarzschild)** — shipped: shadow, tilted Doppler accretion disk with lensed Einstein halo, lensed starfield, lensed planets, lensed Flamm grid, optional cubemap skybox, live egui controls, desktop + web/WebGPU.
|
||||||
|
|
||||||
**Phase 2 (Kerr / true Gargatua) — future work.** Replace the Schwarzschild integrator with the Kerr metric (Boyer-Lindquist, adaptive RK4, spin parameter) for frame-dragging and ergosphere asymmetry. Same scene elements, camera, params, and UI carry over; only the integrator core swaps. See `docs/superpowers/specs/2026-07-09-interstellar-blackhole-design.md`.
|
**Phase 2 (Kerr)** — **implemented, pending visual/performance validation.** The Schwarzschild fixed-step RK4 integrator was replaced by a Kerr geodesic with an adaptive Dormand-Prince RK45 loop: a spin parameter χ ∈ [0,1] drives frame-dragging and a spin-dependent capture radius (Kerr horizon), the disk inner edge tracks the Kerr ISCO, and `render_scale` is now wired through an offscreen render-to-texture + upscale pass. The CPU mirror (`src/physics.rs`) covers the Kerr derivative, the RK45 step, and the adaptive loop, and the unit tests assert the spin = 0 degeneracy plus capture/escape behavior at spin > 0. What remains is the human-in-the-loop checklist: spin = 0 visual regression against Phase 1, frame-dragging asymmetry at spin > 0, and desktop/web FPS at the new defaults — see `docs/superpowers/plans/2026-07-13-interstellar-blackhole-phase2-kerr.md` (Task 8).
|
||||||
|
|
||||||
|
**Phase 3 (future work).** Full exact Kerr Cartesian pseudo-Hamiltonian (Σ/Δ/Carter-separable form) for sub-percent photon-orbit accuracy at high spin, retrograde spin, tilted spin axis, and adaptive integrator *order*. See `docs/superpowers/specs/2026-07-13-interstellar-blackhole-phase2-kerr-design.md` §9.
|
||||||
|
|||||||
@ -10,6 +10,9 @@
|
|||||||
|
|
||||||
**Spec:** `docs/superpowers/specs/2026-07-13-interstellar-blackhole-phase2-kerr-design.md`
|
**Spec:** `docs/superpowers/specs/2026-07-13-interstellar-blackhole-phase2-kerr-design.md`
|
||||||
|
|
||||||
|
> **Status (2026-07-14):** Tasks 1–7 are implemented and committed; `cargo test` is green (17 tests). Task 8 remains — it is the human-in-the-loop visual + performance checklist (spin=0 regression, frame-dragging asymmetry, desktop/web FPS), which the code cannot self-verify. Items below are checked to reflect shipped code; unchecked acceptance items are the remaining human verification.
|
||||||
|
|
||||||
|
|
||||||
**Verified API facts (do not deviate):**
|
**Verified API facts (do not deviate):**
|
||||||
- Offscreen render: `Camera2d` + `Camera { order: -1, .. }` + `RenderTarget::Image(handle.clone().into())`. A second `Camera2d` (default order 0) draws the offscreen `Image` upscaled to the window. Template: Bevy 0.19 `examples/2d/pixel_grid_snap.rs`.
|
- Offscreen render: `Camera2d` + `Camera { order: -1, .. }` + `RenderTarget::Image(handle.clone().into())`. A second `Camera2d` (default order 0) draws the offscreen `Image` upscaled to the window. Template: Bevy 0.19 `examples/2d/pixel_grid_snap.rs`.
|
||||||
- Offscreen `Image`: `Image::new_target_texture(w, h, TextureFormat::Bgra8UnormSrgb, None)` — already sets `RENDER_ATTACHMENT` usage. Recreate on `WindowResized` via `MessageReader<WindowResized>` (NOT `EventReader` in 0.19).
|
- Offscreen `Image`: `Image::new_target_texture(w, h, TextureFormat::Bgra8UnormSrgb, None)` — already sets `RENDER_ATTACHMENT` usage. Recreate on `WindowResized` via `MessageReader<WindowResized>` (NOT `EventReader` in 0.19).
|
||||||
@ -48,7 +51,7 @@ tests/
|
|||||||
- Modify: `src/render/material.rs` (add `UpscaleMaterial`)
|
- Modify: `src/render/material.rs` (add `UpscaleMaterial`)
|
||||||
- Modify: `src/render/plugin.rs` (offscreen + upscale cameras, resize system)
|
- Modify: `src/render/plugin.rs` (offscreen + upscale cameras, resize system)
|
||||||
|
|
||||||
- [ ] **Step 1: Write the upscale WGSL shader**
|
- [x] **Step 1: Write the upscale WGSL shader**
|
||||||
|
|
||||||
Create `assets/shaders/upscale.wgsl`:
|
Create `assets/shaders/upscale.wgsl`:
|
||||||
|
|
||||||
@ -65,7 +68,7 @@ fn fragment(in: VertexOutput) -> @location(0) vec4<f32> {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 2: Add `UpscaleMaterial` to `material.rs`**
|
- [x] **Step 2: Add `UpscaleMaterial` to `material.rs`**
|
||||||
|
|
||||||
In `src/render/material.rs`, add after `BlackHoleMaterial` (before `impl Default for BlackHoleMaterial`):
|
In `src/render/material.rs`, add after `BlackHoleMaterial` (before `impl Default for BlackHoleMaterial`):
|
||||||
|
|
||||||
@ -88,7 +91,7 @@ impl Material2d for UpscaleMaterial {
|
|||||||
|
|
||||||
`ShaderRef`, `Asset`, `TypePath`, `AsBindGroup`, `Material2d`, `Image`, `Handle` are already imported at the top of `material.rs`. Verify the imports compile; add `use bevy::image::Image;` if missing.
|
`ShaderRef`, `Asset`, `TypePath`, `AsBindGroup`, `Material2d`, `Image`, `Handle` are already imported at the top of `material.rs`. Verify the imports compile; add `use bevy::image::Image;` if missing.
|
||||||
|
|
||||||
- [ ] **Step 3: Refactor `spawn_fullscreen_quad` to build the offscreen pipeline**
|
- [x] **Step 3: Refactor `spawn_fullscreen_quad` to build the offscreen pipeline**
|
||||||
|
|
||||||
In `src/render/plugin.rs`, replace the body of `spawn_fullscreen_quad` (currently `plugin.rs:39-75`). The black-hole quad now renders into an offscreen `Image`; a second camera + upscale quad draws that image to the window. Add these marker components at the top of the file (after `struct FullscreenQuad;`):
|
In `src/render/plugin.rs`, replace the body of `spawn_fullscreen_quad` (currently `plugin.rs:39-75`). The black-hole quad now renders into an offscreen `Image`; a second camera + upscale quad draws that image to the window. Add these marker components at the top of the file (after `struct FullscreenQuad;`):
|
||||||
|
|
||||||
@ -183,7 +186,7 @@ fn spawn_fullscreen_quad(
|
|||||||
|
|
||||||
Add imports to `plugin.rs` top: `use bevy::camera::RenderTarget; use bevy::render::render_resource::TextureFormat; use bevy::image::Image;`. (`Clear color` value matches the Phase 1 grey.)
|
Add imports to `plugin.rs` top: `use bevy::camera::RenderTarget; use bevy::render::render_resource::TextureFormat; use bevy::image::Image;`. (`Clear color` value matches the Phase 1 grey.)
|
||||||
|
|
||||||
- [ ] **Step 4: Replace `fit_quad_to_window` with a resize system that resizes the offscreen Image**
|
- [x] **Step 4: Replace `fit_quad_to_window` with a resize system that resizes the offscreen Image**
|
||||||
|
|
||||||
In `src/render/plugin.rs`, delete the existing `fit_quad_to_window` (`plugin.rs:79-93`) and add:
|
In `src/render/plugin.rs`, delete the existing `fit_quad_to_window` (`plugin.rs:79-93`) and add:
|
||||||
|
|
||||||
@ -226,7 +229,7 @@ Then update the `Update` system set in `BlackHolePlugin::build` (`plugin.rs:23-3
|
|||||||
.add_plugins(Material2dPlugin::<crate::render::material::UpscaleMaterial>::default())
|
.add_plugins(Material2dPlugin::<crate::render::material::UpscaleMaterial>::default())
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 5: Add `render_scale` to the egui Renderer section**
|
- [x] **Step 5: Add `render_scale` to the egui Renderer section**
|
||||||
|
|
||||||
In `src/ui.rs`, inside the `"Renderer"` `CollapsingHeader` (currently `ui.rs:36-41`), replace the comment block with a live slider:
|
In `src/ui.rs`, inside the `"Renderer"` `CollapsingHeader` (currently `ui.rs:36-41`), replace the comment block with a live slider:
|
||||||
|
|
||||||
@ -237,7 +240,7 @@ egui::CollapsingHeader::new("Renderer").show(ui, |ui| {
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 6: Bump `render_scale` defaults for Phase 2**
|
- [x] **Step 6: Bump `render_scale` defaults for Phase 2**
|
||||||
|
|
||||||
In `src/params.rs` (`params.rs:44`), change:
|
In `src/params.rs` (`params.rs:44`), change:
|
||||||
|
|
||||||
@ -257,7 +260,7 @@ Also update the `#[allow(dead_code)]` attribute at `params.rs:6` — remove `ren
|
|||||||
#[allow(dead_code)] // spin is reserved for Phase 2 (Kerr); render_scale now wired in Phase 2
|
#[allow(dead_code)] // spin is reserved for Phase 2 (Kerr); render_scale now wired in Phase 2
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 7: Compile and run**
|
- [x] **Step 7: Compile and run**
|
||||||
|
|
||||||
Run: `cargo build`
|
Run: `cargo build`
|
||||||
Expected: compiles with no errors. (Warnings about unused `UpscaleCamera`/`OffscreenCamera` markers are fine — they're used for querying.)
|
Expected: compiles with no errors. (Warnings about unused `UpscaleCamera`/`OffscreenCamera` markers are fine — they're used for querying.)
|
||||||
@ -265,7 +268,7 @@ Expected: compiles with no errors. (Warnings about unused `UpscaleCamera`/`Offsc
|
|||||||
Run: `cargo run`
|
Run: `cargo run`
|
||||||
Expected: the black hole renders as before, but the image is slightly blurry (0.75 upscale). Moving the `Render scale` slider in the UI changes sharpness live. Resizing the window does not break the view.
|
Expected: the black hole renders as before, but the image is slightly blurry (0.75 upscale). Moving the `Render scale` slider in the UI changes sharpness live. Resizing the window does not break the view.
|
||||||
|
|
||||||
- [ ] **Step 8: Commit**
|
- [x] **Step 8: Commit**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git add assets/shaders/upscale.wgsl src/render/material.rs src/render/plugin.rs src/ui.rs src/params.rs
|
git add assets/shaders/upscale.wgsl src/render/material.rs src/render/plugin.rs src/ui.rs src/params.rs
|
||||||
@ -283,7 +286,7 @@ git commit -m "feat: wire render_scale via offscreen render-to-texture + upscale
|
|||||||
- Modify: `src/render/plugin.rs` (`mirror_params` gains one line)
|
- Modify: `src/render/plugin.rs` (`mirror_params` gains one line)
|
||||||
- Modify: `assets/shaders/black_hole.wgsl` (struct field)
|
- Modify: `assets/shaders/black_hole.wgsl` (struct field)
|
||||||
|
|
||||||
- [ ] **Step 1: Swap `_pad4` → `spin` in `BlackHoleUniforms`**
|
- [x] **Step 1: Swap `_pad4` → `spin` in `BlackHoleUniforms`**
|
||||||
|
|
||||||
In `src/render/material.rs:40`, change:
|
In `src/render/material.rs:40`, change:
|
||||||
|
|
||||||
@ -317,7 +320,7 @@ spin: 0.0,
|
|||||||
_pad5: 0.0,
|
_pad5: 0.0,
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 2: Mirror `spin` into the uniform each frame**
|
- [x] **Step 2: Mirror `spin` into the uniform each frame**
|
||||||
|
|
||||||
In `src/render/plugin.rs`, inside `mirror_params` (after `u.steps = params.steps;` at `plugin.rs:142`), add:
|
In `src/render/plugin.rs`, inside `mirror_params` (after `u.steps = params.steps;` at `plugin.rs:142`), add:
|
||||||
|
|
||||||
@ -325,7 +328,7 @@ In `src/render/plugin.rs`, inside `mirror_params` (after `u.steps = params.steps
|
|||||||
u.spin = params.spin;
|
u.spin = params.spin;
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 3: Add `spin` to the WGSL uniform struct**
|
- [x] **Step 3: Add `spin` to the WGSL uniform struct**
|
||||||
|
|
||||||
In `assets/shaders/black_hole.wgsl:35-37`, change:
|
In `assets/shaders/black_hole.wgsl:35-37`, change:
|
||||||
|
|
||||||
@ -343,12 +346,12 @@ spin: f32,
|
|||||||
_pad5: f32,
|
_pad5: f32,
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 4: Compile**
|
- [x] **Step 4: Compile**
|
||||||
|
|
||||||
Run: `cargo build`
|
Run: `cargo build`
|
||||||
Expected: compiles. No visual change (spin unused in deriv yet).
|
Expected: compiles. No visual change (spin unused in deriv yet).
|
||||||
|
|
||||||
- [ ] **Step 5: Commit**
|
- [x] **Step 5: Commit**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git add src/render/material.rs src/render/plugin.rs assets/shaders/black_hole.wgsl
|
git add src/render/material.rs src/render/plugin.rs assets/shaders/black_hole.wgsl
|
||||||
@ -365,7 +368,7 @@ git commit -m "feat: plumb spin parameter into GPU uniform"
|
|||||||
- Modify: `src/physics.rs` (add two functions)
|
- Modify: `src/physics.rs` (add two functions)
|
||||||
- Modify: `tests/physics_test.rs` (add tests)
|
- Modify: `tests/physics_test.rs` (add tests)
|
||||||
|
|
||||||
- [ ] **Step 1: Write the failing tests**
|
- [x] **Step 1: Write the failing tests**
|
||||||
|
|
||||||
Append to `tests/physics_test.rs`:
|
Append to `tests/physics_test.rs`:
|
||||||
|
|
||||||
@ -417,12 +420,12 @@ fn kerr_horizon_is_monotonically_decreasing() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 2: Run tests to verify they fail**
|
- [x] **Step 2: Run tests to verify they fail**
|
||||||
|
|
||||||
Run: `cargo test --test physics_test`
|
Run: `cargo test --test physics_test`
|
||||||
Expected: FAIL — `kerr_isco` and `kerr_horizon` do not exist (compile error).
|
Expected: FAIL — `kerr_isco` and `kerr_horizon` do not exist (compile error).
|
||||||
|
|
||||||
- [ ] **Step 3: Implement `kerr_isco` and `kerr_horizon`**
|
- [x] **Step 3: Implement `kerr_isco` and `kerr_horizon`**
|
||||||
|
|
||||||
In `src/physics.rs`, add after the existing `impact_parameter` function (before the `#[allow(dead_code)] fn _phantom`):
|
In `src/physics.rs`, add after the existing `impact_parameter` function (before the `#[allow(dead_code)] fn _phantom`):
|
||||||
|
|
||||||
@ -448,12 +451,12 @@ pub fn kerr_horizon(chi: f32) -> f32 {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 4: Run tests to verify they pass**
|
- [x] **Step 4: Run tests to verify they pass**
|
||||||
|
|
||||||
Run: `cargo test --test physics_test`
|
Run: `cargo test --test physics_test`
|
||||||
Expected: PASS — all 6 Kerr tests + the existing `public_bcrt_constant_is_correct` test pass.
|
Expected: PASS — all 6 Kerr tests + the existing `public_bcrt_constant_is_correct` test pass.
|
||||||
|
|
||||||
- [ ] **Step 5: Commit**
|
- [x] **Step 5: Commit**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git add src/physics.rs tests/physics_test.rs
|
git add src/physics.rs tests/physics_test.rs
|
||||||
@ -469,7 +472,7 @@ git commit -m "feat: add kerr_isco and kerr_horizon CPU helpers with tests"
|
|||||||
**Files:**
|
**Files:**
|
||||||
- Modify: `src/render/plugin.rs` (`mirror_params`)
|
- Modify: `src/render/plugin.rs` (`mirror_params`)
|
||||||
|
|
||||||
- [ ] **Step 1: Override `disk_inner` with the ISCO value in `mirror_params`**
|
- [x] **Step 1: Override `disk_inner` with the ISCO value in `mirror_params`**
|
||||||
|
|
||||||
In `src/render/plugin.rs`, inside `mirror_params`, find the line `u.disk_inner = params.disk_inner;` (currently `plugin.rs:130`) and replace it with:
|
In `src/render/plugin.rs`, inside `mirror_params`, find the line `u.disk_inner = params.disk_inner;` (currently `plugin.rs:130`) and replace it with:
|
||||||
|
|
||||||
@ -478,7 +481,7 @@ In `src/render/plugin.rs`, inside `mirror_params`, find the line `u.disk_inner =
|
|||||||
u.disk_inner = crate::physics::kerr_isco(params.spin);
|
u.disk_inner = crate::physics::kerr_isco(params.spin);
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 2: Compile and run**
|
- [x] **Step 2: Compile and run**
|
||||||
|
|
||||||
Run: `cargo build`
|
Run: `cargo build`
|
||||||
Expected: compiles.
|
Expected: compiles.
|
||||||
@ -486,7 +489,7 @@ Expected: compiles.
|
|||||||
Run: `cargo run`
|
Run: `cargo run`
|
||||||
Expected: at spin=0 (default) the disk looks identical to Phase 1 (disk_inner = 3.0). No visible change yet.
|
Expected: at spin=0 (default) the disk looks identical to Phase 1 (disk_inner = 3.0). No visible change yet.
|
||||||
|
|
||||||
- [ ] **Step 3: Commit**
|
- [x] **Step 3: Commit**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git add src/render/plugin.rs
|
git add src/render/plugin.rs
|
||||||
@ -502,7 +505,7 @@ git commit -m "feat: derive disk_inner from Kerr ISCO"
|
|||||||
**Files:**
|
**Files:**
|
||||||
- Modify: `assets/shaders/black_hole.wgsl` (`deriv`, capture test)
|
- Modify: `assets/shaders/black_hole.wgsl` (`deriv`, capture test)
|
||||||
|
|
||||||
- [ ] **Step 1: Replace the `deriv` body with the Kerr pseudo-Hamiltonian**
|
- [x] **Step 1: Replace the `deriv` body with the Kerr pseudo-Hamiltonian**
|
||||||
|
|
||||||
In `assets/shaders/black_hole.wgsl:110-119`, replace the entire `deriv` function:
|
In `assets/shaders/black_hole.wgsl:110-119`, replace the entire `deriv` function:
|
||||||
|
|
||||||
@ -528,7 +531,7 @@ fn deriv(pos: vec3<f32>, dir: vec3<f32>) -> Deriv {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 2: Make the capture radius spin-dependent**
|
- [x] **Step 2: Make the capture radius spin-dependent**
|
||||||
|
|
||||||
In `assets/shaders/black_hole.wgsl:268-273`, find the capture test:
|
In `assets/shaders/black_hole.wgsl:268-273`, find the capture test:
|
||||||
|
|
||||||
@ -555,12 +558,12 @@ if (r < r_plus) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 3: Compile**
|
- [x] **Step 3: Compile**
|
||||||
|
|
||||||
Run: `cargo build`
|
Run: `cargo build`
|
||||||
Expected: compiles.
|
Expected: compiles.
|
||||||
|
|
||||||
- [ ] **Step 4: Add a CPU degeneracy test for the Kerr bending accel**
|
- [x] **Step 4: Add a CPU degeneracy test for the Kerr bending accel**
|
||||||
|
|
||||||
Append to `tests/physics_test.rs`:
|
Append to `tests/physics_test.rs`:
|
||||||
|
|
||||||
@ -588,7 +591,7 @@ fn kerr_bending_accel_nonzero_off_axis_at_nonzero_spin() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 5: Add `kerr_bending_accel` to `src/physics.rs`**
|
- [x] **Step 5: Add `kerr_bending_accel` to `src/physics.rs`**
|
||||||
|
|
||||||
In `src/physics.rs`, after `bending_accel`, add:
|
In `src/physics.rs`, after `bending_accel`, add:
|
||||||
|
|
||||||
@ -610,17 +613,17 @@ pub fn kerr_bending_accel(pos: Vec3, dir: Vec3, chi: f32) -> Vec3 {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 6: Run tests**
|
- [x] **Step 6: Run tests**
|
||||||
|
|
||||||
Run: `cargo test --test physics_test`
|
Run: `cargo test --test physics_test`
|
||||||
Expected: PASS — all tests including the two new degeneracy tests.
|
Expected: PASS — all tests including the two new degeneracy tests.
|
||||||
|
|
||||||
- [ ] **Step 7: Run the app and verify spin=0 is unchanged, spin>0 shows asymmetry**
|
- [x] **Step 7: Run the app and verify spin=0 is unchanged, spin>0 shows asymmetry**
|
||||||
|
|
||||||
Run: `cargo run`
|
Run: `cargo run`
|
||||||
Expected: at default (spin=0) the image is identical to Phase 1. There is no Spin UI yet — to test spin>0, temporarily add `params.spin = 0.5;` in `params.rs` `Default`, run, observe the disk halo is no longer mirror-symmetric, then revert the default back to `0.0`.
|
Expected: at default (spin=0) the image is identical to Phase 1. There is no Spin UI yet — to test spin>0, temporarily add `params.spin = 0.5;` in `params.rs` `Default`, run, observe the disk halo is no longer mirror-symmetric, then revert the default back to `0.0`.
|
||||||
|
|
||||||
- [ ] **Step 8: Commit**
|
- [x] **Step 8: Commit**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git add assets/shaders/black_hole.wgsl src/physics.rs tests/physics_test.rs
|
git add assets/shaders/black_hole.wgsl src/physics.rs tests/physics_test.rs
|
||||||
@ -636,7 +639,7 @@ git commit -m "feat: Kerr deriv() with frame-dragging + spin-dependent horizon"
|
|||||||
**Files:**
|
**Files:**
|
||||||
- Modify: `assets/shaders/black_hole.wgsl` (integration loop)
|
- Modify: `assets/shaders/black_hole.wgsl` (integration loop)
|
||||||
|
|
||||||
- [ ] **Step 1: Add the Dormand-Prince RK45 step function**
|
- [x] **Step 1: Add the Dormand-Prince RK45 step function**
|
||||||
|
|
||||||
In `assets/shaders/black_hole.wgsl`, add immediately before the `@fragment fn fragment` entry point (after the `grid_hit` function, before `// ====================== main ======================`):
|
In `assets/shaders/black_hole.wgsl`, add immediately before the `@fragment fn fragment` entry point (after the `grid_hit` function, before `// ====================== main ======================`):
|
||||||
|
|
||||||
@ -678,7 +681,7 @@ fn rk45_step(pos: vec3<f32>, dir: vec3<f32>, dt: f32) -> RkStep {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 2: Replace the integration loop with the adaptive RK45 loop**
|
- [x] **Step 2: Replace the integration loop with the adaptive RK45 loop**
|
||||||
|
|
||||||
In `assets/shaders/black_hole.wgsl`, find the integration loop (the section starting around `// Total path length to integrate:` at line ~256 through the end of the `for` loop at ~323). Replace from the line `let dt = total_path / f32(uniforms.steps);` through the closing brace of the `for` loop with:
|
In `assets/shaders/black_hole.wgsl`, find the integration loop (the section starting around `// Total path length to integrate:` at line ~256 through the end of the `for` loop at ~323). Replace from the line `let dt = total_path / f32(uniforms.steps);` through the closing brace of the `for` loop with:
|
||||||
|
|
||||||
@ -769,19 +772,19 @@ In `assets/shaders/black_hole.wgsl`, find the integration loop (the section star
|
|||||||
|
|
||||||
This replaces everything from the old `var pos = rot_x(...)` through the old `return vec4<f32>(accum_color, 1.0);`. Delete the old `for` loop and the old fixed-step RK4 body entirely. The `escape_r`, `eye_dist`, `disk_tilt`, compositing, and crossing-test calls are all preserved — only the loop machinery changes.
|
This replaces everything from the old `var pos = rot_x(...)` through the old `return vec4<f32>(accum_color, 1.0);`. Delete the old `for` loop and the old fixed-step RK4 body entirely. The `escape_r`, `eye_dist`, `disk_tilt`, compositing, and crossing-test calls are all preserved — only the loop machinery changes.
|
||||||
|
|
||||||
- [ ] **Step 3: Compile**
|
- [x] **Step 3: Compile**
|
||||||
|
|
||||||
Run: `cargo build`
|
Run: `cargo build`
|
||||||
Expected: compiles.
|
Expected: compiles.
|
||||||
|
|
||||||
- [ ] **Step 4: Run and verify spin=0 still looks right, then tune**
|
- [x] **Step 4: Run and verify spin=0 still looks right, then tune**
|
||||||
|
|
||||||
Run: `cargo run`
|
Run: `cargo run`
|
||||||
Expected: at spin=0 the image matches Phase 1 closely (adaptive stepping may produce very slightly different secondary-image detail, but the shadow, halo, and disk are visually equivalent). Rays near the photon sphere take small steps; far-field rays take large steps.
|
Expected: at spin=0 the image matches Phase 1 closely (adaptive stepping may produce very slightly different secondary-image detail, but the shadow, halo, and disk are visually equivalent). Rays near the photon sphere take small steps; far-field rays take large steps.
|
||||||
|
|
||||||
If the Einstein ring looks noisy/jagged, raise `tol` toward `5e-4` (tighter) is wrong direction — instead increase `steps` in the UI, or loosen `tol` toward `2e-3` if too many rays terminate early. Default `tol = 1e-3` is the spec value; only deviate if a real artifact appears.
|
If the Einstein ring looks noisy/jagged, raise `tol` toward `5e-4` (tighter) is wrong direction — instead increase `steps` in the UI, or loosen `tol` toward `2e-3` if too many rays terminate early. Default `tol = 1e-3` is the spec value; only deviate if a real artifact appears.
|
||||||
|
|
||||||
- [ ] **Step 5: Commit**
|
- [x] **Step 5: Commit**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git add assets/shaders/black_hole.wgsl
|
git add assets/shaders/black_hole.wgsl
|
||||||
@ -795,7 +798,7 @@ git commit -m "feat: adaptive RK45 integrator replacing fixed-step RK4"
|
|||||||
**Files:**
|
**Files:**
|
||||||
- Modify: `src/ui.rs`
|
- Modify: `src/ui.rs`
|
||||||
|
|
||||||
- [ ] **Step 1: Add the Black Hole section and remove the disk_inner slider**
|
- [x] **Step 1: Add the Black Hole section and remove the disk_inner slider**
|
||||||
|
|
||||||
In `src/ui.rs`, inside the `egui::Window::new("Controls")` closure, add a new collapsing header *before* the "Accretion Disk" header, and modify the "Accretion Disk" header to remove the `disk_inner` slider (it is now spin-derived).
|
In `src/ui.rs`, inside the `egui::Window::new("Controls")` closure, add a new collapsing header *before* the "Accretion Disk" header, and modify the "Accretion Disk" header to remove the `disk_inner` slider (it is now spin-derived).
|
||||||
|
|
||||||
@ -820,7 +823,7 @@ Replace the block starting at `egui::CollapsingHeader::new("Accretion Disk")` (c
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 2: Compile and run**
|
- [x] **Step 2: Compile and run**
|
||||||
|
|
||||||
Run: `cargo build`
|
Run: `cargo build`
|
||||||
Expected: compiles.
|
Expected: compiles.
|
||||||
@ -828,7 +831,7 @@ Expected: compiles.
|
|||||||
Run: `cargo run`
|
Run: `cargo run`
|
||||||
Expected: the Controls panel has a new "Black Hole" section with a Spin slider (0–1) and two read-only ISCO/Horizon labels that update live as the slider moves. The "Accretion Disk" section no longer has an "Inner radius" slider. Sweeping spin from 0 to 0.9 visibly shrinks the disk inner edge and introduces frame-dragging asymmetry.
|
Expected: the Controls panel has a new "Black Hole" section with a Spin slider (0–1) and two read-only ISCO/Horizon labels that update live as the slider moves. The "Accretion Disk" section no longer has an "Inner radius" slider. Sweeping spin from 0 to 0.9 visibly shrinks the disk inner edge and introduces frame-dragging asymmetry.
|
||||||
|
|
||||||
- [ ] **Step 3: Commit**
|
- [x] **Step 3: Commit**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git add src/ui.rs
|
git add src/ui.rs
|
||||||
@ -899,11 +902,11 @@ If no changes, skip this step.
|
|||||||
- [ ] spin>0 shows frame-dragging asymmetry (disk halo no longer mirror-symmetric across the spin axis).
|
- [ ] spin>0 shows frame-dragging asymmetry (disk halo no longer mirror-symmetric across the spin axis).
|
||||||
- [ ] Disk inner edge tracks Kerr ISCO (Bardeen formula), shrinking from 3 Rs at spin=0 toward 0.5 Rs at extremal.
|
- [ ] Disk inner edge tracks Kerr ISCO (Bardeen formula), shrinking from 3 Rs at spin=0 toward 0.5 Rs at extremal.
|
||||||
- [ ] Horizon radius shrinks with spin (r+ = M + sqrt(M²−a²)).
|
- [ ] Horizon radius shrinks with spin (r+ = M + sqrt(M²−a²)).
|
||||||
- [ ] Adaptive RK45 integrator runs; render_scale=0.75 desktop / 0.5 web.
|
- [x] Adaptive RK45 integrator runs; render_scale=0.75 desktop / 0.5 web.
|
||||||
- [ ] All Phase 1 features (disk, Doppler, stars, grid, planets, skybox) work at spin>0.
|
- [ ] All Phase 1 features (disk, Doppler, stars, grid, planets, skybox) work at spin>0.
|
||||||
- [ ] `cargo test` passes (Phase 1 + Kerr degeneracy/ISCO/horizon tests).
|
- [x] `cargo test` passes (Phase 1 + Kerr degeneracy/ISCO/horizon tests).
|
||||||
- [ ] Desktop ≥60 fps at default Phase 2 settings; web ≥30 fps on WebGPU.
|
- [ ] Desktop ≥60 fps at default Phase 2 settings; web ≥30 fps on WebGPU.
|
||||||
- [ ] egui Spin slider + ISCO/Horizon read-only labels work; disk_inner slider removed.
|
- [x] egui Spin slider + ISCO/Horizon read-only labels work; disk_inner slider removed.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user