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.
This commit is contained in:
wyj committed 2026-10-09 00:14:54 -04:00
1 parent 95c05f84d9
commit 789549ed2f
16 files changed
+2421 -93

No files matched your search

+258 -3
View File
@@ -3,6 +3,7 @@
#include "ray.h"
#include "spacetime.h"
#include <float.h>
#include <math.h>
#include <stdio.h>
@@ -50,6 +51,7 @@ typedef struct {
int sample_nonpositive_radius;
int fail_on_sample_call; /* 1-based callback invocation to fail. */
int sample_call_count;
double frame_origin[3];
} SyntheticContext;
static SpacetimePointStatus synthetic_eval(const SpacetimeSource *source,
@@ -95,7 +97,8 @@ static int synthetic_end(const SpacetimeSource *source, size_t index,
.end_id = 0,
.exterior_kind = kind,
.mass = mass,
.frame_origin = {0.0, 0.0, 0.0},
.frame_origin = {context->frame_origin[0], context->frame_origin[1],
context->frame_origin[2]},
.frame_axes = {{1.0, 0.0, 0.0}, {0.0, 1.0, 0.0}, {0.0, 0.0, 1.0}}};
return 0;
}
@@ -134,12 +137,17 @@ static int synthetic_worldtube(const SpacetimeSource *source,
*out = (SpacetimeEscapeWorldtubeSample){.valid = 0};
return 0;
}
/* The sample contract is the worldtube value at this coordinate time, so the
* reported radius must carry its own time dependence: R(t) = R0 + rr t, with
* dR/dt = radius_rate. A constant `radius` with a nonzero rate would make
* the closed-form segment model and the callback geometry disagree. */
const double radius_t = context->radius + context->radius_rate * t;
if (context->has_segment && t < context->segment_t) {
/* Second segment: center moves toward +x as t decreases. */
*out = (SpacetimeEscapeWorldtubeSample){
.center = {context->segment_t - t, 0.0, 0.0},
.velocity = {-1.0, 0.0, 0.0},
.radius = context->radius,
.radius = radius_t,
.radius_rate = context->radius_rate,
.velocity_constant = context->constant,
.valid = 1};
@@ -148,7 +156,7 @@ static int synthetic_worldtube(const SpacetimeSource *source,
*out = (SpacetimeEscapeWorldtubeSample){
.center = {context->vx * t + 0.5 * context->accel * t * t, 0.0, 0.0},
.velocity = {context->vx + context->accel * t, 0.0, 0.0},
.radius = context->radius,
.radius = radius_t,
.radius_rate = context->radius_rate,
.velocity_constant = context->constant,
.valid = 1};
@@ -710,6 +718,166 @@ static void test_moving_sphere(void) {
"co-moving ray misses");
}
/* Fixed-observer tetrad used by the production Alcubierre observer-track rows
* 63/64, with spatial axes (e1, e2, e3) = (y-hat, z-hat, x-hat). The literal
* values are embedded here so this regression does not depend on the
* untracked observer CSV. */
static ObserverState track_observer(double coordinate_time) {
ObserverState o = {0};
o.coordinate_time = coordinate_time;
o.coordinate_position[0] = 0.0;
o.coordinate_position[1] = -24.0;
o.coordinate_position[2] = 0.0;
o.tetrad[0][0] = 1.0;
o.tetrad[1][2] = 1.0;
o.tetrad[2][3] = 1.0;
o.tetrad[3][1] = 1.0;
return o;
}
/* Two exact production RayPool pre-route samples (frame 63 sample 12315 and
* frame 64 sample 3994). They are grazing (disc/b^2 ~ 1e-4), so the plain
* double root solve left the reconstructed entry state at F ~ 1.0-1.2 x
* geom_tol, which the event layer rejected as OUTSIDE_WORLDTUBE. The moving
* sphere fixture reproduces the Alcubierre worldtube (center = 2 t, radius 5);
* the observer time/position/tetrad and the camera direction are the exact raw
* production values. The route must land inside the geometric tolerance with
* the entry direction (Pi) unchanged. */
static void test_grazing_production_entries(void) {
SyntheticContext context = {.vx = 2.0,
.accel = 0.0,
.radius = 5.0,
.radius_rate = 0.0,
.valid_t_min = -1.0e30,
.constant = 1};
SpacetimeSource source = {.ops = &synthetic_ops, .context = &context};
struct {
double time;
double direction[3];
double pi[3];
} cases[2] = {
{0x1.fa8f5c28f5c29p+3,
{0x1.c7378f8e872d1p-1, 0x1.15bad4e30e8ddp-8, -0x1.d4afba4704cap-2},
{0x1.d4afba4704cap-2, -0x1.c7378f8e872d1p-1, -0x1.15bad4e30e8ddp-8}},
{0x1.fb17e4b17e4b1p+3,
{0x1.bd3bb364ac492p-1, 0x1.102d2a1c6ac74p-7, -0x1.f98ae1a782104p-2},
{0x1.f98ae1a782104p-2, -0x1.bd3bb364ac492p-1, -0x1.102d2a1c6ac74p-7}},
};
for (int c = 0; c < 2; ++c) {
ObserverState observer = track_observer(cases[c].time);
AsymptoticRoute route;
CHECK(asymptotic_route_camera(&source, &observer, cases[c].direction,
&route) == ASYMPTOTIC_OK &&
route.kind == ASYMPTOTIC_ROUTE_ENTRY,
"grazing production entry found");
CHECK(route.entry_fallback_evaluations == 0,
"grazing production entry stays on the fast path");
for (int i = 0; i < 3; ++i)
CHECK(route.Pi[i] == cases[c].pi[i],
"grazing entry direction is unchanged");
SpacetimeEscapeWorldtubeSample sample;
CHECK(spacetime_escape_worldtube_sample(&source, route.end_id,
route.activate_t, &sample) == 0 &&
sample.valid && sample.radius > 0.0,
"grazing entry sample valid");
double d2 = 0.0;
for (int k = 0; k < 3; ++k) {
const double dk = route.x[k] - sample.center[k];
d2 += dk * dk;
}
const double r2 = sample.radius * sample.radius;
double value;
CHECK(asymptotic_worldtube_value(&source, route.end_id, route.activate_t,
route.x, &value) == 0,
"grazing entry worldtube value");
const double geom_tol = 128.0 * DBL_EPSILON * fmax(r2, d2);
/* The event layer rejects the entry (OUTSIDE_WORLDTUBE) exactly when
* F > geom_tol; require the residual to sit inside the tolerance band
* rather than accepting an arbitrary sign. */
CHECK(value <= geom_tol && value >= -geom_tol,
"grazing entry F within geometric tolerance");
}
}
/* Linear (a == 0) entry: a growing sphere whose radius rate cancels the
* relative closing speed, so qq == rr^2 and the quadratic degenerates. The
* stable solver must still take the smallest positive root. The fixture's
* sample() reports the consistent radius R(t) = 10 - t, i.e. R(s) = 10 + s
* along the past parameter s = -t, so the contact point is on the true
* ruled-surface boundary. */
static void test_linear_a_zero_entry(void) {
SyntheticContext context = {.vx = 0.0,
.accel = 0.0,
.radius = 10.0,
.radius_rate = -1.0, /* R(t) = 10 - t */
.valid_t_min = -1.0e30,
.constant = 1};
SpacetimeSource source = {.ops = &synthetic_ops, .context = &context};
const ObserverState camera = flat_observer(100.0, 0.0, 0.0);
AsymptoticRoute route;
CHECK(asymptotic_route_camera(&source, &camera, (double[]){-1.0, 0.0, 0.0},
&route) == ASYMPTOTIC_OK &&
route.kind == ASYMPTOTIC_ROUTE_ENTRY,
"linear a~0 entry");
CHECK(fabs(route.activate_t + 45.0) < 1e-12, "linear entry time");
CHECK(fabs(route.x[0] - 55.0) < 1e-12 && fabs(route.x[1]) < 1e-12 &&
fabs(route.x[2]) < 1e-12,
"linear entry position");
}
/* Large-coordinate-time cancellation: the long-double closed-form root is
* accurate in the frame, but the entry state reconstructed in double at a huge
* t0 loses the sub-ULP part of the event and lands far outside the geometric
* ULP band (F ~ 0.3 >> tol). The common fallback must repropagate from the
* original camera, localize the first entry numerically, and return a
* strict-inside endpoint (F < 0) while preserving the camera direction Pi and
* reference L exactly. The fixture is a legitimate constant-velocity
* worldtube, not a nonlinearity injection. */
static void test_fallback_reconstruction_cancellation(void) {
SyntheticContext context = {.vx = 0x1.999999999999ap-4, /* 0.1 */
.accel = 0.0,
.radius = 10.0,
.radius_rate = 0.0,
.valid_t_min = -1.0e30,
.constant = 1};
SpacetimeSource source = {.ops = &synthetic_ops, .context = &context};
const double t0 = 1.0e15;
/* Exactly 0.1 * 1e15 + 100.123456789, pinned as a hex literal. The offset
* is not aligned to the t0 ULP, so activate_t = t0 - s rounds and the
* reconstructed boundary residual exceeds the tolerance band. */
const double camera_x = 0x1.6bcc41e901908p+46;
ObserverState observer = flat_observer(camera_x, 0.0, 0.0);
observer.coordinate_time = t0;
const double direction[3] = {-1.0, 0.0, 0.0};
AsymptoticRoute route;
CHECK(asymptotic_route_camera(&source, &observer, direction, &route) ==
ASYMPTOTIC_OK &&
route.kind == ASYMPTOTIC_ROUTE_ENTRY,
"cancellation fallback still produces an entry");
CHECK(route.entry_fallback_evaluations > 0,
"cancellation entry used the common fallback");
CHECK(route.failure_reason == RAY_REASON_NONE,
"successful fallback has no failure reason");
double value;
CHECK(asymptotic_worldtube_value(&source, route.end_id, route.activate_t,
route.x, &value) == 0 &&
value <= 0.0,
"fallback entry state is inside the worldtube");
MetricData metric;
GeodesicRayState camera_state;
CHECK(spacetime_eval(&source, t0, observer.coordinate_position, &metric) ==
0 &&
geodesic_initialize_past_ray_metric(&metric, &observer, direction,
&camera_state) == 0,
"cancellation camera state");
for (int i = 0; i < 3; ++i)
CHECK(route.Pi[i] == camera_state.Pi[i],
"fallback preserves the entry direction exactly");
CHECK(route.log_alpha_p0_camera == camera_state.log_alpha_p0,
"fallback preserves the camera reference L exactly");
}
static void test_accelerated_worldtube_unsupported(void) {
/* A genuinely accelerating (non-constant velocity) worldtube has no strict
* relative-motion interval bound, so the route is explicitly unsupported.
@@ -789,11 +957,98 @@ static void test_ray_pool_lifecycle(void) {
spacetime_destroy(&source);
}
static void test_zero_discriminant_is_not_miss(void) {
SpacetimeSource source;
CHECK(spacetime_create_minkowski(&source, 1.0) == 0,
"zero-discriminant source");
const ObserverState observer = flat_observer(1e10, 0.5, 0.0);
AsymptoticRoute route;
/* Forming c = 1e20 + 0.25 - 1 loses the transverse contribution even in
* 80-bit arithmetic; b*b - 4*a*c then rounds to zero despite a real entry. */
CHECK(asymptotic_route_camera(&source, &observer,
(double[]){-1.0, 0.0, 0.0}, &route) ==
ASYMPTOTIC_OK && route.kind == ASYMPTOTIC_ROUTE_ENTRY &&
route.entry_fallback_evaluations > 0,
"rounded zero discriminant uses entry fallback, not escape");
double value = 0.0;
CHECK(asymptotic_worldtube_value(&source, 0, route.activate_t, route.x,
&value) == ASYMPTOTIC_OK && value < 0.0,
"zero-discriminant fallback produces actual inside state");
const ObserverState tangent = flat_observer(1e10, 1.0, 0.0);
CHECK(asymptotic_route_camera(&source, &tangent,
(double[]){-1.0, 0.0, 0.0}, &route) ==
ASYMPTOTIC_OK && route.kind == ASYMPTOTIC_ROUTE_ESCAPED,
"fixed transverse coordinate independently certifies exact tangency");
spacetime_destroy(&source);
}
static void test_positive_reconstructed_minimum_is_not_miss(void) {
SyntheticContext ctx = {.vx = 0.1, .radius = 10.0, .constant = 1};
SpacetimeSource source = {.ops = &synthetic_ops, .context = &ctx};
ObserverState observer = flat_observer(0x1.6bcc41e901904p+46,
0x1.3ffffde7210bfp+3, 0.0);
observer.coordinate_time = 1e15;
const double parameter = 0x1.bca8814065f1ep+6;
const double witness[3] = {observer.coordinate_position[0] - parameter,
observer.coordinate_position[1], 0.0};
double value = 0.0;
CHECK(asymptotic_worldtube_value(&source, 0,
observer.coordinate_time - parameter,
witness, &value) == ASYMPTOTIC_OK && value < 0,
"strict-inside witness exists despite positive reconstructed minimum");
AsymptoticRoute route;
CHECK(asymptotic_route_camera(&source, &observer,
(double[]){-1.0, 0.0, 0.0}, &route) ==
ASYMPTOTIC_INVALID &&
route.failure_reason == RAY_REASON_ENTRY_UNCONFIRMED,
"positive probe without a miss certificate fails explicitly");
RayPool pool;
CHECK(ray_pool_init(&pool, 1) == 0, "ambiguous entry pool");
CHECK(ray_pool_append(&pool, &observer, (double[]){-1.0, 0.0, 0.0},
0, 0) == 0, "ambiguous entry ray");
ray_pool_preroute(&pool, &source);
CHECK(pool.status[0] == RAY_POOL_FAILED &&
pool.endpoint[0].outcome == RAY_OUTCOME_INCOMPLETE &&
pool.endpoint[0].reason == RAY_REASON_ENTRY_UNCONFIRMED,
"pool propagates unconfirmed entry instead of fabricating escape");
ray_pool_destroy(&pool);
}
static void test_translated_frame_cannot_certify_miss(void) {
SyntheticContext ctx = {.radius = 1.0, .constant = 1,
.valid_t_min = -1e100,
.frame_origin = {0.0, 1e10, 0.0}};
SpacetimeSource source = {.ops = &synthetic_ops, .context = &ctx};
const ObserverState observer = flat_observer(1e10, 1.0 - 0x1p-22, 0.0);
double value = 0.0;
const double witness[3] = {0.0, observer.coordinate_position[1], 0.0};
CHECK(asymptotic_worldtube_value(&source, 0, -1e10, witness, &value) ==
ASYMPTOTIC_OK && value < 0.0,
"original untranslated trajectory has an inside witness");
AsymptoticRoute route;
CHECK(asymptotic_route_camera(&source, &observer,
(double[]){-1.0, 0.0, 0.0}, &route) ==
ASYMPTOTIC_INVALID &&
route.failure_reason == RAY_REASON_ENTRY_UNCONFIRMED,
"lossy translated frame must not certify a miss");
ctx.frame_origin[1] = 0.0;
CHECK(asymptotic_route_camera(&source, &observer,
(double[]){-1.0, 0.0, 0.0}, &route) ==
ASYMPTOTIC_OK && route.kind == ASYMPTOTIC_ROUTE_ENTRY,
"same unshifted trajectory confirms an entry");
}
int main(void) {
test_fixed_sphere();
test_large_radius_quadratic();
test_round_trip();
test_moving_sphere();
test_grazing_production_entries();
test_linear_a_zero_entry();
test_fallback_reconstruction_cancellation();
test_zero_discriminant_is_not_miss();
test_positive_reconstructed_minimum_is_not_miss();
test_translated_frame_cannot_certify_miss();
test_accelerated_worldtube_unsupported();
test_piecewise_segment_entry();
test_boundary_semantics_minkowski();
+781
View File
@@ -0,0 +1,781 @@
#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;
}
+65
View File
@@ -0,0 +1,65 @@
/* Exercise the private numerical kernel directly, including coefficient
* ranges that cannot be represented by a public double worldtube fixture.
* The build rule omits the separately compiled asymptotic.c. */
#include "../src/asymptotic.c"
#include <stdio.h>
static int failures;
#define CHECK(condition, message) do { \
if (!(condition)) { \
fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, message); \
++failures; \
} \
} while (0)
static void check_scaled(int exponent) {
const long double scale = scalbnl(1.0L, exponent);
double root = -1.0;
EntryQuadratic k = {scale, -3.0L * scale, 2.0L * scale};
CHECK(entry_solve(&k, &root) == ENTRY_SOLVE_ENTRY && root == 1.0,
"common scale preserves smallest inward root");
k = (EntryQuadratic){scale, -scale, scale};
CHECK(entry_solve(&k, &root) == ENTRY_SOLVE_MISS,
"common scale preserves a clear miss");
k = (EntryQuadratic){0.0L, -scale, 2.0L * scale};
CHECK(entry_solve(&k, &root) == ENTRY_SOLVE_ENTRY && root == 2.0,
"common scale preserves linear entry");
k = (EntryQuadratic){scale, -scale, 0.0L};
CHECK(entry_solve(&k, &root) == ENTRY_SOLVE_ENTRY && root == 0.0,
"common scale preserves boundary entry");
k = (EntryQuadratic){-scale, scale, 2.0L * scale};
CHECK(entry_solve(&k, &root) == ENTRY_SOLVE_ENTRY && root == 2.0,
"common scale preserves concave entry");
}
static void test_product_cancellation(void) {
const long double u = scalbnl(1.0L, 1 - LDBL_MANT_DIG);
const EntryQuadratic k = {1.0L + u, -2.0L, 1.0L - 0.5L * u};
long double scale;
(void)entry_discriminant(&k, &scale);
/* Exact dyadic oracle: 4 - 4(1+u)(1-u/2) = -2u + 2u^2.
* A separately rounded 4*a*c is 4 and loses this nonzero discriminant. */
double root;
CHECK(entry_solve(&k, &root) == ENTRY_SOLVE_UNCERTAIN,
"product cancellation must remain uncertain, not a proven miss");
}
int main(void) {
check_scaled(0);
check_scaled(LDBL_MAX_EXP - 4);
check_scaled(LDBL_MIN_EXP + 4);
check_scaled(LDBL_MIN_EXP - LDBL_MANT_DIG + 2);
test_product_cancellation();
double root;
EntryQuadratic k = {LDBL_MIN, LDBL_MAX / 8.0L, 1.0L};
CHECK(entry_solve(&k, &root) == ENTRY_SOLVE_UNCERTAIN,
"scaling cannot silently erase a nonzero coefficient");
k = (EntryQuadratic){1.0L, 2.0L, -INFINITY};
CHECK(entry_solve(&k, &root) == ENTRY_SOLVE_UNCERTAIN,
"nonfinite coefficient is not a normal entry");
if (!failures)
puts("asymptotic quadratic regression passed");
return failures ? 1 : 0;
}
+120
View File
@@ -6,6 +6,7 @@
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
static int failures = 0;
#define CHECK(condition, message) \
@@ -189,6 +190,8 @@ static void test_preroute_entry(void) {
ASYMPTOTIC_OK &&
route.kind == ASYMPTOTIC_ROUTE_ENTRY,
"outside camera enters");
CHECK(route.entry_fallback_evaluations == 0,
"analytic entry stays on the fast path");
double value;
CHECK(asymptotic_worldtube_value(&source, route.end_id, route.activate_t,
route.x, &value) == 0 &&
@@ -521,6 +524,122 @@ static void test_preroute_branches(void) {
spacetime_destroy(&source);
}
/* Translated-origin wrapper around the analytic Schwarzschild KS source: the
* inner metric is evaluated at x - origin and the worldtube/end are shifted by
* the same origin. This models a black hole at a large coordinate offset; the
* double reconstruction of the boundary state loses the sub-ULP offset and
* trips the common entry fallback, while the exact inward orbit transfer stays
* valid. It exercises the production fallback path, not a synthetic
* nonlinearity. */
typedef struct {
SpacetimeSource inner;
double origin[3];
} ShiftedOriginContext;
static SpacetimePointStatus shifted_origin_eval(const SpacetimeSource *source,
double t, const double x[3],
MetricData *metric) {
const ShiftedOriginContext *ctx = source->context;
const double local[3] = {x[0] - ctx->origin[0], x[1] - ctx->origin[1],
x[2] - ctx->origin[2]};
return spacetime_eval(&ctx->inner, t, local, metric);
}
static SpacetimeRayStatus shifted_origin_classify(const SpacetimeSource *source,
double t,
const double x[3]) {
const ShiftedOriginContext *ctx = source->context;
const double local[3] = {x[0] - ctx->origin[0], x[1] - ctx->origin[1],
x[2] - ctx->origin[2]};
return spacetime_classify(&ctx->inner, t, local);
}
static size_t shifted_origin_end_count(const SpacetimeSource *source) {
const ShiftedOriginContext *ctx = source->context;
return spacetime_asymptotic_end_count(&ctx->inner);
}
static int shifted_origin_end(const SpacetimeSource *source, size_t index,
SpacetimeAsymptoticEnd *out) {
const ShiftedOriginContext *ctx = source->context;
if (spacetime_asymptotic_end(&ctx->inner, index, out))
return -1;
for (int i = 0; i < 3; ++i)
out->frame_origin[i] = ctx->origin[i];
return 0;
}
static int shifted_origin_worldtube(const SpacetimeSource *source,
SpacetimeEndId end_id, double t,
SpacetimeEscapeWorldtubeSample *out) {
const ShiftedOriginContext *ctx = source->context;
if (spacetime_escape_worldtube_sample(&ctx->inner, end_id, t, out))
return -1;
for (int i = 0; i < 3; ++i)
out->center[i] += ctx->origin[i];
return 0;
}
static void shifted_origin_destroy(SpacetimeSource *source) {
ShiftedOriginContext *ctx = source->context;
if (ctx != NULL) {
spacetime_destroy(&ctx->inner);
free(ctx);
}
source->context = NULL;
source->ops = NULL;
}
static const SpacetimeOps shifted_origin_ops = {
.eval = shifted_origin_eval,
.classify = shifted_origin_classify,
.asymptotic_end_count = shifted_origin_end_count,
.asymptotic_end = shifted_origin_end,
.escape_worldtube_sample = shifted_origin_worldtube,
.destroy = shifted_origin_destroy};
static void test_translated_origin_fallback(void) {
ShiftedOriginContext *ctx = malloc(sizeof *ctx);
CHECK(ctx != NULL, "shifted-origin context");
if (ctx == NULL)
return;
ctx->origin[0] = 1.0e6;
ctx->origin[1] = 2.0e6;
ctx->origin[2] = -3.0e6;
CHECK(spacetime_create_schwarzschild_ks(&ctx->inner, 1.0, 256.0) == 0,
"shifted-origin inner source");
SpacetimeSource source = {.ops = &shifted_origin_ops, .context = ctx};
ObserverCamera cam = {.look_ra_deg = 180.0, .look_dec_deg = 0.0};
for (int i = 0; i < 3; ++i)
cam.position[i] = ctx->origin[i];
cam.position[0] += 500.0;
MetricData metric;
CHECK(spacetime_eval(&source, 0.0, cam.position, &metric) == 0,
"shifted-origin camera metric");
ObserverState observer;
CHECK(observer_from_coordinate_camera(&metric, &cam, &observer, NULL) ==
OBSERVER_BUILD_OK,
"shifted-origin camera observer");
const double direction[3] = {cos(0.3), sin(0.3), 0.0};
AsymptoticRoute route;
CHECK(asymptotic_route_camera(&source, &observer, direction, &route) ==
ASYMPTOTIC_OK &&
route.kind == ASYMPTOTIC_ROUTE_ENTRY,
"shifted-origin entry found");
CHECK(route.entry_fallback_evaluations > 0,
"shifted-origin entry used the common fallback");
CHECK(route.failure_reason == RAY_REASON_NONE,
"shifted-origin fallback has no failure reason");
double value;
CHECK(asymptotic_worldtube_value(&source, route.end_id, route.activate_t,
route.x, &value) == 0 &&
value <= 0.0,
"shifted-origin fallback state is inside the worldtube");
CHECK(route.activate_t < 0.0, "shifted-origin entry is in the past");
spacetime_destroy(&source);
}
int main(void) {
test_round_trip();
test_finish_matches_integration();
@@ -532,6 +651,7 @@ int main(void) {
test_time_reference();
test_grazing_reference();
test_preroute_branches();
test_translated_origin_fallback();
if (failures == 0)
puts("asymptotic schwarzschild regression passed");
else
+2
View File
@@ -53,6 +53,8 @@ static int check_reason_names(void) {
RAY_REASON_INTEGRATION_ERROR ||
ray_reason_category(RAY_REASON_INVALID_ESCAPE_DIRECTION) !=
RAY_REASON_INTEGRATION_ERROR ||
ray_reason_category(RAY_REASON_ENTRY_UNCONFIRMED) !=
RAY_REASON_INTEGRATION_ERROR ||
ray_reason_category(RAY_REASON_SLAB_LOAD_FAILED) != RAY_REASON_IO_ERROR) {
fputs("detail reasons map to the wrong coarse category\n", stderr);
failed = 1;