Skip to content

FIX: HID 8-bit hat switch crashes InputDeviceBuilder (UUM-143659)#2424

Open
LeoUnity wants to merge 4 commits into
developfrom
fix/uum-143659-hid-hat-overlap
Open

FIX: HID 8-bit hat switch crashes InputDeviceBuilder (UUM-143659)#2424
LeoUnity wants to merge 4 commits into
developfrom
fix/uum-143659-hid-hat-overlap

Conversation

@LeoUnity
Copy link
Copy Markdown
Collaborator

@LeoUnity LeoUnity commented Jun 1, 2026

Summary

Fixes UUM-143659 — connecting an HID gamepad whose report descriptor declares a hat switch with Report Size of 8 bits (e.g. ESP32-BLE-Gamepad) made InputDeviceBuilder.InsertControlBitRangeNode infinite-loop, raising IndexOutOfRangeException; the device was logged as Could not create a device for ... and never appeared.

Root cause

Two independent overrun bugs converge in DpadControl construction for an HID hat:

  1. HID layer (HID.cs:849-906) adds hat/up/right/down/left with WithBitOffset() but no WithByteOffset(). The layout system's auto-allocator puts each sub-control in a fresh byte. For 4-bit hats they pack into one byte via the bitfield path (sizeInBits % 8 != 0); for 8-bit hats each claims its own byte, pushing them past the device's HID-derived report size.
  2. Bit-range tree builder (InputDeviceBuilder.cs) was processing the synthetic DpadAxis children (hat/x, hat/y) of the Dpad. DpadAxisControl.FinishSetup aliases their state block to a sibling — but FinishSetup runs after FinalizeControlHierarchy, so the tree saw their pre-FinishSetup offsets, which could also fall outside the device.

Either dangling control sent InsertControlBitRangeNode into endless recursion on the recurse-right branch (no partition could ever contain the control), eventually IndexOutOfRangeException.

Fixes

  • HID.cs — anchor each of the four hat sub-controls with WithByteOffset(0) so they overlap the hat's byte after FinalizeControlHierarchyRecursive bakes in the parent's byteOffset.
  • InputDeviceBuilder.cs — skip synthetic controls in FinalizeControlHierarchyRecursive when inserting into the bit-range tree; their bits are already represented by the non-synthetic control they alias.

Both changes are needed: the HID fix handles non-synthetic hat/up/right/down/left; the synthetic-skip fix handles hat/x / hat/y.

Test plan

  • Added Devices_CanCreateGenericHID_FromGamepadWithEightBitHatSwitch — fails on develop, passes with the fix.
  • Run the full Devices + HID Devices test categories in CI — the existing 4-bit-hat test (Devices_CanCreateGenericHID_FromDeviceWithBinaryReportDescriptor) and other Dpad-using tests are the most likely regression candidates.
  • Yamato editor functional tests on 6000.x

Notes

  • Marked draft pending the broader regression sweep.
  • The local repro was on Unity 6000.5.0a7; ticket reproducer is 1.19.0 / 6000.4.7f1 on Arch Linux. The bug is descriptor-driven, not platform-specific.

🤖 Generated with Claude Code

LeoUnity and others added 2 commits June 1, 2026 17:42
Adds Devices_CanCreateGenericHID_FromGamepadWithEightBitHatSwitch to
HIDTests, modelled on the ESP32-BLE-Gamepad descriptor from the ticket:
an HID gamepad whose report descriptor declares a hat switch with
Report Size 8 (instead of the standard 4 bits).

Currently fails on develop: InputDeviceBuilder.InsertControlBitRangeNode
goes into infinite recursion while constructing the control tree;
InputManager catches the resulting IndexOutOfRangeException and logs
"Could not create a device for ...", so the device never appears.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…nge tree (UUM-143659)

For an HID hat switch, the HID layer adds four directional sub-controls
(hat/up, hat/right, hat/down, hat/left) that are meant to overlap the
hat itself and only differ in their DiscreteButton value ranges. Those
sub-controls were added with WithBitOffset() but no WithByteOffset(),
so the layout system's auto-allocator placed each one in a fresh byte.

For a 4-bit hat that's harmless: sizeInBits=4 is bit-addressing and all
four pack into the parent's byte via the bitfield path. For an 8-bit
hat (sizeInBits=8 is byte-addressing) each sub-control consumed its
own byte, pushing them past the device's HID-derived report size. The
dangling controls then sent InputDeviceBuilder.InsertControlBitRangeNode
into infinite recursion because no partition could ever contain them.

