Skip to content

fix(compositor): clamp the click-bounce cursor size at zero - #680

Merged
EtienneLescot merged 3 commits into
mainfrom
fix/cursor-bounce-clamp
Sep 16, 2026
Merged

EtienneLescot merged 3 commits into
mainfrom
fix/cursor-bounce-clamp

Conversation

@EtienneLescot

@EtienneLescot EtienneLescot commented Sep 16, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Negative cursor size. plan_cursor computed 1 + (bounce(t) - 1) * clickBounce with no bound. The press trough of bounce() is 0.76 and the slider goes up to 5, so above 4.17 the factor went negative, and so did the size every backend drew. With both extents negative the sprite was drawn flipped 180°. The factor is now floored at 0 in the one shared place, and a non-positive size draws nothing. The tilted path would otherwise solve a 0/0 warp. At clickBounce 5 the cursor now disappears for about 37 ms at the bottom of the press.

  • The click envelope had no test. cursor::tests::click_bounce_envelope now pins it:

    • exactly 1.0 outside the 260 ms window;
    • continuous at 0, 38 % and 100 % of the window;
    • 0.76 at +49.4 ms and 1.16 at +179.4 ms;
    • a second click restarts the curve.

    A second test sweeps plan_cursor at clickBounce 5.

  • Stale references:

    • the doc comment of bounce() cited nativeCursor.ts, deleted with pixi.js; Rust is now the only reference for the curve;
    • scripts/inspect-native-cursor-click-bounce.mjs divided the intensity by 5, so its report was off by up to 5×; it now copies the Rust formula and defaults to 2.5 like the app;
    • cursor.rs said positions are sampled at ~120 Hz; the recorders sample every 33 ms;
    • cursor.md and generate-default-cursor-sprites.mjs pointed at the deleted file.

Related issue

None filed.

Type of change

  • Bug fix
  • Feature
  • Enhancement
  • Documentation
  • Refactor / maintenance
  • Performance
  • Security

Release impact

  • Patch
  • Minor
  • Major / breaking change
  • No release note needed

Desktop impact

  • Windows
  • macOS
  • Linux
  • Installer / packaging
  • Not platform-specific

