feat: Add a raycast tool for checking what a Game View coordinate hits in 3D physics#1659
Conversation
Ports main's physics raycast query (commit range ending at 90e7483, #1469-family Raycast feature) onto v3-beta's per-tool asmdef and [UnityCliLoopTool] auto-discovery architecture. Raycast lets an agent check what a top-left Game View coordinate hits in 3D physics before driving simulate-mouse-ui, mirroring the coordinate system already used by that tool. - New FirstPartyTools/Raycast/ tool (RaycastTool/Schema/Response, Skill/SKILL.md, own asmdef), following SimulateMouseUi's self-contained pattern rather than ToolContracts, since Raycast has no cross-tool schema sharing yet. - New Common/GameView/ module (GameViewCoordinateUtility, GameViewRaycastUtility) holding the top-left/bottom-left Game View coordinate conversion, shared the same way Common/MouseUi already shares GameObjectPathUtility. - Added the missing RAYCAST_DEFAULT_MAX_DISTANCE and COORDINATE_SYSTEM_*/COORDINATE_CONVERSION_FORMULA_* constants to UnityCliLoopConstants. - Ported RaycastToolTests and GameViewCoordinateUtilityTests, and hardened RaycastToolTests to disable ambient scene colliders before raycasting so it does not depend on the shared Editor scene being clean.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughThis PR adds a new ChangesRaycast Tool
Estimated code review effort: 3 (Moderate) | ~30 minutes Sequence Diagram(s)sequenceDiagram
participant Caller
participant RaycastTool
participant GameViewRaycastUtility
participant GameViewCoordinateUtility
participant CameraMain
participant Physics
Caller->>RaycastTool: ExecuteAsync(RaycastSchema{X,Y,LayerMask,MaxDistance})
RaycastTool->>RaycastTool: validate MaxDistance
RaycastTool->>GameViewRaycastUtility: RaycastFromInputPosition(x, y, layerMask, maxDistance)
GameViewRaycastUtility->>GameViewCoordinateUtility: ConvertInputToUnity(inputPosition, gameViewSize)
GameViewCoordinateUtility-->>GameViewRaycastUtility: GameViewCoordinateConversion
GameViewRaycastUtility->>CameraMain: lookup Camera.main
alt camera missing
CameraMain-->>GameViewRaycastUtility: null
GameViewRaycastUtility-->>RaycastTool: GameViewRaycastResult{CameraFound=false}
RaycastTool-->>Caller: RaycastResponse{Success=false}
else camera found
CameraMain-->>GameViewRaycastUtility: camera
GameViewRaycastUtility->>Physics: SyncTransforms (optional)
GameViewRaycastUtility->>Physics: RaycastAll(ray, layerMask & cullingMask)
Physics-->>GameViewRaycastUtility: RaycastHit[]
GameViewRaycastUtility-->>RaycastTool: GameViewRaycastResult{sorted hits}
RaycastTool-->>Caller: RaycastResponse{Hit, HitGameObjectName, Distance, ...}
end
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
Assets/Tests/Editor/RaycastToolTests.cs (1)
68-165: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsider adding a test for
MaxDistancevalidation.
RaycastTool.ExecuteAsyncrejects non-positive, NaN, or infiniteMaxDistancevalues (returningSuccess = false), but no test in this fixture covers that validation path. Adding a case with an invalidMaxDistance(e.g.,0,-1, orNaN) would protect against regressions in the guard clause.♻️ Suggested test for MaxDistance validation
[Test] public async Task ExecuteAsync_WhenMaxDistanceIsInvalid_ShouldReturnFailure() { JObject parameters = new JObject { ["x"] = 0f, ["y"] = 0f, ["maxDistance"] = 0f }; RaycastTool tool = new RaycastTool(); UnityCliLoopToolResponse baseResponse = await tool.ExecuteAsync(parameters, CancellationToken.None); RaycastResponse response = (RaycastResponse)baseResponse; Assert.That(response.Success, Is.False); Assert.That(response.Message, Does.Contain("MaxDistance")); }🤖 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/RaycastToolTests.cs` around lines 68 - 165, Add a test in RaycastToolTests for the MaxDistance guard path in RaycastTool.ExecuteAsync, since the current fixture only covers hit/miss and camera/culling cases. Use ExecuteRaycast with an invalid MaxDistance value such as 0, negative, or NaN, then assert the response returns Success = false and the expected validation message. Keep the test alongside the existing ExecuteAsync_* cases so the invalid-distance behavior is covered by this fixture.
🤖 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.
Nitpick comments:
In `@Assets/Tests/Editor/RaycastToolTests.cs`:
- Around line 68-165: Add a test in RaycastToolTests for the MaxDistance guard
path in RaycastTool.ExecuteAsync, since the current fixture only covers hit/miss
and camera/culling cases. Use ExecuteRaycast with an invalid MaxDistance value
such as 0, negative, or NaN, then assert the response returns Success = false
and the expected validation message. Keep the test alongside the existing
ExecuteAsync_* cases so the invalid-distance behavior is covered by this
fixture.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 3fe5f60b-b223-4ad0-85ad-b7e725645b4d
⛔ Files ignored due to path filters (17)
Assets/Tests/Editor/GameViewCoordinateUtilityTests.cs.metais excluded by none and included by noneAssets/Tests/Editor/RaycastToolTests.cs.metais excluded by none and included by noneAssets/Tests/Editor/UnityCLILoop.Tests.Editor.asmdefis excluded by none and included by nonePackages/src/Editor/FirstPartyTools/Common/GameView.metais excluded by none and included by nonePackages/src/Editor/FirstPartyTools/Common/GameView/AssemblyInfo.cs.metais excluded by none and included by nonePackages/src/Editor/FirstPartyTools/Common/GameView/GameViewCoordinateUtility.cs.metais excluded by none and included by nonePackages/src/Editor/FirstPartyTools/Common/GameView/GameViewRaycastUtility.cs.metais excluded by none and included by nonePackages/src/Editor/FirstPartyTools/Common/GameView/UnityCLILoop.FirstPartyTools.Common.GameView.Editor.asmdefis excluded by none and included by nonePackages/src/Editor/FirstPartyTools/Common/GameView/UnityCLILoop.FirstPartyTools.Common.GameView.Editor.asmdef.metais excluded by none and included by nonePackages/src/Editor/FirstPartyTools/Raycast.metais excluded by none and included by nonePackages/src/Editor/FirstPartyTools/Raycast/RaycastResponse.cs.metais excluded by none and included by nonePackages/src/Editor/FirstPartyTools/Raycast/RaycastSchema.cs.metais excluded by none and included by nonePackages/src/Editor/FirstPartyTools/Raycast/RaycastTool.cs.metais excluded by none and included by nonePackages/src/Editor/FirstPartyTools/Raycast/Skill.metais excluded by none and included by nonePackages/src/Editor/FirstPartyTools/Raycast/Skill/SKILL.md.metais excluded by none and included by nonePackages/src/Editor/FirstPartyTools/Raycast/UnityCLILoop.FirstPartyTools.Raycast.Editor.asmdefis excluded by none and included by nonePackages/src/Editor/FirstPartyTools/Raycast/UnityCLILoop.FirstPartyTools.Raycast.Editor.asmdef.metais excluded by none and included by none
📒 Files selected for processing (13)
.agents/skills/uloop-raycast/SKILL.md.claude/skills/uloop-raycast/SKILL.mdAssets/Tests/Editor/GameViewCoordinateUtilityTests.csAssets/Tests/Editor/RaycastToolTests.csPackages/src/Editor/FirstPartyTools/Common/GameView/AssemblyInfo.csPackages/src/Editor/FirstPartyTools/Common/GameView/GameViewCoordinateUtility.csPackages/src/Editor/FirstPartyTools/Common/GameView/GameViewRaycastUtility.csPackages/src/Editor/FirstPartyTools/Common/MouseUi/AssemblyInfo.csPackages/src/Editor/FirstPartyTools/Raycast/RaycastResponse.csPackages/src/Editor/FirstPartyTools/Raycast/RaycastSchema.csPackages/src/Editor/FirstPartyTools/Raycast/RaycastTool.csPackages/src/Editor/FirstPartyTools/Raycast/Skill/SKILL.mdPackages/src/Editor/ToolContracts/UnityCliLoopConstants.cs
Fable 5's review of PR #1659 caught that the new raycast tool had no entry in cli/common/tools/default-tools.json. The Go CLI's catalog loader falls back to this embedded catalog when a project has no .uloop/tools.json cache yet, so a fresh checkout could not resolve `uloop raycast` even though the Skill/SKILL.md already advertises it. DefaultToolsCatalogDriftTests only checks embedded-to-live direction, so a missing embedded entry does not fail EditMode tests. Confirmed cli/release-automation's release_trigger_guard.go excludes JSON under cli/common/ from release triggers, so no stamp update is required for this change.
Summary
raycasttool: check what a top-left Game View coordinate hits in Unity's 3D physics before drivingsimulate-mouse-ui.User Impact
uloop raycast --x <x> --y <y>reports the hit object, layer, distance, and hit point/normal, using the same top-left Game View coordinate system assimulate-mouse-ui.Changes
FirstPartyTools/Raycast/tool (Tool/Schema/Response, Skill/SKILL.md, own asmdef), following the same self-contained pattern asSimulateMouseUi.Common/GameView/module (GameViewCoordinateUtility,GameViewRaycastUtility) for the top-left/bottom-left Game View coordinate conversion, shared the same wayCommon/MouseUialready sharesGameObjectPathUtility.UnityCliLoopConstants.RaycastToolTests/GameViewCoordinateUtilityTests, and hardenedRaycastToolTeststo disable ambient scene colliders before raycasting so it does not depend on the shared Editor scene being clean.Verification
dist/darwin-arm64/uloop compile— Success, 0 errors/warningsdist/darwin-arm64/uloop run-tests --test-mode EditMode— 1734/1734 passed (+7 pre-existing unrelated skips)dist/darwin-arm64/uloop run-tests --test-mode PlayMode— 92/92 passed