There is no normal stored anywhere

Normals from the gradient

A mesh ships its normals in the vertex buffer. A distance field has none — so the marcher asks the field four more times, a hair to each side of the hit, and reads the direction the distance grows fastest. That direction is the normal. The whole trick rests on one number, the offset h, and it fails in two opposite ways.

The four taps
map(p + h,  )
map(p − h,  )
map( , p + h)
map( , p − h)
difference in x
difference in y
The normal all the way round drag the probe
Drag to move the probe
agrees with the true normal wrong by 15° or more
Shaded with those normals what you actually see

Lambert shading, one light, upper‑left, applied to a shell just inside the surface — the only place a marcher ever asks for a normal. Nothing about the geometry changes as you drag h; only the normals do, and this is the whole visible consequence.

The offset h
0.002

Two ways to get it wrong both are common

Too large. The four taps spread out far enough to straddle real features. The normal returned is then an average over a neighbourhood rather than a normal at a point, so corners round off and small detail is smoothed away. Slide h up to 0.1 and watch the box corners melt.

Too small. On a GPU the field is evaluated in 32‑bit floats, which carry about seven decimal digits. Once h is small enough that map(p+h) and map(p−h) round to the same float, their difference is not a derivative at all — it is quantisation noise, and the normal points in an essentially random direction. Slide h down to 0.00001 with the precision box ticked and the shading fills with speckle.

Untick the precision box to run the same thing in the 64‑bit arithmetic that JavaScript uses natively, and the noise disappears entirely — which is exactly why this bug is so confusing when it shows up in a shader and not in the CPU prototype.

Offset h
Normal at the probe
Error against the true normal
Worst error on the outline
Four extra evaluations, once per pixel. Note the cost: recovering a normal means calling map() four more times — six in 3D — after the march has already finished. For a scene whose distance function is a page of blended primitives that is not a rounding error in the frame time, and it is why production raymarchers use the tetrahedron trick, which gets the same normal from four taps instead of six. The deeper point is that the field carries this information at all. A mesh has to be told its normals; a distance field is asked, and it answers correctly for any shape you can write down, including ones nobody could sensibly build a vertex buffer for.