docs: def compute(self, now, x, y, angle):
          """
              Call this when vision processing should be enabled
          
              :param now:   The value passed to ``update_sim``
              :param x:     Returned from physics_controller.get_position
              :param y:     Returned from physics_controller.get_position
              :param angle: Returned from physics_controller.get_position
              
              :returns: None or list of tuples of (found=0 or 1, capture_time, offset_degrees, distance).
                        The tuples are ordered by absolute offset from the
                        target. If a list is returned, it is guaranteed to have at
                        least one element in it.
                        
                        Note: If your vision targeting doesn't have the ability
                        to focus on multiple targets, then you should ignore
                        the other elements.
          """
  
          # Normalize angle to [-180,180]
          output = []
          angle = ((angle + math.pi) % (math.pi * 2)) - math.pi
  
          for target in self.targets:
              proposed = target.compute(now, x, y, angle)
              if proposed:
                  output.append(proposed)
  
          if not output:
              output.append((0, now, inf, 0))
              self.distance = None
          else:
              # order by absolute offset
              output.sort(key=lambda i: abs(i[2]))
              self.distance = output[-1][3]
  
          # Only store stuff every once in awhile
          if now - self.last_compute_time > self.update_period:
  
              self.last_compute_time = now
              self.send_queue.appendleft(output)
  
          # simulate latency by delaying camera output
          if self.send_queue:
              output = self.send_queue[-1]
              if now - output[0][1] > self.data_lag:
                  return self.send_queue.pop()