Skip to content

Normalize Dock gesture activation across displays#77

Open
al1ych wants to merge 17 commits into
jurplel:mainfrom
al1ych:codex/fix-high-refresh-gesture-velocity
Open

Normalize Dock gesture activation across displays#77
al1ych wants to merge 17 commits into
jurplel:mainfrom
al1ych:codex/fix-high-refresh-gesture-velocity

Conversation

@al1ych

@al1ych al1ych commented Jun 10, 2026

Copy link
Copy Markdown

Summary

  • normalize non-instant speed presets with one display-aware formula instead of a preset lookup table
  • preserve Normal as the original raw gesture behavior on every display: velocity 40 and near-zero progress
  • scale only the speed offset above Normal by the target display refresh rate relative to the slowest active display
  • split the synthetic gesture into animation and commit phases:
    • Began / Changed keep animation velocity and animation progress
    • Ended gets explicit commit velocity/progress so Dock activates the target Space immediately
  • keep Fast / Faster / Fastest distinct while compensating high-refresh displays such as 240Hz vs 120Hz
  • keep Instant and intentionally higher multi-space velocities unchanged
  • resolve the target display through the selected ISSSpaceInfo.displayID, not by re-reading cursor position during gesture posting

Root cause

There are two separate Dock behaviors involved:

  • Began / Changed fields control perceived animation speed
  • Ended fields control whether Dock commits/activates the target Space immediately

Using one set of fields for every phase either made animation speed diverge between displays or failed to activate the target Space immediately on one display. The current fix treats the final Ended event as an explicit commit phase while leaving Changed as the visual animation phase.

The velocity fix treats 40 as the invariant normal baseline and scales only the non-normal animation velocity offset:

base = 40 + offset * 0.45 + offset * offset * 0.0175
refreshScale = targetDisplayRefreshRate / baselineRefreshRate
changedVelocity = 40 + (base - 40) * refreshScale

The commit phase then applies a separate floor:

endedVelocity = max(changedVelocity, 80 * max(refreshScale, 1))
changedProgress = 0.09
endedProgress = 0.35

baselineRefreshRate is the minimum active display refresh rate. If refresh rate lookup fails, scale falls back to 1.0.

Formula

On a baseline 120Hz display:

Normal:  Changed 40 / FLT_TRUE_MIN, Ended 40 / FLT_TRUE_MIN
Fast:    Changed 46.25 / 0.09,      Ended 80 / 0.35
Faster:  Changed 56 / 0.09,         Ended 80 / 0.35
Fastest: Changed 86 / 0.09,         Ended 86 / 0.35
Instant: 2000 -> 2000

On a 240Hz display with 120Hz baseline:

Normal:  Changed 40 / FLT_TRUE_MIN, Ended 40 / FLT_TRUE_MIN
Fast:    Changed 52.5 / 0.09,       Ended 160 / 0.35
Faster:  Changed 72 / 0.09,         Ended 160 / 0.35
Fastest: Changed 132 / 0.09,        Ended 160 / 0.35
Instant: 2000 -> 2000

Began, Instant, and high multi-space gestures keep near-zero progress.

Commit evolution

  • 88459e1 introduced linear refresh-ratio scaling, but this still generated different activation behavior across displays.
  • cc05f92 used Instant velocity for Ended; it activated immediately but collapsed every speed option into Instant.
  • 2000f2c restored non-instant animation and added progress, but progress still differed by display because it was derived from refresh-scaled velocity.
  • f6a0cae removed refresh-dependent gesture generation and normalized all displays onto a shared Dock activation curve.
  • 07f681b widened the non-instant curve, but the upper presets were still too close for perceived speed differences.
  • 7f605f6 expanded the upper presets more aggressively, but Fast became too fast.
  • 6561c3b softened the curve and lowered fixed non-instant progress.
  • 3d0ec20 slowed the whole non-instant curve further after all presets still felt too fast.
  • 8b8b7e5 anchored Normal back to the original raw gesture and halved the remaining fast-preset curve.
  • fe1bd65 applies refresh-rate normalization only to the velocity offset above Normal, using the selected display's UUID.
  • 9a4b823 applied refresh-rate normalization to all non-instant progress; this improved commit but made perceived speed diverge.
  • 510465a limited progress scaling to Ended, but the commit phase was still not explicit enough.
  • f56c2ea makes Ended a dedicated commit phase with commit velocity floor and commit progress.

Verification

  • swift test --disable-sandbox: 38 tests passed
  • ./dist/build.sh --debug: universal debug app built successfully
  • dev bundle rebuilt and relaunched as build/InstantSpaceSwitcherDev.app
  • dev bundle uses bundle id com.interversehq.InstantSpaceSwitcher.dev, stable Apple Development signing, and GitCommitHash f56c2ea
  • runtime event-tap verification for Faster on the current 120Hz setup confirmed:
    • Began: velocity=56, progress=FLT_TRUE_MIN
    • Changed: velocity=56, progress=0.09
    • Ended: velocity=80, progress=0.35
  • unit tests cover 120Hz/240Hz formula outputs and phase-specific commit fields
  • none of the non-instant presets posts 2000

Fixes #75

@al1ych
al1ych force-pushed the codex/fix-high-refresh-gesture-velocity branch from 7b75857 to b3d4e42 Compare June 10, 2026 20:05
@al1ych
al1ych force-pushed the codex/fix-high-refresh-gesture-velocity branch from b3d4e42 to 743db83 Compare June 10, 2026 20:08
@al1ych
al1ych marked this pull request as draft June 10, 2026 20:10
@al1ych
al1ych marked this pull request as ready for review June 11, 2026 01:10
@al1ych al1ych changed the title Scale gesture velocity on high-refresh displays Normalize Dock gesture activation across displays Jun 11, 2026
@al1ych

al1ych commented Jun 14, 2026

Copy link
Copy Markdown
Author

@zenangst @stitch @jurplel

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Animation speed option fails on some monitors

1 participant