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
8 changes: 6 additions & 2 deletions .agents/skills/uloop-wait-for-pause-point/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,12 @@ uloop wait-for-pause-point --id state-transition-applied --timeout-seconds 30

The hit response always embeds the log entries matching the marker id as `MatchingLogs` (`--matching-logs-max-count` adjusts the limit, default 10), so a separate `get-logs` call while paused is unnecessary. Log embedding is always on; there is no opt-in flag, and a `--include-matching-logs` option no longer exists. An empty `MatchingLogs` array means the fetch succeeded and no matching log exists; if the field is absent, the log fetch itself failed, so fall back to `uloop get-logs --search-text state-transition-applied --max-count 20` while paused.

Read `EvidenceSummary` first when it is present. It groups `EditorState`, pause point hit metadata, matching-log counts, truncation status, and warnings so you can tell whether the evidence is a single clean hit or needs closer inspection. `EditorState.CapturedAt` names when the Unity Editor play/pause state was observed, such as `PausePointHit`, `Current`, or `ClearAll`.

Use `Generation`, `EnabledAtUtc`, and the hit sequence fields from the hit or status response to tell a fresh marker from stale evidence with the same id. `RemainingMilliseconds` and `Expired` are returned directly so you do not need to infer marker lifetime from elapsed time.

5. While Unity is still paused, capture any additional evidence with `uloop execute-dynamic-code`, `uloop get-hierarchy`, `uloop find-game-objects`, and one screenshot.
6. Clear the marker with `uloop clear-pause-point --id state-transition-applied` or stop PlayMode before moving on. Use `uloop clear-pause-point --all` to clear every active marker at once, for example when resetting between E2E scenarios.
6. Clear the marker with `uloop clear-pause-point --id state-transition-applied` or stop PlayMode before moving on. Use `uloop clear-pause-point --all` to clear every active marker at once, for example when resetting between E2E scenarios. The clear response's `EditorState` describes Unity Editor play/pause state, not marker state.

## When To Use

Expand All @@ -48,7 +52,7 @@ The hit response always embeds the log entries matching the marker id as `Matchi

## Timeout Checks

If this command times out, the marker line was not reached while the command waited. Read `error.details.hint` first: it names the most likely cause when PlayMode is not running, Unity is already paused, or the marker was enabled but never hit. A `PAUSE_POINT_EXPIRED` error carries the same hint: it means the marker's own `enable-pause-point --timeout-seconds` window (measured from enable, not from wait) ran out first, so re-enable the marker with a longer timeout. Then inspect `error.details.status`, `hitCount`, `isPlaying`, `isPaused`, `elapsedSinceEnabledMilliseconds`, and `remainingMilliseconds` to distinguish input not being consumed, runtime conditions not being met, an id mismatch, or Unity already being paused. `error.details.MatchingLogs` shows whether the marker's focused log ever appeared. `elapsedSinceEnabledMilliseconds` is measured from `enable-pause-point`, not from `wait-for-pause-point`.
If this command times out, the marker line was not reached while the command waited. Read `error.details.hint` first: it names the most likely cause when PlayMode is not running, Unity is already paused, or the marker was enabled but never hit. A `PAUSE_POINT_EXPIRED` error carries the same hint and shell-neutral `error.details.recommendedNextAction`: it means the marker's own `enable-pause-point --timeout-seconds` window (measured from enable, not from wait) ran out first, so clear and re-enable the marker using the returned `id` and `timeoutSeconds`. Then inspect `error.details.status`, `hitCount`, `generation`, `enabledAtUtc`, `editorState`, `elapsedSinceEnabledMilliseconds`, and `remainingMilliseconds` to distinguish input not being consumed, stale evidence from an older marker generation, runtime conditions not being met, an id mismatch, or Unity already being paused. On `PAUSE_POINT_WAIT_TIMEOUT`, `error.details.MatchingLogs` and `error.details.EvidenceSummary` show whether the marker's focused log ever appeared. `elapsedSinceEnabledMilliseconds` is measured from `enable-pause-point`, not from `wait-for-pause-point`.

Use `uloop pause-point-status --id state-transition-applied` only when you need to confirm the marker is armed or inspect the current hit state. Add focused debug logs before the marker when local variables must be captured.

Expand Down
8 changes: 6 additions & 2 deletions .claude/skills/uloop-wait-for-pause-point/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,12 @@ uloop wait-for-pause-point --id state-transition-applied --timeout-seconds 30

The hit response always embeds the log entries matching the marker id as `MatchingLogs` (`--matching-logs-max-count` adjusts the limit, default 10), so a separate `get-logs` call while paused is unnecessary. Log embedding is always on; there is no opt-in flag, and a `--include-matching-logs` option no longer exists. An empty `MatchingLogs` array means the fetch succeeded and no matching log exists; if the field is absent, the log fetch itself failed, so fall back to `uloop get-logs --search-text state-transition-applied --max-count 20` while paused.

Read `EvidenceSummary` first when it is present. It groups `EditorState`, pause point hit metadata, matching-log counts, truncation status, and warnings so you can tell whether the evidence is a single clean hit or needs closer inspection. `EditorState.CapturedAt` names when the Unity Editor play/pause state was observed, such as `PausePointHit`, `Current`, or `ClearAll`.

Use `Generation`, `EnabledAtUtc`, and the hit sequence fields from the hit or status response to tell a fresh marker from stale evidence with the same id. `RemainingMilliseconds` and `Expired` are returned directly so you do not need to infer marker lifetime from elapsed time.

5. While Unity is still paused, capture any additional evidence with `uloop execute-dynamic-code`, `uloop get-hierarchy`, `uloop find-game-objects`, and one screenshot.
6. Clear the marker with `uloop clear-pause-point --id state-transition-applied` or stop PlayMode before moving on. Use `uloop clear-pause-point --all` to clear every active marker at once, for example when resetting between E2E scenarios.
6. Clear the marker with `uloop clear-pause-point --id state-transition-applied` or stop PlayMode before moving on. Use `uloop clear-pause-point --all` to clear every active marker at once, for example when resetting between E2E scenarios. The clear response's `EditorState` describes Unity Editor play/pause state, not marker state.

## When To Use

Expand All @@ -48,7 +52,7 @@ The hit response always embeds the log entries matching the marker id as `Matchi

