That’s because of interpolation.
I’m not sure if that’s the correct interpolation behavior, but heatmap with interpolate=true looks the same.
You can do:
f, ax, s = surface(X, Y, Z, color=C, lowclip=:blue, interpolate=false, colorrange=(1, 4), shading=NoShading)
But currently interpolate got lost on Makie@0.21 due to the attribute validation refactor.
Will try to add it back in the next release.