OverviewFilesCommitsIssuesDocsReleasesPeers

terrain-lookdev commits / cc23fa7

lookdev: both harness binaries had drifted behind the shared filter
Neither built. Both are consumers of proc-nodes/src/shared, and both were last touched 08-31, before the mip-pyramid work landed in dc-terrain 332df1c on 09-01 — so the instruments were a day behind the thing they exist to measure, and the gates could not run at all. erosion_render.cpp — 14 undeclared-identifier errors, all one cause. The consumer's job changed from "define hblk_height_m" to "declare the storage": erosion_heightmap_sampler_glsl() now emits hblk_sample() and heightmap_gn calls that. So the flat bilinear hblk_height_m is DELETED rather than repaired — nothing called it, and its body still indexed hblk_data[] after the buffer was renamed to custom_data, which is what produced the errors. Added in its place: erosion_heightmap_levels_glsl() for HBLK_LEVELS/OFF/SIDE, a voxel_scale const (the wrapper emits M_VOXEL, a different name, and emits it later, so it cannot be aliased), and the WHOLE pyramid on the SSBO instead of level 0. That last one is #55 section 2.1: with level 0 only, HBLK_OFF[1] points one float past the end of the buffer. The upload sizes itself from the vector, so the count/size pair that produced the 7.9 TB allocation cannot desync here. erode_heightmap.cpp — one argument behind erosion_node_eval_full, which gained max_octaves (the fractional octave limit the engine sets per chunk from the voxel size). Passes 999 = no taper, which is what the call did before the parameter existed. This is the binary the lookdev's erosion panel shells out to, so the panel was dead too. Both verified, not assumed: erosion_render --verify matches the parity oracle at 0.0042 m worst (unchanged, and that path takes no heightmap, so it is a control on the edit); erode_heightmap --check identity 0.000e+00. gate_heightmap then passes for the first time — identity 0.005 m, control discriminates at 1078 m, worst seam z 3.48.
cc23fa797fde2cea9509230e9ebb73079ec424fb
by plankatron <fleeky@gmail.com> · 2026-09-02T13:57:53.000Z

Changed files (2) +43 -25

