Quick answer: Mask positioning uses sprite.rect.topleft. Move & clamp the rect before calling pygame.sprite.collide_mask.

A platformer plays a mask-collision check between player and enemy. Player wraps around screen via clamp_ip. Collision occasionally fires when sprites are visibly apart — mask still uses pre-clamp position.

Mask Position Source

def collide_mask(left, right):
    offset = (right.rect.left - left.rect.left, right.rect.top - left.rect.top)
    return left.mask.overlap(right.mask, offset)

Pygame source: mask test uses each sprite’s rect to position the mask. Update rect first.

Correct Order

class Player(pygame.sprite.Sprite):
    def update(self):
        self.rect.x += self.vx
        self.rect.y += self.vy
        self.rect.clamp_ip(screen.get_rect())

# later in main loop, AFTER all updates:
if pygame.sprite.collide_mask(player, enemy):
    ...

Update all sprites; do all rect movements/clamps; then do collision checks. Eliminates stale-position bugs.

Avoid Recomputing Masks

Masks are derived once: pygame.mask.from_surface(self.image). They don’t change with rect position. Only re-derive if the sprite’s image changes (animation frame).

Rotation Caveat

Rotating a sprite changes the image; the mask must be re-derived. After pygame.transform.rotate, recompute mask:

self.image = pygame.transform.rotate(original, angle)
self.mask = pygame.mask.from_surface(self.image)

Verifying

Visual collisions match logical collisions. No false positives when sprites are clearly apart. No false negatives when sprites overlap.

“Mask follows rect. Move rect first, test second.”

For shmups with hundreds of sprites, mask test is O(area). Use rect-rect prefilter, mask-mask only if rect overlap detected — speeds up dramatically.