Skip to content

fix: captured variables stay readable after re-enabling a pause point while paused#1734

Merged
hatayama merged 3 commits into
v3-betafrom
fix/preserve-raw-capture-on-reenable
Jul 12, 2026
Merged

fix: captured variables stay readable after re-enabling a pause point while paused#1734
hatayama merged 3 commits into
v3-betafrom
fix/preserve-raw-capture-on-reenable

Conversation

@hatayama

@hatayama hatayama commented Jul 12, 2026

Copy link
Copy Markdown
Owner

Summary

  • Re-enabling a pause point while Unity stays paused (for example to refresh its timeout during a Step session) no longer breaks UloopPausePoint.TryGetCapturedValue; the captured variables of the current hit stay readable.
  • Docs: clarified that watch expressions evaluate only while Play Mode is running AND the Editor is paused, so the wording can no longer be read as "also evaluates while the game runs unpaused."

User Impact

  • Before: after a hit, running enable-pause-point again on the same id (a natural move in a long control-play-mode --action Step session, because the enable-time timeout does not extend after hits) silently cleared the raw capture holder. TryGetCapturedValue returned Found=false and watch expressions built on it recorded null from that point on. This was reported from real usage as a suspected Step bug.
  • After: the held captures survive a same-id re-enable, matching the documented lifetime (the holder clears on resume, explicit clear, a replacing hit, or PlayMode exit — a re-enable does not resume Unity, so the paused-window invariant still holds).

Changes

  • Split the registry helper shared by Enable and Clear: Enable now only resets the hit bookkeeping (ForgetHitSnapshotForId), while Clear keeps dropping the raw capture holder (ClearHitSnapshotAndRawCaptureForId). ClearAll, the simulate-* snapshot reset, and the resume/PlayMode-exit lifecycle are unchanged.
  • Added a regression test: hit with a captured frame, re-enable the same id, assert the captured value is still retrievable.
  • Pause-point skill doc (source + two generated copies, byte-identical): documented the re-enable behavior and reworded the watch-expression evaluation timing (docs-only second commit).

Verification

  • TDD: the new test failed (found=false) before the fix and passes after.
  • uloop compile: 0 errors / 0 warnings.
  • EditMode PausePointTests: 48/48 green, including existing clear/replace/resume regressions.
  • E2E on a live Unity 6000.3.15f1 project: hit → Found=true → same-id re-enable → still Found=trueStep ×5 → still Found=true; resume still clears the holder.
  • No IPC wire-format change; protocol version intentionally untouched.

Review in cubic

hatayama added 2 commits July 13, 2026 01:15
…ile paused

Re-enabling the same pause-point id while Unity is still paused (for
example to refresh its timeout during a step session) cleared the raw
capture holder too, so UloopPausePoint.TryGetCapturedValue reported
Found=false even though Unity never resumed.

Enable() shared the same helper Clear() used to drop the hit
snapshot, which unconditionally clears UloopPausePointRawCaptureHolder.
A re-enable does not resume Unity, so the holder's documented
paused-window invariant is not actually violated by keeping the
previous hit's live references across a same-id re-enable.

Split ClearLatestHitSnapshotIfMatches into ForgetHitSnapshotForId
(drops only the hit-history entry / latest-hit pointer, used by
Enable) and ClearHitSnapshotAndRawCaptureForId (also clears the raw
capture holder, used by Clear). Add a regression test that hits a
pause point, re-enables the same id while paused, and asserts the
captured value is still retrievable. Update the pause-point skill doc
(source and its two generated copies) to document the re-enable
behavior.

Manually verified against a running Unity Editor (6000.3.15f1):
hit -> re-enable -> Step x5 keeps the capture, and a subsequent
resume still clears it.
… paused

