Skip to content

Deep sky, jets and outflow, curvature grid, and a tidal stream that actually streams - #1

Merged
algometrix merged 5 commits into
mainfrom
feature/deep-sky-jets-spacetime-tde
Jul 31, 2026
Merged

Deep sky, jets and outflow, curvature grid, and a tidal stream that actually streams#1
algometrix merged 5 commits into
mainfrom
feature/deep-sky-jets-spacetime-tde

Conversation

@algometrix

Copy link
Copy Markdown
Owner

Five commits. The headline is that the tidal disruption never produced the
wound ribbon a real event makes, and fixing that turned up three separate
bugs that were each independently fatal to it.

What changed

The tidal stream. Debris scattered, dived into the hole, and was gone
within seconds. Four causes, each found by probing the simulation rather
than staring at the render:

  • The realistic plunge ran with the cinematic spiral's drag. A zero-energy
    parabolic orbit that bleeds velocity on the way in is no longer parabolic;
    its pericenter collapsed into the hole. Drag is now per-body and exactly
    zero in realistic mode.
  • Debris copied the body's velocity vector, so a particle offset a
    body-length inward got the same speed with far less angular momentum.
    It is now launched onto the body's orbit at its own radius.
  • The tidal energy spread was applied along the direction of motion, which
    strips angular momentum too. Radial now, which changes energy and leaves
    L alone.
  • Material was absorbed the first time it crossed the inner edge. It now has
    to circularize first, tested by speed against the local circular value,
    because radial velocity passes through zero at every pericenter no matter
    how eccentric the orbit is.

Rendering. A procedural deep sky (five star layers on a stellar
temperature sequence, dust that extinguishes what is behind it, emission
nebulae, clusters, sixteen galaxies), a relativistic jet with real delta^3
Doppler beaming between its cones, a super-Eddington outflow driven by the
disc's feed rate, and the disrupted body drawn as a teardrop rather than an
ellipsoid.

Spacetime. Flamm's paraboloid as a wireframe funnel (G), with the
binary's quadrupole gravitational wave rippling outward across it.

Interface. Six one-click presets, a cinematic mode (H), tooltips on
every control, per-folder resets, and folders ordered the way the app is
actually used.

Bugs fixed that predate this work

  • Black polygons around the shadow. The dual-filter bloom kernel is a
    diamond, and at four levels its widest tail was still bright enough after
    tonemapping that the diamond's isoline drew straight edges wherever a very
    bright region bordered a black one. Six levels now, blending instead of
    summing on the way up.
  • NaN from half-float overflow. A beamed disc sample reaches a few
    thousand, which overflows to Inf down the bloom chain; Inf spread by the
    tent upsample becomes NaN, which the tonemap renders as flat black. The
    prefilter clamps.
  • The simulation could freeze outright. On a slow GPU the first frame's
    timestamp predates the startup sky bake, and that negative dt drove the
    fixed-step accumulator tens of seconds into debt.

Verification

tsc --noEmit clean, 51 tests passing (was 36). The new ones test properties
rather than implementation: the stream survives its first pericenter and
wraps past 90 degrees, a compressed disruption clock lands the body in the
same place as an uncompressed one, debris launched onto an orbit carries the
same energy and angular momentum, and the quadrupole wave grows as 1/a and
decays after the merger.

Two beauty reviews were run against this branch and their findings are
folded in, including one real defect: the particle spawn rate had a second
copy of the phase rule that stepBody already owned, and the two had drifted.

🤖 Generated with Claude Code

algometrix and others added 5 commits July 31, 2026 10:18
The disruption never produced the wound ribbon a real event makes: the
debris scattered, dived into the hole, and was gone within seconds. Four
separate causes, each found by probing the simulation rather than the
render.

- The realistic plunge ran with the cinematic spiral's drag. A zero-energy
  parabolic orbit that bleeds velocity on the way in is no longer
  parabolic; its pericenter collapsed into the hole and took the stream
  with it. Drag is now per-body and exactly zero in realistic mode.
