4dc55b1af3
Step 2a green on all four counts, including FSurfaceColumnSource bit-identical to GetSurfaceDensity over 20000 samples. FOverhangShelfMod is the case that justifies where the two spaces were split: its uphill reach grows with altitude (Frac = (Z - TerrainZ) / OverhangHeight), so it is essentially Z-dependent and could not have lived in VoxelHeightOp.h. The boundary falls where the code changes nature. It needs TerrainZ plus a per-column gate (amp, uphill dir) the source computes. Recomputing per voxel would pay the cliff's four resamples per lip voxel; adding a third channel to FVoxelOpSample would put a COLUMN property in a per-voxel slot and pollute a shared contract (section 11 has that open). Instead the source memoises the column and the overhang reads it — the same shape as cliff -> structural. The memo is keyed on (InstanceId, X, Y) with InstanceId from a monotonic atomic counter, not on `this`: a freed stack and a newly allocated one can share an address, a never-decreasing counter cannot collide. The stack evaluates every Z of a column at one XY, so the hit rate is ~1 and this recovers per-column reuse without inventing a second cross-chunk cache. ComputeSurfaceColumn and SurfaceDensityFromColumn are now public: they are the only oracle for the overhang, since GetSurfaceDensity passes OverhangAmp = 0. Private declarations removed. The test's third pass places half its samples inside the overhang window on purpose — a uniform Z draw would almost never hit it and the test would pass having never run the op, the same trap as WaterLevelRelative in the height pass. The in-window count is reported and warns at zero. Still missing before wiring: biome blending (the Mask combiner, section 5's Phase 3 prototype). Do not tick bUseOperatorStack on a SurfaceWorld strate with biomes yet. UNVERIFIED: not compiled. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>