erode_heightmap.cpp +6 -1
……@@ -119,7 +119,12 @@ static float erode_point(const Opts& o, float h_norm, float dhdx, float dhdz,
119119 vec2(o.height_offset_a, 0.0f),
120120 vec4(0.1f, 0.0f, o.rounding_input, 2.0f),
121121 vec4(1.25f, 1.25f, 2.8f, 1.5f),
122 vec2(0.7f, 1.0f));
122 vec2(0.7f, 1.0f),
123 // max_octaves: the FRACTIONAL octave limit erosion_node_eval_full
124 // gained after this tool was last built. The engine sets it per
125 // chunk from the voxel size; this tool has no voxel, so 999 = no
126 // taper, which is what the call did before the parameter existed.
127 999.0f);
123128 if (ridge) *ridge = en.y;
124129 // invert the remap: the filter returned n.x*0.5+0.5 plus its delta.
125130 return (en.x - 0.5f) * 2.0f;
erosion_render.cpp +37 -24
……@@ -92,31 +92,40 @@ static HeightBlock read_hblk(const char* path) {
9292 return b;
9393}
9494
95// The lookup the shared wrapper's heightmap_gn requires. Bilinear, clamped at
96// the edge -- the apron ring means the clamp is only reached OUTSIDE the block's
97// footprint, so the central difference inside it always has real neighbours.
98static std::string hblk_sampler_glsl(const HeightBlock& b) {
99 char buf[2048];
95// THE STORAGE the shared sampler reads -- and nothing else.
96//
97// The split of duties moved. The consumer declares STORAGE; the wrapper
98// declares the SAMPLER: erosion_heightmap_sampler_glsl() emits hblk_texel_l()
99// and hblk_sample(), and heightmap_gn calls those, not any function of ours.
100// So the old flat bilinear hblk_height_m is gone rather than fixed -- nothing
101// called it. (erosion_wrapper_glsl.h:115 still documents the pre-mip contract
102// "the consumer must define float hblk_height_m(...)" and is stale.) Its body
103// also still indexed hblk_data[] after the buffer was renamed to custom_data,
104// which is what produced the undeclared-identifier errors that found this.
105//
106// THE WHOLE PYRAMID, not level 0. custom_data holds every mip level end to
107// end and HBLK_OFF/HBLK_SIDE index into it. Uploading only level 0 is exactly
108// #55 section 2.1: HBLK_OFF[1] then points one float past the end of the
109// buffer, every sample coarser than the block's pitch reads out of bounds and
110// comes back zero, and the terrain is flat wherever the LOD is coarse.
111//
112// voxel_scale is the sampler's LOD selector and has no declaration of its own
113// anywhere: the wrapper emits M_VOXEL, a different name, and emits it AFTER
114// this text, so it cannot be aliased here. It comes from the same
115// wp.voxel_size that feeds M_VOXEL, so the two cannot disagree.
116static std::string hblk_sampler_glsl(const HeightBlock& b,
117 const HeightBlockMips& m,
118 float voxel_size) {
119 char buf[1024];
100120 snprintf(buf, sizeof(buf),
101121 "layout(set=0, binding=1) readonly buffer HBlk { float custom_data[]; };\n"
102122 "const int HBLK_N = %d;\n"
103123 "const float HBLK_OX = %.9g;\n"
104124 "const float HBLK_OZ = %.9g;\n"
105125 "const float HBLK_MPP = %.9g;\n"
106 "float hblk_height_m(float x, float z) {\n"
107 " float u = (x - HBLK_OX) / HBLK_MPP;\n"
108 " float v = (z - HBLK_OZ) / HBLK_MPP;\n"
109 " u = clamp(u, 0.0, float(HBLK_N - 1));\n"
110 " v = clamp(v, 0.0, float(HBLK_N - 1));\n"
111 " int i0 = int(floor(u)); int j0 = int(floor(v));\n"
112 " int i1 = min(i0 + 1, HBLK_N - 1); int j1 = min(j0 + 1, HBLK_N - 1);\n"
113 " float fu = u - float(i0); float fv = v - float(j0);\n"
114 " float h00 = hblk_data[j0*HBLK_N + i0]; float h10 = hblk_data[j0*HBLK_N + i1];\n"
115 " float h01 = hblk_data[j1*HBLK_N + i0]; float h11 = hblk_data[j1*HBLK_N + i1];\n"
116 " return mix(mix(h00, h10, fu), mix(h01, h11, fu), fv);\n"
117 "}\n",
118 b.n, (double)b.ox, (double)b.oz, (double)b.mpp);
119 return std::string(buf);
126 "const float voxel_scale = %.9g;\n",
127 b.n, (double)b.ox, (double)b.oz, (double)b.mpp, (double)voxel_size);
128 return std::string(buf) + erosion_heightmap_levels_glsl(m);
120129}
121130
122131struct PushC {
……@@ -132,7 +141,8 @@ struct PushC {
132141static std::string build_render_shader(const std::string& inc,
133142 const ErosionWrapperParams& wp,
134143 int steps, int bisect,
135 const HeightBlock* hb = nullptr) {
144 const HeightBlock* hb = nullptr,
145 const HeightBlockMips* hbm = nullptr) {
136146 std::string s =
137147 "#version 450\n"
138148 "layout(local_size_x = 8, local_size_y = 8) in;\n"
……@@ -143,8 +153,8 @@ static std::string build_render_shader(const std::string& inc,
143153 "\n";
144154 s += inc;
145155 s += "\n";
146 // Must precede the wrapper: heightmap_gn calls hblk_height_m.
147 if (hb) s += hblk_sampler_glsl(*hb);
156 // Must precede the wrapper: the sampler the wrapper emits reads these.
157 if (hb) s += hblk_sampler_glsl(*hb, *hbm, wp.voxel_size);
148158 s += erosion_wrapper_glsl(wp);
149159
150160 char tail[2048];
……@@ -382,6 +392,7 @@ int main(int argc, char** argv) {
382392 std::string inc = read_file(inc_path);
383393
384394 HeightBlock hb;
395 HeightBlockMips hbm;
385396 bool use_hblk = (hblk_path != nullptr);
386397 if (use_hblk) {
387398 hb = read_hblk(hblk_path);
……@@ -393,16 +404,18 @@ int main(int argc, char** argv) {
393404 ox = hb.ox + hb.mpp;
394405 oz = hb.oz + hb.mpp;
395406 }
407 hbm = erosion_heightmap_build_mips(hb.h.data(), hb.n);
396408 printf("heightfield: %s\n", hblk_path);
397409 printf(" %d x %d px at %g m/px, world x %.0f z %.0f\n",
398410 hb.n, hb.n, hb.mpp, hb.ox, hb.oz);
399411 }
400412
401413 std::string src = build_render_shader(inc, wp, steps, bisect,
402 use_hblk ? &hb : nullptr);
414 use_hblk ? &hb : nullptr,
415 use_hblk ? &hbm : nullptr);
403416
404417 Renderer r;
405 if (use_hblk) r.hblk = hb.h;
418 if (use_hblk) r.hblk = hbm.data;
406419 r.build(src);
407420
408421 if (verify) {