Simplified sphere intersection code.
Add vec3_direction to the mathlib.
Pick the closest intersection point instead of chosing one arbitrarily.
Simplify direction vector calculation code with vec3_direction method.
Fix lighting artifacts due to incorrect light obstruction calculations.

For a visual explanation visit wolframalpha.com and type this in:
ContourPlot[{x^2 + y^2 == 100^2}, {(x-50)^2 + (y-300)^2 == 50^2}, {(x+1.4)^2 + (y-150)^2 == 10^2}, {(x+20)^2 + (y-98)^2 == 10^2}, {(x-37.9)^2 + (y-251)^2 == 10^2}]

Two big circles are the objects, the small one in the middle is the light,
and the other two are ray intersection points. Suppose that the small
circle on the bottom is our shaded pixel; we want to check if there is any
object between that pixel and the light, so we check for intersections
along the line that runs through them. We are only interested in objects that
are BETWEEN the light and the shaded fragment, unfortunately it's not possible to
check for an intersection along a finite line - you can only do it along an
infinite line, hence you're going to get an intersection even if the object
is BEHIND the light.

Once you know what is the problem the fix is pretty easy - you just have to
check whenever the intersection with the obstructing object is further than
the distance to the light.