Skip to content

feat(mobile): map touches to Super Scope and Mouse coordinates - #292

Merged
doublegate merged 3 commits into
mainfrom
feat/mobile-touch-mapping
Aug 1, 2026
Merged

feat(mobile): map touches to Super Scope and Mouse coordinates#292
doublegate merged 3 commits into
mainfrom
feat/mobile-touch-mapping

Conversation

@doublegate

@doublegate doublegate commented Jul 31, 2026

Copy link
Copy Markdown
Owner

Part of v1.30.0 (mobile store-readiness engineering).

docs/mobile-readiness.md has listed "no Mouse/Super Scope/Multitap touch UX" as an open gap since
v1.16.0. This closes the half of it that can be verified, and says plainly which half is left.

The gap

set_superscope and set_mouse were already on the FFI, but neither takes anything a touchscreen
produces:

  • the Scope wants a position in SNES screen space, and the view is letterboxed;
  • the Mouse wants a relative delta in counts, and a slow drag moves less than one count per
    frame.

What landed

crates/rustysnes-mobile/src/touch.rs:

  • map_touch_to_screen — maps a touch through the viewport. It takes the active framebuffer
    size as a parameter rather than assuming 256x224, so aim stays correct when a game switches to a
    hi-res mode mid-scene, and it reports a touch in the letterbox bars as on_screen: false rather
    than snapping it to an edge — several games read the Scope's off-screen state as "reload", so the
    caller has to be able to tell. A degenerate 0x0 viewport is handled rather than assumed away;
    Android reports one between rotation and first layout.
  • TouchMouse — carries the sub-count residual across FFI calls, which is the load-bearing
    part: without it a naive delta as i32 truncates every slow-drag frame to zero and the pointer
    never moves at all.

Why in Rust and not in the shells

Otherwise it is written twice, in Kotlin and in Swift, and the two drift. It is also the only place
it can be tested — neither shell has a test harness and this environment has no macOS toolchain at
all. Keeping the platform layer to "forward the touch, forward the result" makes a mapping bug a
cargo test failure rather than a user aiming half a screen off.

Verification

18 tests. Kotlin bindings generated and inspected — mapTouchToScreen, TouchMouseInterface
(begin/drag/end), and the AimPoint/MouseDelta/Viewport records are all present.

Two mechanisms were injection-tested rather than assumed:

  • replacing the mul_add residual accumulation with a plain multiply fails
    a_slow_drag_still_moves_the_mouse, as intended;
  • the touch-down origin is reset in two places (begin sets it, end clears it), so injecting a
    no-op into either one alone leaves the other covering and the test still passes. Removing both
    fails it. The test comment now records that it pins the pair rather than claiming it pins a single
    guard — the redundancy is deliberate, since a shell that drops a begin (Android delivers
    ACTION_MOVE without a preceding ACTION_DOWN after a gesture-recognizer steal) is still safe.

Still outstanding

The on-screen affordances themselves — a Scope reticle, Mouse button targets, a peripheral picker —
and Multitap, whose port assignment is UI rather than arithmetic. docs/mobile-readiness.md is
updated in both places it made the old claim.

🤖 Generated with Claude Code

Summary

This change claims that mobile touch input maps correctly to Super Scope and Mouse input in Rust.

The claim is false if letterbox bars are treated as on-screen, framebuffer dimensions are ignored, zero-sized viewports panic or produce invalid coordinates, or slow drags lose fractional movement across calls.

map_touch_to_screen maps touches using the active framebuffer size and marks letterbox-bar touches as off-screen. TouchMouse preserves movement residuals across FFI calls. Tests cover mapping, viewport edge cases, lifecycle behavior, and residual bounds. Kotlin bindings were verified.

The change does not add on-screen controls, peripheral selection, or Multitap support. These remain outstanding.

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@doublegate, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 23 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 4b748da9-ce56-491d-94c3-afaa65fd043c

📥 Commits

Reviewing files that changed from the base of the PR and between 6d43e76 and 03d5de4.

📒 Files selected for processing (4)
  • CHANGELOG.md
  • crates/rustysnes-mobile/src/lib.rs
  • crates/rustysnes-mobile/src/touch.rs
  • docs/mobile-readiness.md

Walkthrough

The mobile crate now exposes Rust and UniFFI touch mapping. It converts viewport coordinates to framebuffer aim points, tracks fractional touch-mouse movement, and documents tested mapping alongside outstanding on-screen controls.

Changes

Mobile touch input

