OverviewFilesCommitsIssuesDocsReleasesPeers

dc-terrain commits / d1d252e

tests: emit the wrapper from the shared header, and name the one place the map's constants live
Two changes, both driven by a second consumer appearing (a GPU lookdev renderer that needs exactly this wrapper). 1. build_shader no longer writes the wrapper GLSL out inline; it calls erosion_wrapper_glsl() from proc-nodes/src/shared/erosion_wrapper_glsl.h. The assembled shader is byte-identical -- 33840 bytes, checksum 1254537005, before and after -- and the oracle is green (0.00 m, 9/9). The C++ twins above are deliberately NOT hoisted. This oracle's job is to compare a GLSL transcription against a C++ one; sharing both halves would leave it comparing a thing with itself. 2. erosion_wrapper_params_from_map() is now the ONE place the MAP_/GN_/EN_ constants become wrapper parameters. The second exists because the failure it prevents already happened, the same morning. The lookdev renderer hand-copied these values into its own defaults; 156b2e4 moved MAP_FLOOR_Y from -1000 to -10900 to centre the band on zero; the renderer did not follow and was quietly 7.3 km out: x z oracle renderer delta 0.0 0.0 -1059.81 4201.25 5261.0552 10000.0 10000.0 -5038.45 2098.29 7136.7427 Nothing detected that except the renderer's own --verify gate, which checks it against the nine columns this oracle publishes. After routing both consumers through the mapping above, worst |delta| is 0.0042 m -- the limit of the two decimals the oracle prints. The general form, which is this file's whole thesis one level up: a constant copied is a constant that will drift, and the copy is silent. Name the single place it lives and make every consumer go there. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
d1d252e6ef724ea5437cdd920c12a5f6b67926c7
by plankatron <fleeky@gmail.com> · 2026-08-31T16:12:02.000Z

Changed files (1) +49 -80

