Feat: Complete adaptive geodesic tracing with DP54
Add error-controlled DP5(4) integration and trusted first-crossing localization, including non-monotonic energy thresholds and representable-time stepping. Preserve adaptive state and independent step/time retry grants across RayPool, refinement and movie scheduling. Expose numerical controls, record actual persistent-sample costs, and add v3 lens-map provenance with legacy v2 RK4 import. Use DP54 by default and select an 8M Schwarzschild maximum step from bounded scans and a two-run 4K comparison. Retain the conservative minimum-step guard and document critical-ray and backend capability limits. Archive self-contained benchmark inputs and raw output; keep fixed RK4 HDR references explicit. Validation: make -B -j4 BUILD_TYPE=Debug test passed; explicit RK4 HDR references have zero differences. Bounded convergence checks, benchmark reproduction, Release build and focused reviews passed. No numerical-relativity backend is added.
This commit is contained in:
1 parent
c894fdb11a
commit
0a46a7095b
48 files changed
+11491
-380
No files matched your search
+644
-2
@@ -9,6 +9,107 @@
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/* Self-contained legacy v2 lens-map fixture writer. The production writer now
|
||||
* emits v3, so this serializes a real little-endian v2 map (v2 provenance, no
|
||||
* per-vertex cost counters) from a live mesh to keep the import path covered by
|
||||
* genuine bytes instead of a hand-maintained golden blob. */
|
||||
static uint32_t v2_crc32(uint32_t crc, const void *data, size_t size) {
|
||||
const unsigned char *bytes = data;
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
crc ^= bytes[i];
|
||||
for (int bit = 0; bit < 8; ++bit)
|
||||
crc = (crc >> 1) ^ (0xedb88320u & (uint32_t)-(int)(crc & 1));
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
static int v2_fwrite_u32(FILE *f, uint32_t v) {
|
||||
unsigned char b[4] = {(unsigned char)v, (unsigned char)(v >> 8),
|
||||
(unsigned char)(v >> 16), (unsigned char)(v >> 24)};
|
||||
return fwrite(b, 1, sizeof b, f) == sizeof b ? 0 : -1;
|
||||
}
|
||||
static int v2_fwrite_u64(FILE *f, uint64_t v) {
|
||||
unsigned char b[8];
|
||||
for (int i = 0; i < 8; ++i) b[i] = (unsigned char)(v >> (8 * i));
|
||||
return fwrite(b, 1, sizeof b, f) == sizeof b ? 0 : -1;
|
||||
}
|
||||
static int v2_fwrite_double(FILE *f, double v) {
|
||||
uint64_t bits; memcpy(&bits, &v, sizeof bits);
|
||||
return v2_fwrite_u64(f, bits);
|
||||
}
|
||||
static int write_v2_lens_map(const char *path, int width, int height, double fov,
|
||||
const LensMapProvenance *p,
|
||||
const LensMapFrame *frame) {
|
||||
static const unsigned char magic[8] = {'G', 'R', 'L', 'E', 'N', 'S', 1, 0};
|
||||
FILE *f = fopen(path, "wb");
|
||||
if (f == NULL) return -1;
|
||||
int failed = fwrite(magic, 1, sizeof magic, f) != sizeof magic ||
|
||||
v2_fwrite_u32(f, 2) || v2_fwrite_u32(f, 0x01020304u) ||
|
||||
v2_fwrite_u32(f, (uint32_t)width) || v2_fwrite_u32(f, (uint32_t)height) ||
|
||||
v2_fwrite_double(f, fov) || v2_fwrite_u64(f, 1) ||
|
||||
v2_fwrite_u32(f, p->threshold_kind) ||
|
||||
v2_fwrite_u32(f, p->threshold_policy_version) ||
|
||||
v2_fwrite_double(f, p->threshold_value) ||
|
||||
v2_fwrite_u32(f, p->retry_step_increment) ||
|
||||
v2_fwrite_u32(f, p->max_total_steps) || v2_fwrite_u32(f, p->max_level) ||
|
||||
v2_fwrite_u32(f, p->integrator) ||
|
||||
v2_fwrite_double(f, p->min_edge_pixels) ||
|
||||
v2_fwrite_double(f, p->min_area_pixels2) ||
|
||||
v2_fwrite_double(f, p->coordinate_time_step) ||
|
||||
v2_fwrite_u32(f, p->initial_max_steps);
|
||||
const FrameLensMesh *m = &frame->mesh;
|
||||
failed = failed || v2_fwrite_u64(f, frame->frame_id) ||
|
||||
v2_fwrite_double(f, frame->coordinate_time) ||
|
||||
v2_fwrite_double(f, frame->proper_time) ||
|
||||
v2_fwrite_u64(f, (uint64_t)m->vertex_count) ||
|
||||
v2_fwrite_u64(f, (uint64_t)m->triangle_count) ||
|
||||
v2_fwrite_u64(f, (uint64_t)m->retry_requests);
|
||||
/* The payload CRC covers vertices+triangles only; the header stays
|
||||
* deliberately independent, exactly as in the production writers. */
|
||||
uint32_t crc = UINT32_MAX;
|
||||
for (size_t i = 0; i < m->vertex_count; ++i) {
|
||||
const LensVertex *v = &m->vertices[i];
|
||||
const double vals[9] = {v->image_x, v->image_y, v->camera_direction[0],
|
||||
v->camera_direction[1], v->camera_direction[2],
|
||||
v->n_infinity[0], v->n_infinity[1], v->n_infinity[2],
|
||||
v->log_frequency_ratio};
|
||||
const uint32_t tail[3] = {(uint32_t)v->end_id, (uint32_t)v->outcome,
|
||||
(uint32_t)v->reason};
|
||||
unsigned char b[84]; size_t off = 0;
|
||||
for (int k = 0; k < 9; ++k) {
|
||||
uint64_t bits; memcpy(&bits, &vals[k], sizeof bits);
|
||||
for (int q = 0; q < 8; ++q) b[off++] = (unsigned char)(bits >> (8 * q));
|
||||
}
|
||||
for (int k = 0; k < 3; ++k) {
|
||||
b[off++] = (unsigned char)tail[k];
|
||||
b[off++] = (unsigned char)(tail[k] >> 8);
|
||||
b[off++] = (unsigned char)(tail[k] >> 16);
|
||||
b[off++] = (unsigned char)(tail[k] >> 24);
|
||||
}
|
||||
if (fwrite(b, 1, sizeof b, f) != sizeof b) failed = 1;
|
||||
crc = v2_crc32(crc, b, sizeof b);
|
||||
}
|
||||
for (size_t i = 0; i < m->triangle_count; ++i) {
|
||||
unsigned char b[32]; size_t off = 0;
|
||||
for (int j = 0; j < 3; ++j) {
|
||||
const uint64_t idx = (uint64_t)m->triangles[i].vertex[j];
|
||||
for (int q = 0; q < 8; ++q) b[off++] = (unsigned char)(idx >> (8 * q));
|
||||
}
|
||||
const uint32_t tail[2] = {m->triangles[i].level,
|
||||
(uint32_t)m->triangles[i].approx_black};
|
||||
for (int k = 0; k < 2; ++k) {
|
||||
b[off++] = (unsigned char)tail[k];
|
||||
b[off++] = (unsigned char)(tail[k] >> 8);
|
||||
b[off++] = (unsigned char)(tail[k] >> 16);
|
||||
b[off++] = (unsigned char)(tail[k] >> 24);
|
||||
}
|
||||
if (fwrite(b, 1, sizeof b, f) != sizeof b) failed = 1;
|
||||
crc = v2_crc32(crc, b, sizeof b);
|
||||
}
|
||||
if (v2_fwrite_u32(f, crc ^ UINT32_MAX)) failed = 1;
|
||||
if (fclose(f)) failed = 1;
|
||||
return failed ? -1 : 0;
|
||||
}
|
||||
|
||||
static int mesh_has_hanging_vertex(const FrameLensMesh *mesh) {
|
||||
for (size_t triangle = 0; triangle < mesh->triangle_count; ++triangle)
|
||||
for (size_t side = 0; side < 3; ++side) {
|
||||
@@ -139,6 +240,9 @@ static int review_probe_regressions(void) {
|
||||
m.vertices[i].n_infinity[0]=1;
|
||||
}
|
||||
m.vertices[0].outcome=RAY_OUTCOME_UNRESOLVED;
|
||||
/* A genuinely budget-exhausted vertex has consumed its accepted-step
|
||||
* quota; the retry layer keys on that blocking quota. */
|
||||
m.vertices[0].continuation_steps=10;
|
||||
m.vertices[0].continuation_limit=10;
|
||||
if (frame_lens_mesh_prepare_generation(&m,&c)<1 || m.retry_requests==0)
|
||||
return -1; /* the retry counter is produced by real retry requests */
|
||||
@@ -153,11 +257,408 @@ static int review_probe_regressions(void) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* A budget-exhausted vertex carrying the full adaptive resume state, so the
|
||||
* retry layer sees the same payload the production store_endpoint path writes. */
|
||||
static LensVertex unresolved_vertex(double image_x, double t, double start,
|
||||
unsigned int steps,
|
||||
unsigned int step_limit,
|
||||
double lookback_limit) {
|
||||
LensVertex v;
|
||||
memset(&v, 0, sizeof v);
|
||||
v.image_x = image_x;
|
||||
v.image_y = 0.0;
|
||||
v.camera_direction[0] = 1.0;
|
||||
v.outcome = RAY_OUTCOME_UNRESOLVED;
|
||||
v.traced = 1;
|
||||
v.continuation_t = t;
|
||||
v.continuation_x[0] = 1.0;
|
||||
v.continuation_Pi[0] = -1.0;
|
||||
v.continuation_log_alpha_p0 = 0.125;
|
||||
v.continuation_log_alpha_p0_0 = 0.5;
|
||||
v.continuation_steps = steps;
|
||||
v.continuation_limit = step_limit;
|
||||
v.continuation_integration_start_time = start;
|
||||
v.continuation_next_step = 0.25;
|
||||
v.continuation_rejected_steps = 2;
|
||||
v.continuation_rhs_evaluations = 20;
|
||||
v.continuation_previous_rejected = 0;
|
||||
v.continuation_lookback_limit = lookback_limit;
|
||||
return v;
|
||||
}
|
||||
|
||||
static int uuu_fixture(LensVertex vertices[3], LensTriangle *triangle) {
|
||||
for (int i = 0; i < 3; ++i)
|
||||
vertices[i].camera_direction[0] = 1.0;
|
||||
*triangle = (LensTriangle){{0, 1, 2}, 0, 0, 0};
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Independent step/time retry budgets: a request is allowed only while every
|
||||
* quota that actually blocked the ray can still grow, and the two quotas are
|
||||
* saturated separately. Also verifies the resume-state round trip used by
|
||||
* both the frame and movie paths. */
|
||||
static int review_retry_quota_regressions(void) {
|
||||
/* 1. Step-blocked, both quotas growable: the step quota advances, and the
|
||||
* request records the independently saturated time quota. */
|
||||
{
|
||||
LensVertex vertices[3] = {
|
||||
unresolved_vertex(0.0, -1.0, 0.0, 20, 20, 2.0),
|
||||
unresolved_vertex(10.0, -1.0, 0.0, 20, 20, 2.0),
|
||||
unresolved_vertex(0.0, -1.0, 0.0, 20, 20, 2.0)};
|
||||
LensTriangle triangle;
|
||||
uuu_fixture(vertices, &triangle);
|
||||
FrameLensMesh m = {.vertices = vertices, .vertex_count = 3,
|
||||
.triangles = &triangle, .triangle_count = 1};
|
||||
RefinementConfig c = {.max_level = 0,
|
||||
.retry_step_increment = 10,
|
||||
.max_total_steps = 100,
|
||||
.retry_lookback_increment = 1.0,
|
||||
.max_total_lookback_time = 10.0};
|
||||
if (frame_lens_mesh_prepare_generation(&m, &c) != 3) {
|
||||
free(m.samples); free(m.probe_slots);
|
||||
return -1;
|
||||
}
|
||||
for (size_t i = 0; i < m.sample_count; ++i)
|
||||
if (m.samples[i].kind != FRAME_SAMPLE_RETRY ||
|
||||
m.samples[i].step_limit != 30 ||
|
||||
m.samples[i].lookback_limit != 3.0) {
|
||||
free(m.samples); free(m.probe_slots);
|
||||
return -1;
|
||||
}
|
||||
free(m.samples); free(m.probe_slots);
|
||||
}
|
||||
/* 2. Step quota at its cap while time can still grow: no request, because
|
||||
* more time cannot buy an accepted step. The UUU triangle stays a
|
||||
* budget-incomplete boundary, never blackened. */
|
||||
{
|
||||
LensVertex vertices[3] = {
|
||||
unresolved_vertex(0.0, -1.0, 0.0, 20, 20, 2.0),
|
||||
unresolved_vertex(10.0, -1.0, 0.0, 20, 20, 2.0),
|
||||
unresolved_vertex(0.0, -1.0, 0.0, 20, 20, 2.0)};
|
||||
LensTriangle triangle;
|
||||
uuu_fixture(vertices, &triangle);
|
||||
FrameLensMesh m = {.vertices = vertices, .vertex_count = 3,
|
||||
.triangles = &triangle, .triangle_count = 1};
|
||||
RefinementConfig c = {.max_level = 0,
|
||||
.retry_step_increment = 10,
|
||||
.max_total_steps = 20,
|
||||
.retry_lookback_increment = 1.0,
|
||||
.max_total_lookback_time = 10.0};
|
||||
if (frame_lens_mesh_prepare_generation(&m, &c) != 0) {
|
||||
free(m.samples); free(m.probe_slots);
|
||||
return -1;
|
||||
}
|
||||
FrameBoundaryStats stats;
|
||||
frame_lens_mesh_boundary_stats(&m, &c, &stats);
|
||||
if (stats.uuu != 1 || stats.budget_incomplete_triangles == 0 ||
|
||||
stats.approx_black_triangles != 0) {
|
||||
free(m.samples); free(m.probe_slots);
|
||||
return -1;
|
||||
}
|
||||
free(m.samples); free(m.probe_slots);
|
||||
}
|
||||
/* 3. Time-blocked with a disabled step increment: the time quota alone
|
||||
* enables the retry and the step budget is left untouched. */
|
||||
{
|
||||
LensVertex vertices[3] = {
|
||||
unresolved_vertex(0.0, -3.0, 0.0, 5, 10, 2.0),
|
||||
unresolved_vertex(10.0, -3.0, 0.0, 5, 10, 2.0),
|
||||
unresolved_vertex(0.0, -3.0, 0.0, 5, 10, 2.0)};
|
||||
LensTriangle triangle;
|
||||
uuu_fixture(vertices, &triangle);
|
||||
FrameLensMesh m = {.vertices = vertices, .vertex_count = 3,
|
||||
.triangles = &triangle, .triangle_count = 1};
|
||||
RefinementConfig c = {.max_level = 0,
|
||||
.retry_step_increment = 0,
|
||||
.max_total_steps = 10,
|
||||
.retry_lookback_increment = 1.0,
|
||||
.max_total_lookback_time = 10.0};
|
||||
if (frame_lens_mesh_prepare_generation(&m, &c) != 3) {
|
||||
free(m.samples); free(m.probe_slots);
|
||||
return -1;
|
||||
}
|
||||
for (size_t i = 0; i < m.sample_count; ++i)
|
||||
if (m.samples[i].kind != FRAME_SAMPLE_RETRY ||
|
||||
m.samples[i].step_limit != 10 ||
|
||||
m.samples[i].lookback_limit != 3.0) {
|
||||
free(m.samples); free(m.probe_slots);
|
||||
return -1;
|
||||
}
|
||||
free(m.samples); free(m.probe_slots);
|
||||
}
|
||||
/* 4. Both blocking quotas at their caps: no request. */
|
||||
{
|
||||
LensVertex vertices[3] = {
|
||||
unresolved_vertex(0.0, -6.0, 0.0, 20, 20, 5.0),
|
||||
unresolved_vertex(10.0, -6.0, 0.0, 20, 20, 5.0),
|
||||
unresolved_vertex(0.0, -6.0, 0.0, 20, 20, 5.0)};
|
||||
LensTriangle triangle;
|
||||
uuu_fixture(vertices, &triangle);
|
||||
FrameLensMesh m = {.vertices = vertices, .vertex_count = 3,
|
||||
.triangles = &triangle, .triangle_count = 1};
|
||||
RefinementConfig c = {.max_level = 0,
|
||||
.retry_step_increment = 10,
|
||||
.max_total_steps = 20,
|
||||
.retry_lookback_increment = 1.0,
|
||||
.max_total_lookback_time = 5.0};
|
||||
if (frame_lens_mesh_prepare_generation(&m, &c) != 0) {
|
||||
free(m.samples); free(m.probe_slots);
|
||||
return -1;
|
||||
}
|
||||
free(m.samples); free(m.probe_slots);
|
||||
}
|
||||
/* 5. Resume-state round trip: the helper reproduces every control field, and
|
||||
* install back-fills the vertex with the endpoint's new state and quota. */
|
||||
{
|
||||
LensVertex source = unresolved_vertex(0.0, -1.0, 0.0, 20, 20, 2.0);
|
||||
GeodesicRayState state;
|
||||
if (frame_vertex_continuation_state(&source, &state) ||
|
||||
state.coordinate_time != -1.0 || state.steps != 20 ||
|
||||
state.integration_start_time != 0.0 || state.next_step != 0.25 ||
|
||||
state.rejected_steps != 2 || state.rhs_evaluations != 20 ||
|
||||
state.previous_rejected != 0 || state.log_alpha_p0 != 0.125 ||
|
||||
state.log_alpha_p0_0 != 0.5 || state.x[0] != 1.0 ||
|
||||
state.Pi[0] != -1.0)
|
||||
return -1;
|
||||
LensVertex target = {.image_x = 0.0, .camera_direction = {1.0, 0.0, 0.0}};
|
||||
FrameLensMesh mesh = {.vertices = &target, .vertex_count = 1};
|
||||
RefinementConfig plain = {.max_level = 0};
|
||||
if (frame_lens_mesh_prepare_generation(&mesh, &plain) != 1) {
|
||||
free(mesh.samples); free(mesh.probe_slots);
|
||||
return -1;
|
||||
}
|
||||
const RayEndpoint endpoint = {
|
||||
.magnification = 1.0,
|
||||
.end_id = SPACETIME_END_NONE,
|
||||
.outcome = RAY_OUTCOME_UNRESOLVED,
|
||||
.reason = RAY_REASON_BUDGET_EXHAUSTED,
|
||||
.stop_coordinate_time = -4.0,
|
||||
.accepted_steps = 7,
|
||||
.final_x = {5.0, 0.0, 0.0},
|
||||
.final_Pi = {-1.0, 0.0, 0.0},
|
||||
.final_log_alpha_p0 = 0.125,
|
||||
.final_log_alpha_p0_0 = 0.5,
|
||||
.threshold_value = NAN,
|
||||
.integration_start_time = 0.0,
|
||||
.next_step = 0.5,
|
||||
.rejected_steps = 3,
|
||||
.rhs_evaluations = 70,
|
||||
.previous_rejected = 1,
|
||||
.lookback_limit = 2.5};
|
||||
if (frame_lens_mesh_install_sample(&mesh, 0, &endpoint) ||
|
||||
target.outcome != RAY_OUTCOME_UNRESOLVED ||
|
||||
target.continuation_t != -4.0 || target.continuation_steps != 7 ||
|
||||
target.continuation_limit != 7 ||
|
||||
target.continuation_integration_start_time != 0.0 ||
|
||||
target.continuation_next_step != 0.5 ||
|
||||
target.continuation_rejected_steps != 3 ||
|
||||
target.continuation_rhs_evaluations != 70 ||
|
||||
target.continuation_previous_rejected != 1 ||
|
||||
target.continuation_lookback_limit != 2.5) {
|
||||
free(mesh.samples); free(mesh.probe_slots);
|
||||
return -1;
|
||||
}
|
||||
GeodesicRayState rebuilt;
|
||||
if (frame_vertex_continuation_state(&target, &rebuilt) ||
|
||||
rebuilt.coordinate_time != -4.0 || rebuilt.steps != 7 ||
|
||||
rebuilt.next_step != 0.5 || rebuilt.rejected_steps != 3 ||
|
||||
rebuilt.rhs_evaluations != 70 || rebuilt.previous_rejected != 1) {
|
||||
free(mesh.samples); free(mesh.probe_slots);
|
||||
return -1;
|
||||
}
|
||||
free(mesh.samples); free(mesh.probe_slots);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static GeodesicTraceConfig frame_dp_config(double lookback) {
|
||||
GeodesicTraceConfig c;
|
||||
memset(&c, 0, sizeof c);
|
||||
c.stepper = GEODESIC_STEPPER_DP54;
|
||||
c.coordinate_time_step = 0.5;
|
||||
c.max_steps = 100;
|
||||
c.threshold = (ThresholdPolicy){.kind = THRESHOLD_DISABLED, .value = 0.0,
|
||||
.policy_version = 0};
|
||||
c.atol_x = c.atol_Pi = c.atol_L = c.rtol = 1e-9;
|
||||
c.min_step = 1e-12;
|
||||
c.max_step = 1e6;
|
||||
c.consecutive_rejection_limit = 1000;
|
||||
c.max_lookback_time = lookback;
|
||||
return c;
|
||||
}
|
||||
|
||||
/* A real production trace must record the configured step/time grant distinct
|
||||
* from the spent counts, so a time-limited ray can retry with extra time while
|
||||
* keeping its full step grant. A retry candidate also counts only when it
|
||||
* opens a strictly larger representable region. */
|
||||
static int review_real_grant_and_time_growth(void) {
|
||||
SpacetimeSource source = {0};
|
||||
if (spacetime_create_minkowski(&source, 10.0))
|
||||
return -1;
|
||||
const ObserverState observer = observer_fixed_at_origin();
|
||||
int failed = 0;
|
||||
|
||||
/* A. Real trace at the origin, time-limited after ~2 of 100 granted steps.
|
||||
* With the step increment disabled and the time increment enabled, the
|
||||
* retry must keep step_limit == 100 and only grow the time budget. */
|
||||
{
|
||||
LensVertex vertices[3] = {{.camera_direction = {1.0, 0.0, 0.0}},
|
||||
{.camera_direction = {1.0, 0.0, 0.0}},
|
||||
{.camera_direction = {1.0, 0.0, 0.0}}};
|
||||
LensTriangle triangle = {{0, 1, 2}, 0, 0, 0};
|
||||
FrameLensMesh m = {.vertices = vertices, .vertex_count = 3,
|
||||
.triangles = &triangle, .triangle_count = 1};
|
||||
const GeodesicTraceConfig trace = frame_dp_config(1.0);
|
||||
if (frame_lens_mesh_trace(&m, &source, &observer, &trace))
|
||||
failed = 1;
|
||||
for (size_t i = 0; i < 3 && !failed; ++i) {
|
||||
if (vertices[i].outcome != RAY_OUTCOME_UNRESOLVED ||
|
||||
vertices[i].continuation_limit != 100u ||
|
||||
vertices[i].continuation_steps >= 100u ||
|
||||
vertices[i].continuation_lookback_limit != 1.0)
|
||||
failed = 1;
|
||||
}
|
||||
const RefinementConfig retry = {.max_level = 0,
|
||||
.retry_step_increment = 0,
|
||||
.max_total_steps = 100,
|
||||
.retry_lookback_increment = 1.0,
|
||||
.max_total_lookback_time = 100.0};
|
||||
if (!failed && frame_lens_mesh_prepare_generation(&m, &retry) != 3)
|
||||
failed = 1;
|
||||
for (size_t i = 0; i < m.sample_count && !failed; ++i)
|
||||
if (m.samples[i].kind != FRAME_SAMPLE_RETRY ||
|
||||
m.samples[i].step_limit != 100u ||
|
||||
m.samples[i].lookback_limit != 2.0)
|
||||
failed = 1;
|
||||
free(m.samples);
|
||||
free(m.probe_slots);
|
||||
}
|
||||
|
||||
/* B. Translated time origins (positive, negative and zero) still detect the
|
||||
* lookback boundary with the integrator's own comparison and retry with a
|
||||
* strictly larger time region while preserving the step grant. */
|
||||
for (int origin = 0; origin < 3 && !failed; ++origin) {
|
||||
const double t0 = origin == 0 ? 0.0 : origin == 1 ? 1.0e9 : -1.0e9;
|
||||
ObserverState o = observer_fixed_at_origin();
|
||||
o.coordinate_time = t0;
|
||||
const GeodesicTraceConfig trace = frame_dp_config(0.3);
|
||||
MetricData metric;
|
||||
if (spacetime_eval(&source, t0, o.coordinate_position, &metric)) {
|
||||
failed = 1;
|
||||
break;
|
||||
}
|
||||
GeodesicRayState state;
|
||||
if (geodesic_initialize_past_ray_metric(&metric, &o, (double[]){1, 0, 0},
|
||||
&state)) {
|
||||
failed = 1;
|
||||
break;
|
||||
}
|
||||
const RayEndpoint endpoint =
|
||||
geodesic_trace_past_from_state(&source, &state, &trace);
|
||||
if (endpoint.outcome != RAY_OUTCOME_UNRESOLVED ||
|
||||
endpoint.accepted_step_limit != 100u || endpoint.accepted_steps != 1u) {
|
||||
failed = 1;
|
||||
break;
|
||||
}
|
||||
LensVertex vertices[3];
|
||||
memset(vertices, 0, sizeof vertices);
|
||||
vertices[1].traced = vertices[2].traced = 1;
|
||||
vertices[1].outcome = vertices[2].outcome = RAY_OUTCOME_ESCAPED;
|
||||
vertices[1].n_infinity[0] = vertices[2].n_infinity[0] = 1.0;
|
||||
vertices[1].end_id = vertices[2].end_id = 0;
|
||||
LensTriangle triangle = {{0, 1, 2}, 0, 0, 0};
|
||||
FrameLensMesh m = {.vertices = vertices, .vertex_count = 3,
|
||||
.triangles = &triangle, .triangle_count = 1};
|
||||
const RefinementConfig plain = {.max_level = 0};
|
||||
if (frame_lens_mesh_prepare_generation(&m, &plain) != 1 ||
|
||||
frame_lens_mesh_install_sample(&m, 0, &endpoint) ||
|
||||
vertices[0].continuation_limit != 100u ||
|
||||
vertices[0].continuation_lookback_limit != 0.3) {
|
||||
free(m.samples);
|
||||
free(m.probe_slots);
|
||||
failed = 1;
|
||||
break;
|
||||
}
|
||||
free(m.samples);
|
||||
m.samples = NULL;
|
||||
m.sample_count = m.sample_capacity = 0;
|
||||
const RefinementConfig retry = {.max_level = 0,
|
||||
.retry_step_increment = 0,
|
||||
.max_total_steps = 100,
|
||||
.retry_lookback_increment = 0.3,
|
||||
.max_total_lookback_time = 10.0};
|
||||
if (frame_lens_mesh_prepare_generation(&m, &retry) != 1 ||
|
||||
m.samples[0].kind != FRAME_SAMPLE_RETRY ||
|
||||
m.samples[0].step_limit != 100u ||
|
||||
m.samples[0].lookback_limit != 0.6)
|
||||
failed = 1;
|
||||
free(m.samples);
|
||||
free(m.probe_slots);
|
||||
}
|
||||
|
||||
/* C. A larger quota that does not move the left boundary (increment rounds
|
||||
* away, or a large time origin absorbs it) must not launch a retry. */
|
||||
{
|
||||
LensVertex vertices[3] = {
|
||||
unresolved_vertex(0.0, -1.0e9, 0.0, 1, 100, 1.0e9),
|
||||
unresolved_vertex(10.0, -1.0e9, 0.0, 1, 100, 1.0e9),
|
||||
unresolved_vertex(0.0, -1.0e9, 0.0, 1, 100, 1.0e9)};
|
||||
LensTriangle triangle;
|
||||
uuu_fixture(vertices, &triangle);
|
||||
FrameLensMesh m = {.vertices = vertices, .vertex_count = 3,
|
||||
.triangles = &triangle, .triangle_count = 1};
|
||||
const RefinementConfig rounded = {.max_level = 0,
|
||||
.retry_step_increment = 0,
|
||||
.max_total_steps = 100,
|
||||
.retry_lookback_increment = 1.0e-8,
|
||||
.max_total_lookback_time = 2.0e9};
|
||||
if (frame_lens_mesh_prepare_generation(&m, &rounded) != 0)
|
||||
failed = 1;
|
||||
FrameBoundaryStats stats;
|
||||
frame_lens_mesh_boundary_stats(&m, &rounded, &stats);
|
||||
if (stats.uuu != 1 || stats.budget_incomplete_triangles == 0)
|
||||
failed = 1;
|
||||
free(m.samples);
|
||||
free(m.probe_slots);
|
||||
}
|
||||
{
|
||||
const double start = 1.0e12;
|
||||
const double quota = 1.0e6;
|
||||
LensVertex vertices[3] = {
|
||||
unresolved_vertex(0.0, start - quota, start, 1, 100, quota),
|
||||
unresolved_vertex(10.0, start - quota, start, 1, 100, quota),
|
||||
unresolved_vertex(0.0, start - quota, start, 1, 100, quota)};
|
||||
LensTriangle triangle;
|
||||
uuu_fixture(vertices, &triangle);
|
||||
FrameLensMesh m = {.vertices = vertices, .vertex_count = 3,
|
||||
.triangles = &triangle, .triangle_count = 1};
|
||||
const RefinementConfig absorbed = {.max_level = 0,
|
||||
.retry_step_increment = 0,
|
||||
.max_total_steps = 100,
|
||||
.retry_lookback_increment = 1.0e-6,
|
||||
.max_total_lookback_time = 2.0e6};
|
||||
if (frame_lens_mesh_prepare_generation(&m, &absorbed) != 0)
|
||||
failed = 1;
|
||||
free(m.samples);
|
||||
free(m.probe_slots);
|
||||
}
|
||||
spacetime_destroy(&source);
|
||||
return failed ? -1 : 0;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
if (review_probe_regressions()) {
|
||||
fputs("probe persistence / retry invalidation regression failed\n",stderr);
|
||||
return 1;
|
||||
}
|
||||
if (review_retry_quota_regressions()) {
|
||||
fputs("independent retry quota regression failed\n", stderr);
|
||||
return 1;
|
||||
}
|
||||
if (review_real_grant_and_time_growth()) {
|
||||
fputs("real grant / representable time growth regression failed\n", stderr);
|
||||
return 1;
|
||||
}
|
||||
const int width = 100, height = 100;
|
||||
const double test_exposure = 1e-3;
|
||||
const double psf_relative_tail = 1e-8;
|
||||
@@ -187,7 +688,7 @@ int main(void) {
|
||||
}
|
||||
/* A finalized mesh can be persisted independently of spacetime and then
|
||||
* drive the exact same catalog inverse-map and PSF pass. */
|
||||
const char *lens_map_path = "/tmp/gr_lens_map_test.grlens";
|
||||
const char *lens_map_path = "/tmp/opencode/gr_lens_map_test.grlens";
|
||||
mesh.retry_requests = 2; /* cumulative per-frame retry accounting round-trips */
|
||||
const LensMapFrame saved_frame = {.frame_id = 7,
|
||||
.coordinate_time = 3.0,
|
||||
@@ -259,7 +760,7 @@ int main(void) {
|
||||
/* A version-1 header must be rejected outright: its captured bit cannot be
|
||||
* upgraded into the new dark/unresolved/error provenance. */
|
||||
{
|
||||
const char *legacy_path = "/tmp/gr_lens_map_v1_test.grlens";
|
||||
const char *legacy_path = "/tmp/opencode/gr_lens_map_v1_test.grlens";
|
||||
FILE *legacy = fopen(legacy_path, "wb");
|
||||
int legacy_failed = legacy == NULL;
|
||||
if (!legacy_failed) {
|
||||
@@ -906,6 +1407,147 @@ int main(void) {
|
||||
}
|
||||
free(ud_mesh.probe_slots);
|
||||
}
|
||||
/* v3 DP54 round-trip: every adaptive/quota field and the per-vertex cost
|
||||
* counters must survive the wire exactly. */
|
||||
{
|
||||
const char *dp_path = "/tmp/opencode/gr_lens_map_v3_dp_test.grlens";
|
||||
LensVertex dv[3];
|
||||
for (int i = 0; i < 3; ++i)
|
||||
dv[i] = (LensVertex){.image_x = (double)i, .image_y = 2.0,
|
||||
.camera_direction = {0.0, 0.0, -1.0},
|
||||
.outcome = RAY_OUTCOME_DARK,
|
||||
.reason = RAY_REASON_REDSHIFT_LIMIT,
|
||||
.end_id = SPACETIME_END_NONE, .traced = 1};
|
||||
dv[0].trace_accepted_steps = 11; dv[0].trace_rejected_steps = 2;
|
||||
dv[0].trace_rhs_evaluations = 79;
|
||||
dv[1].trace_accepted_steps = 5;
|
||||
LensTriangle dt = {{0, 1, 2}, 1, 1, 0};
|
||||
FrameLensMesh dm = {.vertices = dv, .triangles = &dt, .vertex_count = 3,
|
||||
.vertex_capacity = 3, .triangle_count = 1,
|
||||
.triangle_capacity = 1};
|
||||
LensMapFrame df = {.frame_id = 3, .coordinate_time = 1.5,
|
||||
.proper_time = 1.25, .mesh = dm};
|
||||
const LensMapProvenance dp = {.threshold_kind = THRESHOLD_LOG_ENERGY_GROWTH,
|
||||
.threshold_policy_version = 3, .threshold_value = 8.0,
|
||||
.retry_step_increment = 64, .max_total_steps = 256, .max_level = 2,
|
||||
.integrator = (uint32_t)GEODESIC_STEPPER_DP54,
|
||||
.min_edge_pixels = 0.5, .min_area_pixels2 = 0.25,
|
||||
.coordinate_time_step = 0.1, .initial_max_steps = 1024,
|
||||
.atol_x = 1e-9, .atol_Pi = 1e-9, .atol_L = 1e-9, .rtol = 1e-9,
|
||||
.min_step = 1e-12, .max_step = 2.0, .max_lookback_time = 102.4,
|
||||
.retry_lookback_increment = 102.4, .max_total_lookback_time = 409.6,
|
||||
.max_consecutive_rejections = 32};
|
||||
LensMap dloaded = {0};
|
||||
if (lens_map_write(dp_path, 4, 3, 30.0, &dp, &df, 1) ||
|
||||
lens_map_read(dp_path, NULL, &dloaded)) {
|
||||
fputs("lens-map v3 DP54 round-trip regression failed\n", stderr);
|
||||
lens_map_destroy(&dloaded); unlink(dp_path); goto done;
|
||||
}
|
||||
const LensMapProvenance *lp = &dloaded.provenance;
|
||||
if (dloaded.file_version != 3 ||
|
||||
lp->integrator != (uint32_t)GEODESIC_STEPPER_DP54 ||
|
||||
lp->atol_x != 1e-9 || lp->atol_Pi != 1e-9 || lp->atol_L != 1e-9 ||
|
||||
lp->rtol != 1e-9 || lp->min_step != 1e-12 || lp->max_step != 2.0 ||
|
||||
lp->max_lookback_time != 102.4 ||
|
||||
lp->retry_lookback_increment != 102.4 ||
|
||||
lp->max_total_lookback_time != 409.6 ||
|
||||
lp->max_consecutive_rejections != 32 ||
|
||||
lp->retry_step_increment != 64 || lp->max_total_steps != 256 ||
|
||||
lp->coordinate_time_step != 0.1 || lp->initial_max_steps != 1024 ||
|
||||
dloaded.frames[0].mesh.vertices[0].trace_accepted_steps != 11 ||
|
||||
dloaded.frames[0].mesh.vertices[0].trace_rejected_steps != 2 ||
|
||||
dloaded.frames[0].mesh.vertices[0].trace_rhs_evaluations != 79 ||
|
||||
dloaded.frames[0].mesh.vertices[1].trace_accepted_steps != 5 ||
|
||||
dloaded.frames[0].mesh.triangles[0].level != 1) {
|
||||
fputs("lens-map v3 DP54 field round-trip regression failed\n", stderr);
|
||||
lens_map_destroy(&dloaded); unlink(dp_path); goto done;
|
||||
}
|
||||
lens_map_destroy(&dloaded);
|
||||
/* Unknown wire code and non-finite/out-of-bounds DP fields must be rejected
|
||||
* by the shared schema validator, not accepted as a usable map. */
|
||||
{
|
||||
/* offset, is_double, double_value, u32_value */
|
||||
const struct { long offset; int is_double; double dvalue; uint32_t uvalue; }
|
||||
corruptions[4] = {{68, 0, 0.0, 7u}, /* unknown integrator code */
|
||||
{100, 1, NAN, 0u}, /* atol_x = NaN */
|
||||
{108, 1, -1.0, 0u}, /* atol_Pi below zero */
|
||||
{156, 1, -1.0, 0u}}; /* max_lookback below zero */
|
||||
for (size_t c = 0; c < 4; ++c) {
|
||||
if (lens_map_write(dp_path, 4, 3, 30.0, &dp, &df, 1)) {
|
||||
fputs("lens-map v3 corruption fixture write failed\n", stderr);
|
||||
unlink(dp_path); goto done;
|
||||
}
|
||||
FILE *bad = fopen(dp_path, "r+b");
|
||||
int bad_failed =
|
||||
bad == NULL || fseek(bad, corruptions[c].offset, SEEK_SET);
|
||||
if (!bad_failed) {
|
||||
if (corruptions[c].is_double)
|
||||
bad_failed = fwrite(&corruptions[c].dvalue,
|
||||
sizeof(double), 1, bad) != 1;
|
||||
else
|
||||
bad_failed = fwrite(&corruptions[c].uvalue,
|
||||
sizeof(uint32_t), 1, bad) != 1;
|
||||
}
|
||||
if (bad != NULL && fclose(bad)) bad_failed = 1;
|
||||
if (bad_failed || !lens_map_read(dp_path, NULL, &dloaded)) {
|
||||
fputs("lens-map v3 invalid DP54 field rejection regression failed\n",
|
||||
stderr);
|
||||
lens_map_destroy(&dloaded); unlink(dp_path); goto done;
|
||||
}
|
||||
lens_map_destroy(&dloaded);
|
||||
}
|
||||
}
|
||||
unlink(dp_path);
|
||||
}
|
||||
/* Legacy v2 import: a real v2 map (no adaptive fields, no cost counters)
|
||||
* must load as RK4 with an explicit zero adaptive policy and render
|
||||
* identically to the live mesh. */
|
||||
{
|
||||
const char *v2_path = "/tmp/opencode/gr_lens_map_v2_legacy_test.grlens";
|
||||
const LensMapProvenance v2p = {.threshold_kind = THRESHOLD_LOG_ALPHA_P0,
|
||||
.threshold_policy_version = 1, .threshold_value = 8.0,
|
||||
.retry_step_increment = 16, .max_total_steps = 64, .max_level = 2,
|
||||
.integrator = (uint32_t)GEODESIC_STEPPER_RK4,
|
||||
.min_edge_pixels = 0.5, .min_area_pixels2 = 0.25,
|
||||
.coordinate_time_step = 0.1, .initial_max_steps = 4096};
|
||||
const LensMapFrame v2f = {.frame_id = 7, .coordinate_time = 3.0,
|
||||
.proper_time = 2.0, .mesh = mesh};
|
||||
LensMap v2loaded = {0};
|
||||
double *v2_hdr = calloc((size_t)width * height * 3, sizeof *v2_hdr);
|
||||
double *live_hdr = calloc((size_t)width * height * 3, sizeof *live_hdr);
|
||||
if (v2_hdr == NULL || live_hdr == NULL ||
|
||||
write_v2_lens_map(v2_path, width, height, 30.0, &v2p, &v2f) ||
|
||||
lens_map_read(v2_path, NULL, &v2loaded) ||
|
||||
v2loaded.file_version != 2 ||
|
||||
v2loaded.provenance.integrator != (uint32_t)GEODESIC_STEPPER_RK4 ||
|
||||
v2loaded.provenance.atol_x != 0.0 ||
|
||||
v2loaded.provenance.max_lookback_time != 0.0 ||
|
||||
v2loaded.provenance.max_total_lookback_time != 0.0 ||
|
||||
v2loaded.provenance.max_consecutive_rejections != 0 ||
|
||||
v2loaded.frames[0].mesh.vertices[0].trace_accepted_steps != 0 ||
|
||||
v2loaded.frames[0].mesh.vertices[0].trace_rhs_evaluations != 0) {
|
||||
fputs("lens-map v2 legacy import regression failed\n", stderr);
|
||||
free(v2_hdr); free(live_hdr); lens_map_destroy(&v2loaded);
|
||||
unlink(v2_path); goto done;
|
||||
}
|
||||
const size_t live_images = frame_splat_catalog(
|
||||
&mesh, &catalog, live_hdr, width, height, test_exposure, &psf, NULL,
|
||||
INFINITY, 1.0, psf_relative_tail, 0.0, 0, 1,
|
||||
FRAME_CATALOG_PREFETCH_FRAME, NULL, NULL, NULL, NULL, NULL);
|
||||
const size_t v2_images = frame_splat_catalog(
|
||||
&v2loaded.frames[0].mesh, &catalog, v2_hdr, width, height,
|
||||
test_exposure, &psf, NULL, INFINITY, 1.0, psf_relative_tail, 0.0, 0, 1,
|
||||
FRAME_CATALOG_PREFETCH_FRAME, NULL, NULL, NULL, NULL, NULL);
|
||||
int render_equal = live_images == v2_images && live_images == images;
|
||||
for (int k = 0; render_equal && k < width * height * 3; ++k)
|
||||
if (live_hdr[k] != v2_hdr[k]) render_equal = 0;
|
||||
free(v2_hdr); free(live_hdr);
|
||||
lens_map_destroy(&v2loaded); unlink(v2_path);
|
||||
if (!render_equal) {
|
||||
fputs("lens-map v2 legacy render mismatch regression failed\n", stderr);
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
result = 0;
|
||||
done:
|
||||
frame_lens_mesh_destroy(&mesh);
|
||||
|
||||
Reference in new issue
Block a user