51 Commits

Author SHA1 Message Date
xfy
d5c0998537 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).
2026-07-14 15:52:49 +08:00
xfy
334e65fc76 test(physics): mirror Kerr RK45 loop in CPU, cover spin=0 degeneracy + spin>0 capture
Phase 2 shipped the Kerr deriv() and the adaptive RK45 integration loop in
the shader (black_hole.wgsl:260, 320-390), but the CPU mirror in physics.rs
only covered the single-step Kerr derivative (kerr_bending_accel), not the
loop. That left the AGENTS.md 'CPU <-> shader mirror' contract broken for
Phase 2: the spin-dependent capture radius, the adaptive step control, and
the budget = accepted-steps-only / dt_min forced-accept semantics existed
only on the GPU, with nothing testable on the CPU side.

Add a faithful CPU mirror and the loop-level tests that close the gap:

- rk45_step(): Dormand-Prince step mirroring black_hole.wgsl:260-285, using
  kerr_bending_accel so it is also the Phase 1 step at chi=0.
- is_captured_rk45(): the full adaptive loop mirroring black_hole.wgsl:320-390
  — same seeding (total_path / steps), same reject/retry at dt_min (the
  forced-accept floor that prevents infinite retry), same accept-then-refine,
  and the spin-dependent capture radius r+(chi) via kerr_horizon.

