Skip to content

fix(input): preserve stick direction outside the dead zone - #1600

Open
bronze-ff wants to merge 2 commits into
NotRexed:mainfrom
bronze-ff:fix/radial-stick-dead-zone
Open

bronze-ff wants to merge 2 commits into
NotRexed:mainfrom
bronze-ff:fix/radial-stick-dead-zone

Conversation

@bronze-ff

@bronze-ff bronze-ff commented Nov 10, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Addresses #1480. The host currently applies the stick dead zone to X and Y independently. For example, with the default cutoff SDL input (12000, -4000) reaches the game as (12000, 0), snapping a diagonal toward horizontal.

Use the same circular cutoff for both sticks in SDL and XInput. Inside the circle both components are zero. Outside it both components pass through with their original magnitude and the existing axis conventions. The six thresholds and the saved option stay the same.

Validation

  • Reproduced the SDL snap on main with synthetic input. The regression now exercises all 65,536 values on each axis at each threshold, diagonals in every quadrant, the exact cutoff, and extreme inputs.
  • On Windows with LLVM-MinGW, passed the stick dead-zone, input-background, menu-keys, rumble-menu, rumble-mapping and HD-rumble tests. Also syntax-checked both host input translation units.
  • The same six tests passed with GCC/G++ in a Linux container.
  • Input was synthetic. A physical DualSense and gameplay were outside this run.

@hckrman101 hckrman101 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Looks good. The circular cutoff is applied per stick in both SDL and XInput, and outside the circle both components pass through unchanged, so diagonals keep their direction. I checked the details: the squared sum can't overflow int32 once both components are clamped to -32767 (2*32767^2 is about 2.147e9, just under INT32_MAX), the SDL Y flip and the invert X/Y handling match the old per-axis behaviour, and on-axis input gives the same result as before. hos_stick_axis has no other callers. The test, menu text and readme are updated to match, and CI is green.

Nit: the Default zone (0) is now a circle of radius 7849 rather than a per-axis cut, so a stick that drifts just past 24% on one axis only will behave slightly differently. That's the intended fix and the readme says it.

@hckrman101

Copy link
Copy Markdown
Collaborator

Heads-up: this conflicts with main now (a lot landed today). Could you merge main into the branch (or rebase) and resolve the conflicts? Thanks!

Keep main's rest-before-steer check in the SDL path (NotRexed#1673, NotRexed#1661) after the
radial filter, and the XInput-only-without-SDL block from NotRexed#1480/NotRexed#1583.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>

@hckrman101 hckrman101 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Re-checked at the new head. The push is only a merge of main: the circular filter is unchanged, and the SDL path keeps main's rest-before-steer check (#1673/#1661) right after the filter, as the merge message says. The resolved hunks, the test, menu text and readme still read right, and CI is green (the two skipped jobs are the release ones). It merges cleanly now.

For the related PRs: #2286 (camera inversion out of rest detection) edits the same SDL block, where the invert flips now sit just above the rest loop, so whichever lands second needs a small rebase. #1945 (camera speed row) should not overlap beyond the right-stick values in pads_poll.

@Joey-Magnus Joey-Magnus left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The circular cutoff in hos_filter_stick (nn_hid.c) still isn't on main. Main still applies hos_stick_axis per axis, so the fix is still wanted. The conflict is the one predicted in the last review: #2286 moved the invert-X/Y flips in pads_poll (host/pad_sdl.c) below the rest-detection loop, so a flipped axis doesn't count as rested. When you rebase, keep main's order: run the filter and the SDL Y flip, then the rested loop, and only then if (inv_x) v[2] = -v[2]; if (inv_y) v[3] = -v[3];. Don't apply the PR's if (!inv_y) before the loop. The readme conflict is the dead-zone line.

One thing to think about, not blocking. With the circle, a phantom axis that reads as held (the #1661/#1673 devices) keeps its partner axis "active", so the partner's drift now passes without its own dead zone once the rest loop admits it. The game's own dead zone still follows, so it's probably fine. Please re-run tests/test_stick_dead_zone.py after the rebase.

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.

3 participants