Skip to content

Click effects land off the drawn cursor under cursor smoothing #1335

Closed

Description

@EtienneLescot

Problem

With the default cursor smoothing (0.67), the flat cursor sprite trails the recorded pointer
by about 190 ms. Every click effect is anchored to the raw click position, so when a click
follows a move closely, the effects land beside the cursor:

  • the impact ring and the click shader effects (ctx.clicks) sit on the raw point;
  • ctx.cursor, given to the same effects, sits on the lagging smoothed point, so an effect
    reading both sees them disagree;
  • auto zooms frame the raw click while the cursor arrives late.

It shows most on scripted takes (eased move, click about 100 ms after the stop) and on fast
human clicks. A human who dwells before clicking hides it: the spring has caught up.

Root cause

crates/compositor/src/frame_geometry.rs, CursorPlacer::at:

fn at(&self, t: f32) -> Option<(f32, f32)> {
    if self.model3d { self.track.pinned_at(t) } else { self.track.at(t) }
}

The 3D cursor already converges on the click (CursorTrack::pinned_at: eases onto the raw click
point over 250 ms, holds it 100 ms, releases over 250 ms). The flat sprite, the default, skips
it and draws track.at(t), the smoothed spring output.

Measurement

Exact port of CursorTrack::smoothed() (240 Hz spring) on two real sidecars, 1920×1080 output.
Distance between the drawn cursor and the click point, at the click.

Demo take (Playwright, eased move, 2 clicks)

smoothing spring lag (c/k) offset at click max offset while moving
0 0 0 px 0 px
0.1 57 ms 3 px 209 px
0.3 95 ms 18 to 22 px 328 px
0.5 171 ms 85 to 119 px 499 px
0.67 (default) 189 ms 106 to 150 px 534 px
1 233 ms 155 to 224 px 602 px

Human take (WGC capture, dwell before each click): 0 px at click for every value.

Also noted: the curve jumps at 0 (passthrough, then 45 ms of lag at 0.01), and the slider's
upper half only covers a third of the spring's extended range.

Proposed fix

  1. Pin the flat sprite too. CursorPlacer::at always returns pinned_at(t). The drawn
    cursor, ctx.cursor, ctx.clicks, the bounce and the impact ring then meet at the click,
    at any smoothing value. One line, both renderers share it, and pinned_at already has its
    test (pinned_at lands on the raw point at 0, 0.5 and 1).
  2. Close clicks each keep the tip. pinned_at starts a click's approach only after the
    previous click's hold: with clicks 200 ms apart, the next approach pulled the tip off the
    first click at its own instant.
  3. Lower the default from 0.67 to 0.3 (src/lib/projectDefaults.ts), for new projects
    only: about 95 ms of lag, still removes capture tremor. Saved projects keep their value.

Not in scope: the auto-follow camera keeps its own damping (follow_at), by design.

Activity

  1. EtienneLescot commented on Oct 21, 2026

    @EtienneLescot
    MemberAuthor

    Fixed on main in #1336: the flat cursor now lands on each click like the 3D cursor, so the drawn tip, the impact ring and ctx.clicks meet whatever the smoothing, and new projects start at 0.3 smoothing. It will ship with the next release. Thanks for the report!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions