Hey thanks for helping out!
Sorry I didn't include the player code. Was trying not to spam. Interestingly, if I commend out the two lines that add the little guy (just creating an instance of the class and then add()-ing it) then I instead get this slightly better result where the new tile draws over the old one:

The answer there is probably at least simply to not have transparent pixels in the tilesheet, which is fine.
The Player class has no gfx.clear() calls, so still not sure what's causing it. If you are happy to have a look, here's the full project. I can't upload a zip as a new user, apparently, so here is the full code. Thanks a lot!
main.lua
import "CoreLibs/object"
import "CoreLibs/graphics"
import "CoreLibs/sprites"
import "CoreLibs/timer"
import "CoreLibs/math"
import "CoreLibs/crank"
import "CoreLibs/animation"
import "player"
local pd <const> = playdate
local gfx <const> = pd.graphics
tilesheet = gfx.imagetable.new("images/tilemap")
tiles = {1,1,1,1,2,5,3,2,4,4,4,4,10,10,10,10}
tm = gfx.tilemap.new()
tm:setImageTable(tilesheet)
tm:setTiles(tiles, 4)
tm:draw(0, 0)
player = Player(100, 60, gfx.imagetable.new("images/player"))
player:add()
function pd.update()
if pd.buttonJustPressed(pd.kButtonA) then
tm:setTileAtPosition(2, 2, 6)
tm:draw(0, 0)
end
gfx.sprite.update()
pd.timer.updateTimers()
end
player.lua (I assume I've done the animation code in a really silly way as well lol but it does actually work at least haha)
import "CoreLibs/object"
import "CoreLibs/graphics"
import "CoreLibs/sprites"
import "CoreLibs/timer"
import "CoreLibs/math"
import "CoreLibs/animation"
local pd <const> = playdate
local gfx <const> = pd.graphics
class('Player').extends(gfx.sprite)
function Player:init(x, y, spritesheet)
self:moveTo(x, y)
self.walk_spd = 2
self.anim_spd = 100
self.setImageTable = spritesheet
self.currentAnimation = gfx.animation.loop.new()
self.walkDownAnimation = gfx.animation.loop.new()
self.walkDownAnimation:setImageTable(self.setImageTable)
self.walkDownAnimation.startFrame = 3
self.walkDownAnimation.endFrame = 4
self.walkDownAnimation.delay = self.anim_spd
self.walkUpAnimation = gfx.animation.loop.new()
self.walkUpAnimation:setImageTable(self.setImageTable)
self.walkUpAnimation.startFrame = 5
self.walkUpAnimation.endFrame = 6
self.walkUpAnimation.delay = self.anim_spd
self.walkLeftAnimation = gfx.animation.loop.new()
self.walkLeftAnimation:setImageTable(self.setImageTable)
self.walkLeftAnimation.startFrame = 9
self.walkLeftAnimation.endFrame = 10
self.walkLeftAnimation.delay = self.anim_spd
self.walkRightAnimation = gfx.animation.loop.new()
self.walkRightAnimation:setImageTable(self.setImageTable)
self.walkRightAnimation.startFrame = 7
self.walkRightAnimation.endFrame = 8
self.walkRightAnimation.delay = self.anim_spd
self.idleAnimation = gfx.animation.loop.new()
self.idleAnimation:setImageTable(self.setImageTable)
self.idleAnimation.startFrame = 1
self.idleAnimation.endFrame = 1
self.idleAnimation.delay = self.anim_spd
self:setImage(self.walkRightAnimation:image())
self.currentAnimation = self.walkRightAnimation
end
function Player:update()
Player.super.update(self)
px = 0
py = 0
--get inputs
if pd.buttonIsPressed(pd.kButtonLeft) then
px = -self.walk_spd
elseif pd.buttonIsPressed(pd.kButtonRight) then
px = self.walk_spd
end
if pd.buttonIsPressed(pd.kButtonUp) then
py = -self.walk_spd
elseif pd.buttonIsPressed(pd.kButtonDown) then
py = self.walk_spd
end
--move
self:moveBy(px, py)
--determine animation
if not (px == 0) then
if px < 0 then
self.currentAnimation = self.walkLeftAnimation
else
self.currentAnimation = self.walkRightAnimation
end
elseif not (py == 0) then
if py < 0 then
self.currentAnimation = self.walkUpAnimation
else
self.currentAnimation = self.walkDownAnimation
end
else
self.currentAnimation = self.idleAnimation
end
self:setImage(self.currentAnimation:image())
end