Layer / File(s) Summary
Touch mapping API
crates/rustysnes-mobile/src/touch.rs, crates/rustysnes-mobile/src/lib.rs
The mobile crate exports viewport, aim-point, and mouse-delta records. Touch coordinates map to active framebuffer coordinates with letterbox detection, clamping, and invalid-viewport handling.
Touch-mouse state and validation
crates/rustysnes-mobile/src/touch.rs, CHANGELOG.md, docs/mobile-readiness.md
MouseAccumulator preserves fractional drag movement. TouchMouse exposes synchronized begin, drag, and end operations. Tests and readiness documentation cover the implemented mapping and remaining on-screen controls.

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

Sequence Diagram(s)

sequenceDiagram
  participant TouchInput
  participant TouchMouse
  participant MouseAccumulator
  TouchInput->>TouchMouse: begin(tx, ty)
  TouchMouse->>MouseAccumulator: initialize drag origin
  TouchInput->>TouchMouse: drag(tx, ty, sensitivity)
  TouchMouse->>MouseAccumulator: calculate whole delta and residual
  MouseAccumulator-->>TouchMouse: MouseDelta
  TouchMouse-->>TouchInput: MouseDelta
  TouchInput->>TouchMouse: end()
  TouchMouse->>MouseAccumulator: reset drag state