## Timeout Checks

If this command times out, the marker line was not reached while the command waited. Read `error.details.hint` first: it names the most likely cause when PlayMode is not running, Unity is already paused, or the marker was enabled but never hit. A `PAUSE_POINT_EXPIRED` error carries the same hint: it means the marker's own `enable-pause-point --timeout-seconds` window (measured from enable, not from wait) ran out first, so re-enable the marker with a longer timeout. Then inspect `error.details.status`, `hitCount`, `isPlaying`, `isPaused`, `elapsedSinceEnabledMilliseconds`, and `remainingMilliseconds` to distinguish input not being consumed, runtime conditions not being met, an id mismatch, or Unity already being paused. `error.details.MatchingLogs` shows whether the marker's focused log ever appeared. `elapsedSinceEnabledMilliseconds` is measured from `enable-pause-point`, not from `wait-for-pause-point`.
If this command times out, the marker line was not reached while the command waited. Read `error.details.hint` first: it names the most likely cause when PlayMode is not running, Unity is already paused, or the marker was enabled but never hit. A `PAUSE_POINT_EXPIRED` error carries the same hint and shell-neutral `error.details.recommendedNextAction`: it means the marker's own `enable-pause-point --timeout-seconds` window (measured from enable, not from wait) ran out first, so clear and re-enable the marker using the returned `id` and `timeoutSeconds`. Then inspect `error.details.status`, `hitCount`, `generation`, `enabledAtUtc`, `editorState`, `elapsedSinceEnabledMilliseconds`, and `remainingMilliseconds` to distinguish input not being consumed, stale evidence from an older marker generation, runtime conditions not being met, an id mismatch, or Unity already being paused. On `PAUSE_POINT_WAIT_TIMEOUT`, `error.details.MatchingLogs` and `error.details.EvidenceSummary` show whether the marker's focused log ever appeared. `elapsedSinceEnabledMilliseconds` is measured from `enable-pause-point`, not from `wait-for-pause-point`.

Use `uloop pause-point-status --id state-transition-applied` only when you need to confirm the marker is armed or inspect the current hit state. Add focused debug logs before the marker when local variables must be captured.

Expand Down
25 changes: 24 additions & 1 deletion Assets/Tests/Editor/PausePointTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,27 @@ public void Pause_WhenPausePointIsEnabled_RecordsHitAndRequestsPause()
Assert.That(snapshot.Status, Is.EqualTo(UloopPausePointStatus.Hit));
Assert.That(snapshot.IsHit, Is.True);
Assert.That(snapshot.IsEnabled, Is.False);
Assert.That(snapshot.IsPaused, Is.True);
Assert.That(snapshot.EditorState.IsPaused, Is.True);
Assert.That(snapshot.EditorState.CapturedAt, Is.EqualTo(UloopPausePointEditorStateCapturedAt.PausePointHit));
Assert.That(snapshot.HitCount, Is.EqualTo(1));
}

