#include "frame.h" #include "lens_map.h" #include "optics.h" #include #include #include #include #include #include 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; } int main(void) { 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, 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/gr_lens_map_test.grlens"; const LensMapFrame saved_frame = {.frame_id = 7, .coordinate_time = 3.0, .proper_time = 2.0, .mesh = mesh}; LensMap loaded_map = {0}; 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, &saved_frame, 1) || lens_map_read(lens_map_path, &loaded_map) || loaded_map.frame_count != 1 || loaded_map.frames[0].frame_id != 7 || loaded_map.width != width || loaded_map.height != height || 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, 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, &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); 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, NULL, &min_y_stats, 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, 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, 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); 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, 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, .status = RAY_ENDPOINT_ESCAPED}, {.image_x = 48, .image_y = 40, .status = RAY_ENDPOINT_ESCAPED}, {.image_x = 40, .image_y = 48, .status = RAY_ENDPOINT_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, 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].status = RAY_ENDPOINT_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, .status = RAY_ENDPOINT_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].status = RAY_ENDPOINT_ESCAPED; adaptive_mesh.vertices[i].n_infinity[0] = 1.0; } adaptive_mesh.vertices[0].status = RAY_ENDPOINT_CAPTURED; 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].status = RAY_ENDPOINT_ESCAPED; adaptive_mesh.vertices[i].n_infinity[0] = 1.0; } adaptive_mesh.vertices[3].status = RAY_ENDPOINT_CAPTURED; adaptive_mesh.vertices[5].status = RAY_ENDPOINT_CAPTURED; 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, .status = RAY_ENDPOINT_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].status = RAY_ENDPOINT_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); result = 0; done: frame_lens_mesh_destroy(&mesh); spacetime_destroy(&spacetime); blackbody_backend_destroy(); free(hdr); return result; }