Tests (tests/physics_test.rs):
- spin=0 RK45 loop captures below bcrit and escapes above (degeneracy).
- spin=0.9 still captures a b~2.0 ray (horizon shrinks but not past b<bcrit).
- capture set does not grow as spin increases across a bcrit-straddling sweep.
- rk45_step error shrinks monotonically with dt (the property the adaptive
  loop's reject/accept decision depends on).

Note: probing the error scaling showed it falls between 2nd and 4th order in
dt, not a clean 5th, because of the per-stage normalize() projection the
shader (and now the mirror) applies. The loop only relies on monotonicity, so
this is correct-as-shipped; documented in a doc-comment on rk45_step so a
future reader is not misled by the '45' in the name.

cargo test: 17 passed (3 Phase 1 inline + 14 integration, up from 12).
2026-07-14 15:51:53 +08:00
xfy
9afab04308 render: resolve clippy warnings in BlackHolePlugin systems
Fixes the following clippy warnings in plugin.rs:
1. clippy::too_many_arguments: Suppressed on spawn_fullscreen_quad since Bevy system functions naturally take many arguments as resources/queries.
2. clippy::field_reassign_with_default: Refactored instantiation of BlackHoleMaterial to set fields in the struct literal instead of post-creation assignment.
3. clippy::type_complexity: Suppressed on resize_offscreen and nudge_camera systems as Bevy queries with Filters/Disjunctions are inherently complex.
2026-07-14 15:09:39 +08:00
xfy
17fbbf98e1 render: derive Default for BlackHoleMaterial
Clippy warned that the manual Default implementation for BlackHoleMaterial
could be derived (clippy::derivable_impls). We now derive Default directly
on the struct and remove the manual implementation block.
2026-07-14 15:09:38 +08:00
xfy
699a8c974e ui: use std::f32::consts::PI instead of approximate 3.14 literal
Fixes clippy::approx_constant errors caused by using the hardcoded value 3.14
for camera yaw and accretion disk tilt sliders. Using std::f32::consts::PI
resolves the errors and uses the precise constant value.
2026-07-14 15:09:36 +08:00
xfy
c3ca1e002a physics: truncate BCRIT constant float precision
Clippy warned about excessive precision in the BCRIT float literal (2.5980762).
Since f32 has 24 bits of mantissa (approx 7 decimal digits of precision), the
excessive decimal digits are truncated to 2.598_076 to compile cleanly under
clippy::excessive_precision.

The integration test still passes as the difference is well within the 1e-5 limit.
2026-07-14 15:09:34 +08:00
xfy
22489f50da fix: group conflicting Transform queries in ParamSet to fix B0001 panic
resize_offscreen declared two Query<&mut Transform> params (FullscreenQuad
and UpscaleQuad filters). Bevy's conflict checker does not treat With<T>
filters as disjoint access — both queries declare write access to the same
component, so the app panicked at startup with error[B0001].

Merge the two queries into a ParamSet (borrowed one at a time) and move
them to the end of the parameter list per the Params convention.
2026-07-14 15:00:05 +08:00
xfy
aadd0c62e1 fix: make dt_min a forced-accept floor in RK45 to prevent GPU hang 2026-07-14 14:48:46 +08:00
xfy
ff7bf82f02 chore: silence dead-code warnings in physics test-mirror module 2026-07-14 14:40:46 +08:00
xfy
7cb5c2880a feat: add Spin slider + ISCO/Horizon read-only labels 2026-07-14 14:37:10 +08:00
xfy
c134661596 feat: adaptive RK45 integrator replacing fixed-step RK4 2026-07-14 14:33:13 +08:00
xfy
d8b77c00c8 feat: Kerr deriv() with frame-dragging + spin-dependent horizon 2026-07-14 14:22:37 +08:00
xfy
7d4a53358f feat: derive disk_inner from Kerr ISCO 2026-07-14 14:18:36 +08:00
xfy
1bde9f382c feat: add kerr_isco and kerr_horizon CPU helpers with tests 2026-07-14 14:15:25 +08:00
xfy
61edcfabd2 feat: plumb spin parameter into GPU uniform 2026-07-14 14:12:31 +08:00
xfy
e158c8c668 feat: wire render_scale via offscreen render-to-texture + upscale 2026-07-14 14:09:39 +08:00
xfy
15cbf52fb6 docs: Phase 2 (Kerr) implementation plan 2026-07-13 18:36:12 +08:00
xfy
0693c3d165 docs: Phase 2 (Kerr) design spec 2026-07-13 18:24:49 +08:00
xfy
80c2b5dbe2 fix(camera): adjust default pitch and invert zoom scroll direction
- Set default pitch to 0.7 rad to keep the camera away from the π/2 gimbal pole, preventing horizontal drag from degenerating into an in-plane roll.
- Adjust scroll zoom logic to divide instead of multiply by the zoom factor, so that scrolling up zooms in (reduces distance) rather than out.
2026-07-13 18:11:04 +08:00
xfy
72a9996780 docs: add AGENTS.md for future agent sessions
Compact onboarding file capturing hard-won, non-obvious repo facts:

- Dual-target build (desktop + web/WebGPU) and the wasm-only
  web_sys_unstable_apis rustflag (.cargo/config.toml) that is a no-op
  on desktop — easy to break during cleanup.
- Always run --release for visual checks; the debug ray tracer is too
  slow and will look broken.
- The CPU<->shader mirror in src/physics.rs: it is a hand-maintained
  duplicate of black_hole.wgsl kept solely so the capture/escape
  boundary is testable. Editing one side without the other makes tests
  pass on code the shader contradicts.
- The three Bevy 0.19 gotchas that cause the recurring grey screen
  (nudge_camera for issue #24448, EguiPrimaryContextPass placement,
  real storage buffer asset instead of default handle) — exactly what
  this branch's recent commits fix.
- Reserved-but-intentionally-unwired fields (render_scale, spin) and
  the Rs=1 natural-units convention so an agent does not 'fix'
  intentional dead code or invent unit conversions.
- Git workflow: self-judge whether to commit, granular detailed
  commits, never push.

No code changes.
2026-07-13 17:41:03 +08:00
xfy
f80a24d191 fix: invert orbit camera yaw/pitch to view-follows-cursor direction 2026-07-13 17:38:02 +08:00
xfy
4556376b4a fix: make the black hole actually render (was stuck on grey clear color)
The app showed only the camera clear color (grey srgb 43,44,47) — the
fullscreen quad's fragment shader produced no output. Two issues; the
second was the real blocker.

1. render/material.rs: skybox.wgsl declared binding 1 as texture_cube<f32>
   but the material's #[texture(1)] defaulted to D2. The mismatched
   bind-group layout vs. shader caused the pipeline to fail to specialize.
   Fix: dimension = "cube" on the texture attribute so the layout matches.

2. SHADER COMPOSITION (the actual blocker): the shader was split across
   naga_oil modules (ray_gen, geodesic, stars, disk, planets, grid, skybox)
   pulled in via #import singularity::... This compiled and validated with
   zero errors, but at runtime calling ANY cross-module-imported function
   made the fragment output nothing (only the clear color showed). Local
   functions were fine. Confirmed by bisecting a minimal repro with the
   user watching the screen: local fn -> renders; imported fn (even pure
   math) -> grey.

   Rather than chase the naga_oil 0.22 / Bevy 0.19 composition bug, inline
   every function into black_hole.wgsl and drop the #import singularity::*
   lines. The 7 standalone module files are removed. Rendering now works.

Also fixes scene/planets.rs: upload_planets was allocating a fresh
ShaderBuffer (new handle) every frame, which re-triggered the
AsBindGroup RetryNextUpdate that the previous commit's startup pre-fill
was meant to eliminate. Now mutates the existing buffer asset in place
via set_data, keeping the handle stable.
2026-07-13 16:43:01 +08:00
xfy
0b158c57d3 chore: add matt-pocock skills and lockfile 2026-07-13 14:37:04 +08:00
xfy
ee15ddecb7 fix: make the renderer actually draw (step size, storage buffer, quad fit)
The committed Phase 1 code rendered a black screen due to three
independent bugs, fixed here:

- black_hole.wgsl: dt was sized as |eye|/steps, so the full integration
  only traveled |eye| units total — rays never reached capture or escape
  and every pixel fell through to accum=black. Now dt covers
  eye_dist + escape_r across the configured step count, and the escape
  test uses the dynamic escape_r instead of a hardcoded 1000.0.
- render/plugin.rs: the planets binding was Handle::default(), causing
  AsBindGroup to return RetryNextUpdate every frame and silently skip
  the fullscreen quad's draw. Pre-fill a MAX_PLANETS-sized zeroed
  ShaderBuffer at startup so the binding resolves immediately.
- render/plugin.rs: scale the fullscreen quad by half the window size
  to match Camera2d's default WindowSize projection (1 unit = 1 px);
  the old aspect-based scaling letterboxed the image.
- render/plugin.rs: add nudge_camera to work around Bevy 0.19 #24448,
  where a static camera stops rendering after the first frame.
- planets.wgsl: rotate world-space planet centers into disk-local space
  before the ray-sphere test (ray and centers were in different spaces).
2026-07-13 11:47:31 +08:00
xfy
87b089eb35 docs: README + silence reserved-field dead-code warning
README documents the renderer, run instructions (desktop + web), controls,
how the geodesic pipeline works, performance notes, and Phase 2 (Kerr) as
future work. Allow dead_code on BlackHoleParams since render_scale + spin
are intentionally reserved for Phase 2.
2026-07-13 10:04:44 +08:00
xfy
76d302b4e4 feat: web release build + web-tuned defaults verified 2026-07-13 10:03:01 +08:00
xfy
1e8cd93bc3 fix: keep full-screen quad filled on window resize
The quad mesh was built once at startup with the initial aspect ratio, so
widening the window afterward left empty edges. Add a FullscreenQuad marker
component and a fit_quad_to_window system that rescales the quad's Transform
to the live aspect each frame (only mutating on change).

Also remove the misleading 'Render scale' slider — it isn't wired to a real
sub-resolution render target in Phase 1 (the full-screen quad always renders
at window resolution). Lowering Steps is the real perf lever. Documented in
a code comment for future work.
2026-07-13 10:00:42 +08:00
xfy
7e0ac65d55 feat: full egui control panel + pointer routing 2026-07-13 09:57:54 +08:00
xfy
90e8c9b3a4 feat: cubemap skybox layer (optional, procedural stars default) 2026-07-13 09:54:21 +08:00
xfy
5fa2621e6a fix(shaders): use #define_import_path + namespaced imports
The whole-file #import "file.wgsl" form (without ::) does not reliably
bring functions into scope in naga_oil/Bevy 0.19 — the renderer was
failing at runtime with 'no definition in scope for identifier:
ray_direction' (and rot_x, deriv, etc.), producing a blank screen despite
'cargo build' passing (shaders aren't compile-checked at build time).

Fix: add #define_import_path to each module file and import symbols
explicitly via namespace::name (the canonical Bevy pattern from the
shader_material_2d example). Verified the full shader pipeline compiles
and runs at runtime with zero WGSL errors.

This was a pre-existing bug masked by incomplete runtime verification in
earlier tasks; it is NOT specific to Task 15's grid work. All earlier
visual features (shadow, Doppler disk, Einstein halo, stars, planets,
grid) now actually render.
2026-07-13 09:51:08 +08:00
xfy
661d28a6a1 feat: lensed Flamm paraboloid curvature grid 2026-07-13 09:37:25 +08:00
xfy
8931904495 feat: lensed planet ray-sphere intersection in geodesic tracer
Add planets as ray-traced spheres inside the RK4 geodesic loop. A new
planets.wgsl shader tests each integrator segment against a storage
buffer of SphereData (center/radius/color/emissive) and composites hits
front-to-back alongside the accretion disk. Lambert shading with a fixed
light direction; emissive flag bypasses shading.

- assets/shaders/planets.wgsl: segment-sphere intersection + shading
- src/scene/: Planet component, upload_planets system, default planet
- black_hole.wgsl: wire planet_hit into the compositing loop
- geodesic_schwarzschild.wgsl: replace tuple return with Deriv struct
2026-07-10 17:06:19 +08:00
xfy
983413e9af feat: egui UI wired with live disk brightness + steps 2026-07-10 10:54:22 +08:00
xfy
1b1eda0772 feat: accretion disk with Doppler beaming + lensed Einstein halo 2026-07-10 10:48:31 +08:00
xfy
fbe1685a38 feat: procedural lensed starfield background 2026-07-10 10:45:24 +08:00
xfy
d5e3008343 test: CPU-mirrored geodesic physics + bcrit/capture unit tests 2026-07-10 10:43:11 +08:00
xfy
9d81df1c14 feat: Schwarzschild RK4 geodesic integrator + black shadow 2026-07-10 10:39:46 +08:00
xfy
9665d12348 feat: per-pixel camera ray generation 2026-07-10 10:36:18 +08:00
xfy
265ec9925d feat: BlackHoleUniforms + camera/params mirror system 2026-07-10 10:33:10 +08:00
xfy
1a2b157ef3 feat: BlackHoleParams tunable resource 2026-07-10 10:27:10 +08:00
xfy
d039064efa feat: orbit camera controller (yaw/pitch/zoom) 2026-07-10 10:25:16 +08:00
xfy
a45d42c07a feat: full-screen quad + BlackHoleMaterial pipeline 2026-07-10 10:20:38 +08:00
xfy
9c8f384d0e feat: web build via trunk with WebGPU detection + fallback 2026-07-10 10:12:57 +08:00
xfy
25e2207e0d feat: minimal bevy window opens 2026-07-10 09:48:38 +08:00
xfy
ea6b15524d build: add bevy 0.19 and bevy_egui 0.41 dependencies 2026-07-10 09:45:59 +08:00
xfy
02368a0a74 docs(plan): Phase 1 Schwarzschild implementation plan
20 tasks covering: deps, minimal window, web/Trunk/WebGPU, full-screen
Material2d pipeline, orbit camera, params resource, uniforms+mirror, ray
gen, RK4 Schwarzschild integrator, CPU physics tests, lensed starfield,
Doppler accretion disk w/ Einstein halo, egui panel, storage-buffer
planets, Flamm grid, cubemap skybox, render scale, web verification,
README. Verified against Bevy 0.19 API (group-2 material bind group,
component spawning, WgpuSettings defaults).
2026-07-09 18:57:42 +08:00
xfy
041e8f3b21 docs(spec): correct Bevy 0.19 API details (verified)
- Material2d bind group is group 2 (#{MATERIAL_BIND_GROUP}), not group 1
- RenderPlugin has no wgpu_backends field; enable bevy 'webgpu' feature instead
- WgpuSettings::default() auto-selects BROWSER_WEBGPU on wasm32
- Imports from bevy::sprite_render (Material2d, AlphaMode2d)
- Component-based spawning (Camera2d, Mesh2d/MeshMaterial2d), no bundles
- getrandom 0.4/wasm_js handled transitively by Bevy
2026-07-09 18:47:44 +08:00
xfy
c1bfe2b25e docs(spec): add desktop + web (WebGPU) platform support
- WebGPU-only web target (Bevy forces WebGPU/WebGL2 at build time; WebGL2
  lacks storage buffers our planets feature needs)
- Add §13 Platform support: build toolchain (trunk/wasm32), backend gating,
  fallback message, web defaults (render_scale 0.75, steps 200)
- Add src/web.rs, web/index.html, Trunk.toml to file structure
- Add web-sys dep; add web build + perf verification milestones
- Update storage-buffer risk note: valid on native + WebGPU
2026-07-09 18:30:56 +08:00
xfy
60bfdb9ca6 docs(spec): add egui parameter UI (bevy_egui 0.41) + dependencies section
- Switch interaction from keyboard to a collapsible egui 'Controls' panel
  with grouped sections (Camera/Disk/Doppler/Renderer/Background/Grid/Planets)
- Pin bevy_egui 0.41.0 (verified against crates.io: bevy ^0.19 + egui ^0.35)
- Add Dependencies section §0; add src/ui.rs to file structure
- Clarify mouse-input routing between egui panel and orbit controller
2026-07-09 18:26:24 +08:00
xfy
c2d4bf779a docs: add Interstellar black hole renderer design spec
Analyzes reference video (BH + tilted Doppler disk + lensed halo + stars),
specifies full 8-feature scope (adds cubemap skybox, lensed planets, Flamm
grid), Schwarzschild Phase 1 with Kerr Phase 2 follow-up, Bevy 0.19
full-screen Material2d architecture.
2026-07-09 18:20:38 +08:00