Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .agents/skills/uloop-simulate-mouse-input/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ uloop simulate-mouse-input --dry-run --x <x> --y <y> [--layer-mask <mask>] [--ma

### Pause Point Inspection (Standard for E2E)

For standard frame proof when this input drives a state transition, follow the `uloop-pause-point` skill — it covers line placement and interruption semantics. Tool-specific note: if `InterruptedByPausePoint: true`, Unity is paused and input bookkeeping was safely released. Clear inspection-only pause points (`uloop clear-pause-point --all`) before final validation.
For standard frame proof when this input drives a state transition, follow the `uloop-pause-point` skill — it covers line placement and interruption semantics. Tool-specific note: if `InterruptedByPausePoint: true`, Unity is paused; read `PressDeliveredToGame` before retrying. Clear inspection-only pause points (`uloop clear-pause-point --all`) before final validation.

## When to use this vs simulate-mouse-ui

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Returns JSON with:
- `InjectedUnityPositionX` / `InjectedUnityPositionY`: Coordinates injected into `Mouse.current.position` (or used for dry-run ScreenPointToRay)
- `CoordinateConversionFormula`: Conversion formula used by the tool
- `InterruptedByPausePoint` / `PausePointId` / `PausePointHitCount` / `PausePointHits`: Pause-point interruption info (all nullable except the boolean). `PausePointHits` lists every marker hit during this input in hit order; `PausePointId` only names the latest one. See the Pause Point Inspection section in SKILL.md
- `PressDeliveredToGame` (boolean, nullable): Set only when a `Click` / `LongPress` was interrupted by a pause point. `true`: the Input System processed the press edge in a gameplay update before the pause, so game code polling that frame observed it and the world state may already have changed (for example a block was mined). Do not retry; re-check the affected state and `pause-point-status`. `false`: the queued edge was discarded before any gameplay update, the game never observed a press, and a retry after resume is safe. `null` for other actions and uninterrupted responses

Verify visual outcome with a follow-up screenshot.

Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/uloop-simulate-mouse-input/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ uloop simulate-mouse-input --dry-run --x <x> --y <y> [--layer-mask <mask>] [--ma

### Pause Point Inspection (Standard for E2E)

For standard frame proof when this input drives a state transition, follow the `uloop-pause-point` skill — it covers line placement and interruption semantics. Tool-specific note: if `InterruptedByPausePoint: true`, Unity is paused and input bookkeeping was safely released. Clear inspection-only pause points (`uloop clear-pause-point --all`) before final validation.
For standard frame proof when this input drives a state transition, follow the `uloop-pause-point` skill — it covers line placement and interruption semantics. Tool-specific note: if `InterruptedByPausePoint: true`, Unity is paused; read `PressDeliveredToGame` before retrying. Clear inspection-only pause points (`uloop clear-pause-point --all`) before final validation.

## When to use this vs simulate-mouse-ui

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Returns JSON with:
- `InjectedUnityPositionX` / `InjectedUnityPositionY`: Coordinates injected into `Mouse.current.position` (or used for dry-run ScreenPointToRay)
- `CoordinateConversionFormula`: Conversion formula used by the tool
- `InterruptedByPausePoint` / `PausePointId` / `PausePointHitCount` / `PausePointHits`: Pause-point interruption info (all nullable except the boolean). `PausePointHits` lists every marker hit during this input in hit order; `PausePointId` only names the latest one. See the Pause Point Inspection section in SKILL.md
- `PressDeliveredToGame` (boolean, nullable): Set only when a `Click` / `LongPress` was interrupted by a pause point. `true`: the Input System processed the press edge in a gameplay update before the pause, so game code polling that frame observed it and the world state may already have changed (for example a block was mined). Do not retry; re-check the affected state and `pause-point-status`. `false`: the queued edge was discarded before any gameplay update, the game never observed a press, and a retry after resume is safe. `null` for other actions and uninterrupted responses

Verify visual outcome with a follow-up screenshot.

Expand Down
68 changes: 68 additions & 0 deletions Assets/Tests/Editor/MouseInputSimulationResponseFactoryTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#if ULOOP_HAS_INPUT_SYSTEM
#nullable enable
using NUnit.Framework;
using UnityEngine;

using io.github.hatayama.UnityCliLoop.FirstPartyTools;
using io.github.hatayama.UnityCliLoop.ToolContracts;

namespace io.github.hatayama.UnityCliLoop.Tests.Editor
{
/// <summary>
/// Pins the pause-point interruption verdicts of simulate-mouse-input button responses.
/// </summary>
[TestFixture]
public sealed class MouseInputSimulationResponseFactoryTests
{
/// <summary>
/// Verifies a press applied before the pause reports a definite delivered verdict and re-check guidance instead of "may have".
/// </summary>
[Test]
public void InterruptedButtonResult_WhenPressWasApplied_ReportsPressDeliveredToGame()
{
SimulateMouseInputResponse response = MouseInputSimulationResponseFactory.InterruptedButtonResult(
UnityCliLoopMouseInputAction.Click,
"Left",
new Vector2(10f, 20f),
pressWasApplied: true);

Assert.That(response.Success, Is.True);
Assert.That(response.InterruptedByPausePoint, Is.True);
Assert.That(response.PressDeliveredToGame, Is.True);
Assert.That(response.Message, Does.Contain("was delivered to the game before the pause"));
Assert.That(response.Message, Does.Contain("world state may already have changed"));
Assert.That(response.Message, Does.Not.Contain("may have registered"));
}

/// <summary>
/// Verifies a press discarded before apply reports a definite not-delivered verdict and says a retry is safe.
/// </summary>
[Test]
public void InterruptedButtonResult_WhenPressWasDiscarded_ReportsPressNotDeliveredToGame()
{
SimulateMouseInputResponse response = MouseInputSimulationResponseFactory.InterruptedButtonResult(
UnityCliLoopMouseInputAction.LongPress,
"Right",
new Vector2(1f, 2f),
pressWasApplied: false);

Assert.That(response.PressDeliveredToGame, Is.False);
Assert.That(response.Message, Does.Contain("never observed"));
Assert.That(response.Message, Does.Contain("safe to retry"));
}

/// <summary>
/// Verifies non-button interruptions leave the press verdict absent because no press edge was involved.
/// </summary>
[Test]
public void InterruptedActionResult_LeavesPressDeliveredToGameNull()
{
SimulateMouseInputResponse response = MouseInputSimulationResponseFactory.InterruptedActionResult(
UnityCliLoopMouseInputAction.MoveDelta);

Assert.That(response.InterruptedByPausePoint, Is.True);
Assert.That(response.PressDeliveredToGame, Is.Null);
}
}
}
#endif

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,18 @@ internal static SimulateMouseInputResponse SuccessButtonResult(
// (b) WaitForPressLifetime after a successful apply leaves pressWasApplied=true — the press
// already landed (including when that press itself fired the pause point). Claiming
// "discarded" in (b) inverts the diagnosis this message exists to prevent.
// The verdict is definite: apply only completes inside an Input System update of the
// configured gameplay type, so the game's polling in that frame observed the press. A
// hedged "may have registered" left callers re-deriving this from world state.
internal static SimulateMouseInputResponse InterruptedButtonResult(
UnityCliLoopMouseInputAction action,
string buttonName,
Vector2 inputPos,
bool pressWasApplied)
{
string message = pressWasApplied
? $"Mouse input stopped because Unity paused during Pause Point inspection. Button '{buttonName}' press was already delivered to the game before the pause; Unity CLI Loop released it from bookkeeping, so the game may have registered the press."
: $"Mouse input stopped because Unity paused during Pause Point inspection. Button '{buttonName}' was released from Unity CLI Loop bookkeeping; the queued input edge was discarded.";
? $"Mouse input stopped because Unity paused during Pause Point inspection. Button '{buttonName}' press was delivered to the game before the pause: the Input System processed the press edge in a gameplay update, so game code polling that frame observed it and the world state may already have changed. Do not retry the press; re-check the affected state (and pause-point-status) before deciding the next step."

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1: The reworded interruption messages break the existing exact-match assertions in Assets/Tests/PlayMode/MouseInputSimulationResponseFactoryTests.cs (InterruptedButtonResult_WithMultiplePausePointHits_MapsPauseEvidenceAndButtonPosition and InterruptedButtonResult_WhenPressWasApplied_ReportsDeliveredBeforePauseMessage), which still assert the old messages via Is.EqualTo. Update those tests to the new wording (and assert PressDeliveredToGame) so the PlayMode suite keeps passing.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Packages/src/Editor/FirstPartyTools/SimulateMouseInput/MouseInputSimulationResponseFactory.cs, line 60:

<comment>The reworded interruption messages break the existing exact-match assertions in Assets/Tests/PlayMode/MouseInputSimulationResponseFactoryTests.cs (InterruptedButtonResult_WithMultiplePausePointHits_MapsPauseEvidenceAndButtonPosition and InterruptedButtonResult_WhenPressWasApplied_ReportsDeliveredBeforePauseMessage), which still assert the old messages via Is.EqualTo. Update those tests to the new wording (and assert PressDeliveredToGame) so the PlayMode suite keeps passing.</comment>

<file context>
@@ -47,15 +47,18 @@ internal static SimulateMouseInputResponse SuccessButtonResult(
             string message = pressWasApplied
-                ? $"Mouse input stopped because Unity paused during Pause Point inspection. Button '{buttonName}' press was already delivered to the game before the pause; Unity CLI Loop released it from bookkeeping, so the game may have registered the press."
-                : $"Mouse input stopped because Unity paused during Pause Point inspection. Button '{buttonName}' was released from Unity CLI Loop bookkeeping; the queued input edge was discarded.";
+                ? $"Mouse input stopped because Unity paused during Pause Point inspection. Button '{buttonName}' press was delivered to the game before the pause: the Input System processed the press edge in a gameplay update, so game code polling that frame observed it and the world state may already have changed. Do not retry the press; re-check the affected state (and pause-point-status) before deciding the next step."
+                : $"Mouse input stopped because Unity paused during Pause Point inspection. Button '{buttonName}' was released from Unity CLI Loop bookkeeping; the queued input edge was discarded before any gameplay update processed it, so the game never observed a press and it is safe to retry after resume.";
             SimulateMouseInputResponse result = new()
</file context>

: $"Mouse input stopped because Unity paused during Pause Point inspection. Button '{buttonName}' was released from Unity CLI Loop bookkeeping; the queued input edge was discarded before any gameplay update processed it, so the game never observed a press and it is safe to retry after resume.";
SimulateMouseInputResponse result = new()
{
Success = true,
Expand All @@ -64,7 +67,8 @@ internal static SimulateMouseInputResponse InterruptedButtonResult(
Button = buttonName,
PositionX = inputPos.x,
PositionY = inputPos.y,
InterruptedByPausePoint = true
InterruptedByPausePoint = true,
PressDeliveredToGame = pressWasApplied
};
AttachPausePointHit(result);
return result;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@ public class SimulateMouseInputResponse : UnityCliLoopToolResponse
public string CoordinateConversionFormula { get; set; } = "";
public bool InterruptedByPausePoint { get; set; }
/// <summary>
/// Set only on pause-point-interrupted Click/LongPress. True when the Input System processed
/// the press edge in a gameplay update before the pause (game code polling that frame
/// observed it, so the world state may already have changed); false when the queued edge
/// was discarded before any gameplay update, so the game never observed a press. Null for
/// non-button actions and for uninterrupted responses.
/// </summary>
public bool? PressDeliveredToGame { get; set; }
/// <summary>
/// Id of the pause point that refused this call before it ran anything, null otherwise.
/// Distinct from PausePointId, which reports a marker hit *during* the call: a refusal means
/// no input was injected at all, so a caller reading only Success would miss that the action
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ uloop simulate-mouse-input --dry-run --x <x> --y <y> [--layer-mask <mask>] [--ma

### Pause Point Inspection (Standard for E2E)

For standard frame proof when this input drives a state transition, follow the `uloop-pause-point` skill — it covers line placement and interruption semantics. Tool-specific note: if `InterruptedByPausePoint: true`, Unity is paused and input bookkeeping was safely released. Clear inspection-only pause points (`uloop clear-pause-point --all`) before final validation.
For standard frame proof when this input drives a state transition, follow the `uloop-pause-point` skill — it covers line placement and interruption semantics. Tool-specific note: if `InterruptedByPausePoint: true`, Unity is paused; read `PressDeliveredToGame` before retrying. Clear inspection-only pause points (`uloop clear-pause-point --all`) before final validation.

## When to use this vs simulate-mouse-ui

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Returns JSON with:
- `InjectedUnityPositionX` / `InjectedUnityPositionY`: Coordinates injected into `Mouse.current.position` (or used for dry-run ScreenPointToRay)
- `CoordinateConversionFormula`: Conversion formula used by the tool
- `InterruptedByPausePoint` / `PausePointId` / `PausePointHitCount` / `PausePointHits`: Pause-point interruption info (all nullable except the boolean). `PausePointHits` lists every marker hit during this input in hit order; `PausePointId` only names the latest one. See the Pause Point Inspection section in SKILL.md
- `PressDeliveredToGame` (boolean, nullable): Set only when a `Click` / `LongPress` was interrupted by a pause point. `true`: the Input System processed the press edge in a gameplay update before the pause, so game code polling that frame observed it and the world state may already have changed (for example a block was mined). Do not retry; re-check the affected state and `pause-point-status`. `false`: the queued edge was discarded before any gameplay update, the game never observed a press, and a retry after resume is safe. `null` for other actions and uninterrupted responses

Verify visual outcome with a follow-up screenshot.

Expand Down
Loading