Files
GR-raytracing/tests/test_asymptotic_entry.c
T
wyj 789549ed2f Fix: Validate asymptotic entries with precise roots and fallback
Use scaled long-double quadratic arithmetic without explicit FMA. Validate entry candidates against backend geometry and localize uncertain entries along the original exterior trajectory.

Preserve conservative miss semantics and propagate concrete entry failures. Add production-sample and numerical regression coverage.
2026-10-09 00:14:54 -04:00

782 lines
32 KiB
C

#include "asymptotic_entry.h"
#include "spacetime.h"
#include <float.h>
#include <math.h>
#include <stdio.h>
/* Backend-independent core regression for the numerical entry localizer. It
* deliberately links no analytic backend and no geodesic integrator: the fake
* SpacetimeSource exposes only `escape_worldtube_sample` (plus a deliberately
* trapped `eval`), and the evaluator is an analytic path-parameter callback.
*
* Nothing here depends on an untracked production track, CSV or binary. */
static int failures = 0;
#define CHECK(condition, message) \
do { \
if (!(condition)) { \
fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, message); \
++failures; \
} \
} while (0)
#ifndef TEST_PI
#define TEST_PI 3.14159265358979323846
#endif
/* ------------------------------------------------------------------ */
/* Fake worldtube source */
/* ------------------------------------------------------------------ */
typedef struct {
double center0[3];
double center_vel[3]; /* dc/dt */
double radius0;
double radius_rate; /* dR/dt */
double valid_t_min; /* sample is valid for t >= valid_t_min */
int callback_fails; /* always return -1 */
int fail_at_call; /* 1-based sample-call index to fail, 0 disabled */
int nan_radius;
int zero_radius;
double hole_center; /* isolated invalid time window */
double hole_halfwidth; /* 0 disables the window */
int call_count;
int eval_calls; /* trap: how often the metric eval callback ran */
} EntryWorldtube;
static SpacetimePointStatus entry_eval_trap(const SpacetimeSource *source,
double t, const double x[3],
MetricData *metric) {
EntryWorldtube *wt = source->context;
++wt->eval_calls;
(void)t;
(void)x;
(void)metric;
/* This source is deliberately outside the metric domain. The localizer must
* never reach here because it does no metric evaluation. */
return SPACETIME_POINT_OUT_OF_DOMAIN;
}
static int entry_worldtube_cb(const SpacetimeSource *source,
SpacetimeEndId end_id, double t,
SpacetimeEscapeWorldtubeSample *out) {
EntryWorldtube *wt = source->context;
if (end_id != 0)
return -1;
++wt->call_count;
if (wt->fail_at_call > 0 && wt->call_count == wt->fail_at_call)
return -1;
if (wt->callback_fails)
return -1;
if (!isfinite(t) || t < wt->valid_t_min) {
*out = (SpacetimeEscapeWorldtubeSample){.valid = 0};
return 0;
}
if (wt->hole_halfwidth > 0.0 &&
fabs(t - wt->hole_center) <= wt->hole_halfwidth) {
*out = (SpacetimeEscapeWorldtubeSample){.valid = 0};
return 0;
}
if (wt->nan_radius) {
*out = (SpacetimeEscapeWorldtubeSample){.radius = NAN, .valid = 1};
return 0;
}
if (wt->zero_radius) {
*out = (SpacetimeEscapeWorldtubeSample){.radius = 0.0, .valid = 1};
return 0;
}
*out = (SpacetimeEscapeWorldtubeSample){
.center = {wt->center0[0] + wt->center_vel[0] * t,
wt->center0[1] + wt->center_vel[1] * t,
wt->center0[2] + wt->center_vel[2] * t},
.velocity = {wt->center_vel[0], wt->center_vel[1], wt->center_vel[2]},
.radius = wt->radius0 + wt->radius_rate * t,
.radius_rate = wt->radius_rate,
.velocity_constant = 1,
.valid = 1};
return 0;
}
static const SpacetimeOps entry_ops = {
.eval = entry_eval_trap,
.escape_worldtube_sample = entry_worldtube_cb,
};
static SpacetimeSource entry_source(EntryWorldtube *wt) {
return (SpacetimeSource){.ops = &entry_ops, .context = wt};
}
/* ------------------------------------------------------------------ */
/* Analytic path-parameter evaluator */
/* ------------------------------------------------------------------ */
typedef struct {
double camera_t;
double camera_x[3];
double w[3]; /* unit past direction (straight mode) */
int arc_mode;
double arc_center[3];
double arc_radius;
double arc_theta0;
double L0;
double L0camera;
int evaluator_fails_at;
AsymptoticStatus fail_status;
int evaluator_call_count;
int nonfinite_at;
int reversed_time_at;
} EntryEvaluator;
static void entry_trajectory(const EntryEvaluator *c, double parameter,
double x[3], double w[3], double *t) {
if (c->arc_mode) {
/* Circular analytic arc: parameter is arc length. Not a physical
* geodesic, but a generic curved callback that exercises the driver beyond
* straight lines. */
const double theta = c->arc_theta0 + parameter / c->arc_radius;
x[0] = c->arc_center[0] + c->arc_radius * cos(theta);
x[1] = c->arc_center[1] + c->arc_radius * sin(theta);
x[2] = c->arc_center[2];
w[0] = -sin(theta);
w[1] = cos(theta);
w[2] = 0.0;
} else {
for (int i = 0; i < 3; ++i) {
x[i] = c->camera_x[i] + parameter * c->w[i];
w[i] = c->w[i];
}
}
*t = c->camera_t - parameter;
}
static AsymptoticStatus entry_evaluator_cb(void *context, double parameter,
AsymptoticRoute *state) {
EntryEvaluator *c = context;
++c->evaluator_call_count;
if (c->evaluator_fails_at > 0 &&
c->evaluator_call_count == c->evaluator_fails_at)
return c->fail_status;
double x[3], w[3], t;
entry_trajectory(c, parameter, x, w, &t);
*state = (AsymptoticRoute){0};
state->kind = ASYMPTOTIC_ROUTE_ENTRY;
state->end_id = 0;
state->activate_t = t;
for (int i = 0; i < 3; ++i) {
state->x[i] = x[i];
state->Pi[i] = -w[i];
}
state->log_alpha_p0 = c->L0;
state->log_alpha_p0_camera = c->L0camera;
if (c->evaluator_call_count == c->nonfinite_at)
state->log_alpha_p0_camera = NAN;
if (c->evaluator_call_count == c->reversed_time_at)
state->activate_t = c->camera_t + 1.0;
return ASYMPTOTIC_OK;
}
/* Independent test-side oracle: the same worldtube F the driver sees, but
* computed directly from the analytic trajectory. Used only to find the true
* first entry for comparison. */
typedef struct {
const EntryEvaluator *ev;
const EntryWorldtube *wt;
} EntryOracle;
static double entry_oracle_F(void *context, double parameter) {
const EntryOracle *o = context;
double x[3], w[3], t;
entry_trajectory(o->ev, parameter, x, w, &t);
double d[3];
for (int i = 0; i < 3; ++i)
d[i] = x[i] - (o->wt->center0[i] + o->wt->center_vel[i] * t);
const double d2 = d[0] * d[0] + d[1] * d[1] + d[2] * d[2];
const double radius = o->wt->radius0 + o->wt->radius_rate * t;
return d2 - radius * radius;
}
static double entry_oracle_root(const EntryEvaluator *ev,
const EntryWorldtube *wt, double lo,
double hi) {
EntryOracle o = {.ev = ev, .wt = wt};
if (!(entry_oracle_F(&o, lo) >= 0.0 && entry_oracle_F(&o, hi) < 0.0))
return NAN;
for (int i = 0; i < 200; ++i) {
const double mid = 0.5 * (lo + hi);
if (!(mid > lo && mid < hi))
break;
if (entry_oracle_F(&o, mid) >= 0.0)
lo = mid;
else
hi = mid;
}
return 0.5 * (lo + hi);
}
static double path_parameter(const EntryEvaluator *ev,
const AsymptoticRoute *state) {
return ev->camera_t - state->activate_t;
}
/* ------------------------------------------------------------------ */
/* Tests */
/* ------------------------------------------------------------------ */
static void test_geometry_contract(void) {
EntryWorldtube wt = {.radius0 = 10.0, .valid_t_min = -1.0e300};
SpacetimeSource source = entry_source(&wt);
double F = NAN, tol = NAN;
RayReason reason = RAY_REASON_COUNT;
CHECK(asymptotic_entry_geometry(&source, 0, 0.0, (double[]){10.0, 0.0, 0.0},
&F, &tol, &reason) == ASYMPTOTIC_OK,
"boundary geometry status");
CHECK(F == 0.0, "boundary F is exactly zero");
CHECK(tol > 0.0 && isfinite(tol), "boundary tolerance finite positive");
CHECK(reason == RAY_REASON_NONE, "boundary reason none");
CHECK(asymptotic_entry_geometry(&source, 0, 0.0, (double[]){20.0, 0.0, 0.0},
&F, &tol, &reason) == ASYMPTOTIC_OK &&
F == 300.0,
"outside F is positive 300");
CHECK(asymptotic_entry_geometry(&source, 0, 0.0, (double[]){5.0, 0.0, 0.0},
&F, &tol, &reason) == ASYMPTOTIC_OK &&
F == -75.0,
"inside F is negative 75");
/* History exhaustion beats a miss. */
EntryWorldtube hole = {.radius0 = 10.0, .valid_t_min = 0.0};
source = entry_source(&hole);
CHECK(asymptotic_entry_geometry(&source, 0, -1.0, (double[]){10.0, 0.0, 0.0},
&F, &tol, &reason) ==
ASYMPTOTIC_TIME_RANGE_EXHAUSTED &&
reason == RAY_REASON_TIME_RANGE_EXHAUSTED,
"valid=0 is history exhaustion");
/* Callback failure is distinct from an invalid geometry. */
EntryWorldtube fail = {.radius0 = 10.0, .callback_fails = 1};
source = entry_source(&fail);
CHECK(asymptotic_entry_geometry(&source, 0, 0.0, (double[]){10.0, 0.0, 0.0},
&F, &tol, &reason) == ASYMPTOTIC_INVALID &&
reason == RAY_REASON_WORLDTUBE_SAMPLE_FAILED,
"callback failure reason");
EntryWorldtube nanr = {.radius0 = 10.0, .nan_radius = 1};
source = entry_source(&nanr);
CHECK(asymptotic_entry_geometry(&source, 0, 0.0, (double[]){10.0, 0.0, 0.0},
&F, &tol, &reason) == ASYMPTOTIC_INVALID &&
reason == RAY_REASON_WORLDTUBE_GEOMETRY_INVALID,
"NaN radius is invalid geometry");
EntryWorldtube zeror = {.radius0 = 10.0, .zero_radius = 1};
source = entry_source(&zeror);
CHECK(asymptotic_entry_geometry(&source, 0, 0.0, (double[]){10.0, 0.0, 0.0},
&F, &tol, &reason) == ASYMPTOTIC_INVALID &&
reason == RAY_REASON_WORLDTUBE_GEOMETRY_INVALID,
"non-positive radius is invalid geometry");
source = entry_source(&wt);
CHECK(asymptotic_entry_geometry(&source, 0, NAN, (double[]){10.0, 0.0, 0.0},
&F, &tol, &reason) == ASYMPTOTIC_INVALID &&
reason == RAY_REASON_WORLDTUBE_GEOMETRY_INVALID,
"NaN time is invalid geometry");
CHECK(asymptotic_entry_geometry(NULL, 0, 0.0, (double[]){10.0, 0.0, 0.0},
&F, &tol, &reason) == ASYMPTOTIC_INVALID &&
reason == RAY_REASON_INVALID_ARGUMENT,
"NULL source rejected");
CHECK(asymptotic_entry_geometry(&source, 0, 0.0, NULL, &F, &tol,
&reason) == ASYMPTOTIC_INVALID,
"NULL position rejected");
}
static void test_validate_contract(void) {
EntryWorldtube wt = {.radius0 = 10.0, .valid_t_min = -1.0e300};
SpacetimeSource source = entry_source(&wt);
int valid = -1;
RayReason reason = RAY_REASON_COUNT;
AsymptoticRoute candidate = {.kind = ASYMPTOTIC_ROUTE_ENTRY,
.end_id = 0,
.activate_t = 0.0,
.x = {10.0, 0.0, 0.0},
.Pi = {-1.0, 0.0, 0.0},
.log_alpha_p0 = 0.5,
.log_alpha_p0_camera = 0.25};
CHECK(asymptotic_entry_validate(&source, 0, &candidate, &valid, &reason) ==
ASYMPTOTIC_OK &&
valid == 1,
"boundary candidate is valid");
candidate.x[0] = 11.0; /* F = 21 > tol */
CHECK(asymptotic_entry_validate(&source, 0, &candidate, &valid, &reason) ==
ASYMPTOTIC_OK &&
valid == 0 && reason == RAY_REASON_NONE,
"outside candidate is valid=0 with OK status");
candidate.x[0] = 5.0; /* F = -75, far inside */
CHECK(asymptotic_entry_validate(&source, 0, &candidate, &valid, &reason) ==
ASYMPTOTIC_OK &&
valid == 0,
"deep-inside candidate is valid=0 with OK status");
candidate.x[0] = 10.0;
candidate.kind = ASYMPTOTIC_ROUTE_ESCAPED;
CHECK(asymptotic_entry_validate(&source, 0, &candidate, &valid, &reason) ==
ASYMPTOTIC_INVALID &&
valid == 0 && reason == RAY_REASON_PROTOCOL_ERROR,
"wrong candidate kind is a protocol error");
candidate.kind = ASYMPTOTIC_ROUTE_ENTRY;
candidate.x[0] = NAN;
CHECK(asymptotic_entry_validate(&source, 0, &candidate, &valid, &reason) ==
ASYMPTOTIC_INVALID &&
reason == RAY_REASON_PROTOCOL_ERROR,
"non-finite candidate is a protocol error");
/* History exhaustion propagates through validation. */
candidate.x[0] = 10.0;
candidate.activate_t = -1.0;
EntryWorldtube hole = {.radius0 = 10.0, .valid_t_min = 0.0};
source = entry_source(&hole);
CHECK(asymptotic_entry_validate(&source, 0, &candidate, &valid, &reason) ==
ASYMPTOTIC_TIME_RANGE_EXHAUSTED &&
reason == RAY_REASON_TIME_RANGE_EXHAUSTED,
"validation propagates history exhaustion");
}
static void test_fixed_sphere_localize(void) {
EntryWorldtube wt = {.radius0 = 10.0, .valid_t_min = -1.0e300};
SpacetimeSource source = entry_source(&wt);
EntryEvaluator ev = {.camera_t = 0.0,
.camera_x = {50.0, 0.0, 0.0},
.w = {-1.0, 0.0, 0.0},
.L0 = 0.75,
.L0camera = 0.5};
AsymptoticRoute out;
unsigned int evaluations = 0;
RayReason reason = RAY_REASON_COUNT;
CHECK(asymptotic_entry_localize(&source, 0, entry_evaluator_cb, &ev, 30.0,
45.0, &out, &evaluations,
&reason) == ASYMPTOTIC_OK,
"fixed-sphere localize succeeds");
CHECK(out.kind == ASYMPTOTIC_ROUTE_ENTRY && out.end_id == 0,
"localized kind and end");
const double s = path_parameter(&ev, &out);
const double s_true = entry_oracle_root(&ev, &wt, 30.0, 45.0);
CHECK(isfinite(s_true), "oracle found the same bracket");
CHECK(s >= s_true && s - s_true <= 1e-9,
"localized just past first entry");
CHECK(fabs(s - 40.0) <= 1e-9, "fixed-sphere entry at s=40");
CHECK(out.Pi[0] == 1.0 && out.Pi[1] == 0.0 && out.Pi[2] == 0.0,
"direction preserved exactly");
CHECK(out.log_alpha_p0 == 0.75 && out.log_alpha_p0_camera == 0.5,
"L and camera L preserved exactly");
CHECK(evaluations >= 2 && evaluations <= 260, "evaluation count bounded");
CHECK(ev.evaluator_call_count == (int)evaluations,
"evaluator calls counted once each");
CHECK(wt.eval_calls == 0, "no metric evaluation outside the worldtube");
}
static void test_too_early_hint(void) {
/* Outside endpoint is the camera (a deliberately too-early, corrupted
* bracket); the localizer still returns the true first entry, not the
* inside hint and not the camera. */
EntryWorldtube wt = {.radius0 = 10.0, .valid_t_min = -1.0e300};
SpacetimeSource source = entry_source(&wt);
EntryEvaluator ev = {.camera_t = 0.0,
.camera_x = {50.0, 0.0, 0.0},
.w = {-1.0, 0.0, 0.0},
.L0 = 0.1,
.L0camera = 0.2};
AsymptoticRoute out;
unsigned int evaluations = 0;
RayReason reason = RAY_REASON_COUNT;
CHECK(asymptotic_entry_localize(&source, 0, entry_evaluator_cb, &ev, 0.0,
45.0, &out, &evaluations,
&reason) == ASYMPTOTIC_OK,
"too-early hint still localizes");
const double s = path_parameter(&ev, &out);
CHECK(fabs(s - 40.0) <= 1e-9, "returns actual first entry, not the hint");
CHECK(s > 1.0 && s < 45.0, "not the camera and not the inside hint");
CHECK(evaluations <= 260, "hint evaluation budget");
}
static void test_moving_sphere_localize(void) {
EntryWorldtube wt = {.radius0 = 10.0,
.center_vel = {0.5, 0.0, 0.0},
.valid_t_min = -1.0e300};
SpacetimeSource source = entry_source(&wt);
EntryEvaluator ev = {.camera_t = 0.0,
.camera_x = {100.0, 0.0, 0.0},
.w = {-1.0, 0.0, 0.0},
.L0 = 0.3,
.L0camera = 0.4};
AsymptoticRoute out;
unsigned int evaluations = 0;
RayReason reason = RAY_REASON_COUNT;
CHECK(asymptotic_entry_localize(&source, 0, entry_evaluator_cb, &ev, 150.0,
200.0, &out, &evaluations,
&reason) == ASYMPTOTIC_OK,
"translated+ moving sphere localize");
const double s = path_parameter(&ev, &out);
const double s_true = entry_oracle_root(&ev, &wt, 150.0, 200.0);
CHECK(fabs(s - s_true) <= 1e-8 && fabs(s - 180.0) <= 1e-8,
"moving-sphere entry at s=180");
CHECK(evaluations <= 260, "moving-sphere evaluation budget");
}
static void test_radius_rate_localize(void) {
/* radius(t) = radius0 + radius_rate * t with radius_rate = -1 and t = -s, so
* R grows as 10 + s; the entry is at s = 45. */
EntryWorldtube wt = {.radius0 = 10.0,
.radius_rate = -1.0,
.valid_t_min = -1.0e300};
SpacetimeSource source = entry_source(&wt);
EntryEvaluator ev = {.camera_t = 0.0,
.camera_x = {100.0, 0.0, 0.0},
.w = {-1.0, 0.0, 0.0},
.L0 = 0.6,
.L0camera = 0.6};
AsymptoticRoute out;
unsigned int evaluations = 0;
RayReason reason = RAY_REASON_COUNT;
CHECK(asymptotic_entry_localize(&source, 0, entry_evaluator_cb, &ev, 30.0,
60.0, &out, &evaluations,
&reason) == ASYMPTOTIC_OK,
"linear radius-rate localize");
const double s = path_parameter(&ev, &out);
const double s_true = entry_oracle_root(&ev, &wt, 30.0, 60.0);
CHECK(fabs(s - s_true) <= 1e-8 && fabs(s - 45.0) <= 1e-8,
"linear radius-rate entry at s=45");
CHECK(evaluations <= 260, "radius-rate evaluation budget");
}
static void test_rotated_frame_localize(void) {
/* Camera on a rotated axis: (40,30,0), past direction toward the origin. */
EntryWorldtube wt = {.radius0 = 10.0, .valid_t_min = -1.0e300};
SpacetimeSource source = entry_source(&wt);
EntryEvaluator ev = {.camera_t = 0.0,
.camera_x = {40.0, 30.0, 0.0},
.w = {-0.8, -0.6, 0.0},
.L0 = 0.2,
.L0camera = 0.1};
AsymptoticRoute out;
unsigned int evaluations = 0;
RayReason reason = RAY_REASON_COUNT;
CHECK(asymptotic_entry_localize(&source, 0, entry_evaluator_cb, &ev, 30.0,
45.0, &out, &evaluations,
&reason) == ASYMPTOTIC_OK,
"rotated flat frame localize");
const double s = path_parameter(&ev, &out);
const double s_true = entry_oracle_root(&ev, &wt, 30.0, 45.0);
CHECK(fabs(s - s_true) <= 1e-9 && fabs(s - 40.0) <= 1e-9,
"rotated-frame entry at s=40");
CHECK(fabs(out.x[1] - 6.0) <= 1e-6, "rotated entry position on sphere");
}
static void test_grazing_first_entry(void) {
/* Grazing pass: the camera is offset by 9.9 from the sphere axis. The first
* entry at s ~ 48.589 is inside the bracket; the exit at s ~ 51.410 is not.
* Bisection must return the first entry, not the later exit. */
EntryWorldtube wt = {.radius0 = 10.0, .valid_t_min = -1.0e300};
SpacetimeSource source = entry_source(&wt);
EntryEvaluator ev = {.camera_t = 0.0,
.camera_x = {50.0, 9.9, 0.0},
.w = {-1.0, 0.0, 0.0},
.L0 = 0.0,
.L0camera = 0.0};
AsymptoticRoute out;
unsigned int evaluations = 0;
RayReason reason = RAY_REASON_COUNT;
CHECK(asymptotic_entry_localize(&source, 0, entry_evaluator_cb, &ev, 48.0,
50.0, &out, &evaluations,
&reason) == ASYMPTOTIC_OK,
"grazing first entry localizes");
const double s = path_parameter(&ev, &out);
const double first = 50.0 - sqrt(100.0 - 9.9 * 9.9);
CHECK(fabs(s - first) <= 1e-8, "grazing entry is the first crossing");
CHECK(s < 51.4, "not the later exit crossing");
CHECK(fabs(out.x[1] - 9.9) <= 1e-9, "grazing impact parameter preserved");
CHECK(evaluations <= 260, "grazing evaluation budget");
}
static void test_curved_arc_localize(void) {
/* Circular analytic arc of radius 30 and worldtube centered at (25,0,0)
* radius 8; entry at arc length ~ 87.40. */
EntryWorldtube wt = {.radius0 = 8.0,
.center0 = {25.0, 0.0, 0.0},
.valid_t_min = -1.0e300};
SpacetimeSource source = entry_source(&wt);
EntryEvaluator ev = {.camera_t = 0.0,
.arc_mode = 1,
.arc_center = {0.0, 0.0, 0.0},
.arc_radius = 30.0,
.arc_theta0 = TEST_PI,
.L0 = 0.9,
.L0camera = 0.8};
AsymptoticRoute out;
unsigned int evaluations = 0;
RayReason reason = RAY_REASON_COUNT;
const double lo = 30.0 * (5.9 - TEST_PI);
const double hi = 30.0 * (6.2 - TEST_PI);
const double s_true = entry_oracle_root(&ev, &wt, lo, hi);
CHECK(isfinite(s_true), "curved oracle bracket");
CHECK(asymptotic_entry_localize(&source, 0, entry_evaluator_cb, &ev, lo, hi,
&out, &evaluations,
&reason) == ASYMPTOTIC_OK,
"curved arc localize");
const double s = path_parameter(&ev, &out);
CHECK(s >= s_true - 1e-9 && s - s_true <= 1e-8,
"curved arc entry matches the oracle");
/* d^2(theta) = 1525 - 1500 cos(theta) = 8^2 on the arc. */
const double expected =
30.0 * (2.0 * TEST_PI - acos((1525.0 - 64.0) / 1500.0) - TEST_PI);
CHECK(fabs(s - expected) <= 1e-8, "curved arc entry matches analytic root");
CHECK(evaluations <= 260, "curved arc evaluation budget");
}
static void test_boundary_entry_exact(void) {
EntryWorldtube wt = {.radius0 = 10.0, .valid_t_min = -1.0e300};
SpacetimeSource source = entry_source(&wt);
EntryEvaluator ev = {.camera_t = 0.0,
.camera_x = {50.0, 0.0, 0.0},
.w = {-1.0, 0.0, 0.0},
.L0 = 0.4,
.L0camera = 0.4};
AsymptoticRoute out;
unsigned int evaluations = 0;
RayReason reason = RAY_REASON_COUNT;
/* F == 0 exactly at the outside endpoint and strictly inside at 45. */
CHECK(asymptotic_entry_localize(&source, 0, entry_evaluator_cb, &ev, 40.0,
45.0, &out, &evaluations,
&reason) == ASYMPTOTIC_OK,
"exact boundary entry localize");
CHECK(out.kind == ASYMPTOTIC_ROUTE_ENTRY && out.activate_t == -40.0 &&
out.x[0] == 10.0,
"boundary endpoint returned directly");
CHECK(evaluations == 2, "boundary path needs no bisection");
}
static void test_unconfirmed_bracket(void) {
EntryWorldtube wt = {.radius0 = 10.0, .valid_t_min = -1.0e300};
SpacetimeSource source = entry_source(&wt);
EntryEvaluator ev = {.camera_t = 0.0,
.camera_x = {50.0, 0.0, 0.0},
.w = {-1.0, 0.0, 0.0}};
AsymptoticRoute out;
unsigned int evaluations = 0;
RayReason reason = RAY_REASON_COUNT;
/* Both endpoints outside: no strict-inside bracket. */
CHECK(asymptotic_entry_localize(&source, 0, entry_evaluator_cb, &ev, 10.0,
20.0, &out, &evaluations,
&reason) == ASYMPTOTIC_INVALID &&
reason == RAY_REASON_ENTRY_UNCONFIRMED,
"false candidate outside bracket is unconfirmed, not escaped");
/* Both endpoints strictly inside: also no entry bracket. */
CHECK(asymptotic_entry_localize(&source, 0, entry_evaluator_cb, &ev, 45.0,
50.0, &out, &evaluations,
&reason) == ASYMPTOTIC_INVALID &&
reason == RAY_REASON_ENTRY_UNCONFIRMED,
"both-inside bracket is unconfirmed");
/* Reversed bracket ordering. */
CHECK(asymptotic_entry_localize(&source, 0, entry_evaluator_cb, &ev, 45.0,
30.0, &out, &evaluations,
&reason) == ASYMPTOTIC_INVALID &&
reason == RAY_REASON_INVALID_ARGUMENT,
"reversed bracket rejected");
}
static void test_callback_failure_propagation(void) {
EntryEvaluator ev = {.camera_t = 0.0,
.camera_x = {50.0, 0.0, 0.0},
.w = {-1.0, 0.0, 0.0}};
AsymptoticRoute out;
unsigned int evaluations = 0;
RayReason reason = RAY_REASON_COUNT;
EntryWorldtube fail = {.radius0 = 10.0,
.valid_t_min = -1.0e300,
.callback_fails = 1};
SpacetimeSource source = entry_source(&fail);
CHECK(asymptotic_entry_localize(&source, 0, entry_evaluator_cb, &ev, 30.0,
45.0, &out, &evaluations,
&reason) == ASYMPTOTIC_INVALID &&
reason == RAY_REASON_WORLDTUBE_SAMPLE_FAILED,
"endpoint callback failure propagates");
fail = (EntryWorldtube){.radius0 = 10.0,
.valid_t_min = -1.0e300,
.fail_at_call = 2};
source = entry_source(&fail);
CHECK(asymptotic_entry_localize(&source, 0, entry_evaluator_cb, &ev, 30.0,
45.0, &out, &evaluations,
&reason) == ASYMPTOTIC_INVALID &&
reason == RAY_REASON_WORLDTUBE_SAMPLE_FAILED,
"inside endpoint callback failure propagates");
fail = (EntryWorldtube){.radius0 = 10.0,
.valid_t_min = -1.0e300,
.fail_at_call = 3};
source = entry_source(&fail);
CHECK(asymptotic_entry_localize(&source, 0, entry_evaluator_cb, &ev, 30.0,
45.0, &out, &evaluations,
&reason) == ASYMPTOTIC_INVALID &&
reason == RAY_REASON_WORLDTUBE_SAMPLE_FAILED,
"midpoint callback failure propagates");
}
static void test_history_hole_propagation(void) {
/* The inside endpoint falls past the valid history: the driver must report
* TIME_RANGE_EXHAUSTED, never a miss or a fabricated entry. */
EntryWorldtube wt = {.radius0 = 10.0, .valid_t_min = -40.0};
SpacetimeSource source = entry_source(&wt);
EntryEvaluator ev = {.camera_t = 0.0,
.camera_x = {50.0, 0.0, 0.0},
.w = {-1.0, 0.0, 0.0}};
AsymptoticRoute out;
unsigned int evaluations = 0;
RayReason reason = RAY_REASON_COUNT;
CHECK(asymptotic_entry_localize(&source, 0, entry_evaluator_cb, &ev, 30.0,
45.0, &out, &evaluations,
&reason) == ASYMPTOTIC_TIME_RANGE_EXHAUSTED &&
reason == RAY_REASON_TIME_RANGE_EXHAUSTED,
"endpoint history hole propagates");
/* A midpoint-only history hole: both endpoints are valid, but the first
* bisection midpoint (t = -37.5) falls in an isolated invalid window. */
wt = (EntryWorldtube){.radius0 = 10.0,
.valid_t_min = -1.0e300,
.hole_center = -37.5,
.hole_halfwidth = 0.5};
source = entry_source(&wt);
CHECK(asymptotic_entry_localize(&source, 0, entry_evaluator_cb, &ev, 30.0,
45.0, &out, &evaluations,
&reason) == ASYMPTOTIC_TIME_RANGE_EXHAUSTED &&
reason == RAY_REASON_TIME_RANGE_EXHAUSTED,
"midpoint history hole propagates, never a miss");
}
static void test_evaluator_failure_propagation(void) {
EntryWorldtube wt = {.radius0 = 10.0, .valid_t_min = -1.0e300};
SpacetimeSource source = entry_source(&wt);
AsymptoticRoute out;
unsigned int evaluations = 0;
RayReason reason = RAY_REASON_COUNT;
EntryEvaluator ev = {.camera_t = 0.0,
.camera_x = {50.0, 0.0, 0.0},
.w = {-1.0, 0.0, 0.0},
.evaluator_fails_at = 1,
.fail_status = ASYMPTOTIC_TIME_RANGE_EXHAUSTED};
CHECK(asymptotic_entry_localize(&source, 0, entry_evaluator_cb, &ev, 30.0,
45.0, &out, &evaluations,
&reason) == ASYMPTOTIC_TIME_RANGE_EXHAUSTED &&
reason == RAY_REASON_TIME_RANGE_EXHAUSTED,
"evaluator history failure propagates");
ev = (EntryEvaluator){.camera_t = 0.0,
.camera_x = {50.0, 0.0, 0.0},
.w = {-1.0, 0.0, 0.0},
.evaluator_fails_at = 1,
.fail_status = ASYMPTOTIC_INVALID};
CHECK(asymptotic_entry_localize(&source, 0, entry_evaluator_cb, &ev, 30.0,
45.0, &out, &evaluations,
&reason) == ASYMPTOTIC_INVALID &&
reason == RAY_REASON_ENTRY_UNCONFIRMED,
"evaluator invalid failure maps to unconfirmed");
ev = (EntryEvaluator){.camera_t = 0.0,
.camera_x = {50.0, 0.0, 0.0},
.w = {-1.0, 0.0, 0.0},
.evaluator_fails_at = 2,
.fail_status = ASYMPTOTIC_INVALID};
CHECK(asymptotic_entry_localize(&source, 0, entry_evaluator_cb, &ev, 30.0,
45.0, &out, &evaluations,
&reason) == ASYMPTOTIC_INVALID,
"inside endpoint evaluator failure propagates");
}
static AsymptoticStatus wide_parameter_path(void *context, double parameter,
AsymptoticRoute *state) {
(void)context;
*state = (AsymptoticRoute){.kind = ASYMPTOTIC_ROUTE_ENTRY,
.end_id = 0,
.activate_t = -parameter,
.x = {2.0 - parameter * 1e-308, 0.0, 0.0},
.Pi = {1.0, 0.0, 0.0}};
return ASYMPTOTIC_OK;
}
static void test_representability_and_state_checks(void) {
EntryWorldtube wt = {.radius0 = 1.0, .valid_t_min = -DBL_MAX};
SpacetimeSource source = entry_source(&wt);
AsymptoticRoute out;
unsigned int evaluations = 0;
RayReason reason = RAY_REASON_COUNT;
/* Both endpoints are finite, but subtracting them overflows. This must not
* be mistaken for an adjacent bracket and return the far-inside endpoint. */
CHECK(asymptotic_entry_localize(&source, 0, wide_parameter_path, NULL,
-1.6e308, 1.6e308, &out, &evaluations,
&reason) == ASYMPTOTIC_OK,
"overflow-safe parameter midpoint");
CHECK(fabs(out.x[0] - 1.0) < 1e-14 && evaluations > 2,
"wide bracket contracts to entry, not initial inside endpoint");
wt.radius0 = 10.0;
EntryEvaluator ev = {.camera_x = {50.0, 0.0, 0.0},
.w = {-1.0, 0.0, 0.0}, .nonfinite_at = 1};
CHECK(asymptotic_entry_localize(&source, 0, entry_evaluator_cb, &ev, 40.0,
45.0, &out, &evaluations, &reason) ==
ASYMPTOTIC_INVALID && reason == RAY_REASON_ENTRY_UNCONFIRMED,
"boundary shortcut rejects nonfinite camera energy reference");
ev.evaluator_call_count = 0;
ev.nonfinite_at = 3;
CHECK(asymptotic_entry_localize(&source, 0, entry_evaluator_cb, &ev, 35.0,
45.0, &out, &evaluations, &reason) ==
ASYMPTOTIC_INVALID && reason == RAY_REASON_ENTRY_UNCONFIRMED,
"midpoint rejects nonfinite camera energy reference");
ev.evaluator_call_count = 0;
ev.nonfinite_at = 0;
ev.reversed_time_at = 3;
CHECK(asymptotic_entry_localize(&source, 0, entry_evaluator_cb, &ev, 35.0,
45.0, &out, &evaluations, &reason) ==
ASYMPTOTIC_INVALID && reason == RAY_REASON_ENTRY_UNCONFIRMED,
"midpoint cannot reverse coordinate time");
}
int main(void) {
test_geometry_contract();
test_validate_contract();
test_fixed_sphere_localize();
test_too_early_hint();
test_moving_sphere_localize();
test_radius_rate_localize();
test_rotated_frame_localize();
test_grazing_first_entry();
test_curved_arc_localize();
test_boundary_entry_exact();
test_unconfirmed_bracket();
test_callback_failure_propagation();
test_history_hole_propagation();
test_evaluator_failure_propagation();
test_representability_and_state_checks();
if (failures == 0)
puts("asymptotic entry regression passed");
else
fprintf(stderr, "%d asymptotic entry regression failures\n", failures);
return failures == 0 ? 0 : 1;
}