Fixes:
  1. HID.cs: add WithByteOffset(0) to each of the four hat sub-controls
     so they're anchored to the hat parent's byte and overlap it after
     FinalizeControlHierarchy bakes in the parent's byteOffset.
  2. InputDeviceBuilder.cs: skip synthetic controls when building the
     control bit-range tree. Synthetic controls (e.g. DpadControl's x/y,
     StickControl's up/down/left/right) get their state block aliased to
     a sibling/parent in FinishSetup -- but FinishSetup runs *after*
     FinalizeControlHierarchy, so the tree was being built with the
     pre-FinishSetup offsets, which could also fall outside the device's
     bit range. Their bits are already covered by the non-synthetic
     control they alias, so they don't need their own tree entry.

Either fix alone is not sufficient: the HID fix resolves the
non-synthetic hat sub-controls; the synthetic skip resolves
DpadAxis hat/x and hat/y, which suffer from the same overrun
via a different mechanism.

Adds a CHANGELOG entry. Test:
Devices_CanCreateGenericHID_FromGamepadWithEightBitHatSwitch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Collaborator

@ekcoh ekcoh left a comment

Choose a reason for hiding this comment

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

Fix is legit IMO as smokes out two bugs in the HID parser / InputDeviceBuilder.

Comment thread Assets/Tests/InputSystem/Plugins/HIDTests.cs
@ekcoh
Copy link
Copy Markdown
Collaborator

ekcoh commented Jun 1, 2026

@LeoUnity Looks legit, needs a formatter run.

@LeoUnity LeoUnity marked this pull request as ready for review June 2, 2026 10:57
Copy link
Copy Markdown
Contributor

@u-pr u-pr Bot left a comment

Choose a reason for hiding this comment

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

May require changes

The proposed fix for the HID device layout crash is solid, but excluding synthetic controls from the bit-range tree introduces a critical regression in button press tracking and cache invalidation. Reverting this exclusion while keeping the underlying layout fixes will ensure the engine remains stable.

🤖 Helpful? 👍/👎

Comment thread Packages/com.unity.inputsystem/InputSystem/Runtime/Devices/InputDeviceBuilder.cs Outdated
@codecov-github-com
Copy link
Copy Markdown

codecov-github-com Bot commented Jun 2, 2026

Codecov Report

All modified and coverable lines are covered by tests ✅

@@             Coverage Diff             @@
##           develop    #2424      +/-   ##
===========================================
+ Coverage    78.13%   78.89%   +0.76%     
===========================================
  Files          483      762     +279     
  Lines        98770   139282   +40512     
===========================================
+ Hits         77169   109885   +32716     
- Misses       21601    29397    +7796     
Flag Coverage Δ
inputsystem_MacOS_6000.0 5.32% <ø> (+0.01%) ⬆️
inputsystem_MacOS_6000.0_project 77.28% <100.00%> (-0.02%) ⬇️
inputsystem_MacOS_6000.3 5.32% <ø> (+0.01%) ⬆️
inputsystem_MacOS_6000.3_project 77.28% <100.00%> (+0.01%) ⬆️
inputsystem_MacOS_6000.4 5.33% <ø> (+0.01%) ⬆️
inputsystem_MacOS_6000.4_project 77.29% <100.00%> (+0.01%) ⬆️
inputsystem_MacOS_6000.5 5.32% <ø> (+0.01%) ⬆️
inputsystem_MacOS_6000.5_project 77.33% <100.00%> (+0.01%) ⬆️
inputsystem_MacOS_6000.6_project 77.33% <100.00%> (+0.01%) ⬆️
inputsystem_Ubuntu_6000.0 5.33% <ø> (+0.01%) ⬆️
inputsystem_Ubuntu_6000.0_project 77.19% <100.00%> (-0.02%) ⬇️
inputsystem_Ubuntu_6000.3 5.33% <ø> (+0.01%) ⬆️
inputsystem_Ubuntu_6000.3_project 77.19% <100.00%> (+0.01%) ⬆️
inputsystem_Ubuntu_6000.4 5.33% <ø> (+0.01%) ⬆️
inputsystem_Ubuntu_6000.4_project 77.20% <100.00%> (+0.01%) ⬆️
inputsystem_Ubuntu_6000.5 5.32% <ø> (+0.01%) ⬆️
inputsystem_Ubuntu_6000.5_project 77.23% <100.00%> (+0.01%) ⬆️
inputsystem_Ubuntu_6000.6_project 77.23% <100.00%> (+0.01%) ⬆️
inputsystem_Windows_6000.0 5.32% <ø> (+0.01%) ⬆️
inputsystem_Windows_6000.0_project 77.41% <100.00%> (-0.02%) ⬇️
inputsystem_Windows_6000.3 5.32% <ø> (+0.01%) ⬆️
inputsystem_Windows_6000.3_project 77.41% <100.00%> (+0.01%) ⬆️
inputsystem_Windows_6000.4 5.33% <ø> (+0.01%) ⬆️
inputsystem_Windows_6000.4_project 77.41% <100.00%> (+0.01%) ⬆️
inputsystem_Windows_6000.5 5.32% <ø> (+0.01%) ⬆️
inputsystem_Windows_6000.5_project 77.46% <100.00%> (+0.01%) ⬆️
inputsystem_Windows_6000.6_project 77.46% <100.00%> (+0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
Assets/Tests/InputSystem/Plugins/HIDTests.cs 97.97% <100.00%> (+0.03%) ⬆️
...inputsystem/InputSystem/Runtime/Plugins/HID/HID.cs 69.98% <ø> (ø)

... and 209 files with indirect coverage changes

ℹ️ Need help interpreting these results?

…icient

The HID.cs WithByteOffset(0) change makes hat/right and hat/up land at
the hat's own byte. The synthetic hat/x and hat/y inherit those offsets
via useStateFrom, so the pre-FinishSetup tree-build no longer sees
out-of-range synthetics. The InputDeviceBuilder synthetic-skip is no
longer needed to fix UUM-143659.

Keeping that skip broke APIVerificationTests.API_PrecompiledLayoutsAreUpToDate
for Keyboard/Mouse/Touchscreen: removing synthetic controls from the
bit-range tree changed the serialized layout of every device, so the
checked-in Fast{Keyboard,Mouse,Touchscreen}.cs no longer match what the
builder produces.

Verified locally:
  - Devices_CanCreateGenericHID_FromGamepadWithEightBitHatSwitch still
    passes with the HID-only fix.
  - API_PrecompiledLayoutsAreUpToDate passes for all three devices.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@LeoUnity
Copy link
Copy Markdown
Collaborator Author

LeoUnity commented Jun 3, 2026

CI triage (post-revert, commit b463697)

After the synthetic-skip revert the test regression is gone — APIVerificationTests.API_PrecompiledLayoutsAreUpToDate for Keyboard/Mouse/Touchscreen is passing locally, and Editor functional tests pass on 6000.0/3/4/5/6. Of the 12 jobs still failing in CI, none are caused by this PR's changes:

# Cluster Jobs Diagnosis Action
1 Cascade All Functional Tests, Publish Dry Run inputsystem Children failed Auto-resolves
2 Ubuntu Standalone timeouts StandaloneFunctionalTests - 6000.0/3/4 - Ubuntu "Timed out" — no test output Retry
3 Ubuntu Standalone build error StandaloneFunctionalTests - 6000.5 - Ubuntu EditorTestRunnerPluginBase.Build failed pre-test; same builder fails 3 other versions on Ubuntu Retry
4 MacOS Player build crashes StandaloneFunctionalTests - 6000.6 - MacOS, StandaloneIl2CppFunctionalTests - 6000.0 - MacOS, StandaloneIl2CppFunctionalTests - 6000.6 - MacOS BeeBuildPostprocessor error / "process crashed during cleanup". 0 tests ran. Retry; if persists → separate Unity engine bug
5 Validate 6000.6 PVP rules Validate - inputsystem - 6000.6 - macos13/ubuntu2204/win10 PVP-301-1 violations on pre-existing code: DEVELOPMENT_BUILD and UNITY_64 preprocessor deprecations (~30 sites in InputSystem.cs, InputManager.cs, InputControlExtensions.cs, MemoryHelpers.cs, etc.) + missing [Serializable] on HID.cs:326/520/521. Validate passes on 6000.0/3/4/5 — only 6000.6 alpha enforces these. None of the flagged lines are touched by this PR. Separate follow-up PR to clean up (off develop, not this branch)

This PR only modifies the 4 hat sub-control AddControl calls at HID.cs:871-905 (WithByteOffset(0)) plus the new test and CHANGELOG entry. None of the failing jobs reference those lines or the DpadControl / hat-switch path.

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