I can (finally!) confirm that this is a bug. I don't have a fix yet but I did make a nice little tester app to make it easier to visualize:

If anyone is curious, here is the code for the tester app (it's kind of fun to play with!):
import 'CoreLibs/graphics'
import 'CoreLibs/sprites'
local gfx <const> = playdate.graphics
local geo <const> = playdate.geometry
local wall1 = gfx.sprite.new()
wall1:setBounds(300, 50, 10, 150)
wall1:setCollideRect(0, 0, 10, 150)
wall1:add()
function wall1:draw(x, y, width, height)
gfx.fillRect(x, y, width, height)
end
local wall2 = gfx.sprite.new()
wall2:setBounds(150, 190, 150, 10)
wall2:setCollideRect(0, 0, 150, 10)
wall2:add()
function wall2:draw(x, y, width, height)
gfx.fillRect(x, y, width, height)
end
local wall3 = gfx.sprite.new()
wall3:setBounds(140, 70, 10, 130)
wall3:setCollideRect(0, 0, 10, 150)
wall3:add()
function wall3:draw(x, y, width, height)
gfx.fillRect(x, y, width, height)
end
local distance = 300
local angle = 110
local playerOrigin = geo.point.new(225, 170)
local player = gfx.sprite.new()
player:setSize(10, 10)
player:setCollideRect(0, 0, 10, 10)
player:moveTo(playerOrigin)
player.collisionResponse = gfx.sprite.kCollisionTypeBounce
player:add()
function player:draw(x, y, w, h)
gfx.fillCircleInRect(x, y, w, h)
end
local function positionForAngle(length, angle)
angle = (angle + 270) % 360 -- rotate -90° so that 0° is north instead of east
local rad = math.rad(angle)
local x = length * math.cos(rad)
local y = length * math.sin(rad)
return x, y
end
function playdate.update()
gfx.clear()
if playdate.buttonIsPressed( playdate.kButtonUp ) then
playerOrigin:offset(0, -2)
end
if playdate.buttonIsPressed( playdate.kButtonRight ) then
playerOrigin:offset(2, 0)
end
if playdate.buttonIsPressed( playdate.kButtonDown ) then
playerOrigin:offset(0, 2)
end
if playdate.buttonIsPressed( playdate.kButtonLeft ) then
playerOrigin:offset(-2, 0)
end
player:moveTo(playerOrigin)
gfx.sprite.update()
local dx, dy = positionForAngle(distance, angle)
gfx.drawLine(player.x, player.y, player.x + dx, player.y+dy)
gfx.drawCircleAtPoint(player.x + dx, player.y+dy, 5)
-- draw midpoints
gfx.setPattern({ 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55 })
local step = 12
for i = step, distance, step do
player:moveTo(playerOrigin)
local dx, dy = positionForAngle(i, angle)
actualX, actualY, _, _ = player:moveWithCollisions(player.x + dx, player.y+dy)
gfx.drawCircleAtPoint(actualX, actualY, 5)
end
player:moveTo(playerOrigin)
actualX, actualY, collisions, length = player:moveWithCollisions(player.x + dx, player.y+dy)
gfx.drawText("Collision Count: " .. length, 10, 210)
gfx.fillCircleAtPoint(actualX, actualY, 5)
gfx.setColor(gfx.kColorBlack)
gfx.drawCircleAtPoint(actualX, actualY, 5)
gfx.drawText("Crank to adjust adjust distance", 10, 10)
gfx.drawText("A + Crank to adjust angle", 10, 30)
gfx.drawText("D-pad to move starting point", 10, 50)
gfx.drawText("Distance: " .. string.format("%.1f", distance), 10, 170)
gfx.drawText("Angle: " .. string.format("%.1f", angle), 10, 190)
end
function playdate.cranked(change)
if playdate.buttonIsPressed( playdate.kButtonA ) then
angle += change
else
distance += change
if distance < 0 then distance = 0 end
end
end