Quick answer: rect.clamp_ip(bounds) modifies rect, not bounds. The _ip suffix means “in place on self”. If you want a copy, use rect.clamp(bounds).

A player movement clamp uses world.clamp_ip(player). After play, the world rect has shrunk to the player’s size; the player flies off into nothing. The clamp ran on the wrong rect because _ip mutates self, not the argument.

The _ip Convention

Pygame Rect’s in-place methods all follow the same pattern: rect.METHOD_ip(args) modifies rect and returns None. Examples:

The non-_ip versions return new rects without mutation:

The Right Way to Clamp Player to World

# player rect should stay inside world rect
player.rect.clamp_ip(world_rect)
# after: player.rect’s position is clamped, world_rect untouched

Call clamp_ip on the rect you want to change. The argument stays the same.

Common Inversion Pattern

Developers reading “clamp X to Y” sometimes write Y.clamp_ip(X) by mistake — intuitive English, wrong semantics. Mnemonic: in Pygame, verbs act on self. player.clamp_ip(world) — player does the clamping action; player is the one that changes.

Verifying

Print rects before and after the call. With player.clamp_ip(world), player’s position changes; world doesn’t. With world.clamp_ip(player) (the bug), world shrinks. Easy to spot once you log both.

“The _ip suffix means ‘in place on self’. The method acts on the receiver, not the argument. Always.”

When you read someone’s code calling rect_a.clamp_ip(rect_b), mentally rewrite it as “a is clamped to b” — b is the constraint, a is what changes.