Feat: Add directed asymptotic escape and analytic Schwarzschild exterior

Replace radius-only escape termination with a common asymptotic exterior protocol: declared ends, moving escape worldtubes, directed inside->outside crossings, and a PENDING_ENTRY lifecycle shared by single-frame and movie tracing.

Add an analytic Carlson-integral Schwarzschild monopole exterior (angle primitive, bracketed turning radius, ingoing Kerr-Schild coordinate-time transfer, conserved-energy frequency) so a camera outside the escape sphere is traced through an entry event.

Make the lifecycle tri-state (no ends / ready / protocol error), carry end_id through the endpoint and lens mesh, validate sources in constructors via spacetime_source_finalize(), and refresh the Schwarzschild reference images for the corrected finish.
This commit is contained in:
wyj committed 2026-10-04 03:32:28 -04:00
1 parent 04611e3e5a
commit 09a7417961
24 files changed
+3566 -58

No files matched your search

+4 -3
View File
@@ -123,10 +123,11 @@ int main(int argc, char **argv) {
const RayEndpoint ray = geodesic_trace_past(&source, &state, (double[]){1, 0, 0}, &trace);
CHECK(ray.status == RAY_ENDPOINT_ESCAPED);
CHECK(fabs(ray.n_infinity[0] - 1) < 1e-12);
/* Radial ingoing KS photon has k^r=-k^t and conserved E=k^t.
* Current escape convention measures Eulerian energy at finite R=256. */
/* Radial ingoing KS photon has k^r=-k^t and conserved E=k^t. The
* asymptotic exterior transfers the photon to infinity, where
* g = E_camera / E_infinity = 1 / k^t. */
const double energy = state.tetrad[0][0] - state.tetrad[1][0];
CHECK(fabs(ray.frequency_ratio - sqrt(1 + 2.0 / 256) / energy) < 2e-6);
CHECK(fabs(ray.frequency_ratio - 1.0 / energy) < 1e-10 * (1.0 / energy));
memset(camera.velocity, 0, sizeof camera.velocity);
if (i > 0)
CHECK(observer_from_coordinate_camera(&metric, &camera, &state, NULL) == OBSERVER_BUILD_NON_TIMELIKE);