I'm unsure how to approach the next step in my marching cubes terrain generator. I have each chunk reading the appropriate noise values in its cubic area, fetched from a dictionary of points of the form points[Vector3(16, 32, 64)]: With everything else pretty much optimized the result is predictable and looking good!

As you can see I get an infinite maze of caves. It's pretty but I obviously want this to only happen underground: At the surface, starting around height level 0 (water level later), I wish to have a flat surface with casual cave entrances otherwise flat meadows or mountains... not counting oceans yet as water isn't implemented but the biome for it should ideally be there.
Obviously this happens because I use read from one noise object directly. My question is how should I interpret this noise, possibly mixing it with another noise, to get a proper landscape at the surface? In other words what should I add to:
var noise = OpenSimplexNoise.new()
noise.seed = randi()
noise.octaves = 3
noise.lacunarity = 2
noise.period = 16
noise.persistence = 0.5
func _get_noise(pos):
return noise.get_noise_3dv(pos)