- Debris copied the body's velocity vector. A particle offset a body-length
  inward along the strand got the same speed with far less angular
  momentum, so the inner half of every stream plunged. Debris is now
  launched onto the body's orbit at its own radius: same energy, same
  angular momentum, tangential speed L/r and the rest radial.
- The tidal energy spread was applied along the direction of motion, which
  strips angular momentum too. It is now radial, which changes the specific
  energy while leaving L alone, so the bound half keeps the star's
  pericenter and swings back out. The bound/unbound split lands near the
  50/50 real disruptions show, read back out of the energy.
- Material was absorbed the first time it crossed the inner edge. A fresh
  stream is violently eccentric and whips through pericenter; it now has to
  circularize first, tested by speed against the local circular value.
  Radial velocity is the wrong test: it passes through zero at every
  pericenter no matter how eccentric the orbit is.

Also: mass loss now begins while the body is merely stretching, at a small
absolute rate that ramps with depth, so a still-intact star is connected to
the hole by a thin leader before it comes apart. The rate is absolute
rather than a fraction of the shedding rate, because realistic mode sheds
at 0.9/s against cinematic's 0.08 and a fixed fraction stripped the star
before it reached the shedding radius.

The disruption and its debris now run on a compressed clock, the same trick
the binary inspiral already used, because an orbit at 12 r_s takes ~370
time units and at 1:1 the debris never completes a lap. Unlike the binary's
closed-form advance this one is numerical, so stepWorld walks it in
substeps of at most 0.1 time units; a test asserts the body lands in the
same place at x1 over 1200 ticks as at x20 over 60.

New tests cover the stream's shape (survives first pericenter, spreads over
a wide radial range, wraps past 90 degrees, still feeding hundreds of
seconds later), the compressed clock, and the gravitational-wave state
extracted from main.ts into its own pure module.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The background was three thin layers of grey dots. It is now a procedural
deep field baked once into an HDR cubemap and lensed by the same geodesics
as everything else: five star layers on a stellar-temperature sequence with
a luminosity function skewed to faint stars, diffraction spikes on the
bright ones, a warped galactic band whose dust lanes extinguish what lies
behind them, emission nebulae in hydrogen red and oxygen teal, globular
clusters that crowd stars into their cores, and sixteen galaxies with
bulges, arms and dust lanes. Every noise lookup is rotated off the cubic
lattice, because a cubemap face is axis-aligned with it and the lattice
otherwise shows through as angular patches in the sky.

New in the raymarch, all integrated inside the geodesic march so they bend
with the light:

- A relativistic jet with braided filaments. The brightness difference
  between the two cones is the real optically-thin Doppler boost, delta^3.
- A super-Eddington outflow, wide and ragged and un-collimated, whose
  strength is driven by the disc's feed rate so it swells during fallback
  and dies with it.
- The shredded body is now a teardrop rather than an ellipsoid, a bright
  head with a thin tail. That is not a quadric, so the bounding ellipsoid is
  solved analytically and the teardrop field is marched inside it. Its
  corona uses the same field, so it tapers along the tail.

Flamm's paraboloid is drawn as a wireframe funnel (G), with the binary's
quadrupole gravitational wave rippling outward across it: two crests per
orbit wound into a trailing spiral, amplitude growing as 1/a and falling as
1/r. All of it is placed by the vertex shader, so an inspiral costs nothing
on the CPU.

Bloom fixes, both visible as artifacts:

- The dual-filter kernel is a diamond, and with four levels its widest tail
  was still bright enough after tonemapping that the diamond's isoline drew
  straight-edged black polygons wherever a very bright region bordered a
  black one, most obviously around the shadow itself. Six levels now, and
  the upsample blends instead of summing so the extra levels do not
  multiply the total energy and wash the frame out.
