// ============================================================================
// EROSION HEIGHTFIELD DUMPER — our generator's surface, as a raw grid, so the
// SAME spectrum probe can be run on it as on terrain-diffusion's output.
//
// WHY IT INCLUDES A TEST FILE
//
// The CPU wrapper (map_dc_erosion's terrain layers + erosion_node_eval + the
// detail layer + the y-mapping) is not reachable outside a running engine;
// test_erosion_parity.cpp already reconstructs it, with every constant
// annotated by file and line, and that reconstruction is GUARDED green by the
// parity oracle against the GPU. Copying it here would create a fifth copy of
// the thing #45 spent a session reducing to one, and it would drift silently.
//
// So this file includes that translation unit and calls its `cpu_wrapper` and
// `find_surface` directly. `main` is renamed out of the way. If the parity
// oracle's reconstruction changes, this dumper changes with it -- which is the
// property we want.
//
// Build (no engine, no godot-cpp, no scons, nothing into bin/):
// g++ -O2 -std=c++17 -I ../proc-nodes/src/shared -I ../dc-terrain \
// -o /tmp/dump_erosion dump_erosion_heightfield.cpp -lvulkan
//
// Run (from the dc-terrain root, so the default --inc path resolves):
// /tmp/dump_erosion --n 512 --metres-per-px 30 --out /tmp/erosion.f32
//
// Output: N*N float32 little-endian surface heights in metres, row-major.
// A column with no surface writes NAN and is counted in the summary.
// ============================================================================
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <vector>
#define main parity_main_unused
#include "tests/test_erosion_parity.cpp"
#undef main
int main(int argc, char** argv) {
int n = 512;
float mpp = 30.0f; // metres per pixel — match the diffusion tile
float ox = 0.0f, oz = 0.0f;
const char* out = "/tmp/erosion.f32";
float voxel = -1.0f; // default: band-limit to the sampling grid
for (int i = 1; i < argc; i++) {
if (!strcmp(argv[i], "--n") && i + 1 < argc) n = atoi(argv[++i]);
else if (!strcmp(argv[i], "--metres-per-px") && i+1<argc) mpp = (float)atof(argv[++i]);
else if (!strcmp(argv[i], "--ox") && i + 1 < argc) ox = (float)atof(argv[++i]);
else if (!strcmp(argv[i], "--oz") && i + 1 < argc) oz = (float)atof(argv[++i]);
else if (!strcmp(argv[i], "--out") && i + 1 < argc) out = argv[++i];
else if (!strcmp(argv[i], "--voxel") && i + 1 < argc) voxel = (float)atof(argv[++i]);
else { fprintf(stderr, "unknown arg %s\n", argv[i]); return 2; }
}
// BAND-LIMIT THE GENERATOR TO THE SAMPLING GRID. The erosion filter runs
// octaves down to the voxel size; sampling that on a 30 m grid would alias
// fine octaves into low bins and corrupt exactly the spectrum we are
// measuring. The filter already has Nyquist octave clamping keyed to
// MAP_VOXEL_SIZE (erosion_filter.inc nyquist_octaves), so setting it to
// 2*pixel makes the generator itself band-limited -- the same thing it does
// at a coarse LOD. Override with --voxel to sample it unclamped on purpose.
MAP_VOXEL_SIZE = (voxel > 0.0f) ? voxel : 2.0f * mpp;
printf("erosion heightfield: %dx%d px @ %g m/px = %.2f km across\n",
n, n, mpp, n * mpp / 1000.0f);
printf(" origin (%g, %g) m voxel size %g m detail scale %g\n",
ox, oz, MAP_VOXEL_SIZE, MAP_DETAIL_SCALE);
std::vector<float> h((size_t)n * n);
int misses = 0;
double lo = 1e30, hi = -1e30, sum = 0.0;
for (int j = 0; j < n; j++) {
for (int i = 0; i < n; i++) {
float x = ox + (float)i * mpp;
float z = oz + (float)j * mpp;
float y;
if (find_surface(cpu_wrapper, x, z, y)) {
h[(size_t)j * n + i] = y;
if (y < lo) lo = y;
if (y > hi) hi = y;
sum += y;
} else {
h[(size_t)j * n + i] = NAN;
misses++;
}
}
if ((j & 63) == 0) { printf("\r row %d/%d", j, n); fflush(stdout); }
}
printf("\r done \n");
long hits = (long)n * n - misses;
printf(" surface found in %ld of %d columns (%d misses)\n", hits, n * n, misses);
if (hits > 0)
printf(" height min %.1f m max %.1f m mean %.1f m\n",
lo, hi, sum / (double)hits);
FILE* f = fopen(out, "wb");
if (!f) { perror("fopen"); return 1; }
fwrite(h.data(), sizeof(float), h.size(), f);
fclose(f);
printf(" wrote %zu floats to %s\n", h.size(), out);
return misses == n * n ? 1 : 0;
}