"while Unity is both playing and paused" was ambiguous enough that a
real usage report misread it as "watches also evaluate while the game
is running," and reported that behavior as an implementation bug. The
actual implementation (from PR #1729/#1733) only evaluates a watch
once per changed Time.frameCount when Play Mode is running AND the
Editor is paused (on each hit pause and each Step); nothing is
recorded while the game runs unpaused.

Reword the sentence in the pause-point skill doc (source and its two
generated copies) so it cannot be misread as "playing" meaning
"running unpaused."
@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f3c6b9f6-08e8-466e-8cab-b0247e68b432

📥 Commits

Reviewing files that changed from the base of the PR and between c9d1814 and 6664dca.

📒 Files selected for processing (2)
  • Assets/Tests/Editor/PausePointTests.cs
  • Packages/src/Runtime/PausePoints/UloopPausePointRegistry.cs
🚧 Files skipped from review as they are similar to previous changes (1)
  • Packages/src/Runtime/PausePoints/UloopPausePointRegistry.cs

📝 Walkthrough

Walkthrough

The pause-point registry now preserves raw captures when the same pause point is re-enabled while paused, while clear and lifecycle operations still remove them appropriately. Editor tests and synchronized skill documentation cover capture and watch-expression semantics.

Changes

Pause-point capture behavior

Layer / File(s) Summary
Capture lifecycle and registry bookkeeping
Packages/src/Runtime/PausePoints/UloopPausePointRegistry.cs
Registry helpers separate hit-snapshot removal from raw-capture clearing, preserving captures during same-id re-enable and clearing them when the pause point is cleared.
Same-id re-enable and clear regression coverage
Assets/Tests/Editor/PausePointTests.cs
Tests verify that re-enabling the same pause point while paused preserves its raw capture, while clearing it removes the capture.
Pause-point and watch semantics documentation
.agents/skills/..., .claude/skills/..., Packages/src/Editor/CliOnlyTools~/PausePoint/SKILL.md
Documentation specifies raw-capture lifetime and watch evaluation only while Play Mode runs with the Editor paused.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main behavior fix around captured variables remaining readable after same-id re-enable while paused.
Description check ✅ Passed The description is directly related to the changeset and accurately covers the fix, tests, and documentation updates.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/preserve-raw-capture-on-reenable

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
Assets/Tests/Editor/PausePointTests.cs (1)

581-598: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

LGTM for the re-enable-preserves-raw-capture test. Consider adding a companion test for the re-enable-then-clear path to guard against the ClearHitSnapshotAndRawCaptureForId issue flagged on UloopPausePointRegistry.cs.

🧪 Suggested companion test
[Test]
public void TryGetCapturedValue_WhenSamePausePointIsReenabledThenCleared_StillClearsRawCapture()
{
    // Verifies Clear(id) after a same-id re-enable still drops the raw capture holder,
    // even though _latestHitSnapshot was nulled by the re-enable.
    UloopPausePointRegistry.Enable("jump", 30);
    UloopPausePointCapturedVariableFrame frame = new(
        new[] { new UloopPausePointCapturedVariableEntry("speed", UloopCapturedVariableScope.Local, 1) },
        false);
    UloopPausePointRegistry.HitWithCapturedFrame("jump", frame, Array.Empty<UloopCapturedVariable>(), false);

    UloopPausePointRegistry.Enable("jump", 30);
    UloopPausePointRegistry.Clear("jump");

    (bool found, object value) = UloopPausePoint.TryGetCapturedValue("speed");
    Assert.That(found, Is.False);
    Assert.That(value, Is.Null);
    Assert.That(UloopPausePoint.GetCapturedPausePointId(), Is.Empty);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Assets/Tests/Editor/PausePointTests.cs` around lines 581 - 598, Add a
companion test near
TryGetCapturedValue_WhenSamePausePointIsReenabledWhilePaused_KeepsLatestHitRawCapture
that re-enables the same pause point, calls
UloopPausePointRegistry.Clear("jump"), and verifies TryGetCapturedValue("speed")
returns false with a null value and GetCapturedPausePointId() is empty. This
should cover the re-enable-then-clear path and ensure
ClearHitSnapshotAndRawCaptureForId removes the held raw capture.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Packages/src/Runtime/PausePoints/UloopPausePointRegistry.cs`:
- Around line 262-271: Update ClearHitSnapshotAndRawCaptureForId to determine
ownership using UloopPausePointRawCaptureHolder.GetCapturedPausePointId() rather
than _latestHitSnapshot. Clear the holder when its captured pause-point ID
matches id, including after re-enable has removed the latest snapshot, while
preserving the existing ForgetHitSnapshotForId behavior.

---

Nitpick comments:
In `@Assets/Tests/Editor/PausePointTests.cs`:
- Around line 581-598: Add a companion test near
TryGetCapturedValue_WhenSamePausePointIsReenabledWhilePaused_KeepsLatestHitRawCapture
that re-enables the same pause point, calls
UloopPausePointRegistry.Clear("jump"), and verifies TryGetCapturedValue("speed")
returns false with a null value and GetCapturedPausePointId() is empty. This
should cover the re-enable-then-clear path and ensure
ClearHitSnapshotAndRawCaptureForId removes the held raw capture.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 3fb1a08f-c037-43e9-a435-c1c549ae804a

📥 Commits

Reviewing files that changed from the base of the PR and between 875835f and c9d1814.

📒 Files selected for processing (5)
  • .agents/skills/uloop-pause-point/SKILL.md
  • .claude/skills/uloop-pause-point/SKILL.md
  • Assets/Tests/Editor/PausePointTests.cs
  • Packages/src/Editor/CliOnlyTools~/PausePoint/Skill/SKILL.md
  • Packages/src/Runtime/PausePoints/UloopPausePointRegistry.cs

Comment thread Packages/src/Runtime/PausePoints/UloopPausePointRegistry.cs Outdated
CodeRabbit flagged that ClearHitSnapshotAndRawCaptureForId judged raw
capture ownership from _latestHitSnapshot, but Enable() already nulls
that field for the same id via ForgetHitSnapshotForId as part of a
same-id re-enable. So the sequence hit -> re-enable -> Clear(id) left
the raw capture holder alive, contradicting the documented contract
that an explicit Clear drops captures.

Check ownership against UloopPausePointRawCaptureHolder's own captured
id instead: the holder already tracks which pause point last stored
into it and is updated in lockstep with every hit, so this is
equivalent to the old semantics on all other paths and immune to
_latestHitSnapshot being cleared early by a re-enable.

Added a regression test verifying Clear(id) after a same-id re-enable
still clears the holder.
@hatayama

Copy link
Copy Markdown
Owner Author

Addressed the nitpick as well: added the suggested companion test TryGetCapturedValue_WhenSamePausePointIsReenabledThenCleared_StillClearsRawCapture in 6664dca (it was the red-first repro for the flagged ClearHitSnapshotAndRawCaptureForId issue).

@hatayama hatayama merged commit 4e63cc5 into v3-beta Jul 12, 2026
10 checks passed
@hatayama hatayama deleted the fix/preserve-raw-capture-on-reenable branch July 12, 2026 16:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant