diff --git a/Assets/Tests/InputSystem/CoreTests_Controls.cs b/Assets/Tests/InputSystem/CoreTests_Controls.cs
index 9776706bff..ee0d8d21df 100644
--- a/Assets/Tests/InputSystem/CoreTests_Controls.cs
+++ b/Assets/Tests/InputSystem/CoreTests_Controls.cs
@@ -1428,7 +1428,7 @@ public void Controls_OptimizedControls_TrivialControlsAreOptimized()
Assert.That(mouse.position.x.optimizedControlDataType, Is.EqualTo(InputStateBlock.FormatFloat));
Assert.That(mouse.position.y.optimizedControlDataType, Is.EqualTo(InputStateBlock.FormatFloat));
Assert.That(mouse.position.optimizedControlDataType, Is.EqualTo(InputStateBlock.FormatVector2));
- Assert.That(mouse.leftButton.optimizedControlDataType, Is.EqualTo(InputStateBlock.FormatInvalid));
+ Assert.That(mouse.leftButton.optimizedControlDataType, Is.EqualTo(InputStateBlock.FormatBit));
InputSystem.settings.SetInternalFeatureFlag(InputFeatureNames.kUseOptimizedControls, false);
Assert.That(mouse.position.x.optimizedControlDataType, Is.EqualTo(InputStateBlock.FormatInvalid));
diff --git a/Assets/Tests/InputSystem/Plugins/XRTests.cs b/Assets/Tests/InputSystem/Plugins/XRTests.cs
index 6f0e9cd1fd..b67fd13ffa 100644
--- a/Assets/Tests/InputSystem/Plugins/XRTests.cs
+++ b/Assets/Tests/InputSystem/Plugins/XRTests.cs
@@ -1222,6 +1222,52 @@ public void Controls_OptimizedControls_PoseControl_IsOptimized()
Assert.That((device["posecontrol"] as PoseControl).optimizedControlDataType, Is.EqualTo(InputStateBlock.FormatPose));
}
+ [Test]
+ [Category("Controls")]
+ [TestCase(true)]
+ [TestCase(false)]
+ public void Controls_PoseControl_IsTracked_ReadsCorrectly(bool useOptimizedControls)
+ {
+ InputSystem.settings.SetInternalFeatureFlag(InputFeatureNames.kUseOptimizedControls, useOptimizedControls);
+
+ runtime.ReportNewInputDevice(PoseDeviceState.CreateDeviceDescription().ToJson());
+
+ InputSystem.Update();
+
+ var device = InputSystem.devices[0];
+ var poseControl = device["posecontrol"] as PoseControl;
+
+ // Queue state with isTracked = true (byte value 1)
+ InputSystem.QueueStateEvent(device, new PoseDeviceState
+ {
+ isTracked = 1,
+ trackingState = (uint)(InputTrackingState.Position | InputTrackingState.Rotation),
+ position = new Vector3(1, 2, 3),
+ rotation = Quaternion.identity,
+ });
+ InputSystem.Update();
+
+ // Reading isTracked as a ButtonControl should return true
+ Assert.That(poseControl.isTracked.isPressed, Is.True, "isTracked.isPressed should be true when tracked");
+ Assert.That(poseControl.isTracked.ReadValue(), Is.EqualTo(1.0f).Within(0.001f), "isTracked.ReadValue() should be 1.0f when tracked");
+
+ // Reading the full PoseState should also have isTracked = true
+ var poseState = poseControl.ReadValue();
+ Assert.That(poseState.isTracked, Is.True, "PoseState.isTracked should be true when tracked");
+
+ // Queue state with isTracked = false (byte value 0)
+ InputSystem.QueueStateEvent(device, new PoseDeviceState
+ {
+ isTracked = 0,
+ trackingState = 0,
+ });
+ InputSystem.Update();
+
+ Assert.That(poseControl.isTracked.isPressed, Is.False, "isTracked.isPressed should be false when not tracked");
+ Assert.That(poseControl.isTracked.ReadValue(), Is.EqualTo(0.0f).Within(0.001f), "isTracked.ReadValue() should be 0.0f when not tracked");
+ Assert.That(poseControl.ReadValue().isTracked, Is.False, "PoseState.isTracked should be false when not tracked");
+ }
+
// ISXB-405
[Test]
[Category("Devices")]
diff --git a/Packages/com.unity.inputsystem/CHANGELOG.md b/Packages/com.unity.inputsystem/CHANGELOG.md
index 73b3864462..ac9ae472b5 100644
--- a/Packages/com.unity.inputsystem/CHANGELOG.md
+++ b/Packages/com.unity.inputsystem/CHANGELOG.md
@@ -32,6 +32,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Fixed an incorrect ArraysHelper.HaveDuplicateReferences implementation that didn't use its arguments right [ISXB-1792] (https://github.com/Unity-Technologies/InputSystem/pull/2376)
- Fixed `InputAction.IsPressed`, `WasPressedThisFrame`, and `WasReleasedThisFrame` using a `ButtonControl`'s `pressPoint` when a binding also had an explicit `PressInteraction` with its own `pressPoint`, which could make those APIs disagree with the interaction's press and release behavior. Action-level press APIs now follow the interaction threshold when both are set explicitly.
- Fixed `IndexOutOfRangeException` in `InputDeviceBuilder` when connecting an HID gamepad whose report descriptor declares a hat switch with Report Size 8 (e.g. ESP32-BLE-Gamepad). The HID layer now anchors the hat's directional sub-controls to the hat's own byte instead of letting the layout system auto-allocate a fresh byte for each [UUM-143659](https://jira.unity3d.com/browse/UUM-143659).
+- Fixed `PoseControl.isTracked` always returning false when read through non-optimized code paths (e.g. Input Debugger) due to `sizeInBits = 8` causing the bool value to be normalized as `1/255` instead of `1.0`.
### Changed
- Action-level `IsPressed`, `WasPressedThisFrame`, and `WasReleasedThisFrame` for bindings to `Vector2Control` / `StickControl` no longer consult a per-control `pressPoint` on the vector (that field was removed). Use a `Press` interaction to set a custom threshold, or rely on `defaultButtonPressPoint`.
diff --git a/Packages/com.unity.inputsystem/InputSystem/Runtime/Controls/ButtonControl.cs b/Packages/com.unity.inputsystem/InputSystem/Runtime/Controls/ButtonControl.cs
index 1b729b3d84..793250bd4d 100644
--- a/Packages/com.unity.inputsystem/InputSystem/Runtime/Controls/ButtonControl.cs
+++ b/Packages/com.unity.inputsystem/InputSystem/Runtime/Controls/ButtonControl.cs
@@ -378,6 +378,20 @@ internal void UpdateWasPressedEditor()
#endif // UNITY_EDITOR
+ protected override FourCC CalculateOptimizedControlDataType()
+ {
+ if (clamp == Clamp.None &&
+ invert == false &&
+ normalize == false &&
+ scale == false &&
+ m_StateBlock.format == InputStateBlock.FormatBit &&
+ m_StateBlock.sizeInBits == 1 &&
+ m_StateBlock.bitOffset == 0)
+ return InputStateBlock.FormatBit;
+
+ return base.CalculateOptimizedControlDataType();
+ }
+
// We make the current global default button press point available as a static so that we don't have to
// constantly make the hop from InputSystem.settings -> InputManager.m_Settings -> defaultButtonPressPoint.
internal static float s_GlobalDefaultButtonPressPoint;
diff --git a/Packages/com.unity.inputsystem/InputSystem/Runtime/Plugins/XR/Controls/PoseControl.cs b/Packages/com.unity.inputsystem/InputSystem/Runtime/Plugins/XR/Controls/PoseControl.cs
index db4881f631..0fd8b43dbc 100644
--- a/Packages/com.unity.inputsystem/InputSystem/Runtime/Plugins/XR/Controls/PoseControl.cs
+++ b/Packages/com.unity.inputsystem/InputSystem/Runtime/Plugins/XR/Controls/PoseControl.cs
@@ -60,7 +60,7 @@ public PoseState(bool isTracked, TrackingState trackingState, Vector3 position,
///
/// Fully tracked means that the pose is accurate and not using any simulated or extrapolated positions, and the system tracking this pose is able to confidently track this object.
///
- [FieldOffset(0), InputControl(displayName = "Is Tracked", layout = "Button", sizeInBits = 8 /* needed to ensure optimization kicks-in */)]
+ [FieldOffset(0), InputControl(displayName = "Is Tracked", layout = "Button", sizeInBits = 1)]
public bool isTracked;
///
@@ -252,7 +252,7 @@ protected override FourCC CalculateOptimizedControlDataType()
if (
m_StateBlock.sizeInBits == PoseState.kSizeInBytes * 8 &&
m_StateBlock.bitOffset == 0 &&
- isTracked.optimizedControlDataType == InputStateBlock.kFormatByte &&
+ isTracked.optimizedControlDataType == InputStateBlock.kFormatBit &&
trackingState.optimizedControlDataType == InputStateBlock.kFormatInt &&
position.optimizedControlDataType == InputStateBlock.kFormatVector3 &&
rotation.optimizedControlDataType == InputStateBlock.kFormatQuaternion &&