Testing

  • Windows: cargo test -p openscreen-compositor --lib gives 206 passed. Removing the clamp makes the new frame_geometry test fail with a size of -0.0138 (mutation run).
  • Linux: under WSL (nix develop .#compositor-view, pinned ffmpeg SDK, nix lavapipe), cargo test -p openscreen-compositor --lib --tests gives 224 passed.
  • macOS: not compiled locally; only shared Rust code changed.
  • Biome on the two touched scripts. The inspect script was not run against a real sidecar.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Prevented cursor sprites from rendering with negative sizes during extreme click-bounce values.
    • Updated click-bounce calculations to match compositor behavior and use the standard default value.
  • Tests

    • Added coverage for click-bounce envelopes and maximum-bounce rendering behavior.
  • Documentation

    • Corrected cursor sampling-rate details and updated references for cursor sprite assets and hotspots.

plan_cursor scales the bounce deviation by the raw clickBounce setting,
whose slider goes up to 5. The press trough of CursorTrack::bounce is
0.76, so above 1/0.24 (about 4.17) the factor went negative and a
negative size_px reached every backend's sprite draw, drawing the
cursor rotated by 180 degrees for a few frames after each click.

Floor the factor at 0 in plan_cursor, the one site all three backends
share, and return None when the size is not positive. Skipping is
safer than drawing a zero-size sprite: on a tilted plane its four
corners coincide and the mode 13 inverse warp solves 0/0, whose
rejection only holds if NaN comparisons behave, which Metal's
fast-math does not promise. The motion-blur trail reuses the same
size_px, so it is covered too.

The new test sweeps a click at clickBounce 5 and fails without the
clamp (size -0.0138 px at t = 0.531 s).
CursorTrack::bounce had no test. The new one pins exactly 1.0 outside
the 260 ms window, continuity at elapsed fractions 0, 0.38 and 1, the
0.76 trough at +49.4 ms, the 1.16 peak at +179.4 ms, and that a later
click restarts the curve.

Stale references cleaned up on the way:
- the bounce doc comment cited getNativeCursorClickBounceScale in
  nativeCursor.ts, deleted with pixi.js; Rust is now the only
  authority and the comment says so;
- scripts/inspect-native-cursor-click-bounce.mjs divided clickBounce
  by 5, so its report was off by up to 5x from what the compositor
  renders. It now scales the deviation by the raw value and floors at
  0 like plan_cursor, and defaults to the app default (2.5);
- the types comment claimed ~120 Hz cursor sampling; every recorder
  samples every 33 ms (CURSOR_SAMPLE_INTERVAL_MS) and no faster path
  exists.
@coderabbitai

coderabbitai Bot commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 6073cdfd-0560-4f89-9f65-f8967d50da7c

📥 Commits

Reviewing files that changed from the base of the PR and between dc956d5 and 38d742c.

📒 Files selected for processing (5)
  • crates/compositor/src/cursor.rs
  • crates/compositor/src/frame_geometry.rs
  • scripts/generate-default-cursor-sprites.mjs
  • scripts/inspect-native-cursor-click-bounce.mjs
  • technical-documentation/architecture/cursor.md

Included review availability: Your plan provides up to 8 included reviews per hour; 6 remain after this review.


📝 Walkthrough

Walkthrough

The compositor now handles extreme cursor bounce without negative sprite sizes. Tests cover the bounce envelope and cursor planning. The inspection script matches compositor defaults and scaling. Cursor asset documentation reflects the removed TypeScript source.

Changes

Cursor bounce behavior

Layer / File(s) Summary
Bounce behavior and envelope tests
crates/compositor/src/cursor.rs
The documentation now states the 33 ms sampling interval and removed TypeScript source. Tests cover envelope boundaries, extrema, and repeated clicks.
Non-positive cursor size handling
crates/compositor/src/frame_geometry.rs
plan_cursor floors bounce-derived sizes at zero and returns None for non-positive sizes. Tests cover maximum bounce and reuse a full-frame geometry helper.
Tooling and architecture references
scripts/inspect-native-cursor-click-bounce.mjs, scripts/generate-default-cursor-sprites.mjs, technical-documentation/architecture/cursor.md
The inspection script uses a default bounce of 2.5 and matches the compositor envelope. Comments and documentation identify the generated sprite table as the hotspot source of truth.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix · Severity of issue fixed: Low

Merge Risk: ⚪ Minimal · up to 38d74

The extreme click-bounce case now skips invalid cursor geometry instead of rendering an inverted sprite. No actionable merge risk remains.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 78.57% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 14 functions across 4 files. (1 skipped: … Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly and concisely describes the primary change: clamping the compositor cursor size caused by click-bounce intensity.
Description check ✅ Passed The description covers the change, related issue status, change types, release and platform impact, and detailed testing. It does not include a separate Screenshots / video section, but this omission …
Full details: Docstring Coverage

Explanation

Docstring coverage is 78.57% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 14 functions across 4 files. (1 skipped: 1 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/cursor-bounce-clamp

Warning

Git: CodeRabbit could not clone the repository, so clone-backed analysis was skipped and this review may be incomplete. Verify repository clone access, such as SSH credentials, before requesting another full review. If clone access is intentionally unavailable, use path_filters to narrow the review scope.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@EtienneLescot
EtienneLescot marked this pull request as draft September 16, 2026 09:11
@EtienneLescot
EtienneLescot marked this pull request as ready for review September 16, 2026 09:14
@EtienneLescot
EtienneLescot merged commit 74f60b1 into main Sep 16, 2026
23 checks passed
@EtienneLescot
EtienneLescot deleted the fix/cursor-bounce-clamp branch September 16, 2026 09:42
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.

1 participant