Sprite leaving artifacts/not being marked dirty properly

Hi - I've made a scene manager and I'm running into an issue where after the transition, some sprites are not being marked dirty properly and are leaving some artifacts behind. Not sure if this is intended behavior, or some sort of bug. I've made a smaller demo code snippet that reproduces the bug. It consists of a single square sprite that you press "right" on the d-pad to move to the right. The expected behavior occurs when you first boot the example, where the square moves to the right leaving nothing behind. Pressing "A" will create a wipe scene transition, and after the transition finishes, and you move the square, it leaves an artifact behind. Highlighting screen updates shows a discrepancy where originally there was a screen update at the starting location of the sprite, but after the transition, there isn't an update. Forcing a screen update at that position removes the artifact as expected. I'm not too sure what the issue is, but a possible guess is it has something to do with creating a sprite behind another sprite?

PlaydateSimulator_GRX0Z4Svsu

import "CoreLibs/graphics"
import "CoreLibs/sprites"
import "CoreLibs/timer"

local pd <const> = playdate
local gfx <const> = playdate.graphics

local squareImage = gfx.image.new(30, 30, gfx.kColorBlack)
local squareSprite = gfx.sprite.new(squareImage)
squareSprite:add()
squareSprite:moveTo(20, 120)

function wipeTransition(startValue, endValue)
    local filledRect = gfx.image.new(400, 240, gfx.kColorBlack)
    local transitionSprite = gfx.sprite.new(filledRect)
    transitionSprite:setClipRect(0, 0, 400, 240)
    transitionSprite:moveTo(200, 120)
    transitionSprite:setClipRect(0, 0, startValue, 240)
    transitionSprite:add()

    local transitionTimer = pd.timer.new(1000, startValue, endValue, pd.easingFunctions.inOutCubic)
    transitionTimer.updateCallback = function(timer)
        transitionSprite:setClipRect(0, 0, timer.value, 240)
    end
    return transitionTimer
end

function transition()
    local transitionTimer = wipeTransition(0, 400)

    transitionTimer.timerEndedCallback = function()
        gfx.sprite.removeAll()
        squareSprite:add()
        squareSprite:moveTo(20, 120)
        transitionTimer = wipeTransition(400, 0)
    end
end

function pd.update()
    if pd.buttonJustPressed(pd.kButtonRight) then
        squareSprite:moveBy(50, 0)
    end

    if pd.buttonJustPressed(pd.kButtonA) then
        transition()
    end

    gfx.sprite.update()
    pd.timer.updateTimers()
end
2 Likes

Ok so I found that by marking every sprite dirty after the transition ends fixes the issue, like so.

local allSprites = gfx.sprite.getAllSprites()
for i=1,#allSprites do
    allSprites[i]:markDirty()
end

So it seems like the bug is related to sprites not being correctly marked as dirty somehow if they're added behind something that has a higher z-index? Not too sure, and this might be expected behavior, but it seems counterintuitive. Hopefully this gives some more direction.

1 Like