SpatiallyPartitioned was calculating larger that necessary bounding regions as this was simple to do and does not impact correctness. By being careful to calculate the upper bounds exactly we can avoid checking more bins than we need to during updates and queries.
Items with no size act unexpectedly as they can fail to be returned when querying partition bins as they do not intersect along the top or left edges of the bin. We prevent such items being added in the first place to avoid this scenario.
As a side effect - we must now prevent any Immobile items that do not have size from being added to the screen map.
If a search in a spatial partition is taking place entirely within a single bin, the cost of tracking possible duplicate items with a set can be avoided for a small speedup.
Reduce code duplication by extracting a common class to deal with spatial partitioning of actors, and use some (cached) delegates to reduce duplication further without affecting performance too much.
Speed up updates and removal of actors by caching their location so we only need to update or remove them from bins they are actually in (typically very few), compared to having to check every bin for removals which is much more work in comparison.
Speed up checking for actors inside a region by checking if items are located entirely within the bin they are located in. If so, we don't need to add them to the hash-set for de-duplication purposes which is fairly expensive.