Loading
🚥 Pre-merge checks | ✅ 9 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docs-As-Spec ⚠️ Warning The PR adds observable mobile APIs and touch behavior under crates/rustysnes-mobile/, but it does not edit the required docs/mobile.md; only docs/mobile-readiness.md changed. Add or update docs/mobile.md in this pull request to specify map_touch_to_screen, TouchMouse residual handling, and viewport behavior.
✅ Passed checks (9 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
Changelog Entry ✅ Passed The full diff against main adds a 16-line CHANGELOG.md entry under Unreleased/Added for mobile touch mapping and TouchMouse behavior.
Accuracysnes Bookkeeping ✅ Passed The PR diff against origin/main changes only mobile and documentation files; it adds or removes no test or scene under tests/roms/AccuracySNES/gen/src/.
No Panic On Untrusted Input ✅ Passed Changed Rust code adds no .unwrap(), .expect(), or panic!() outside #[cfg(test)]; TouchMouse recovers poisoned locks with unwrap_or_else and touch inputs use total handling.
Safety Comment On New Unsafe ✅ Passed The commit adds no unsafe block or unsafe fn; added Rust lines in lib.rs and new touch.rs contain no unsafe token.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title uses valid Conventional Commit syntax and accurately describes the mobile touch-to-Super Scope and Mouse coordinate mapping changes.

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/rustysnes-mobile/src/touch.rs`:
- Around line 176-214: Extract a private helper on TouchMouse that locks self.0
and applies the existing PoisonError::into_inner recovery, returning the mutex
guard. Replace the repeated lock expressions in begin, drag, and end with calls
to this helper, preserving each method’s current behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: e52cfe95-ac24-443b-a600-2ba5054ac568

📥 Commits

Reviewing files that changed from the base of the PR and between ee34709 and 6d43e76.

📒 Files selected for processing (4)
  • CHANGELOG.md
  • crates/rustysnes-mobile/src/lib.rs
  • crates/rustysnes-mobile/src/touch.rs
  • docs/mobile-readiness.md
📜 Review details
⏰ Context from checks skipped due to timeout. (5)
  • GitHub Check: test-light
  • GitHub Check: accuracysnes
  • GitHub Check: lint
  • GitHub Check: build
  • GitHub Check: build demo + docs
🧰 Additional context used
📓 Path-based instructions (12)
docs/**/*.md

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Before changing a subsystem, consult docs/architecture.md, docs/STATUS.md, CONTRIBUTING.md, the relevant subsystem documentation, and applicable ADRs.

New subsystems must add documentation under docs/.

Files:

  • docs/mobile-readiness.md
**/*.{rs,md}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Chip-behavior changes must update both the chip implementation and the corresponding docs/<subsystem>.md documentation.

A chip change must update both the chip implementation and its corresponding docs/<chip>.md documentation in the same change.

Files:

  • docs/mobile-readiness.md
  • crates/rustysnes-mobile/src/lib.rs
  • CHANGELOG.md
  • crates/rustysnes-mobile/src/touch.rs
**/*

📄 CodeRabbit inference engine (CONTRIBUTING.md)

**/*: Do not commit or vendor the generated snesdev_wiki/ mirror; it is gitignored and intended only as a local reference.
Keep commits focused and use Conventional Commits: <type>(<scope>): <subject>, with an imperative subject of at most 72 characters.
Do not use emojis in code, comments, or commit messages.
Before opening a PR, ensure formatting, Clippy, workspace tests, the core embedded build, rustdoc with warnings denied, documentation coverage, and changelog requirements pass.
Ticket completion must be reflected in the relevant to-dos/ sprint file.

**/*: Preserve the one-directional crate graph: chip crates must not depend on one another; rustysnes-core ties them together.
Never commit commercial ROMs; only commit derived screenshots and hashes.
Keep docs/STATUS.md as the authoritative per-subsystem status and update project documentation in the same PR as code changes.
Do not treat RustyNES v2.0 or engine-lineage anchors as project releases.

Files:

  • docs/mobile-readiness.md
  • crates/rustysnes-mobile/src/lib.rs
  • CHANGELOG.md
  • crates/rustysnes-mobile/src/touch.rs
docs/**/*

📄 CodeRabbit inference engine (docs/testing-strategy.md)

Chip crates should exceed 90% unit-test coverage, and each chip should be fuzzable in isolation.

Files:

  • docs/mobile-readiness.md
docs/**

⚙️ CodeRabbit configuration file

docs/**: Docs are the spec, not a history log. Flag claims that contradict the code, counts that
contradict the generated docs/accuracysnes-coverage.md, and any statement of coverage that
is broader than what the corresponding test actually asserts.

Files:

  • docs/mobile-readiness.md
**/*.md

⚙️ CodeRabbit configuration file

**/*.md: Docs are the spec, not a changelog. Flag prose that has drifted from the code it describes
rather than style nits. The markdownlint gate is pinned to v0.39.0 via pre-commit —
do not report rules that version does not have (MD060 in particular).

Files:

  • docs/mobile-readiness.md
  • CHANGELOG.md
crates/**/*.rs

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

crates/**/*.rs: Preserve the master-clock lockstep timing model.
rustysnes-core::Bus owns mutable machine state, and the CPU borrows &mut Bus.
Preserve determinism: seed, ROM, and input must produce bit-identical output.
Treat test ROMs as the behavioral specification; when documentation disagrees with passing ROM behavior, update the documentation.
Keep unsafe confined to existing allowed areas, namely frontend and FFI code, and document every unsafe block with a // SAFETY: comment.

Files:

  • crates/rustysnes-mobile/src/lib.rs
  • crates/rustysnes-mobile/src/touch.rs
**/*.rs

📄 CodeRabbit inference engine (CONTRIBUTING.md)

**/*.rs: Use Rust edition 2024 and the toolchain pinned in rust-toolchain.toml (Rust 1.96).
Run cargo fmt --all --check; Rust code must remain rustfmt-compliant.
Run Clippy with cargo clippy --workspace --all-targets -- -D warnings; warnings must not remain.
New public Rust items must have rustdoc because missing_docs is a workspace lint.
Do not run cargo clippy --all-features; scripting and script-wasm are mutually exclusive. Use explicit per-feature jobs instead.

**/*.rs: Do not introduce .unwrap(), .expect(), or panic!() on untrusted external input—such as ROM/save-state bytes, netplay messages, Lua or scripting input, or user-supplied paths—outside #[cfg(test)] code. Use typed errors at those boundaries; locally constructed values or values immediately protected by a checked invariant are allowed.
Every new unsafe { ... } block or unsafe fn must have an adjacent // SAFETY: comment naming the relied-on invariant and its guarantor. Unsafe code outside the frontend and FFI shims should additionally be questioned because unsafe_code is a workspace lint.

**/*.rs: Use Rust edition 2024 with the pinned 1.96 toolchain; satisfy workspace pedantic, nursery, missing_docs, and unsafe_code warnings because CI runs with -D warnings. Document every public item.
Keep unsafe code restricted to the frontend and FFI, and include a // SAFETY: justification for each use.
Keep hot paths allocation-free.
Treat rustysnes_core::Bus as the owner of mutable emulator state; the CPU borrows &mut Bus.
Use the master clock at 21477270 Hz as the timing master; advance the scheduler in lockstep and run other chips on their divisors.
Maintain determinism: seed, ROM, and input must produce bit-identical audio/video; frontend rate control must not alter emulation results.
When implementing hardware behavior, pin and run the failing test ROM first; treat test ROMs as the specification.

Files:

  • crates/rustysnes-mobile/src/lib.rs
  • crates/rustysnes-mobile/src/touch.rs
crates/rustysnes-*/**/*

📄 CodeRabbit inference engine (Custom checks)

For the full pull request diff against its base branch, any observable behavior change under crates/rustysnes-<chip>/ must be accompanied by an edit to the matching docs/<chip>.md; a crate change passes without documentation only when it does not alter observable behavior, with the non-behavioral change stated explicitly.

Files:

  • crates/rustysnes-mobile/src/lib.rs
  • crates/rustysnes-mobile/src/touch.rs
**/*.{rs,toml}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{rs,toml}: Additive features must be default-off so shipped/native, no_std, and wasm builds remain byte-identical.
Never use or configure --all-features; validate opt-in feature combinations individually as required by the project recipe.

Files:

  • crates/rustysnes-mobile/src/lib.rs
  • crates/rustysnes-mobile/src/touch.rs
crates/**

⚙️ CodeRabbit configuration file

crates/**: Emulator core. Hot paths are allocation-free; unsafe requires a // SAFETY: comment
naming the invariant. Any change to save-stated fields needs a FORMAT_VERSION bump and a
docs/adr/0006 bump-log entry. Behavior changes must update the matching docs/<chip>.md
in the same change.

Files:

  • crates/rustysnes-mobile/src/lib.rs
  • crates/rustysnes-mobile/src/touch.rs
CHANGELOG.md

📄 CodeRabbit inference engine (CONTRIBUTING.md)

User-visible changes must be recorded under the [Unreleased] section.

For the full pull request diff against its base branch, modify CHANGELOG.md when user-visible behavior changes, including emulator output, frontend features, CLI flags, public APIs, or AccuracySNES cartridge contents. Do not require it for purely internal changes, tests, comments, or CI configuration.

Files:

  • CHANGELOG.md
🔇 Additional comments (7)
crates/rustysnes-mobile/src/touch.rs (3)

78-105: LGTM!


112-168: LGTM!


216-364: LGTM!

crates/rustysnes-mobile/src/lib.rs (1)

36-37: LGTM!

CHANGELOG.md (1)

14-29: LGTM!

docs/mobile-readiness.md (2)

217-225: LGTM!


264-265: LGTM!

Comment thread crates/rustysnes-mobile/src/touch.rs
@doublegate
doublegate force-pushed the feat/mobile-touch-mapping branch 2 times, most recently from 049fb4c to cf3a0ee Compare July 31, 2026 23:48
doublegate added a commit that referenced this pull request Jul 31, 2026
The `.lock().unwrap_or_else(PoisonError::into_inner)` chain was written
out in begin, drag and end, so the poisoning policy was stated three
times and could drift. CodeRabbit's finding on #292.

The helper lives in a separate plain impl block: `#[uniffi::export]`
exports every method it contains, and a MutexGuard has no FFI
representation. Regenerated the Kotlin bindings to confirm the exported
interface is unchanged -- begin/drag/end only, no leaked `get`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
doublegate and others added 3 commits July 31, 2026 20:21
The FFI exposed set_superscope and set_mouse, but both take units a
touchscreen does not have: the Scope wants SNES screen space, the Mouse
wants a relative delta in counts. rustysnes-mobile::touch closes that
gap.

map_touch_to_screen maps through the letterboxed viewport and takes the
active framebuffer size as a parameter, so aim stays correct when a game
switches to a hi-res mode mid-scene; a touch in the letterbox bars is
reported off-screen rather than snapped to an edge, because several
games read the Scope's off-screen state as "reload".

TouchMouse carries the sub-count residual across FFI calls. Without it a
slow drag truncates to zero every frame and the pointer never moves at
all -- the load-bearing behaviour here, and the one the injection test
confirms.

This lives in Rust rather than in the shells because otherwise it is
written twice, in Kotlin and in Swift, and the two drift; and because
this crate is the only place it can be tested at all, neither shell
having a test harness and this environment having no macOS toolchain.
Kotlin bindings verified to generate.

The on-screen controls that would drive it are still outstanding;
docs/mobile-readiness.md now records which half is done.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every existing case in touch.rs used `view.y == 0`, which made the
`- view.y` term in the vertical mapping untested: injecting
`rel_y = ty / view.height` -- dropping the origin entirely -- passed all
18 tests.

A portrait phone holding a 4:3 picture has bars on the top and bottom,
not the sides, so this is the orientation most users would actually
hit; aim would have been off by the bar height for all of them.

The new case fails under that injection.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The `.lock().unwrap_or_else(PoisonError::into_inner)` chain was written
out in begin, drag and end, so the poisoning policy was stated three
times and could drift. CodeRabbit's finding on #292.

The helper lives in a separate plain impl block: `#[uniffi::export]`
exports every method it contains, and a MutexGuard has no FFI
representation. Regenerated the Kotlin bindings to confirm the exported
interface is unchanged -- begin/drag/end only, no leaked `get`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@doublegate
doublegate force-pushed the feat/mobile-touch-mapping branch from 4972f17 to 03d5de4 Compare August 1, 2026 00:21
Copilot AI review requested due to automatic review settings August 1, 2026 00:21
@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown

Antigravity review (Gemini via Ultra)

Adds rustysnes-mobile::touch to handle viewport coordinate translation for the Super Scope and relative drag accumulation with residual sub-pixel tracking for the SNES Mouse across UniFFI.

Blocking issues

None found.

Suggestions

  • crates/rustysnes-mobile/src/touch.rs:83: view.width <= 0.0 || view.height <= 0.0 does not guard against NaN values because IEEE 754 comparisons with NaN return false. If view.width or view.height is NaN, execution proceeds to (tx - view.x) / view.width and produces NaN residuals. Use !view.width.is_finite() or explicit .is_nan() checks.
  • crates/rustysnes-mobile/src/touch.rs:139: drag() accepts any f32 for sensitivity without checking for non-finite (NaN or Infinity) values. A single NaN passed over FFI poisons residual_x and residual_y, keeping all subsequent drag() calls returning (0, 0) until begin() or end() is invoked.
  • crates/rustysnes-mobile/src/touch.rs:152: If self.residual_x.trunc() exceeds i32::MIN..=i32::MAX due to extreme sensitivity or a large touch jump, dx as i32 saturates to i32::MAX while self.residual_x -= dx subtracts the full float dx, silently dropping the excess motion delta. Clamp dx to (i32::MIN as f32)..=(i32::MAX as f32) before updating residual_x.

Nitpicks

  • crates/rustysnes-mobile/src/touch.rs:128, 162: begin and end are marked const fn on &mut self, which offers no practical benefit for runtime FFI methods.

Automated first-pass review by agy on a self-hosted runner -- not a human review.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Not ready to approve

There is a confirmed accumulator correctness issue when mouse deltas exceed i32 range due to residual subtraction using the pre-saturated float value.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.

Pull request overview

Adds a Rust-side, UniFFI-exported touch-mapping layer for mobile so touch events can be converted into Super Scope screen-space aim points and SNES Mouse relative deltas (with residual accumulation), and updates mobile-readiness documentation and the changelog to reflect what’s now covered vs. still missing UI.

Changes:

  • Introduce rustysnes-mobile::touch with map_touch_to_screen / map_aim and a stateful TouchMouse (mutex-backed) that preserves sub-count mouse movement across FFI calls.
  • Add unit tests covering viewport letterboxing, degenerate viewports, hi-res framebuffer sizing, and slow-drag residual behavior.
  • Update docs/mobile-readiness.md and CHANGELOG.md to clarify that arithmetic/mapping is implemented and tested, while on-screen controls and Multitap UX remain outstanding.
File summaries
File Description
docs/mobile-readiness.md Updates the mobile-readiness gap statement to distinguish tested arithmetic from still-missing on-screen UX and Multitap UI.
crates/rustysnes-mobile/src/touch.rs Implements touch→screen mapping and drag→mouse-count accumulation, exported via UniFFI with unit tests.
crates/rustysnes-mobile/src/lib.rs Exposes the new touch module from the mobile UniFFI bridge crate.
CHANGELOG.md Adds release notes describing the new mobile touch-to-peripheral mapping and what remains out of scope.
Review details
  • Files reviewed: 4/4 changed files
  • Comments generated: 1
  • Review effort level: Lite

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

Comment on lines +153 to +159
let dx = self.residual_x.trunc();
let dy = self.residual_y.trunc();
self.residual_x -= dx;
self.residual_y -= dy;

#[allow(clippy::cast_possible_truncation)]
(dx as i32, dy as i32)
@doublegate
doublegate merged commit 0e1aa13 into main Aug 1, 2026
18 checks passed
@doublegate
doublegate deleted the feat/mobile-touch-mapping branch August 1, 2026 00:45
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.

2 participants