From d8f369b0a2b3e28fbbd5e982cdab4593d1f38020 Mon Sep 17 00:00:00 2001 From: Morgan Hoarau Date: Wed, 18 Feb 2026 11:11:13 +0000 Subject: [PATCH 1/4] Skip initial state check for inactive touch controls Add ShouldSkipInitialStateCheck and use it in InputActionState's initial-state loop to avoid treating preserved touch data as actuated during binding re-resolution. --- .../InputSystem/Actions/InputActionState.cs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionState.cs b/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionState.cs index 33d782a39c..89ff46598c 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionState.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionState.cs @@ -1321,6 +1321,9 @@ private void OnBeforeInitialUpdate() if (IsActiveControl(bindingIndex, controlIndex)) continue; + if (ShouldSkipInitialStateCheck(control)) + continue; + if (!control.CheckStateIsAtDefault()) { // Update press times. @@ -1348,6 +1351,24 @@ private void OnBeforeInitialUpdate() k_InputInitialActionStateCheckMarker.End(); } + private static bool ShouldSkipInitialStateCheck(InputControl control) + { + // UUM-100125 + // Touch controls intentionally preserve state such as position even when no touch is currently active. + // During binding re-resolution this can make inactive touches look actuated and cause invalid triggers. + if (control is TouchControl touchControl) + { + return !touchControl.isInProgress; + } + + if (control.parent is TouchControl parentTouchControl) + { + return !parentTouchControl.isInProgress; + } + + return false; + } + // Called from InputManager when one of our state change monitors has fired. // Tells us the time of the change *according to the state events coming in*. // Also tells us which control of the controls we are binding to triggered the From 6ce4494ba5b9c214d977df899d6216cc5bbd9023 Mon Sep 17 00:00:00 2001 From: Morgan Hoarau Date: Wed, 18 Feb 2026 11:11:56 +0000 Subject: [PATCH 2/4] Add reg test to ensure inactive touch does not trigger --- Assets/Tests/InputSystem/CoreTests_Actions.cs | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/Assets/Tests/InputSystem/CoreTests_Actions.cs b/Assets/Tests/InputSystem/CoreTests_Actions.cs index 322a50a250..7ad23c41f7 100644 --- a/Assets/Tests/InputSystem/CoreTests_Actions.cs +++ b/Assets/Tests/InputSystem/CoreTests_Actions.cs @@ -1400,6 +1400,45 @@ public void Actions_ValueActionsEnabledInOnEvent_DoNotReactToCurrentStateOfContr } } + // Regression test for UUM-100125. + [Test] + [Category("Actions")] + public void Actions_InitialStateCheckAfterConfigurationChange_DoesNotTriggerForInactiveTouch() + { + var touchscreen = InputSystem.AddDevice(); + var action = new InputAction(type: InputActionType.Value, binding: "/primaryTouch/position"); + action.Enable(); + + // Run the first initial state check from enabling the action. + InputSystem.Update(); + + using (var trace = new InputActionTrace(action)) + { + BeginTouch(1, new Vector2(123, 234)); + EndTouch(1, new Vector2(345, 456)); + + Assert.That(touchscreen.primaryTouch.isInProgress, Is.False); + Assert.That(touchscreen.primaryTouch.position.ReadValue(), Is.Not.EqualTo(default(Vector2))); + + trace.Clear(); + + // Configuration change causes full re-resolve and schedules initial state check. + InputSystem.QueueConfigChangeEvent(touchscreen); + InputSystem.Update(); + InputSystem.Update(); + + // Full re-resolve may cancel the current action state. What must NOT happen is a synthetic + // Started/Performed pair from persisted inactive touch state. + Assert.AreEqual(1, trace.count); + foreach (var eventPtr in trace) + { + // The trace should only contain a Canceled event for the action. + Assert.AreEqual(InputActionPhase.Canceled, eventPtr.phase, + $"inactive touch state should not produce action callbacks, but received {eventPtr.phase}."); + } + } + } + // https://fogbugz.unity3d.com/f/cases/1192972/ [Test] [Category("Actions")] From 9fb9209d747e782e4899df9c0af7a74479d5a872 Mon Sep 17 00:00:00 2001 From: Morgan Hoarau Date: Mon, 2 Mar 2026 17:00:27 +0000 Subject: [PATCH 3/4] Fixes legacy expected behaviour in test --- Assets/Tests/InputSystem/CoreTests_Actions.cs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/Assets/Tests/InputSystem/CoreTests_Actions.cs b/Assets/Tests/InputSystem/CoreTests_Actions.cs index 7ad23c41f7..29e305e197 100644 --- a/Assets/Tests/InputSystem/CoreTests_Actions.cs +++ b/Assets/Tests/InputSystem/CoreTests_Actions.cs @@ -12506,17 +12506,20 @@ public void Actions_WithMultipleCompositeBindings_WithoutEvaluateMagnitude_Works // Now when enabling actionMap .. actionMap.Enable(); - // On the following update we will trigger OnBeforeUpdate which will rise started/performed - // from InputActionState.OnBeforeInitialUpdate as controls are "actuated" + // Inactive touches (ended before action was enabled) must NOT produce started/performed from + // OnBeforeInitialUpdate. Their persisted state (position, touchId) is non-default due to + // dontReset, but only TouchControl.isInProgress should be considered for initial-state check. + // Related to UUM-100125 and Actions_InitialStateCheckAfterConfigurationChange_DoesNotTriggerForInactiveTouch. InputSystem.Update(); - Assert.That(values.Count, Is.EqualTo(prepopulateTouchesBeforeEnablingAction ? 2 : 0)); // started+performed arrive from OnBeforeUpdate + Assert.That(values.Count, Is.EqualTo(0)); values.Clear(); - // Now subsequent touches should not be ignored BeginTouch(200, new Vector2(1, 1)); - Assert.That(values.Count, Is.EqualTo(1)); - Assert.That(values[0].InputId, Is.EqualTo(200)); - Assert.That(values[0].Position, Is.EqualTo(new Vector2(1, 1))); + // If prepopulated, action was never actuated (synthetic initial-check is suppressed), + // so BeginTouch fires started+performed (2 events). + Assert.That(values.Count, Is.EqualTo(prepopulateTouchesBeforeEnablingAction ? 2 : 1)); + Assert.That(values[values.Count - 1].InputId, Is.EqualTo(200)); + Assert.That(values[values.Count - 1].Position, Is.EqualTo(new Vector2(1, 1))); } // FIX: This test is currently checking if shortcut support is enabled by testing that the unwanted behaviour exists. From 4d835b6c702431604c278f14333185823185f689 Mon Sep 17 00:00:00 2001 From: Morgan Hoarau Date: Tue, 3 Mar 2026 17:21:45 +0000 Subject: [PATCH 4/4] Traverse ancestors to detect TouchControl state Addresses U-PR feedback Previously the code only checked the control and its immediate parent for a TouchControl, which could miss deeper nested touch controls and cause inactive touches to appear actuated during binding re-resolution. Replace the checks with a loop that walks up the control hierarchy and returns based on the first ancestor TouchControl's isInProgress value, preventing invalid triggers from preserved touch state. --- .../InputSystem/Actions/InputActionState.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionState.cs b/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionState.cs index 89ff46598c..d2cb897f20 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionState.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Actions/InputActionState.cs @@ -1356,14 +1356,12 @@ private static bool ShouldSkipInitialStateCheck(InputControl control) // UUM-100125 // Touch controls intentionally preserve state such as position even when no touch is currently active. // During binding re-resolution this can make inactive touches look actuated and cause invalid triggers. - if (control is TouchControl touchControl) + for (var current = control; current != null; current = current.parent) { - return !touchControl.isInProgress; - } - - if (control.parent is TouchControl parentTouchControl) - { - return !parentTouchControl.isInProgress; + if (current is TouchControl touchControl) + { + return !touchControl.isInProgress; + } } return false;