[Test]
public void Pause_WhenPausePointIsEnabled_RecordsHitEvidence()
{
// Verifies a hit snapshot includes stable timing and sequence evidence.
UloopPausePointRegistry.Enable("jump", 30);
_nowUtc = _nowUtc.AddMilliseconds(125);

UloopPausePoint.Pause("jump");

UloopPausePointSnapshot snapshot = UloopPausePointRegistry.GetStatus("jump");
Assert.That(snapshot.FirstHitAtUtc, Is.EqualTo("2026-06-03T00:00:00.1250000Z"));
Assert.That(snapshot.LastHitAtUtc, Is.EqualTo("2026-06-03T00:00:00.1250000Z"));
Assert.That(snapshot.FirstHitSequence, Is.EqualTo(1));
Assert.That(snapshot.LastHitSequence, Is.EqualTo(1));
}

[Test]
public void Pause_WhenPausePointIsEnabled_StoresLatestHitSnapshot()
{
Expand Down Expand Up @@ -162,6 +179,7 @@ public void GetStatus_WhenEnabled_ReportsTimingAndGenerationFields()
Assert.That(snapshot.RemainingMilliseconds, Is.EqualTo(29750));
Assert.That(snapshot.Generation, Is.EqualTo(1));
Assert.That(snapshot.Expired, Is.False);
Assert.That(snapshot.EditorState.CapturedAt, Is.EqualTo(UloopPausePointEditorStateCapturedAt.Current));
}

[Test]
Expand Down Expand Up @@ -238,6 +256,9 @@ public async Task ClearAll_WhenNothingActive_ReportsNoActiveMessage()
PausePointResponse response = (PausePointResponse)await tool.ExecuteAsync(parameters, CancellationToken.None);

Assert.That(response.ClearedCount, Is.EqualTo(0));
Assert.That(response.EditorState.IsPlaying, Is.True);
Assert.That(response.EditorState.IsPaused, Is.False);
Assert.That(response.EditorState.CapturedAt, Is.EqualTo(UloopPausePointEditorStateCapturedAt.ClearAll));
Assert.That(response.Message, Is.EqualTo("No active pause points to clear."));
}

Expand All @@ -251,6 +272,7 @@ public async Task Enable_WhenMarkerCreated_ReturnsStateManagementFields()
Assert.That(response.RemainingMilliseconds, Is.EqualTo(30000));
Assert.That(response.Generation, Is.EqualTo(1));
Assert.That(response.Expired, Is.False);
Assert.That(response.EditorState.CapturedAt, Is.EqualTo(UloopPausePointEditorStateCapturedAt.Current));
Assert.That(response.RecommendedNextAction, Is.Empty);
}

Expand All @@ -271,6 +293,7 @@ public void PausePointStatusBridge_WhenMarkerExpired_ReturnsRecoveryAction()
Assert.That(response.EnabledAtUtc, Is.EqualTo("2026-06-03T00:00:00.0000000Z"));
Assert.That(response.RemainingMilliseconds, Is.EqualTo(0));
Assert.That(response.Generation, Is.EqualTo(1));
Assert.That(response.EditorState.CapturedAt, Is.EqualTo(UloopPausePointEditorStateCapturedAt.Current));
Assert.That(
response.RecommendedNextAction,
Is.EqualTo("Clear this marker, then re-enable it with the same Id and TimeoutSeconds values."));
Expand Down
8 changes: 5 additions & 3 deletions Packages/src/Editor/CliOnlyTools~/PausePoint/Skill/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,12 @@ uloop wait-for-pause-point --id state-transition-applied --timeout-seconds 30

The hit response always embeds the log entries matching the marker id as `MatchingLogs` (`--matching-logs-max-count` adjusts the limit, default 10), so a separate `get-logs` call while paused is unnecessary. Log embedding is always on; there is no opt-in flag, and a `--include-matching-logs` option no longer exists. An empty `MatchingLogs` array means the fetch succeeded and no matching log exists; if the field is absent, the log fetch itself failed, so fall back to `uloop get-logs --search-text state-transition-applied --max-count 20` while paused.