- The prefilter clamps its input. A beamed disc sample reaches a few
  thousand, which overflows a half-float target to Inf on the way down the
  chain, and Inf spread by the tent upsample becomes NaN, which the tonemap
  renders as flat black.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The panel was a flat list of folders with bare labels. It now follows the
order someone actually uses: presets, then put something in the scene, then
time, then everything else collapsed. Every control carries a hover
description of what it does and what it will not do, each folder has a
reset button, and there is a global one.

- Six one-click scenes (a star devoured, spaghettification, a binary merger
  with waves, bare curved spacetime, a jetted quasar, a wallpaper frame).
  Presets are plain data; anything a preset does not mention is left as the
  user had it.
- Cinematic mode (H) fades the panel and readout for a clean frame, with a
  toast saying how to get them back. G toggles the curvature grid.
- The disruption clock is exposed as "Disruption speed", and the inspiral
  slider now says plainly that it does nothing without a second hole and
  that a wide pair barely moves at any setting, because the separation
  decays as 1/a^3.

Fixes a frame-time bug that could freeze the simulation outright: the first
frame's timestamp can predate the startup sky bake on a slow GPU, and that
negative dt drove the fixed-step accumulator tens of seconds into debt,
after which nothing moved until it climbed back out. The frame delta is now
clamped at both ends.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two captures from the app, and a physics section covering what was added:
the tidal-stream mechanics and why each decision is what it is, the
optically-thin Doppler boost behind the jet's asymmetry, Flamm's paraboloid
and the quadrupole ripple on it, the two compressed clocks and which one is
exact, and the structure the procedural sky is modelled on.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The spawn rate duplicated the phase rule that stepBody already owns, and the
two had already drifted: mass loss carried massLossRate's radial ramp and the
particle count did not, so particles-per-shed-mass was not constant and the
disc-feed normalisation drifted with it. stepBody now returns the mass it
shed and world.ts spawns strictly in proportion to it, so the rule exists
once. shedRateFraction replaces spillFraction and covers every phase.

The orbit reconstruction inside spawnFromBody is now sim/orbit.ts, with
tests asserting the property the whole stream depends on: a particle
launched onto the body's orbit at a different radius carries the same
specific energy and angular momentum, so it keeps the body's pericenter.
That was previously only visible in the emergent shape.

Honesty fixes to the knobs and the panel:

- TDE_TUNING.boundDrag was documented as bound-debris drag and read by
  nothing. Removed.
- starTidalRadius and planetTidalRadius are assigned to rShed, with the
  actual tidal radius appearing only as a bare 1.5 multiplier in placeBody.
  Renamed to starShedRadius and planetShedRadius with stretchRadiusFactor
  named alongside them.
- Four debug sliders (tidal radius, shed radius, body drag, mass-loss rate)
  are snapshotted into the Body when it is placed, so moving them changed
  nothing on screen, and the tidal-radius slider's range excluded its own
  shipped value. Dropped; the folder now holds only knobs read live.
- Preset.look was typed as any Settings key, but applyPreset only re-fires
  side effects for sky and photon paths, so a preset setting quality or
  volume would silently do nothing. Narrowed to the keys the frame loop
  reads every frame.
- Both clocks now default from config, and settings no longer hardcodes a
  number config already owns. The world tests state their clocks explicitly
  rather than inheriting a default that would multiply every duration they
  assert.
- The auto-degrade note sat in the readout for the rest of the session.

Substeps are capped at six. updatePool walks every live particle, so at the
compression slider's maximum an uncapped count swept 48k particles ten times
per frame.

In the shaders: the three emission scales that decide whether a frame is fog
or empty are named next to the geometry constants, the jet and wind reaches
are clamped to the escape sphere (past a merger uRs grows and the tip fell
outside the marched volume), and the teardrop's two ends had picked up
opposite names in adjacent comments.

Also repairs three comments left ungrammatical by the em-dash removal.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@algometrix
algometrix merged commit fa75a4a into main Jul 31, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant