Really noticeable input delay with getCrankPosition()

Hi, I was working on a rhythm game, and noticed quite a bit of input delay on the crank. I asked around on the discord, and most agreed that the value is somehow interpolated. Is there any way to get the raw value of the crank position, or is this delay caused by hardware?

Video of the problem in regular speed

Video of the problem in slow motion

Code download

1 Like

What refresh rate is your game running at?

Does running at 50 or 0 (unlimited) change things?

edit: source says 0 (unlimited)

The lag is present equally no matter what the refresh rate is, sorry for not mentioning earlier. In the source I sent though, it is set to 0.

1 Like

Update: My guess was inaccurate — see this post instead.

I think this is drawing lag, not input lag. If you crank very slowly, there's no perceptible lag. It's only when you make a large movement that it appears to fall behind.

The reason for this is that you're trying to draw every "frame" of the crank's movement. But your frame rate doesn't increase with cranking speed, so as the crank turns faster you'll either have to drop frames or sacrifice smoothness. Fortunately, our eyes are accustomed to filling in the gaps for us when the motion is fast enough — this is how traditional animation works. It's really only important for motion to be smooth when it's slow.

So rather than polling getCrankPosition() every update, it would be better to implement the playdate.cranked() callback and set variables representing the positions where your images should be drawn next. Then in playdate.update() you just draw them at those positions, not worrying about input at all. That should help your animation feel synchronized with the crank motion at all speeds.

3 Likes

Thanks, this makes a lot of sense

Ah thanks for clearing this up!

While this makes sense once it's explained, I dont think it's a super obvious conclusion one would come to.

As such I think it would be super helpful to include this information in some shape or form in either the "Designing for Playdate" doc or for the getCrankPosition() function specifically.

2 Likes

I noticed a lag when using "Use device as a controller". I can see the lag between the actual physical crank, and the crank angle indicator on the simulator.
Is this normal?

Yeah, the crank indicator in the Simulator can lag under heavy load, but it's not necessarily your code's fault. Does the lag occur when your game is paused (e.g. the system menu is open) or not running?

Video Imgur: The magic of the Internet

I'm only running this demo.
Lua/08_Crank · main · Rachel Wil Sha Singh / playdate-tests · GitLab
This minimal code is still showing crank lag.
Uploading the game into the device and using the device as controller, both cases show the lag.

In fact, even with this empty game, the simulator is lagging behind the real crank


local gfx = playdate.graphics
playdate.display.setRefreshRate( 0 )

function playdate.update()

end

1 Like

I'm not sure I understand this. If the screen is being refreshed 50 times a second, and the crank position is being polled every frame, then how can they not be completely in sync? Either getCrankPosition returns the exact current position, or it doesn't. If it was drawing lag then you would be able to observe the same delays with button inputs as well. In the system input test you can see the crank has some kind of smoothing applied, like if you quickly flick the crank up or down it will take a small amount of time to smoothly lerp to the correct angle. I don't know if this is software smoothing, or maybe a limitation of the sensor. Either way, I'm not saying it's a major issue. It's not perceptible most of the time unless you are trying to make very quick precise movements with the crank.

2 Likes

I agree, I don't think this is a refresh rate issue.
Here is another video with the program on the device itself Imgur: The magic of the Internet
Notice when the crank is tapped, how long it takes for the crank-change to settle to 0.
And the change of speed is also different. The tap made an abrupt start and stop of the physical crank, but in the display the movement have an ease-in and ease-out.

I think all of these behaviors are caused by averaging of the input. It seems the input values are averaged over 100-200ms. Hopefully they will allow us to read the raw values.

I also tested this with the buttons (not shown in the video), there is no such lag, or at least it is very minimal.

2 Likes

The sensor can update at 3.3kHz, or 100Hz in low power mode or 10Hz in ultra-low power mode (https://www.infineon.com/dgdl/Infineon-TLV493D-A1B6-DataSheet-v01_10-EN.pdf?fileId=5546d462525dbac40152a6b85c760e80)

Maybe it’s using 10Hz and smoothing the in-between frames? :thinking:

Yeah, for most purposes it doesn’t matter, but for a precise rotary rhythm game, the way it currently lags is a deal breaker.

After a bit of testing I think the delay is more around 250ms to 300ms between stable position and stable value which is not negligible. But it is much faster to get a smoothed value that is reasonably close to the raw value so in practice the lag might be more around 100ms.

This looks like the firmware is simply smoothing the raw values from the sensor to avoid jittering values but I feel this is a tad too strong.

I will create a feature request to tweak the amount of smoothing.

2 Likes

Good news, everyone: I was misinformed. We sample the crank angle at 100Hz regardless of refresh rate, but we are smoothing the value in firmware before reporting it (we compute a moving average of the most recent n samples). Without the smoothing, the signal is too noisy at the precision we need, especially for small angle changes. But we may be able to solve this by reducing the smoothing when cranking faster. Will report back when I know more. Sorry for the misdirect!

6 Likes