Why are RaycastHit surface normals of corners not interpolated or otherwise correct?

I am using a BoxCast to test a (custom) kinematic character controller moving through the scene before actually updating the character’s transform. Using a traditional collide and slide algorithm, I project the velocity along the RaycastHit’s surface normal if I hit something, allowing me to slide along angled surfaces, like the cylinder in the below images.

In the first image, the yellow lines indicate where collisions are detected but are correctly projected. Red lines are where collisions are detected but fail to project (but these red lines in the first image are expected, as these are auto-step tests). The red lines all properly indicate the surface normal, which is easier to see on the lines at the bottom of the mesh.

In the second image, the red ray is pointed in the opposite direction (-Z) of my character movement (+Z). This surface normal results in a projection that puts the character controller moving in the opposite direction, cancelling out the movement.

Why would this surface normal be pointed in this direction, and not simply be an interpolation of the two neighbouring surfaces? Is this expected behaviour, and if so, how does one work around something like this to correctly round a corner? While I cannot use a capsule collider for this character controller (due to the cons of a rounded bottom outweighing the pros of a rounded body), I tested this with a capsule anyway and still get what look like incorrect normals, so I can rule out the type of collider being an issue. Any help at this point would be greatly appreciated! Thanks.

I love having to answer my own obscure questions. I still don’t know why this happens, but I have a workaround that is holding up so far, so I hope this helps someone else looking for this.

When I hit something in a BoxCast, I check the surface normal. If the dot product between my movement’s direction and the surface normal is close to -1, I can assume I’m either walking into a flat wall, or, a corner. In my project, I separate horizontal movement from vertical, so I only check this when moving in my “side pass” Either way, I perform a single Raycast (Collider.Raycast, so I’m only checking against that one collider). If I hit the obstacle again, I update my previous collision’s surface normal with the “true” surface normal, because Raycast doesn’t seem to have this issue, only box/capsule casts.

This may not catch every possible case, but so far so good for me! Hope this helps.

// In some cases, walking into a corner can result in a normal that is the inverse of the desired movement
if (pass == EPass.SIDE)
{
   float threshold = -0.98f;
   if (Vector3.Dot(direction, closestHit.normal) <= threshold)
   {
       // Raycast from the volume's center, into the collision point we know we hit before
       Vector3 rayOrigin = m_Collider.transform.position + m_Collider.center;
       Vector3 rayDirection = (closestHit.point - rayOrigin).normalized;
       float rayDistance = Vector3.Distance(closestHit.point, rayOrigin) + m_Params.ContactOffset;
       Ray ray = new Ray(rayOrigin, rayDirection);
 
       // If we were able to hit the collider, grab the "true" surface normal so the volume can project collisions correctly
       if (closestHit.collider.Raycast(ray, out RaycastHit hitInfo, Vector3.Distance(closestHit.point, rayOrigin) + Physics.defaultContactOffset))
       {
           closestHit.normal = hitInfo.normal;
       }
   }
}