Note to anyone testing this algorithm out, it works well except for a small bug.
Because the $seen matrix is being passed into sub-calls by reference, any changes to it will still be there when the recursive find_words call returns. That messes up further calls to find_words because the seen-matrix is lying, in a sense.
To fix it, just add this line as the last line of the find_words sub (outside of the two for-loops), so that the seen-matrix is properly reset for a given call's ancestors:
$seen->[ $x ][ $y ] = 0;