Use `Generation` and `EnabledAtUtc` from the hit or status response to tell a fresh marker from stale evidence with the same id. `RemainingMilliseconds` and `Expired` are returned directly so you do not need to infer marker lifetime from elapsed time.
Read `EvidenceSummary` first when it is present. It groups `EditorState`, pause point hit metadata, matching-log counts, truncation status, and warnings so you can tell whether the evidence is a single clean hit or needs closer inspection. `EditorState.CapturedAt` names when the Unity Editor play/pause state was observed, such as `PausePointHit`, `Current`, or `ClearAll`.

Use `Generation`, `EnabledAtUtc`, and the hit sequence fields from the hit or status response to tell a fresh marker from stale evidence with the same id. `RemainingMilliseconds` and `Expired` are returned directly so you do not need to infer marker lifetime from elapsed time.

5. While Unity is still paused, capture any additional evidence with `uloop execute-dynamic-code`, `uloop get-hierarchy`, `uloop find-game-objects`, and one screenshot.
6. Clear the marker with `uloop clear-pause-point --id state-transition-applied` or stop PlayMode before moving on. Use `uloop clear-pause-point --all` to clear every active marker at once, for example when resetting between E2E scenarios.
6. Clear the marker with `uloop clear-pause-point --id state-transition-applied` or stop PlayMode before moving on. Use `uloop clear-pause-point --all` to clear every active marker at once, for example when resetting between E2E scenarios. The clear response's `EditorState` describes Unity Editor play/pause state, not marker state.

## When To Use

Expand All @@ -50,7 +52,7 @@ Use `Generation` and `EnabledAtUtc` from the hit or status response to tell a fr

## Timeout Checks

If this command times out, the marker line was not reached while the command waited. Read `error.details.hint` first: it names the most likely cause when PlayMode is not running, Unity is already paused, or the marker was enabled but never hit. A `PAUSE_POINT_EXPIRED` error carries the same hint and shell-neutral `error.details.recommendedNextAction`: it means the marker's own `enable-pause-point --timeout-seconds` window (measured from enable, not from wait) ran out first, so clear and re-enable the marker using the returned `id` and `timeoutSeconds`. Then inspect `error.details.status`, `hitCount`, `generation`, `enabledAtUtc`, `isPlaying`, `isPaused`, `elapsedSinceEnabledMilliseconds`, and `remainingMilliseconds` to distinguish input not being consumed, stale evidence from an older marker generation, runtime conditions not being met, an id mismatch, or Unity already being paused. `error.details.MatchingLogs` shows whether the marker's focused log ever appeared. `elapsedSinceEnabledMilliseconds` is measured from `enable-pause-point`, not from `wait-for-pause-point`.
If this command times out, the marker line was not reached while the command waited. Read `error.details.hint` first: it names the most likely cause when PlayMode is not running, Unity is already paused, or the marker was enabled but never hit. A `PAUSE_POINT_EXPIRED` error carries the same hint and shell-neutral `error.details.recommendedNextAction`: it means the marker's own `enable-pause-point --timeout-seconds` window (measured from enable, not from wait) ran out first, so clear and re-enable the marker using the returned `id` and `timeoutSeconds`. Then inspect `error.details.status`, `hitCount`, `generation`, `enabledAtUtc`, `editorState`, `elapsedSinceEnabledMilliseconds`, and `remainingMilliseconds` to distinguish input not being consumed, stale evidence from an older marker generation, runtime conditions not being met, an id mismatch, or Unity already being paused. On `PAUSE_POINT_WAIT_TIMEOUT`, `error.details.MatchingLogs` and `error.details.EvidenceSummary` show whether the marker's focused log ever appeared. `elapsedSinceEnabledMilliseconds` is measured from `enable-pause-point`, not from `wait-for-pause-point`.

Use `uloop pause-point-status --id state-transition-applied` only when you need to confirm the marker is armed or inspect the current hit state. Add focused debug logs before the marker when local variables must be captured.

Expand Down
2 changes: 1 addition & 1 deletion Packages/src/Editor/Domain/CliConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public static class CliConstants
// Why: the runtime IPC gate compares this contract generation, not release numbers.
// Bump it together with cli/contract.json protocolVersion only when this package can
// no longer interoperate with a different CLI protocol generation.
public const int REQUIRED_CLI_PROTOCOL_VERSION = 1;
public const int REQUIRED_CLI_PROTOCOL_VERSION = 2;
// Why: setup installs this pinned release; it must always point at a published CLI
// release, so it advances after releases, never inside feature PRs.
public const string MINIMUM_REQUIRED_CLI_VERSION = "3.0.0-beta.32";
Expand Down
Loading
Loading