#include "asymptotic_entry.h" #include "spacetime.h" #include #include #include /* 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; }