diff --git a/.agents/skills/uloop-simulate-keyboard/references/output.md b/.agents/skills/uloop-simulate-keyboard/references/output.md
index 2c6f502993..d9a8060334 100644
--- a/.agents/skills/uloop-simulate-keyboard/references/output.md
+++ b/.agents/skills/uloop-simulate-keyboard/references/output.md
@@ -17,10 +17,10 @@ Returns JSON with:
- `RejectedByActivePausePointId` (string, nullable): Set when an active pause point rejected this call before any input was injected — distinct from `PausePointId`, which reports a marker hit during the call. When set, the action never happened, so do not read `Success` alone
- `PressEdgeObserved` (boolean, nullable): For `Press` and `KeyDown`, whether the press edge (`wasPressedThisFrame`) was visible inside a gameplay input update. `false` means the CLI succeeded but gameplay polling most likely missed the edge — verify with a focused log instead of trusting `Success` alone. `null` only for `KeyUp` and timed-out responses; pause-point interruptions still report the observed value. When a single-shot pause point is armed, do not blindly retry on `false`: the input may still have registered late, so check `pause-point-status` for a hit first — a blind retry can consume a re-enabled marker or double-fire the scenario
- `PressHoldExtendedFrames` (integer, nullable): Extra observation frames the key stayed held beyond the normal duration window while waiting for `wasPressedThisFrame`; `null` when the release was not delayed
-- `PressEdgeConsumedByUpdateType` / `PressEdgeAnyDynamicUpdateObserved` / `PressEdgeKeyAlreadyPressedBeforeQueue` (nullable): Diagnostics populated only when `PressEdgeObserved` is `false` (all `null` when the edge was observed). `Message` carries the same diagnosis as text. Read them before retrying:
+- `PressEdgeConsumedByUpdateType` / `PressEdgeAnyGameplayUpdateObserved` / `PressEdgeKeyAlreadyPressedBeforeQueue` (nullable): Diagnostics populated only when `PressEdgeObserved` is `false` (all `null` when the edge was observed). `Message` carries the same diagnosis as text. Read them before retrying:
| Diagnostic | Meaning | Next action |
|---|---|---|
| `PressEdgeKeyAlreadyPressedBeforeQueue=true` | The key was already held; no press transition could occur | Release with `KeyUp`, then press again |
-| `PressEdgeConsumedByUpdateType` names a non-`Dynamic` update type (e.g. `Editor`) | An editor-side update consumed the edge before gameplay polling saw it | Retry: rerun `Press` directly; for `KeyDown` the key is now held, so `KeyUp` first (a held key rejects a second `KeyDown`) |
-| `PressEdgeAnyDynamicUpdateObserved=false` | No `Dynamic`-type input update ran during the press window (this flag does not track `Fixed`/`Manual` gameplay updates) | Check that PlayMode is running and unpaused; do not retry blindly |
+| `PressEdgeConsumedByUpdateType` is `Editor` | An editor-side update consumed the edge before gameplay polling saw it | Retry: rerun `Press` directly; for `KeyDown` the key is now held, so `KeyUp` first (a held key rejects a second `KeyDown`) |
+| `PressEdgeAnyGameplayUpdateObserved=false` | No gameplay input update (`Dynamic`, `Fixed`, or `Manual`) ran during the press window; `Editor` ticks do not count | Check that PlayMode is running and unpaused; do not retry blindly |
diff --git a/.claude/skills/uloop-simulate-keyboard/references/output.md b/.claude/skills/uloop-simulate-keyboard/references/output.md
index 2c6f502993..d9a8060334 100644
--- a/.claude/skills/uloop-simulate-keyboard/references/output.md
+++ b/.claude/skills/uloop-simulate-keyboard/references/output.md
@@ -17,10 +17,10 @@ Returns JSON with:
- `RejectedByActivePausePointId` (string, nullable): Set when an active pause point rejected this call before any input was injected — distinct from `PausePointId`, which reports a marker hit during the call. When set, the action never happened, so do not read `Success` alone
- `PressEdgeObserved` (boolean, nullable): For `Press` and `KeyDown`, whether the press edge (`wasPressedThisFrame`) was visible inside a gameplay input update. `false` means the CLI succeeded but gameplay polling most likely missed the edge — verify with a focused log instead of trusting `Success` alone. `null` only for `KeyUp` and timed-out responses; pause-point interruptions still report the observed value. When a single-shot pause point is armed, do not blindly retry on `false`: the input may still have registered late, so check `pause-point-status` for a hit first — a blind retry can consume a re-enabled marker or double-fire the scenario
- `PressHoldExtendedFrames` (integer, nullable): Extra observation frames the key stayed held beyond the normal duration window while waiting for `wasPressedThisFrame`; `null` when the release was not delayed
-- `PressEdgeConsumedByUpdateType` / `PressEdgeAnyDynamicUpdateObserved` / `PressEdgeKeyAlreadyPressedBeforeQueue` (nullable): Diagnostics populated only when `PressEdgeObserved` is `false` (all `null` when the edge was observed). `Message` carries the same diagnosis as text. Read them before retrying:
+- `PressEdgeConsumedByUpdateType` / `PressEdgeAnyGameplayUpdateObserved` / `PressEdgeKeyAlreadyPressedBeforeQueue` (nullable): Diagnostics populated only when `PressEdgeObserved` is `false` (all `null` when the edge was observed). `Message` carries the same diagnosis as text. Read them before retrying:
| Diagnostic | Meaning | Next action |
|---|---|---|
| `PressEdgeKeyAlreadyPressedBeforeQueue=true` | The key was already held; no press transition could occur | Release with `KeyUp`, then press again |
-| `PressEdgeConsumedByUpdateType` names a non-`Dynamic` update type (e.g. `Editor`) | An editor-side update consumed the edge before gameplay polling saw it | Retry: rerun `Press` directly; for `KeyDown` the key is now held, so `KeyUp` first (a held key rejects a second `KeyDown`) |
-| `PressEdgeAnyDynamicUpdateObserved=false` | No `Dynamic`-type input update ran during the press window (this flag does not track `Fixed`/`Manual` gameplay updates) | Check that PlayMode is running and unpaused; do not retry blindly |
+| `PressEdgeConsumedByUpdateType` is `Editor` | An editor-side update consumed the edge before gameplay polling saw it | Retry: rerun `Press` directly; for `KeyDown` the key is now held, so `KeyUp` first (a held key rejects a second `KeyDown`) |
+| `PressEdgeAnyGameplayUpdateObserved=false` | No gameplay input update (`Dynamic`, `Fixed`, or `Manual`) ran during the press window; `Editor` ticks do not count | Check that PlayMode is running and unpaused; do not retry blindly |
diff --git a/Assets/Tests/Editor/InputUpdateTypeResolverTests.cs b/Assets/Tests/Editor/InputUpdateTypeResolverTests.cs
new file mode 100644
index 0000000000..f8e0717f6f
--- /dev/null
+++ b/Assets/Tests/Editor/InputUpdateTypeResolverTests.cs
@@ -0,0 +1,33 @@
+#nullable enable
+#if ULOOP_HAS_INPUT_SYSTEM
+using NUnit.Framework;
+using UnityEngine.InputSystem.LowLevel;
+
+using io.github.hatayama.UnityCliLoop.FirstPartyTools;
+
+namespace io.github.hatayama.UnityCliLoop.Tests.Editor
+{
+ ///
+ /// Pure unit tests for the gameplay update-type classification shared by press-edge observation and diagnostics.
+ ///
+ public sealed class InputUpdateTypeResolverTests
+ {
+ [TestCase(InputUpdateType.Dynamic)]
+ [TestCase(InputUpdateType.Fixed)]
+ [TestCase(InputUpdateType.Manual)]
+ public void IsGameplayUpdate_WhenGameplayInputUpdateType_ReturnsTrue(InputUpdateType updateType)
+ {
+ // Verifies every update type gameplay polling can observe a press edge in counts as a gameplay update.
+ Assert.That(InputUpdateTypeResolver.IsGameplayUpdate(updateType), Is.True);
+ }
+
+ [TestCase(InputUpdateType.Editor)]
+ [TestCase(InputUpdateType.None)]
+ public void IsGameplayUpdate_WhenNotGameplayInputUpdateType_ReturnsFalse(InputUpdateType updateType)
+ {
+ // Verifies Editor ticks and the absence of an update are not mistaken for gameplay input updates.
+ Assert.That(InputUpdateTypeResolver.IsGameplayUpdate(updateType), Is.False);
+ }
+ }
+}
+#endif
diff --git a/Assets/Tests/Editor/InputUpdateTypeResolverTests.cs.meta b/Assets/Tests/Editor/InputUpdateTypeResolverTests.cs.meta
new file mode 100644
index 0000000000..de44aad7e6
--- /dev/null
+++ b/Assets/Tests/Editor/InputUpdateTypeResolverTests.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 9f14fa0cbe405449f849316350a9bcd7
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Tests/Editor/PressEdgeDiagnosticsMessageFormatterTests.cs b/Assets/Tests/Editor/PressEdgeDiagnosticsMessageFormatterTests.cs
index e7d09495b1..db1b7eb4c7 100644
--- a/Assets/Tests/Editor/PressEdgeDiagnosticsMessageFormatterTests.cs
+++ b/Assets/Tests/Editor/PressEdgeDiagnosticsMessageFormatterTests.cs
@@ -16,31 +16,31 @@ public void BuildSuffix_WhenKeyAlreadyPressedBeforeQueue_ReportsNoTransition()
// Verifies a pre-existing down state (no edge possible) is reported over other causes.
string suffix = PressEdgeDiagnosticsMessageFormatter.BuildSuffix(
consumedByUpdateType: "Dynamic",
- anyDynamicUpdateObserved: true,
+ anyGameplayUpdateObserved: true,
keyAlreadyPressedBeforeQueue: true);
Assert.That(suffix, Does.Contain("already down"));
}
[Test]
- public void BuildSuffix_WhenNotConsumedAndNoDynamicUpdateRan_ReportsNoDynamicUpdate()
+ public void BuildSuffix_WhenNotConsumedAndNoGameplayUpdateRan_ReportsNoGameplayUpdate()
{
- // Verifies a stalled Dynamic update loop is distinguished from a dropped event.
+ // Verifies a stalled gameplay update loop is distinguished from a dropped event.
string suffix = PressEdgeDiagnosticsMessageFormatter.BuildSuffix(
consumedByUpdateType: null,
- anyDynamicUpdateObserved: false,
+ anyGameplayUpdateObserved: false,
keyAlreadyPressedBeforeQueue: false);
- Assert.That(suffix, Does.Contain("no Dynamic update ran"));
+ Assert.That(suffix, Does.Contain("no gameplay input update"));
}
[Test]
- public void BuildSuffix_WhenNotConsumedButDynamicUpdatesRan_ReportsDroppedEvent()
+ public void BuildSuffix_WhenNotConsumedButGameplayUpdatesRan_ReportsDroppedEvent()
{
// Verifies the event-never-consumed case is distinguished from a stalled update loop.
string suffix = PressEdgeDiagnosticsMessageFormatter.BuildSuffix(
consumedByUpdateType: null,
- anyDynamicUpdateObserved: true,
+ anyGameplayUpdateObserved: true,
keyAlreadyPressedBeforeQueue: false);
Assert.That(suffix, Does.Contain("not consumed by any recorded"));
@@ -52,7 +52,7 @@ public void BuildSuffix_WhenConsumedByEditorUpdate_ReportsEditorInvisibility()
// Verifies the Editor-update-consumed case names the reason gameplay could not see it.
string suffix = PressEdgeDiagnosticsMessageFormatter.BuildSuffix(
consumedByUpdateType: "Editor",
- anyDynamicUpdateObserved: false,
+ anyGameplayUpdateObserved: false,
keyAlreadyPressedBeforeQueue: false);
Assert.That(suffix, Does.Contain("Editor update"));
@@ -64,7 +64,7 @@ public void BuildSuffix_WhenConsumedByOtherUpdateType_NamesThatUpdateType()
// Verifies an unexpected consuming update type (e.g. Fixed) is surfaced by name.
string suffix = PressEdgeDiagnosticsMessageFormatter.BuildSuffix(
consumedByUpdateType: "Fixed",
- anyDynamicUpdateObserved: true,
+ anyGameplayUpdateObserved: true,
keyAlreadyPressedBeforeQueue: false);
Assert.That(suffix, Does.Contain("Fixed update"));
diff --git a/Assets/Tests/Editor/SimulateKeyboardResponseContractTests.cs b/Assets/Tests/Editor/SimulateKeyboardResponseContractTests.cs
index 8086df8a77..e03496c7da 100644
--- a/Assets/Tests/Editor/SimulateKeyboardResponseContractTests.cs
+++ b/Assets/Tests/Editor/SimulateKeyboardResponseContractTests.cs
@@ -22,7 +22,7 @@ public sealed class SimulateKeyboardResponseContractTests
+ "\"PausePointHits\":[{\"Id\":\"hit\",\"HitCount\":1}],"
+ "\"PressEdgeObserved\":true,\"PressHoldExtendedFrames\":2,"
+ "\"PressEdgeConsumedByUpdateType\":\"Dynamic\","
- + "\"PressEdgeAnyDynamicUpdateObserved\":true,"
+ + "\"PressEdgeAnyGameplayUpdateObserved\":true,"
+ "\"PressEdgeKeyAlreadyPressedBeforeQueue\":false,"
+ "\"KeyStateTrackedHeld\":true,\"KeyStateDeviceIsPressed\":false,"
+ "\"ReleasedKeys\":[\"Space\"],\"Success\":true}";
@@ -66,7 +66,7 @@ public void SimulateKeyboardResponse_WhenOptionalFieldsAreSet_IncludesThoseKeysI
PressEdgeObserved = true,
PressHoldExtendedFrames = 2,
PressEdgeConsumedByUpdateType = "Dynamic",
- PressEdgeAnyDynamicUpdateObserved = true,
+ PressEdgeAnyGameplayUpdateObserved = true,
PressEdgeKeyAlreadyPressedBeforeQueue = false,
KeyStateTrackedHeld = true,
KeyStateDeviceIsPressed = false,
diff --git a/Packages/src/Editor/FirstPartyTools/Common/InputSystem/InputUpdateTypeResolver.cs b/Packages/src/Editor/FirstPartyTools/Common/InputSystem/InputUpdateTypeResolver.cs
index 70335d60b7..c875e20f62 100644
--- a/Packages/src/Editor/FirstPartyTools/Common/InputSystem/InputUpdateTypeResolver.cs
+++ b/Packages/src/Editor/FirstPartyTools/Common/InputSystem/InputUpdateTypeResolver.cs
@@ -42,6 +42,18 @@ public static bool IsMatch(InputUpdateType current, InputUpdateType expected)
return (current & expected) == expected;
}
+ // A press edge is visible to gameplay polling only when the Input System processed it
+ // in one of the player-loop update types; the Editor tick never surfaces
+ // wasPressedThisFrame to gameplay Update, and None means no update ran at all. Press-edge
+ // observation and its miss diagnostics must agree on this set, otherwise a Fixed or Manual
+ // project reads "no gameplay update ran" while its gameplay updates did run.
+ public static bool IsGameplayUpdate(InputUpdateType updateType)
+ {
+ return updateType == InputUpdateType.Dynamic
+ || updateType == InputUpdateType.Fixed
+ || updateType == InputUpdateType.Manual;
+ }
+
public static bool RequiresExplicitUpdate()
{
InputSettings? settings = InputSystem.settings;
diff --git a/Packages/src/Editor/FirstPartyTools/Common/InputSystem/PressEdgeDiagnosticsMessageFormatter.cs b/Packages/src/Editor/FirstPartyTools/Common/InputSystem/PressEdgeDiagnosticsMessageFormatter.cs
index 3af3798ff1..c4ba76d0b0 100644
--- a/Packages/src/Editor/FirstPartyTools/Common/InputSystem/PressEdgeDiagnosticsMessageFormatter.cs
+++ b/Packages/src/Editor/FirstPartyTools/Common/InputSystem/PressEdgeDiagnosticsMessageFormatter.cs
@@ -13,7 +13,7 @@ internal static class PressEdgeDiagnosticsMessageFormatter
{
public static string BuildSuffix(
string? consumedByUpdateType,
- bool anyDynamicUpdateObserved,
+ bool anyGameplayUpdateObserved,
bool keyAlreadyPressedBeforeQueue)
{
if (keyAlreadyPressedBeforeQueue)
@@ -23,9 +23,9 @@ public static string BuildSuffix(
if (consumedByUpdateType == null)
{
- return anyDynamicUpdateObserved
- ? " (the key-down event was not consumed by any recorded Input System update, even though Dynamic updates ran during the wait)"
- : " (no Dynamic update ran during the wait, so gameplay Update polling never had a chance to observe it)";
+ return anyGameplayUpdateObserved
+ ? " (the key-down event was not consumed by any recorded Input System update, even though gameplay input updates ran during the wait)"
+ : " (no gameplay input update (Dynamic, Fixed, or Manual) ran during the wait, so gameplay polling never had a chance to observe it)";
}
if (consumedByUpdateType == "Editor")
diff --git a/Packages/src/Editor/FirstPartyTools/SimulateKeyboard/DeferredPlayerLatchSyncDecision.cs b/Packages/src/Editor/FirstPartyTools/SimulateKeyboard/DeferredPlayerLatchSyncDecision.cs
index 49348acea8..15afc9a802 100644
--- a/Packages/src/Editor/FirstPartyTools/SimulateKeyboard/DeferredPlayerLatchSyncDecision.cs
+++ b/Packages/src/Editor/FirstPartyTools/SimulateKeyboard/DeferredPlayerLatchSyncDecision.cs
@@ -13,13 +13,12 @@ internal static class DeferredPlayerLatchSyncDecision
/// Decides whether this Input System update should sync stale player latches and
/// then drop the callback. Why Editor is kept: pause only runs Editor updates, so
/// consuming the one-shot there would skip the first player update after resume.
- /// Why not HasFlag: Default includes Editor and would look like a player update.
+ /// Why shared predicate: the latch sync and the press-edge visibility check must agree
+ /// on which update types are player updates, so both read InputUpdateTypeResolver.
///
internal static DeferredLatchSyncTickDecision Decide(InputUpdateType currentUpdateType)
{
- bool isPlayerUpdate = currentUpdateType == InputUpdateType.Dynamic
- || currentUpdateType == InputUpdateType.Fixed
- || currentUpdateType == InputUpdateType.Manual;
+ bool isPlayerUpdate = InputUpdateTypeResolver.IsGameplayUpdate(currentUpdateType);
return new DeferredLatchSyncTickDecision(isPlayerUpdate, isPlayerUpdate);
}
}
diff --git a/Packages/src/Editor/FirstPartyTools/SimulateKeyboard/KeyboardInputActionExecutor.cs b/Packages/src/Editor/FirstPartyTools/SimulateKeyboard/KeyboardInputActionExecutor.cs
index 4b211ebcca..fa0ffa8a0f 100644
--- a/Packages/src/Editor/FirstPartyTools/SimulateKeyboard/KeyboardInputActionExecutor.cs
+++ b/Packages/src/Editor/FirstPartyTools/SimulateKeyboard/KeyboardInputActionExecutor.cs
@@ -215,7 +215,7 @@ private static SimulateKeyboardResponse BuildPressSuccessResponse(
PressEdgeObserved = pressEdgeObserved,
PressHoldExtendedFrames = pressHoldExtendedFrames > 0 ? pressHoldExtendedFrames : null,
PressEdgeConsumedByUpdateType = pressEdgeObserved ? null : edgeMissDiagnostics.ConsumedByUpdateType,
- PressEdgeAnyDynamicUpdateObserved = pressEdgeObserved ? null : edgeMissDiagnostics.AnyDynamicUpdateObserved,
+ PressEdgeAnyGameplayUpdateObserved = pressEdgeObserved ? null : edgeMissDiagnostics.AnyGameplayUpdateObserved,
PressEdgeKeyAlreadyPressedBeforeQueue = pressEdgeObserved ? null : edgeMissDiagnostics.KeyAlreadyPressedBeforeQueue
};
}
@@ -240,7 +240,7 @@ private static string BuildPressEdgeText(
" (press edge was not observed via wasPressedThisFrame; gameplay polling may have missed it, so retry or verify with a focused log)" +
PressEdgeDiagnosticsMessageFormatter.BuildSuffix(
edgeMissDiagnostics.ConsumedByUpdateType,
- edgeMissDiagnostics.AnyDynamicUpdateObserved,
+ edgeMissDiagnostics.AnyGameplayUpdateObserved,
edgeMissDiagnostics.KeyAlreadyPressedBeforeQueue);
}
@@ -352,7 +352,7 @@ await KeyboardInputMainThreadCleanup.RollbackHeldKey(
: " (press edge was not observed via wasPressedThisFrame; gameplay polling may have missed it)" +
PressEdgeDiagnosticsMessageFormatter.BuildSuffix(
edgeMissDiagnostics.ConsumedByUpdateType,
- edgeMissDiagnostics.AnyDynamicUpdateObserved,
+ edgeMissDiagnostics.AnyGameplayUpdateObserved,
edgeMissDiagnostics.KeyAlreadyPressedBeforeQueue);
return new SimulateKeyboardResponse
{
@@ -362,7 +362,7 @@ await KeyboardInputMainThreadCleanup.RollbackHeldKey(
KeyName = keyName,
PressEdgeObserved = pressEdgeObserved,
PressEdgeConsumedByUpdateType = pressEdgeObserved ? null : edgeMissDiagnostics.ConsumedByUpdateType,
- PressEdgeAnyDynamicUpdateObserved = pressEdgeObserved ? null : edgeMissDiagnostics.AnyDynamicUpdateObserved,
+ PressEdgeAnyGameplayUpdateObserved = pressEdgeObserved ? null : edgeMissDiagnostics.AnyGameplayUpdateObserved,
PressEdgeKeyAlreadyPressedBeforeQueue = pressEdgeObserved ? null : edgeMissDiagnostics.KeyAlreadyPressedBeforeQueue
};
}
@@ -429,11 +429,11 @@ await KeyboardInputMainThreadCleanup.ReleaseKeyStateIfPossible(
};
}
- // Runs inside InputSystem.onAfterUpdate. Editor updates are excluded because a press
- // consumed there never surfaces as wasPressedThisFrame to gameplay Update polling.
+ // Runs inside InputSystem.onAfterUpdate. Only gameplay update types count because a press
+ // consumed in the Editor tick never surfaces as wasPressedThisFrame to gameplay Update polling.
private static bool IsGameplayPressEdgeVisible(Keyboard keyboard, Key key)
{
- if (InputState.currentUpdateType == InputUpdateType.Editor)
+ if (!InputUpdateTypeResolver.IsGameplayUpdate(InputState.currentUpdateType))
{
return false;
}
@@ -443,17 +443,18 @@ private static bool IsGameplayPressEdgeVisible(Keyboard keyboard, Key key)
// Runs inside InputSystem.onAfterUpdate alongside the edge visibility check above.
// Why: the root cause of an unobserved edge could not be reproduced (see Round-6
// investigation), so this records which update type (if any) actually saw
- // wasPressedThisFrame become true, and whether a Dynamic update ran at all, to diagnose
- // the next real occurrence directly from the response instead of guessing.
+ // wasPressedThisFrame become true, and whether any gameplay update ran at all, to diagnose
+ // the next real occurrence directly from the response instead of guessing. The gameplay
+ // predicate is shared with IsGameplayPressEdgeVisible so the flag can never contradict it.
private static void RecordPressEdgeMissDiagnostics(
Keyboard keyboard,
Key key,
PressEdgeMissDiagnostics diagnostics)
{
InputUpdateType currentUpdateType = InputState.currentUpdateType;
- if (currentUpdateType == InputUpdateType.Dynamic)
+ if (InputUpdateTypeResolver.IsGameplayUpdate(currentUpdateType))
{
- diagnostics.AnyDynamicUpdateObserved = true;
+ diagnostics.AnyGameplayUpdateObserved = true;
}
if (diagnostics.ConsumedByUpdateType == null && keyboard[key].wasPressedThisFrame)
@@ -468,7 +469,7 @@ private static void RecordPressEdgeMissDiagnostics(
private sealed class PressEdgeMissDiagnostics
{
public string? ConsumedByUpdateType;
- public bool AnyDynamicUpdateObserved;
+ public bool AnyGameplayUpdateObserved;
public bool KeyAlreadyPressedBeforeQueue;
}
}
diff --git a/Packages/src/Editor/FirstPartyTools/SimulateKeyboard/SimulateKeyboardResponse.cs b/Packages/src/Editor/FirstPartyTools/SimulateKeyboard/SimulateKeyboardResponse.cs
index 58dfa7a840..eb9359a8fb 100644
--- a/Packages/src/Editor/FirstPartyTools/SimulateKeyboard/SimulateKeyboardResponse.cs
+++ b/Packages/src/Editor/FirstPartyTools/SimulateKeyboard/SimulateKeyboardResponse.cs
@@ -58,10 +58,11 @@ public class SimulateKeyboardResponse : UnityCliLoopToolResponse
public string? PressEdgeConsumedByUpdateType { get; set; }
///
- /// Whether any Dynamic update ran while waiting for the press edge to be observed.
+ /// Whether any gameplay input update (Dynamic, Fixed, or Manual) ran while waiting for
+ /// the press edge to be observed. Editor updates do not count.
///
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
- public bool? PressEdgeAnyDynamicUpdateObserved { get; set; }
+ public bool? PressEdgeAnyGameplayUpdateObserved { get; set; }
///
/// Whether the key was already pressed before this action was queued, meaning no press
diff --git a/Packages/src/Editor/FirstPartyTools/SimulateKeyboard/Skill/references/output.md b/Packages/src/Editor/FirstPartyTools/SimulateKeyboard/Skill/references/output.md
index 2c6f502993..d9a8060334 100644
--- a/Packages/src/Editor/FirstPartyTools/SimulateKeyboard/Skill/references/output.md
+++ b/Packages/src/Editor/FirstPartyTools/SimulateKeyboard/Skill/references/output.md
@@ -17,10 +17,10 @@ Returns JSON with:
- `RejectedByActivePausePointId` (string, nullable): Set when an active pause point rejected this call before any input was injected — distinct from `PausePointId`, which reports a marker hit during the call. When set, the action never happened, so do not read `Success` alone
- `PressEdgeObserved` (boolean, nullable): For `Press` and `KeyDown`, whether the press edge (`wasPressedThisFrame`) was visible inside a gameplay input update. `false` means the CLI succeeded but gameplay polling most likely missed the edge — verify with a focused log instead of trusting `Success` alone. `null` only for `KeyUp` and timed-out responses; pause-point interruptions still report the observed value. When a single-shot pause point is armed, do not blindly retry on `false`: the input may still have registered late, so check `pause-point-status` for a hit first — a blind retry can consume a re-enabled marker or double-fire the scenario
- `PressHoldExtendedFrames` (integer, nullable): Extra observation frames the key stayed held beyond the normal duration window while waiting for `wasPressedThisFrame`; `null` when the release was not delayed
-- `PressEdgeConsumedByUpdateType` / `PressEdgeAnyDynamicUpdateObserved` / `PressEdgeKeyAlreadyPressedBeforeQueue` (nullable): Diagnostics populated only when `PressEdgeObserved` is `false` (all `null` when the edge was observed). `Message` carries the same diagnosis as text. Read them before retrying:
+- `PressEdgeConsumedByUpdateType` / `PressEdgeAnyGameplayUpdateObserved` / `PressEdgeKeyAlreadyPressedBeforeQueue` (nullable): Diagnostics populated only when `PressEdgeObserved` is `false` (all `null` when the edge was observed). `Message` carries the same diagnosis as text. Read them before retrying:
| Diagnostic | Meaning | Next action |
|---|---|---|
| `PressEdgeKeyAlreadyPressedBeforeQueue=true` | The key was already held; no press transition could occur | Release with `KeyUp`, then press again |
-| `PressEdgeConsumedByUpdateType` names a non-`Dynamic` update type (e.g. `Editor`) | An editor-side update consumed the edge before gameplay polling saw it | Retry: rerun `Press` directly; for `KeyDown` the key is now held, so `KeyUp` first (a held key rejects a second `KeyDown`) |
-| `PressEdgeAnyDynamicUpdateObserved=false` | No `Dynamic`-type input update ran during the press window (this flag does not track `Fixed`/`Manual` gameplay updates) | Check that PlayMode is running and unpaused; do not retry blindly |
+| `PressEdgeConsumedByUpdateType` is `Editor` | An editor-side update consumed the edge before gameplay polling saw it | Retry: rerun `Press` directly; for `KeyDown` the key is now held, so `KeyUp` first (a held key rejects a second `KeyDown`) |
+| `PressEdgeAnyGameplayUpdateObserved=false` | No gameplay input update (`Dynamic`, `Fixed`, or `Manual`) ran during the press window; `Editor` ticks do not count | Check that PlayMode is running and unpaused; do not retry blindly |