Files
GR-raytracing/tests/test_frame.c
T
wyj 0a46a7095b 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.
2026-10-05 20:27:42 -04:00

1559 lines
72 KiB
C

#include "frame.h"
#include "lens_map.h"
#include "optics.h"
#include <math.h>
#include <omp.h>
#include <stdio.h>
#include <stdlib.h>
#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) {
const LensVertex *a = &mesh->vertices[mesh->triangles[triangle].vertex[side]];
const LensVertex *b =
&mesh->vertices[mesh->triangles[triangle].vertex[(side + 1) % 3]];
const double dx = b->image_x - a->image_x;
const double dy = b->image_y - a->image_y;
const double length_squared = dx * dx + dy * dy;
for (size_t vertex = 0; vertex < mesh->vertex_count; ++vertex) {
if (vertex == mesh->triangles[triangle].vertex[side] ||
vertex == mesh->triangles[triangle].vertex[(side + 1) % 3])
continue;
const LensVertex *p = &mesh->vertices[vertex];
const double px = p->image_x - a->image_x;
const double py = p->image_y - a->image_y;
const double cross = px * dy - py * dx;
const double position = (px * dx + py * dy) / length_squared;
if (fabs(cross) <= 1e-12 * length_squared && position > 1e-12 &&
position < 1.0 - 1e-12)
return 1;
}
}
return 0;
}
static int mesh_has_same_winding_shared_edge(const FrameLensMesh *mesh) {
for (size_t left_triangle = 0; left_triangle < mesh->triangle_count;
++left_triangle)
for (size_t left_side = 0; left_side < 3; ++left_side) {
const size_t from = mesh->triangles[left_triangle].vertex[left_side];
const size_t to =
mesh->triangles[left_triangle].vertex[(left_side + 1) % 3];
for (size_t right_triangle = left_triangle + 1;
right_triangle < mesh->triangle_count; ++right_triangle)
for (size_t right_side = 0; right_side < 3; ++right_side) {
const size_t other_from =
mesh->triangles[right_triangle].vertex[right_side];
const size_t other_to =
mesh->triangles[right_triangle].vertex[(right_side + 1) % 3];
if ((from == other_from && to == other_to) ||
(from == other_to && to == other_from)) {
if (from == other_from && to == other_to)
return 1;
}
}
}
return 0;
}
static int review_probe_regressions(void) {
RefinementConfig c = {.max_level=2, .min_edge_pixels=.5,
.min_area_pixels2=.25, .angle_absolute_rad=3.14, .angle_relative=1e6,
.retry_step_increment=10, .max_total_steps=100};
RayEndpoint flat = {.outcome=RAY_OUTCOME_ESCAPED, .frequency_ratio=1,
.n_infinity={1,0,0}, .end_id=0};
for (int unresolved=0; unresolved<2; ++unresolved) {
FrameLensMesh m={0};
if (frame_lens_mesh_build_coarse(&m,100,100,100,30)) return -1;
for (size_t i=0;i<m.vertex_count;++i) {
m.vertices[i].traced=1; m.vertices[i].outcome=RAY_OUTCOME_ESCAPED;
m.vertices[i].n_infinity[0]=1;
}
if (frame_lens_mesh_prepare_generation(&m,&c)!=1) return -1;
RayEndpoint bad = {.outcome=unresolved?RAY_OUTCOME_UNRESOLVED:RAY_OUTCOME_INCOMPLETE,
.reason=unresolved?RAY_REASON_BUDGET_EXHAUSTED:RAY_REASON_INVALID_METRIC,
.accepted_steps=10, .stop_coordinate_time=-1,
.final_x={30,0,0}, .final_Pi={1,0,0}};
if (frame_lens_mesh_install_sample(&m,0,&bad) ||
frame_lens_mesh_finish_generation(&m,&c)<0) return -1;
FrameBoundaryStats stats;
frame_lens_mesh_boundary_stats(&m,&c,&stats);
if ((!unresolved && !stats.error) ||
(unresolved && !stats.budget_incomplete_triangles)) return -1;
if (unresolved) {
const int retry = frame_lens_mesh_prepare_generation(&m,&c);
if (retry != 1 || m.samples[0].kind != FRAME_SAMPLE_RETRY) return -1;
if (frame_lens_mesh_install_sample(&m,0,&flat) ||
frame_lens_mesh_finish_generation(&m,&c)<0) return -1;
frame_lens_mesh_boundary_stats(&m,&c,&stats);
if (stats.error || stats.budget_incomplete_triangles) return -1;
/* The resolved witness and the settled triangle converge. */
for (int guard = 0; guard < 8; ++guard) {
const int request = frame_lens_mesh_prepare_generation(&m,&c);
if (request == 0) break;
if (request < 0 || guard == 7) return -1;
for (size_t i = 0; i < m.sample_count; ++i)
if (frame_lens_mesh_install_sample(&m,i,&flat)) return -1;
if (frame_lens_mesh_finish_generation(&m,&c)<0) return -1;
}
}
frame_lens_mesh_destroy(&m);
}
/* R5: a witness retry that triggers the split in the same generation must
* promote the existing witness in place, leaving one vertex and no second
* continuation for the same edge. */
{
FrameLensMesh m={0};
if (frame_lens_mesh_build_coarse(&m,100,100,100,30)) return -1;
for (size_t i=0;i<m.vertex_count;++i) {
m.vertices[i].traced=1; m.vertices[i].outcome=RAY_OUTCOME_ESCAPED;
m.vertices[i].n_infinity[0]=1;
}
if (frame_lens_mesh_prepare_generation(&m,&c)!=1) return -1;
RayEndpoint badU={.outcome=RAY_OUTCOME_UNRESOLVED,
.reason=RAY_REASON_BUDGET_EXHAUSTED, .accepted_steps=10,
.stop_coordinate_time=-1, .final_x={30,0,0}, .final_Pi={1,0,0}};
if (frame_lens_mesh_install_sample(&m,0,&badU) ||
frame_lens_mesh_finish_generation(&m,&c)<0) return -1;
const int retry = frame_lens_mesh_prepare_generation(&m,&c);
if (retry != 1 || m.samples[0].kind != FRAME_SAMPLE_RETRY) return -1;
RayEndpoint dark={.outcome=RAY_OUTCOME_DARK,.reason=RAY_REASON_REDSHIFT_LIMIT};
if (frame_lens_mesh_install_sample(&m,0,&dark) ||
frame_lens_mesh_finish_generation(&m,&c)<0) return -1;
FrameBoundaryStats stats;
frame_lens_mesh_boundary_stats(&m,&c,&stats);
if (m.diagnostic_probe_count != 0 || stats.error ||
stats.budget_incomplete_triangles) return -1;
frame_lens_mesh_destroy(&m);
}
/* A retry that resolves to DARK must invalidate the settled leaves so the
* newly discontinuous EED boundary is refined rather than silently frozen. */
{
FrameLensMesh m={0};
if (frame_lens_mesh_build_coarse(&m,200,100,100,30)) return -1;
for (size_t i=0;i<m.vertex_count;++i) {
m.vertices[i].traced=1; m.vertices[i].outcome=RAY_OUTCOME_ESCAPED;
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 */
RayEndpoint dark={.outcome=RAY_OUTCOME_DARK,.reason=RAY_REASON_REDSHIFT_LIMIT};
for (size_t i=0;i<m.sample_count;++i)
if (frame_lens_mesh_install_sample(&m,i,
m.samples[i].kind==FRAME_SAMPLE_RETRY?&dark:&flat)) return -1;
if (frame_lens_mesh_finish_generation(&m,&c)<0 ||
frame_lens_mesh_prepare_generation(&m,&c)==0) return -1;
frame_lens_mesh_destroy(&m);
}
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;
const PointSpreadFunction psf = {.fwhm_pixels = 2.7, .moffat_beta = 4.5};
const GeodesicTraceConfig trace = {.coordinate_time_step = 0.25,
.max_steps = 100};
const ObserverState observer = observer_fixed_at_origin();
Star star = {
.direction = {0.0, 0.0, -1.0}, .temperature_K = 7000.0, .amplitude = 1.0};
StarCatalog catalog = {.stars = &star, .count = 1};
SpacetimeSource spacetime = {0};
FrameLensMesh mesh = {0};
double *hdr = calloc((size_t)width * height * 3, sizeof *hdr);
int result = 1;
if (blackbody_backend_init(NULL, 0, NAN, NAN, NULL, stderr) ||
hdr == NULL || spacetime_create_minkowski(&spacetime, 10.0) ||
frame_lens_mesh_build_coarse(&mesh, width, height, 20, 30.0) ||
frame_lens_mesh_trace(&mesh, &spacetime, &observer, &trace))
goto done;
const size_t images =
frame_splat_catalog(&mesh, &catalog, 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);
if (images != 1 || hdr[3 * (50 * width + 50)] <= 0.0) {
fputs("flat-space inverse lens-map regression failed\n", stderr);
goto done;
}
/* 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/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,
.proper_time = 2.0,
.mesh = mesh};
LensMap loaded_map = {0};
const LensMapProvenance provenance = {.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 = 0,
.min_edge_pixels = 0.5,
.min_area_pixels2 = 0.25,
.coordinate_time_step = 0.1,
.initial_max_steps = 4096};
double *roundtrip_hdr = calloc((size_t)width * height * 3, sizeof *roundtrip_hdr);
if (roundtrip_hdr == NULL ||
lens_map_write(lens_map_path, width, height, 30.0, &provenance,
&saved_frame, 1) ||
lens_map_read(lens_map_path, NULL, &loaded_map) ||
loaded_map.frame_count != 1 ||
loaded_map.frames[0].frame_id != 7 || loaded_map.width != width ||
loaded_map.height != height ||
loaded_map.provenance.threshold_kind != THRESHOLD_LOG_ALPHA_P0 ||
loaded_map.provenance.threshold_value != 8.0 ||
loaded_map.provenance.retry_step_increment != 16 ||
loaded_map.provenance.max_total_steps != 64 ||
loaded_map.provenance.max_level != 2 ||
loaded_map.provenance.coordinate_time_step != 0.1 ||
loaded_map.provenance.initial_max_steps != 4096 ||
loaded_map.frames[0].mesh.retry_requests != 2 ||
loaded_map.frames[0].mesh.vertex_count != mesh.vertex_count ||
frame_splat_catalog(&loaded_map.frames[0].mesh, &catalog, roundtrip_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) != images) {
fputs("lens-map round-trip regression failed\n", stderr);
free(roundtrip_hdr); lens_map_destroy(&loaded_map); unlink(lens_map_path);
goto done;
}
for (int value = 0; value < width * height * 3; ++value)
if (hdr[value] != roundtrip_hdr[value]) {
fputs("lens-map round-trip HDR regression failed\n", stderr);
free(roundtrip_hdr); lens_map_destroy(&loaded_map); unlink(lens_map_path);
goto done;
}
free(roundtrip_hdr);
lens_map_destroy(&loaded_map);
/* A damaged payload must not be mistaken for a reusable physical map. */
FILE *damaged = fopen(lens_map_path, "r+b");
int damage_failed = damaged == NULL;
if (!damage_failed) {
if (fseek(damaged, -5L, SEEK_END))
damage_failed = 1;
const int original = damage_failed ? EOF : fgetc(damaged);
if (damage_failed || fseek(damaged, -5L, SEEK_END) || original == EOF ||
fputc(original ^ 0xff, damaged) == EOF)
damage_failed = 1;
}
if (damaged != NULL && fclose(damaged))
damage_failed = 1;
if (damage_failed || !lens_map_read(lens_map_path, NULL, &loaded_map)) {
fputs("lens-map corruption rejection regression failed\n", stderr);
lens_map_destroy(&loaded_map); unlink(lens_map_path);
goto done;
}
unlink(lens_map_path);
/* 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/opencode/gr_lens_map_v1_test.grlens";
FILE *legacy = fopen(legacy_path, "wb");
int legacy_failed = legacy == NULL;
if (!legacy_failed) {
const unsigned char magic[8] = {'G', 'R', 'L', 'E', 'N', 'S', 1, 0};
const unsigned char header[32] = {
1, 0, 0, 0, /* version 1 */
4, 3, 2, 1, /* endian 0x01020304 */
8, 0, 0, 0, 8, 0, 0, 0, /* width, height */
0, 0, 0, 0, 0, 0, 0, 0, /* fov */
1, 0, 0, 0, 0, 0, 0, 0 /* frame_count 1 */
};
legacy_failed = fwrite(magic, 1, sizeof magic, legacy) != sizeof magic ||
fwrite(header, 1, sizeof header, legacy) != sizeof header;
}
if (legacy != NULL && fclose(legacy))
legacy_failed = 1;
if (legacy_failed || !lens_map_read(legacy_path, NULL, &loaded_map)) {
fputs("lens-map v1 rejection regression failed\n", stderr);
lens_map_destroy(&loaded_map); unlink(legacy_path);
goto done;
}
unlink(legacy_path);
}
memset(hdr, 0, (size_t)width * height * 3 * sizeof *hdr);
PsfSplatStats min_y_stats = {0};
if (frame_splat_catalog(&mesh, &catalog, hdr, width, height, test_exposure,
&psf, NULL, INFINITY, 1.0, psf_relative_tail,
1e300, 0, 1, FRAME_CATALOG_PREFETCH_FRAME, NULL, &min_y_stats, NULL, NULL, NULL) != 1 ||
min_y_stats.discarded_below_min_y != 1 ||
hdr[3 * (50 * width + 50)] != 0.0) {
fputs("PSF minimum-Y discard regression failed\n", stderr);
goto done;
}
/* Private HDR accumulation must preserve the serial splat result. */
double *serial_hdr = calloc((size_t)width * height * 3, sizeof *serial_hdr);
double *parallel_hdr = calloc((size_t)width * height * 3, sizeof *parallel_hdr);
if (serial_hdr == NULL || parallel_hdr == NULL) {
free(serial_hdr);
free(parallel_hdr);
goto done;
}
const int original_threads = omp_get_max_threads();
omp_set_dynamic(0);
omp_set_num_threads(1);
const size_t serial_images = frame_splat_catalog(
&mesh, &catalog, serial_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);
omp_set_num_threads(4);
const size_t parallel_images = frame_splat_catalog(
&mesh, &catalog, parallel_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);
omp_set_num_threads(original_threads);
for (int value = 0; value < width * height * 3; ++value)
if (fabs(serial_hdr[value] - parallel_hdr[value]) >
1e-12 * fmax(1.0, fabs(serial_hdr[value]))) {
fputs("parallel catalog splat regression failed\n", stderr);
free(serial_hdr);
free(parallel_hdr);
goto done;
}
free(serial_hdr);
free(parallel_hdr);
if (serial_images != 1 || parallel_images != serial_images) {
fputs("parallel catalog image-count regression failed\n", stderr);
goto done;
}
const LinearRgb cool = blackbody_to_linear_rgb(3000.0);
const LinearRgb hot = blackbody_to_linear_rgb(10000.0);
if (!(cool.r > cool.b && hot.b > hot.r &&
hot.r + hot.g + hot.b > cool.r + cool.g + cool.b)) {
fputs("blackbody spectral-color regression failed\n", stderr);
goto done;
}
/* The Moffat is flux-normalized and retains a measurable, continuous wing
* beyond the former Gaussian's 3-sigma raster box. */
memset(hdr, 0, (size_t)width * height * 3 * sizeof *hdr);
splat_moffat(hdr, width, height, 50.5, 50.5,
(LinearRgb){1.0, 1.0, 1.0}, 1.0, &psf, psf_relative_tail, 0.0);
double moffat_flux = 0.0;
for (int pixel = 0; pixel < width * height; ++pixel)
moffat_flux += hdr[3 * pixel];
if (fabs(moffat_flux - 1.0) > 0.01 ||
hdr[3 * (50 * width + 62)] <= 0.0) {
fputs("Moffat normalization or wing regression failed\n", stderr);
goto done;
}
/* The cache stores 4-point pixel-area integrals over a 64x64 sub-pixel
* lattice. Compare its bilinear interpolation with the independent 8-point
* direct reference at phases on both sides of a pixel boundary. */
PsfKernelCache cache = {0};
double *cached_hdr = calloc((size_t)width * height * 3, sizeof *cached_hdr);
double *reference_hdr = calloc((size_t)width * height * 3, sizeof *reference_hdr);
if (cached_hdr == NULL || reference_hdr == NULL ||
psf_kernel_cache_init(&cache, &psf, psf_relative_tail)) {
free(cached_hdr);
free(reference_hdr);
psf_kernel_cache_destroy(&cache);
fputs("PSF cache construction regression failed\n", stderr);
goto done;
}
PsfKernelCache loose_tail_cache = {0};
if (psf_kernel_cache_init(&loose_tail_cache, &psf, 1e-5) ||
loose_tail_cache.relative_tail_fraction != 1e-5 ||
loose_tail_cache.radius_pixels >= cache.radius_pixels) {
fputs("PSF relative-tail cache-radius regression failed\n", stderr);
psf_kernel_cache_destroy(&loose_tail_cache);
free(cached_hdr); free(reference_hdr); psf_kernel_cache_destroy(&cache);
goto done;
}
psf_kernel_cache_destroy(&loose_tail_cache);
/* This is the exact eligibility split that a future event sink exposes to
* HIP: cache event, CPU direct fallback, or min-Y discard. */
PsfCachedEvent prepared = {0};
if (psf_prepare_cached_event(&prepared, 12.25, 14.75,
(LinearRgb){1.0, 1.0, 1.0}, 1.0, &psf, &cache,
1.0, psf_relative_tail, 0.0) != 0 ||
prepared.x != 12.25 || prepared.y != 14.75 ||
!(prepared.support_radius > 0.0) ||
psf_prepare_cached_event(&prepared, 12.25, 14.75,
(LinearRgb){1.0, 1.0, 1.0}, 1000.0, &psf, &cache,
1.0, psf_relative_tail, 0.0) != 1 ||
psf_prepare_cached_event(&prepared, 12.25, 14.75,
(LinearRgb){1.0, 1.0, 1.0}, 1.0, &psf, &cache,
1.0, psf_relative_tail, 1.0) != 3) {
fputs("PSF event eligibility regression failed\n", stderr);
free(cached_hdr); free(reference_hdr); psf_kernel_cache_destroy(&cache);
goto done;
}
const double phases[][2] = {{0.01, 0.99}, {0.499, 0.501}, {0.999, 0.001}};
for (size_t phase = 0; phase < sizeof phases / sizeof *phases; ++phase) {
memset(cached_hdr, 0, (size_t)width * height * 3 * sizeof *cached_hdr);
memset(reference_hdr, 0, (size_t)width * height * 3 * sizeof *reference_hdr);
if (splat_moffat_cached(cached_hdr, width, height, 50.0 + phases[phase][0],
50.0 + phases[phase][1], (LinearRgb){1.0, 1.0, 1.0},
1.0, &psf, &cache, 1.0, psf_relative_tail, 0.0) != 0) {
fputs("ordinary PSF cache unexpectedly fell back\n", stderr);
free(cached_hdr); free(reference_hdr); psf_kernel_cache_destroy(&cache);
goto done;
}
splat_moffat_direct(reference_hdr, width, height,
50.0 + phases[phase][0], 50.0 + phases[phase][1],
(LinearRgb){1.0, 1.0, 1.0}, 1.0, &psf, psf_relative_tail, 0.0);
double peak = 0.0, max_error = 0.0;
for (int value = 0; value < width * height * 3; ++value) {
peak = fmax(peak, reference_hdr[value]);
max_error = fmax(max_error, fabs(cached_hdr[value] - reference_hdr[value]));
}
if (peak <= 0.0 || max_error > 4e-5 * peak) {
fputs("PSF cache interpolation accuracy regression failed\n", stderr);
free(cached_hdr); free(reference_hdr); psf_kernel_cache_destroy(&cache);
goto done;
}
}
if (splat_moffat_cached(cached_hdr, width, height, 50.5, 50.5,
(LinearRgb){1.0, 1.0, 1.0}, 1.0, &psf, &cache,
1.0, psf_relative_tail, 1.0) != 3) {
fputs("PSF minimum-Y cached discard regression failed\n", stderr);
free(cached_hdr); free(reference_hdr); psf_kernel_cache_destroy(&cache);
goto done;
}
/* A bright event must avoid a cached hard cutoff by selecting the direct
* reference path when the requested support exceeds the cache. */
if (splat_moffat_cached(cached_hdr, width, height, 50.5, 50.5,
(LinearRgb){1.0, 1.0, 1.0}, 1000.0, &psf, &cache, 1.0,
psf_relative_tail, 0.0) != 1) {
fputs("bright PSF direct-fallback regression failed\n", stderr);
free(cached_hdr); free(reference_hdr); psf_kernel_cache_destroy(&cache);
goto done;
}
memset(cached_hdr, 0, (size_t)width * height * 3 * sizeof *cached_hdr);
memset(reference_hdr, 0, (size_t)width * height * 3 * sizeof *reference_hdr);
if (splat_moffat_cached(cached_hdr, width, height, 50.5, 50.5,
(LinearRgb){1.0, 1.0, 1.0}, 1000.0, &psf, &cache,
1000.0, psf_relative_tail, 0.0) != 2) {
fputs("bright PSF cached-wing-clipping regression failed\n", stderr);
free(cached_hdr); free(reference_hdr); psf_kernel_cache_destroy(&cache);
goto done;
}
splat_moffat_direct(reference_hdr, width, height, 50.5, 50.5,
(LinearRgb){1.0, 1.0, 1.0}, 1000.0, &psf, psf_relative_tail, 0.0);
const size_t center = 3 * (50 * width + 50);
if (cached_hdr[center] <= 0.0 ||
fabs(cached_hdr[center] - reference_hdr[center]) >
4e-5 * reference_hdr[center]) {
fputs("cached-wing clipping changed the bright PSF core\n", stderr);
free(cached_hdr); free(reference_hdr); psf_kernel_cache_destroy(&cache);
goto done;
}
free(cached_hdr);
free(reference_hdr);
psf_kernel_cache_destroy(&cache);
/* Fast mode: a nearest deposit must reproduce the current pixel-integrated
* Moffat at the snapped supersampled centre, preserve total flux, and honour
* the min-Y discard rule; bilinear deposition must preserve the centroid. */
{
int fast_ok = 1;
const int supersample = 2;
FastPsfAccumulator fast = {0};
FastPsfAccumulator bilinear = {0};
FastPsfAccumulator min_y_fast = {0};
FastPsfAccumulator accumulation = {0};
double *fast_hdr = calloc((size_t)width * height * 3, sizeof *fast_hdr);
double *direct_hdr = calloc((size_t)width * height * 3, sizeof *direct_hdr);
double *background_hdr =
calloc((size_t)width * height * 3, sizeof *background_hdr);
if (fast_hdr == NULL || direct_hdr == NULL || background_hdr == NULL ||
fast_psf_accumulator_init(&fast, width, height, supersample,
FAST_PSF_DEPOSIT_NEAREST, &psf,
psf_relative_tail, 0.0, 1) ||
fast_psf_accumulator_init(&bilinear, width, height, supersample,
FAST_PSF_DEPOSIT_BILINEAR, &psf,
psf_relative_tail, 0.0, 1) ||
fast_psf_accumulator_init(&min_y_fast, width, height, supersample,
FAST_PSF_DEPOSIT_NEAREST, &psf,
psf_relative_tail, 0.5, 1) ||
fast_psf_accumulator_init(&accumulation, width, height, supersample,
FAST_PSF_DEPOSIT_NEAREST, &psf,
psf_relative_tail, 0.0, 1)) {
fputs("fast-mode accumulator construction regression failed\n", stderr);
fast_ok = 0;
goto fast_done;
}
/* (50.2, 50.2) snaps to supersampled cell 100, centre (100.5, 100.5) in
* ss coordinates, i.e. final position (50.25, 50.25). */
if (fast_psf_accumulator_deposit(&fast, 50.2, 50.2,
(LinearRgb){1.0, 1.0, 1.0}, 1.0) == 3 ||
fast_psf_accumulator_resolve(&fast, fast_hdr, 4)) {
fputs("fast-mode nearest deposit regression failed\n", stderr);
fast_ok = 0;
goto fast_done;
}
splat_moffat_direct(direct_hdr, width, height, 50.25, 50.25,
(LinearRgb){1.0, 1.0, 1.0}, 1.0, &psf,
psf_relative_tail, 0.0);
double peak = 0.0, max_error = 0.0, fast_flux = 0.0, direct_flux = 0.0;
for (int value = 0; value < width * height * 3; ++value) {
peak = fmax(peak, direct_hdr[value]);
max_error = fmax(max_error, fabs(fast_hdr[value] - direct_hdr[value]));
fast_flux += fast_hdr[value];
direct_flux += direct_hdr[value];
}
if (!(peak > 0.0) || max_error > 1e-4 * peak ||
fabs(fast_flux - direct_flux) > 1e-4) {
fputs("fast-mode nearest semantics regression failed\n", stderr);
fast_ok = 0;
goto fast_done;
}
/* Bilinear keeps the exact continuous centroid. */
memset(fast_hdr, 0, (size_t)width * height * 3 * sizeof *fast_hdr);
if (fast_psf_accumulator_deposit(&bilinear, 50.37, 50.62,
(LinearRgb){1.0, 1.0, 1.0}, 1.0) == 3 ||
fast_psf_accumulator_resolve(&bilinear, fast_hdr, 4)) {
fputs("fast-mode bilinear deposit regression failed\n", stderr);
fast_ok = 0;
goto fast_done;
}
double weight_sum = 0.0, cx = 0.0, cy = 0.0;
for (int row = 0; row < height; ++row)
for (int column = 0; column < width; ++column) {
const double weight = fast_hdr[3 * (row * width + column)];
weight_sum += weight;
cx += weight * (column + 0.5);
cy += weight * (row + 0.5);
}
if (!(weight_sum > 0.0) ||
hypot(cx / weight_sum - 50.37, cy / weight_sum - 50.62) > 1e-6) {
fputs("fast-mode bilinear centroid regression failed\n", stderr);
fast_ok = 0;
goto fast_done;
}
/* The min-Y cutoff discards an event whose peak luminance is below it. */
if (fast_psf_accumulator_deposit(&fast, 10.5, 10.5,
(LinearRgb){1.0, 1.0, 1.0}, 1.0) != 0 ||
fast_psf_accumulator_deposit(&min_y_fast, 10.5, 10.5,
(LinearRgb){1.0, 1.0, 1.0}, 1.0) != 3) {
fputs("fast-mode min-Y discard regression failed\n", stderr);
fast_ok = 0;
goto fast_done;
}
/* End-to-end plumbing through the frame splat path. */
memset(fast_hdr, 0, (size_t)width * height * 3 * sizeof *fast_hdr);
PsfSplatStats fast_stats = {0};
const size_t fast_images = frame_splat_catalog(
&mesh, &catalog, fast_hdr, width, height, test_exposure, &psf, NULL,
INFINITY, 1.0, psf_relative_tail, 0.0, 0, 1,
FRAME_CATALOG_PREFETCH_FRAME, NULL, &fast_stats, NULL, &fast, NULL);
if (fast_images != 1 || fast_stats.discarded_below_min_y != 0) {
fputs("fast-mode frame splat regression failed\n", stderr);
fast_ok = 0;
goto fast_done;
}
/* HDR accumulation semantics: resolve must add onto an existing
* background, not overwrite it. A prefilled buffer plus one deposit must
* preserve the far-field background exactly and add the PSF core. */
for (int value = 0; value < width * height * 3; ++value)
background_hdr[value] = 0.25;
if (fast_psf_accumulator_deposit(&accumulation, 10.5, 10.5,
(LinearRgb){1.0, 1.0, 1.0}, 1.0) == 3 ||
fast_psf_accumulator_resolve(&accumulation, background_hdr, 4)) {
fputs("fast-mode HDR accumulation regression failed\n", stderr);
fast_ok = 0;
goto fast_done;
}
/* (90, 90) is far outside the kernel support of a star at (10.5, 10.5). */
if (background_hdr[3 * (90 * width + 90)] != 0.25) {
fputs("fast-mode HDR accumulation lost the background\n", stderr);
fast_ok = 0;
goto fast_done;
}
if (!(background_hdr[3 * (10 * width + 10)] > 0.25)) {
fputs("fast-mode HDR accumulation did not add the deposit\n", stderr);
fast_ok = 0;
goto fast_done;
}
fast_done:
free(fast_hdr);
free(direct_hdr);
free(background_hdr);
fast_psf_accumulator_destroy(&fast);
fast_psf_accumulator_destroy(&bilinear);
fast_psf_accumulator_destroy(&min_y_fast);
fast_psf_accumulator_destroy(&accumulation);
if (!fast_ok)
goto done;
}
frame_draw_mesh(&mesh, hdr, width, height, 0.5, 0.5);
if (hdr[3 * (10 * width + 20)] != 0.25) {
fputs("mesh diagnostic overlay regression failed\n", stderr);
goto done;
}
/* A fixed absolute edge tolerance used to make tiny source triangles claim
* sources far outside their field. */
FrameLensMesh fine_mesh = {0};
Star fine_stars[2] = {{.direction = {0.0, 0.0, -1.0},
.temperature_K = 7000.0,
.amplitude = 1.0},
{.direction = {0.01, 0.0, -0.9999499987499375},
.temperature_K = 7000.0,
.amplitude = 1.0}};
StarCatalog fine_catalog = {.stars = fine_stars, .count = 2};
memset(hdr, 0, (size_t)width * height * 3 * sizeof *hdr);
if (frame_lens_mesh_build_coarse(&fine_mesh, width, height, 1, 0.1) ||
frame_lens_mesh_trace(&fine_mesh, &spacetime, &observer, &trace) ||
frame_splat_catalog(&fine_mesh, &fine_catalog, 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) != 1) {
fputs("fine source-triangle containment regression failed\n", stderr);
frame_lens_mesh_destroy(&fine_mesh);
goto done;
}
frame_lens_mesh_destroy(&fine_mesh);
/* Schwarzschild level-4/J=0.2 ring triangle: the old edge tolerance accepts
* (1,0,0) although it is outside, producing unsigned weights summing to
* 1.09608. Keep a genuine interior source after it to check that rejecting
* one source does not discard subsequent stars. Exercise both parities. */
const double thin_directions[3][3] = {
{0.99999998891116071, 0.00013431364716360775, 6.4323579270168807e-05},
{0.99999997228355786, -0.00021216644782556991, -0.00010206998544149922},
{0.9999927016945267, -0.0034455730513416835, -0.001650631403158936}};
LensVertex thin_vertices[3] = {
{.image_x = 40, .image_y = 40, .outcome = RAY_OUTCOME_ESCAPED},
{.image_x = 48, .image_y = 40, .outcome = RAY_OUTCOME_ESCAPED},
{.image_x = 40, .image_y = 48, .outcome = RAY_OUTCOME_ESCAPED}};
LensTriangle thin_triangle = {.vertex = {0, 1, 2}};
FrameLensMesh thin_mesh = {.vertices = thin_vertices, .vertex_count = 3,
.triangles = &thin_triangle, .triangle_count = 1};
Star thin_stars[2] = {
{.direction = {1, 0, 0}, .temperature_K = 7000, .amplitude = 1},
{.temperature_K = 7000, .amplitude = 1}};
for (int i = 0; i < 3; ++i) {
memcpy(thin_vertices[i].n_infinity, thin_directions[i],
sizeof thin_directions[i]);
memcpy(thin_vertices[i].camera_direction, thin_directions[i],
sizeof thin_directions[i]);
for (int axis = 0; axis < 3; ++axis)
thin_stars[1].direction[axis] += thin_directions[i][axis];
}
const double thin_norm = hypot(hypot(thin_stars[1].direction[0],
thin_stars[1].direction[1]),
thin_stars[1].direction[2]);
for (int axis = 0; axis < 3; ++axis)
thin_stars[1].direction[axis] /= thin_norm;
StarCatalog thin_catalog = {.stars = thin_stars, .count = 2};
for (int parity = 0; parity < 2; ++parity) {
thin_triangle.vertex[1] = parity ? 2 : 1;
thin_triangle.vertex[2] = parity ? 1 : 2;
memset(hdr, 0, (size_t)width * height * 3 * sizeof *hdr);
if (frame_splat_catalog(&thin_mesh, &thin_catalog, hdr, width, height,
test_exposure, &psf, NULL, 1.0, 1.0,
psf_relative_tail, 0.0, 0, 1, FRAME_CATALOG_PREFETCH_FRAME, NULL, NULL, NULL, NULL, NULL) != 1 ||
hdr[3 * (43 * width + 43)] <= 0.0) {
fputs("thin source-triangle inverse-map regression failed\n", stderr);
goto done;
}
}
/* Refinement probes are temporary until their generation is complete. A
* shared diagonal probe must produce one stable midpoint and conforming
* children only after its endpoint has been installed. */
FrameLensMesh adaptive_mesh = {0};
RefinementConfig refine = {.max_level = 1,
.angle_absolute_rad = 1e-4,
.angle_relative = 1e-4,
.jacobian_minimum = 1e-3,
.min_edge_pixels = 1.0,
.min_area_pixels2 = 1.0};
if (frame_lens_mesh_build_coarse(&adaptive_mesh, width, height, 100, 30.0))
goto done;
for (size_t i = 0; i < adaptive_mesh.vertex_count; ++i) {
adaptive_mesh.vertices[i].traced = 1;
adaptive_mesh.vertices[i].outcome = RAY_OUTCOME_ESCAPED;
adaptive_mesh.vertices[i].n_infinity[0] = 1.0;
}
if (frame_lens_mesh_prepare_generation(&adaptive_mesh, &refine) != 1) {
fputs("adaptive shared-edge probe setup regression failed\n", stderr);
frame_lens_mesh_destroy(&adaptive_mesh);
goto done;
}
const RayEndpoint bent_probe = {.n_infinity = {0.0, 1.0, 0.0},
.frequency_ratio = 1.0,
.outcome = RAY_OUTCOME_ESCAPED};
if (frame_lens_mesh_install_sample(&adaptive_mesh, 0, &bent_probe) ||
frame_lens_mesh_finish_generation(&adaptive_mesh, &refine) != 1 ||
adaptive_mesh.vertex_count != 5 || adaptive_mesh.triangle_count != 4) {
fputs("adaptive shared-edge split regression failed\n", stderr);
frame_lens_mesh_destroy(&adaptive_mesh);
goto done;
}
frame_lens_mesh_destroy(&adaptive_mesh);
/* A capture/escape discontinuity is a shadow boundary, not a smooth map
* error: request all three midpoint rays and red-refine in one generation. */
if (frame_lens_mesh_build_coarse(&adaptive_mesh, width, height, 100, 30.0))
goto done;
adaptive_mesh.triangle_count = 1;
for (size_t i = 0; i < adaptive_mesh.vertex_count; ++i) {
adaptive_mesh.vertices[i].traced = 1;
adaptive_mesh.vertices[i].outcome = RAY_OUTCOME_ESCAPED;
adaptive_mesh.vertices[i].n_infinity[0] = 1.0;
}
adaptive_mesh.vertices[0].outcome = RAY_OUTCOME_DARK;
refine.max_level = 1;
refine.angle_absolute_rad = 3.14159265358979323846;
refine.angle_relative = 1e6;
refine.jacobian_minimum = 1e-12;
if (frame_lens_mesh_prepare_generation(&adaptive_mesh, &refine) != 3) {
fputs("shadow-boundary red-probe setup regression failed\n", stderr);
frame_lens_mesh_destroy(&adaptive_mesh);
goto done;
}
for (size_t i = 0; i < adaptive_mesh.sample_count; ++i)
if (frame_lens_mesh_install_sample(&adaptive_mesh, i, &bent_probe)) {
fputs("shadow-boundary red-probe installation regression failed\n", stderr);
frame_lens_mesh_destroy(&adaptive_mesh);
goto done;
}
if (frame_lens_mesh_finish_generation(&adaptive_mesh, &refine) != 3 ||
adaptive_mesh.vertex_count != 7 || adaptive_mesh.triangle_count != 4) {
fputs("shadow-boundary red-refinement regression failed\n", stderr);
frame_lens_mesh_destroy(&adaptive_mesh);
goto done;
}
frame_lens_mesh_destroy(&adaptive_mesh);
/* Two shadow leaves can force two edges of an escaped neighbour. That
* neighbour must use a local three-child blue split, not create a third
* requested edge that spreads red refinement farther outward. */
if (frame_lens_mesh_build_coarse(&adaptive_mesh, 200, 100, 100, 30.0))
goto done;
adaptive_mesh.triangle_count = 3;
for (size_t i = 0; i < adaptive_mesh.vertex_count; ++i) {
adaptive_mesh.vertices[i].traced = 1;
adaptive_mesh.vertices[i].outcome = RAY_OUTCOME_ESCAPED;
adaptive_mesh.vertices[i].n_infinity[0] = 1.0;
}
adaptive_mesh.vertices[3].outcome = RAY_OUTCOME_DARK;
adaptive_mesh.vertices[5].outcome = RAY_OUTCOME_DARK;
if (frame_lens_mesh_prepare_generation(&adaptive_mesh, &refine) != 6) {
fputs("shadow-boundary blue-neighbour probe setup regression failed\n", stderr);
frame_lens_mesh_destroy(&adaptive_mesh);
goto done;
}
for (size_t i = 0; i < adaptive_mesh.sample_count; ++i)
if (frame_lens_mesh_install_sample(&adaptive_mesh, i, &bent_probe)) {
fputs("shadow-boundary blue-neighbour probe installation regression failed\n", stderr);
frame_lens_mesh_destroy(&adaptive_mesh);
goto done;
}
if (frame_lens_mesh_finish_generation(&adaptive_mesh, &refine) != 6 ||
adaptive_mesh.vertex_count != 12 || adaptive_mesh.triangle_count != 11 ||
mesh_has_hanging_vertex(&adaptive_mesh) ||
mesh_has_same_winding_shared_edge(&adaptive_mesh)) {
fputs("shadow-boundary blue-neighbour refinement regression failed\n", stderr);
frame_lens_mesh_destroy(&adaptive_mesh);
goto done;
}
frame_lens_mesh_destroy(&adaptive_mesh);
const RayEndpoint flat_probe = {.n_infinity = {1.0, 0.0, 0.0},
.frequency_ratio = 1.0,
.outcome = RAY_OUTCOME_ESCAPED};
/* Opposite nonzero discrete-Jacobian signs on the two sides of the shared
* diagonal require a sufficiently small magnitude before requesting it. */
refine.jacobian_minimum = 10.0;
refine.angle_absolute_rad = 3.14159265358979323846;
refine.angle_relative = 1e6;
if (frame_lens_mesh_build_coarse(&adaptive_mesh, width, height, 100, 30.0))
goto done;
const double source_directions[4][3] = {
{1.0, 0.0, 0.0},
{sqrt(0.99), 0.0, 0.1},
{sqrt(0.99), 0.0, 0.1},
{sqrt(0.98), 0.1, 0.1}};
for (size_t i = 0; i < adaptive_mesh.vertex_count; ++i) {
adaptive_mesh.vertices[i].traced = 1;
adaptive_mesh.vertices[i].outcome = RAY_OUTCOME_ESCAPED;
memcpy(adaptive_mesh.vertices[i].n_infinity, source_directions[i],
sizeof source_directions[i]);
}
if (frame_lens_mesh_prepare_generation(&adaptive_mesh, &refine) != 1 ||
frame_lens_mesh_install_sample(&adaptive_mesh, 0, &flat_probe) ||
frame_lens_mesh_finish_generation(&adaptive_mesh, &refine) != 1 ||
adaptive_mesh.vertex_count != 5 || adaptive_mesh.triangle_count != 4) {
fputs("adaptive fold-parity split regression failed\n", stderr);
frame_lens_mesh_destroy(&adaptive_mesh);
goto done;
}
frame_lens_mesh_destroy(&adaptive_mesh);
/* E/D/U accounting. A UUU triangle must request one merged retry per
* unresolved vertex with the next budget increment, and must not be
* blackened. */
{
LensVertex uuu_vertices[3] = {
{.image_x = 0, .image_y = 0, .outcome = RAY_OUTCOME_UNRESOLVED,
.traced = 1, .continuation_t = -1.0, .continuation_steps = 5,
.continuation_limit = 5},
{.image_x = 10, .image_y = 0, .outcome = RAY_OUTCOME_UNRESOLVED,
.traced = 1, .continuation_t = -1.0, .continuation_steps = 5,
.continuation_limit = 5},
{.image_x = 0, .image_y = 10, .outcome = RAY_OUTCOME_UNRESOLVED,
.traced = 1, .continuation_t = -1.0, .continuation_steps = 5,
.continuation_limit = 5}};
for (int i = 0; i < 3; ++i)
uuu_vertices[i].camera_direction[0] = 1.0;
LensTriangle uuu_triangle = {{0, 1, 2}, 0, 0, 0};
FrameLensMesh uuu_mesh = {.vertices = uuu_vertices,
.vertex_count = 3,
.triangles = &uuu_triangle,
.triangle_count = 1};
RefinementConfig uuu_config = {.max_level = 1,
.angle_absolute_rad = 1.0,
.angle_relative = 1.0,
.jacobian_minimum = 1e-3,
.min_edge_pixels = 1.0,
.min_area_pixels2 = 1.0,
.retry_step_increment = 10,
.max_total_steps = 25};
if (frame_lens_mesh_prepare_generation(&uuu_mesh, &uuu_config) != 3) {
fputs("UUU forced-retry regression failed\n", stderr);
free(uuu_mesh.samples);
free(uuu_mesh.probe_slots);
goto done;
}
for (size_t i = 0; i < uuu_mesh.sample_count; ++i) {
if (uuu_mesh.samples[i].kind != FRAME_SAMPLE_RETRY ||
uuu_mesh.samples[i].step_limit != 15) {
fputs("UUU retry shape regression failed\n", stderr);
free(uuu_mesh.samples);
free(uuu_mesh.probe_slots);
goto done;
}
}
free(uuu_mesh.samples);
free(uuu_mesh.probe_slots);
}
/* UUD/UDD is red-refined while the geometry can still support children.
* At the geometric stop scale it becomes an approximate-black boundary
* triangle while its shared U vertex keeps its unresolved outcome. */
{
LensVertex ud_vertices[3] = {
{.image_x = 0, .image_y = 0, .outcome = RAY_OUTCOME_UNRESOLVED,
.traced = 1, .continuation_limit = 5},
{.image_x = 10, .image_y = 0, .outcome = RAY_OUTCOME_UNRESOLVED,
.traced = 1, .continuation_limit = 5},
{.image_x = 0, .image_y = 10, .outcome = RAY_OUTCOME_DARK,
.traced = 1}};
for (int i = 0; i < 3; ++i)
ud_vertices[i].camera_direction[0] = 1.0;
LensTriangle ud_triangle = {{0, 1, 2}, 0, 0, 0};
FrameLensMesh ud_mesh = {.vertices = ud_vertices,
.vertex_count = 3,
.triangles = &ud_triangle,
.triangle_count = 1};
RefinementConfig red_config = {.max_level = 1,
.angle_absolute_rad = 1.0,
.angle_relative = 1.0,
.jacobian_minimum = 1e-3,
.min_edge_pixels = 0.5,
.min_area_pixels2 = 0.5,
.retry_step_increment = 10,
.max_total_steps = 25};
if (frame_lens_mesh_prepare_generation(&ud_mesh, &red_config) != 3) {
fputs("UUD red-refinement probe regression failed\n", stderr);
free(ud_mesh.probe_slots);
goto done;
}
for (size_t i = 0; i < ud_mesh.sample_count; ++i)
if (ud_mesh.samples[i].kind != FRAME_SAMPLE_PROBE) {
fputs("UUD red-refinement sample-kind regression failed\n", stderr);
free(ud_mesh.probe_slots);
goto done;
}
free(ud_mesh.samples);
free(ud_mesh.probe_slots);
ud_mesh.samples = NULL;
ud_mesh.sample_count = ud_mesh.sample_capacity = 0;
ud_mesh.probe_slots = NULL;
ud_mesh.probe_slot_capacity = 0;
RefinementConfig stop_config = red_config;
stop_config.min_edge_pixels = 1e6;
stop_config.min_area_pixels2 = 1e6;
if (frame_lens_mesh_prepare_generation(&ud_mesh, &stop_config) != 0) {
fputs("UUD stop-scale retry regression failed\n", stderr);
free(ud_mesh.probe_slots);
goto done;
}
FrameBoundaryStats ud_stats;
frame_lens_mesh_boundary_stats(&ud_mesh, &stop_config, &ud_stats);
if (ud_stats.uud_udd != 1 || ud_stats.approx_black_triangles != 1 ||
ud_stats.approx_black_area_pixels2 <= 0.0 ||
ud_stats.escaped_only != 0 || !ud_triangle.approx_black ||
ud_vertices[0].outcome != RAY_OUTCOME_UNRESOLVED) {
fputs("UUD approximate-black regression failed\n", stderr);
free(ud_mesh.probe_slots);
goto done;
}
stop_config = red_config;
stop_config.max_level = 0;
frame_lens_mesh_boundary_stats(&ud_mesh, &stop_config, &ud_stats);
if (ud_stats.approx_black_triangles != 1 ||
ud_stats.approx_black_level_stops != 1 ||
ud_stats.approx_black_max_edge_pixels < 10 ||
ud_stats.approx_black_area_pixels2 != 50) {
fputs("max-level approximate-black provenance regression failed\n",stderr);
goto done;
}
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);
spacetime_destroy(&spacetime);
blackbody_backend_destroy();
free(hdr);
return result;
}