tests/test_erosion_parity.cpp +49 -80
……@@ -86,6 +86,7 @@
8686#include <vulkan/vulkan.h>
8787
8888#include "erosion_cpp_shim.h" // brings in erosion_filter.inc as C++
89#include "erosion_wrapper_glsl.h" // the map wrapper, emitted once (see that file)
8990
9091#include <cstdio>
9192#include <cstdlib>
……@@ -449,6 +450,42 @@ static uint32_t checksum(const std::string& s) {
449450 return h;
450451}
451452
453// The ONE place the map's constants above become wrapper parameters. It exists
454// because it was got wrong immediately: the lookdev renderer hand-copied these
455// values into its own defaults, MAP_FLOOR_Y moved from -1000 to -10900 in
456// 156b2e4 the same morning, and the renderer was quietly 7.3 km out until its
457// verify gate caught it. A second consumer must call THIS, not retype them.
458static ErosionWrapperParams erosion_wrapper_params_from_map() {
459 ErosionWrapperParams wp;
460 wp.floor_y = MAP_FLOOR_Y;
461 wp.ceiling_y = MAP_CEILING_Y;
462 wp.terrain_height = MAP_TERRAIN_HEIGHT;
463 wp.mountain_scale = MAP_MOUNTAIN_SCALE;
464 wp.mountain_height = MAP_MOUNTAIN_HEIGHT;
465 wp.mountain_gain = MAP_MOUNTAIN_GAIN;
466 wp.mountain_variance = MAP_MOUNTAIN_VARIANCE;
467 wp.mountain_octaves = MAP_MOUNTAIN_OCT;
468 wp.voxel_size = MAP_VOXEL_SIZE;
469 wp.relief_scale = MAP_RELIEF_SCALE;
470 wp.relief_floor = MAP_RELIEF_FLOOR;
471 wp.detail_scale = MAP_DETAIL_SCALE;
472 wp.detail_height = MAP_DETAIL_HEIGHT;
473 wp.detail_gain = MAP_DETAIL_GAIN;
474 wp.detail_octaves = MAP_DETAIL_OCT;
475 wp.noise_lacunarity = MAP_NOISE_LACUNARITY;
476 wp.gn_scale = GN_SCALE;
477 wp.gn_gain = GN_GAIN;
478 wp.gn_detail = GN_DETAIL;
479 wp.en_height_scale = EN_HEIGHT_SCALE;
480 wp.en_strength = EN_STRENGTH;
481 wp.en_slope_power = EN_SLOPE_POWER;
482 wp.en_cell_scale = EN_CELL_SCALE;
483 wp.en_gain = EN_GAIN;
484 wp.en_octaves = EN_OCTAVES;
485 wp.en_erosion_scale = EN_EROSION_SCALE;
486 return wp;
487}
488
452489static std::string build_shader(const std::string& inc) {
453490 std::string s =
454491 "#version 450\n"
……@@ -460,88 +497,20 @@ static std::string build_shader(const std::string& inc) {
460497 s += inc;
461498 s += "\n";
462499
463 // The two wrappers, expressed in GLSL exactly as the C++ above expresses
464 // them. Kept adjacent to their C++ twins in this file so a change to one
465 // that is not made to the other is visible in a single diff hunk.
466 char cfg[4096];
467 snprintf(cfg, sizeof(cfg),
468 "const float M_FLOOR_Y=%.9g, M_CEIL_Y=%.9g, M_TH=%.9g;\n"
469 "const float M_MSCALE=%.9g, M_MHEIGHT=%.9g, M_MGAIN=%.9g;\n"
470 "const float M_MVAR=%.9g;\n"
471 "const float M_VOXEL=%.9g;\n"
472 "const int M_MOCT=%d;\n"
473 "const float M_RSCALE=%.9g, M_RFLOOR=%.9g;\n"
474 "const float M_DSCALE=%.9g, M_DHEIGHT=%.9g, M_DGAIN=%.9g;\n"
475 "const int M_DOCT=%d;\n"
476 "const float M_LAC=%.9g;\n"
477 "const float GN_SCALE=%.9g, GN_GAIN=%.9g;\n"
478 "const int GN_DETAIL=%d;\n"
479 "const float EN_HS=%.9g, EN_STR=%.9g, EN_SP=%.9g, EN_CS=%.9g, EN_GAIN=%.9g;\n"
480 "const int EN_OCT=%d;\n"
481 "const float EN_ESCALE=%.9g;\n",
482 MAP_FLOOR_Y, MAP_CEILING_Y, MAP_TERRAIN_HEIGHT,
483 MAP_MOUNTAIN_SCALE, MAP_MOUNTAIN_HEIGHT, MAP_MOUNTAIN_GAIN,
484 MAP_MOUNTAIN_VARIANCE, MAP_VOXEL_SIZE, MAP_MOUNTAIN_OCT,
485 MAP_RELIEF_SCALE, MAP_RELIEF_FLOOR,
486 MAP_DETAIL_SCALE, MAP_DETAIL_HEIGHT, MAP_DETAIL_GAIN, MAP_DETAIL_OCT,
487 MAP_NOISE_LACUNARITY,
488 GN_SCALE, GN_GAIN, GN_DETAIL,
489 EN_HEIGHT_SCALE, EN_STRENGTH, EN_SLOPE_POWER, EN_CELL_SCALE, EN_GAIN, EN_OCTAVES,
490 EN_EROSION_SCALE);
491 s += cfg;
500 // The two wrappers used to be written out here as GLSL text, adjacent to
501 // their C++ twins above. They now come from erosion_wrapper_glsl.h, which
502 // emits the identical string -- hoisted the moment a SECOND GPU consumer
503 // (the lookdev renderer) needed the same wrapper and would otherwise have
504 // become a third copy. The C++ twins above are deliberately NOT hoisted:
505 // this oracle's job is to compare a GLSL transcription against a C++ one,
506 // and sharing both halves would leave it comparing a thing with itself.
507 //
508 // Verified byte-for-byte at the hoist: 33840 bytes, checksum 1254537005,
509 // before and after.
510 ErosionWrapperParams wp = erosion_wrapper_params_from_map();
511 s += erosion_wrapper_glsl(wp);
492512
493513 s +=
494 "vec3 terrain_offset(float x, float z) {\n"
495 " float inv_scale = 1.0 / M_TH;\n"
496 " float mtn_lim = nyquist_octaves(M_TH / M_MSCALE, M_VOXEL, M_LAC);\n"
497 " vec3 mtn = fractal_multifractal_2d_lod(vec2(x,z)*inv_scale*M_MSCALE, 1.0, M_MOCT, M_LAC, M_MGAIN, M_MVAR, mtn_lim);\n"
498 " float relief_amp = 1.0;\n"
499 " if (M_RSCALE > 0.0) {\n"
500 " vec3 rel = fractal_noise_2d_lod(vec2(x,z)*inv_scale*M_RSCALE, 1.0, 2, 2.0, 0.5, nyquist_octaves(M_TH/M_RSCALE, M_VOXEL, 2.0));\n"
501 " float t = clamp(rel.x*0.5+0.5, 0.0, 1.0);\n"
502 " relief_amp = M_RFLOOR + (1.0-M_RFLOOR)*t;\n"
503 " }\n"
504 " float amp = M_MHEIGHT * relief_amp;\n"
505 " return vec3(mtn.x, mtn.y*M_MSCALE, mtn.z*M_MSCALE) * (amp * 8.0);\n"
506 "}\n"
507 "float detail_offset(float x, float z) {\n"
508 " if (M_DSCALE <= 0.0) return 0.0;\n"
509 " float inv_scale = 1.0 / M_TH;\n"
510 " float y_range = M_CEIL_Y - M_FLOOR_Y;\n"
511 " vec3 det = fractal_noise_2d_lod(vec2(x,z)*inv_scale*M_DSCALE, 1.0, M_DOCT, M_LAC, M_DGAIN, nyquist_octaves(M_TH/M_DSCALE, M_VOXEL, M_LAC));\n"
512 " return det.x * (M_DHEIGHT / y_range);\n"
513 "}\n"
514 "float y_mapped_of(float y, float det_off) {\n"
515 " float y_range = M_CEIL_Y - M_FLOOR_Y;\n"
516 " return (y - M_FLOOR_Y) * (EN_HS / y_range) - det_off * EN_HS;\n"
517 "}\n"
518 "float clip_d(float d, float y) {\n"
519 " d = max(d, (M_FLOOR_Y - y) * 2.0);\n"
520 " d = min(d, (M_CEIL_Y - y) * 2.0);\n"
521 " return d;\n"
522 "}\n"
523 "float gpu_wrapper(float x, float y, float z) {\n"
524 " float inv_scale = 1.0 / M_TH;\n"
525 " float y_range = M_CEIL_Y - M_FLOOR_Y;\n"
526 " float y_mapped = y_mapped_of(y, detail_offset(x, z));\n"
527 " vec2 p = vec2(x*inv_scale, z*inv_scale);\n"
528 " vec3 gn = fractal_noise_2d(p, GN_SCALE, GN_DETAIL, 2.0, GN_GAIN);\n"
529 " vec2 en = erosion_node_eval(p, gn + terrain_offset(x,z), EN_HS, EN_STR, EN_SP, EN_CS, EN_OCT, EN_GAIN, 4.8, EN_ESCALE);\n"
530 " float d = en.x - y_mapped;\n"
531 " d *= (y_range / EN_HS);\n"
532 " return clip_d(d, y);\n"
533 "}\n"
534 "float cpu_wrapper(float x, float y, float z) {\n"
535 " float inv_scale = 1.0 / M_TH;\n"
536 " float y_range = M_CEIL_Y - M_FLOOR_Y;\n"
537 " float y_mapped = y_mapped_of(y, detail_offset(x, z));\n"
538 " vec2 p = vec2(x*inv_scale, z*inv_scale);\n"
539 " vec3 gn = fractal_noise_2d(p, GN_SCALE, GN_DETAIL, 2.0, GN_GAIN);\n"
540 " vec2 en = erosion_node_eval(p, gn + terrain_offset(x,z), EN_HS, EN_STR, EN_SP, EN_CS, EN_OCT, EN_GAIN, 4.8, EN_ESCALE);\n"
541 " float d = en.x - y_mapped;\n"
542 " d *= (y_range / EN_HS);\n"
543 " return clip_d(d, y);\n"
544 "}\n"
545514 "void main() {\n"
546515 " uint i = gl_GlobalInvocationID.x;\n"
547516 " if (i >= pc.count) return;\n"