Quick answer: A plain pygame.sprite.Group has no guaranteed draw order. Use pygame.sprite.LayeredUpdates and set each sprite’s _layer for deterministic stacking.

A newly-spawned effect sometimes draws under the player, sometimes over — the plain Group draws in whatever internal order it happens to have.

Group Order Is Unspecified

Group.draw() iterates the group’s internal storage. Add order roughly correlates but isn’t a contract — and removing/re-adding sprites scrambles it. Don’t rely on it for layering.

Use LayeredUpdates

group = pygame.sprite.LayeredUpdates()

# layer is read from sprite._layer when added,
# or pass it explicitly:
group.add(background, layer=0)
group.add(player, layer=10)
group.add(effect, layer=20)

LayeredUpdates.draw() renders strictly low layer to high. Spawn order no longer matters.

Change Layer at Runtime

group.change_layer(effect, 5)   # move it behind the player

For dynamic z-changes (an item picked up, a character ducking behind cover), change_layer re-sorts that sprite immediately.

Within a Layer

Sprites sharing a layer still draw in add order among themselves. For per-sprite ordering inside a layer (top-down y-sort), give each a unique fractional layer or use a sort key — e.g. layer = int(sprite.rect.bottom).

Verifying

Spawn effects in any order — they always draw at their assigned layer. Moving a sprite’s layer re-stacks it immediately.

“Plain Group order is luck. LayeredUpdates plus explicit layers makes draw order data, not accident.”

For top-down games, set layer from the sprite’s y each frame — cheap, and you get correct depth sorting for free.